diff --git a/.github/scripts/verify_package.sh b/.github/scripts/verify_package.sh index f8c37b4db0..2b295a63b9 100755 --- a/.github/scripts/verify_package.sh +++ b/.github/scripts/verify_package.sh @@ -53,7 +53,7 @@ trap cleanup EXIT # Copy package and test sources into working directory cp "${PKG_NAME}" "${WORK_DIR}" -cp -r test/integration/typescript/* "${WORK_DIR}" +cp -r test/integration/postcheck/* "${WORK_DIR}" cp test/resources/mock.key.json "${WORK_DIR}" # Enter work dir @@ -68,6 +68,10 @@ npm install -S "${PKG_NAME}" echo "> tsc -p tsconfig.json" ./node_modules/.bin/tsc -p tsconfig.json -MOCHA_CLI="./node_modules/.bin/mocha -r ts-node/register" -echo "> $MOCHA_CLI src/*.test.ts" -$MOCHA_CLI src/*.test.ts +MOCHA_CLI="./node_modules/.bin/mocha" +TS_MOCHA_CLI="${MOCHA_CLI} -r ts-node/register" +echo "> $TS_MOCHA_CLI typescript/*.test.ts" +$TS_MOCHA_CLI typescript/*.test.ts + +echo "> $MOCHA_CLI esm/*.js" +$MOCHA_CLI esm/*.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 244d123dd6..604dba4eef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: - node-version: [10.x, 12.x, 14.x] + node-version: [12.x, 14.x] steps: - uses: actions/checkout@v1 diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index daf85f44dc..8f51c0331d 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -49,7 +49,7 @@ jobs: - name: Verify public API run: npm run api-extractor - + - name: Run emulator-based integration tests run: | npm install -g firebase-tools @@ -88,7 +88,7 @@ jobs: subject: 'Nightly build ${{github.run_id}} of ${{github.repository}} failed!' html: > Nightly workflow ${{github.run_id}} failed on: ${{github.repository}} -

Navigate to the +

Navigate to the failed workflow. continue-on-error: true @@ -103,6 +103,6 @@ jobs: subject: 'Nightly build ${{github.run_id}} of ${{github.repository}} cancelled!' html: > Nightly workflow ${{github.run_id}} cancelled on: ${{github.repository}} -

Navigate to the +

Navigate to the cancelled workflow. continue-on-error: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d2a2797765..29681b50dc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,7 +47,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v1 with: - node-version: 10.x + node-version: 12.x - name: Install and build run: | @@ -116,7 +116,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v1 with: - node-version: 10.x + node-version: 12.x - name: Publish preflight check id: preflight diff --git a/.gitignore b/.gitignore index 4c60db05ce..9331c650de 100644 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,4 @@ test/resources/appid.txt # Release tarballs should not be checked in firebase-admin-*.tgz -docgen/html/ +docgen/markdown/ diff --git a/api-extractor.json b/api-extractor.json index 140b645cfe..43ef780464 100644 --- a/api-extractor.json +++ b/api-extractor.json @@ -1,338 +1,32 @@ -/** - * Config file for API Extractor. For more info, please visit: https://api-extractor.com - */ { "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", - - /** - * Optionally specifies another JSON config file that this file extends from. This provides a way for - * standard settings to be shared across multiple projects. - * - * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains - * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be - * resolved using NodeJS require(). - * - * SUPPORTED TOKENS: none - * DEFAULT VALUE: "" - */ - // "extends": "./shared/api-extractor-base.json" - // "extends": "my-package/include/api-extractor-base.json" - - /** - * Determines the "" token that can be used with other config file settings. The project folder - * typically contains the tsconfig.json and package.json config files, but the path is user-defined. - * - * The path is resolved relative to the folder of the config file that contains the setting. - * - * The default value for "projectFolder" is the token "", which means the folder is determined by traversing - * parent folders, starting from the folder containing api-extractor.json, and stopping at the first folder - * that contains a tsconfig.json file. If a tsconfig.json file cannot be found in this way, then an error - * will be reported. - * - * SUPPORTED TOKENS: - * DEFAULT VALUE: "" - */ - // "projectFolder": "..", - - /** - * (REQUIRED) Specifies the .d.ts file to be used as the starting point for analysis. API Extractor - * analyzes the symbols exported by this module. - * - * The file extension must be ".d.ts" and not ".ts". - * - * The path is resolved relative to the folder of the config file that contains the setting; to change this, - * prepend a folder token such as "". - * - * SUPPORTED TOKENS: , , - */ - // We point to the firebase-namespace.d.ts file since index.d.ts uses namespace imports that are - // not supported by API Extractor. See https://github.com/microsoft/rushstack/issues/1029 and - // https://github.com/microsoft/rushstack/issues/2338. - "mainEntryPointFilePath": "/lib/firebase-namespace.d.ts", - - /** - * A list of NPM package names whose exports should be treated as part of this package. - * - * For example, suppose that Webpack is used to generate a distributed bundle for the project "library1", - * and another NPM package "library2" is embedded in this bundle. Some types from library2 may become part - * of the exported API for library1, but by default API Extractor would generate a .d.ts rollup that explicitly - * imports library2. To avoid this, we can specify: - * - * "bundledPackages": [ "library2" ], - * - * This would direct API Extractor to embed those types directly in the .d.ts rollup, as if they had been - * local files for library1. - */ + "mainEntryPointFilePath": "/lib/default-namespace.d.ts", "bundledPackages": [], - - /** - * Determines how the TypeScript compiler engine will be invoked by API Extractor. - */ "compiler": { - /** - * Specifies the path to the tsconfig.json file to be used by API Extractor when analyzing the project. - * - * The path is resolved relative to the folder of the config file that contains the setting; to change this, - * prepend a folder token such as "". - * - * Note: This setting will be ignored if "overrideTsconfig" is used. - * - * SUPPORTED TOKENS: , , - * DEFAULT VALUE: "/tsconfig.json" - */ - // "tsconfigFilePath": "/tsconfig.json", - /** - * Provides a compiler configuration that will be used instead of reading the tsconfig.json file from disk. - * The object must conform to the TypeScript tsconfig schema: - * - * http://json.schemastore.org/tsconfig - * - * If omitted, then the tsconfig.json file will be read from the "projectFolder". - * - * DEFAULT VALUE: no overrideTsconfig section - */ - // "overrideTsconfig": { - // . . . - // } - /** - * This option causes the compiler to be invoked with the --skipLibCheck option. This option is not recommended - * and may cause API Extractor to produce incomplete or incorrect declarations, but it may be required when - * dependencies contain declarations that are incompatible with the TypeScript engine that API Extractor uses - * for its analysis. Where possible, the underlying issue should be fixed rather than relying on skipLibCheck. - * - * DEFAULT VALUE: false - */ - // "skipLibCheck": true, - }, - /** - * Configures how the API report file (*.api.md) will be generated. - */ + }, "apiReport": { - /** - * (REQUIRED) Whether to generate an API report. - */ - "enabled": true - - /** - * The filename for the API report files. It will be combined with "reportFolder" or "reportTempFolder" to produce - * a full file path. - * - * The file extension should be ".api.md", and the string should not contain a path separator such as "\" or "/". - * - * SUPPORTED TOKENS: , - * DEFAULT VALUE: ".api.md" - */ - // "reportFileName": ".api.md", - - /** - * Specifies the folder where the API report file is written. The file name portion is determined by - * the "reportFileName" setting. - * - * The API report file is normally tracked by Git. Changes to it can be used to trigger a branch policy, - * e.g. for an API review. - * - * The path is resolved relative to the folder of the config file that contains the setting; to change this, - * prepend a folder token such as "". - * - * SUPPORTED TOKENS: , , - * DEFAULT VALUE: "/etc/" - */ - // "reportFolder": "/etc/", - - /** - * Specifies the folder where the temporary report file is written. The file name portion is determined by - * the "reportFileName" setting. - * - * After the temporary file is written to disk, it is compared with the file in the "reportFolder". - * If they are different, a production build will fail. - * - * The path is resolved relative to the folder of the config file that contains the setting; to change this, - * prepend a folder token such as "". - * - * SUPPORTED TOKENS: , , - * DEFAULT VALUE: "/temp/" - */ - // "reportTempFolder": "/temp/" + "enabled": true, + "reportFileName": "" }, - - /** - * Configures how the doc model file (*.api.json) will be generated. - */ "docModel": { - /** - * (REQUIRED) Whether to generate a doc model file. - */ "enabled": true - - /** - * The output path for the doc model file. The file extension should be ".api.json". - * - * The path is resolved relative to the folder of the config file that contains the setting; to change this, - * prepend a folder token such as "". - * - * SUPPORTED TOKENS: , , - * DEFAULT VALUE: "/temp/.api.json" - */ - // "apiJsonFilePath": "/temp/.api.json" }, - - /** - * Configures how the .d.ts rollup file will be generated. - */ "dtsRollup": { - /** - * (REQUIRED) Whether to generate the .d.ts rollup file. - */ "enabled": false - - /** - * Specifies the output path for a .d.ts rollup file to be generated without any trimming. - * This file will include all declarations that are exported by the main entry point. - * - * If the path is an empty string, then this file will not be written. - * - * The path is resolved relative to the folder of the config file that contains the setting; to change this, - * prepend a folder token such as "". - * - * SUPPORTED TOKENS: , , - * DEFAULT VALUE: "/dist/.d.ts" - */ - // "untrimmedFilePath": "/dist/.d.ts", - - /** - * Specifies the output path for a .d.ts rollup file to be generated with trimming for a "beta" release. - * This file will include only declarations that are marked as "@public" or "@beta". - * - * The path is resolved relative to the folder of the config file that contains the setting; to change this, - * prepend a folder token such as "". - * - * SUPPORTED TOKENS: , , - * DEFAULT VALUE: "" - */ - // "betaTrimmedFilePath": "/dist/-beta.d.ts", - - /** - * Specifies the output path for a .d.ts rollup file to be generated with trimming for a "public" release. - * This file will include only declarations that are marked as "@public". - * - * If the path is an empty string, then this file will not be written. - * - * The path is resolved relative to the folder of the config file that contains the setting; to change this, - * prepend a folder token such as "". - * - * SUPPORTED TOKENS: , , - * DEFAULT VALUE: "" - */ - // "publicTrimmedFilePath": "/dist/-public.d.ts", - - /** - * When a declaration is trimmed, by default it will be replaced by a code comment such as - * "Excluded from this release type: exampleMember". Set "omitTrimmingComments" to true to remove the - * declaration completely. - * - * DEFAULT VALUE: false - */ - // "omitTrimmingComments": true }, - - /** - * Configures how the tsdoc-metadata.json file will be generated. - */ "tsdocMetadata": { - /** - * Whether to generate the tsdoc-metadata.json file. - * - * DEFAULT VALUE: true - */ - // "enabled": true, - /** - * Specifies where the TSDoc metadata file should be written. - * - * The path is resolved relative to the folder of the config file that contains the setting; to change this, - * prepend a folder token such as "". - * - * The default value is "", which causes the path to be automatically inferred from the "tsdocMetadata", - * "typings" or "main" fields of the project's package.json. If none of these fields are set, the lookup - * falls back to "tsdoc-metadata.json" in the package folder. - * - * SUPPORTED TOKENS: , , - * DEFAULT VALUE: "" - */ - // "tsdocMetadataFilePath": "/dist/tsdoc-metadata.json" + "enabled": false }, - - /** - * Specifies what type of newlines API Extractor should use when writing output files. By default, the output files - * will be written with Windows-style newlines. To use POSIX-style newlines, specify "lf" instead. - * To use the OS's default newline kind, specify "os". - * - * DEFAULT VALUE: "crlf" - */ - // "newlineKind": "crlf", - - /** - * Configures how API Extractor reports error and warning messages produced during analysis. - * - * There are three sources of messages: compiler messages, API Extractor messages, and TSDoc messages. - */ "messages": { - /** - * Configures handling of diagnostic messages reported by the TypeScript compiler engine while analyzing - * the input .d.ts files. - * - * TypeScript message identifiers start with "TS" followed by an integer. For example: "TS2551" - * - * DEFAULT VALUE: A single "default" entry with logLevel=warning. - */ "compilerMessageReporting": { - /** - * Configures the default routing for messages that don't match an explicit rule in this table. - */ "default": { - /** - * Specifies whether the message should be written to the the tool's output log. Note that - * the "addToApiReportFile" property may supersede this option. - * - * Possible values: "error", "warning", "none" - * - * Errors cause the build to fail and return a nonzero exit code. Warnings cause a production build fail - * and return a nonzero exit code. For a non-production build (e.g. when "api-extractor run" includes - * the "--local" option), the warning is displayed but the build will not fail. - * - * DEFAULT VALUE: "warning" - */ "logLevel": "warning" - - /** - * When addToApiReportFile is true: If API Extractor is configured to write an API report file (.api.md), - * then the message will be written inside that file; otherwise, the message is instead logged according to - * the "logLevel" option. - * - * DEFAULT VALUE: false - */ - // "addToApiReportFile": false } - - // "TS2551": { - // "logLevel": "warning", - // "addToApiReportFile": true - // }, - // - // . . . }, - - /** - * Configures handling of messages reported by API Extractor during its analysis. - * - * API Extractor message identifiers start with "ae-". For example: "ae-extra-release-tag" - * - * DEFAULT VALUE: See api-extractor-defaults.json for the complete table of extractorMessageReporting mappings - */ "extractorMessageReporting": { "default": { "logLevel": "warning" - // "addToApiReportFile": false }, "ae-missing-release-tag": { @@ -343,26 +37,10 @@ "logLevel": "none" } }, - - /** - * Configures handling of messages reported by the TSDoc parser when analyzing code comments. - * - * TSDoc message identifiers start with "tsdoc-". For example: "tsdoc-link-tag-unescaped-text" - * - * DEFAULT VALUE: A single "default" entry with logLevel=warning. - */ "tsdocMessageReporting": { "default": { "logLevel": "none" - // "addToApiReportFile": false } - - // "tsdoc-link-tag-unescaped-text": { - // "logLevel": "warning", - // "addToApiReportFile": true - // }, - // - // . . . } } } diff --git a/docgen/content-sources/node/HOME.md b/docgen/content-sources/node/HOME.md deleted file mode 100644 index 4e253f7733..0000000000 --- a/docgen/content-sources/node/HOME.md +++ /dev/null @@ -1,9 +0,0 @@ -# Firebase Admin Node.js SDK Reference - -The Admin SDK is a set of server libraries that lets you interact with Firebase from privileged environments. -You can install it via our [npm package](https://www.npmjs.com/package/firebase-admin). - -To get started using the Firebase Admin Node.js SDK, see -[Add the Firebase Admin SDK to your server](https://firebase.google.com/docs/admin/setup). - -For source code, see the [Firebase Admin Node.js SDK GitHub repo](https://github.com/firebase/firebase-admin-node). diff --git a/docgen/content-sources/node/toc.yaml b/docgen/content-sources/node/toc.yaml deleted file mode 100644 index c563a522ba..0000000000 --- a/docgen/content-sources/node/toc.yaml +++ /dev/null @@ -1,315 +0,0 @@ -toc: -- title: "admin" - path: /docs/reference/admin/node/admin - section: - - title: "AppOptions" - path: /docs/reference/admin/node/admin.AppOptions - - title: "FirebaseArrayIndexError" - path: /docs/reference/admin/node/admin.FirebaseArrayIndexError - - title: "FirebaseError" - path: /docs/reference/admin/node/admin.FirebaseError - - title: "GoogleOAuthAccessToken" - path: /docs/reference/admin/node/admin.GoogleOAuthAccessToken - - title: "ServiceAccount" - path: /docs/reference/admin/node/admin.ServiceAccount - -- title: "admin.app" - path: /docs/reference/admin/node/admin.app - section: - - title: "App" - path: /docs/reference/admin/node/admin.app.App-1 - -- title: "admin.appCheck" - path: /docs/reference/admin/node/admin.appCheck - section: - - title: "AppCheck" - path: /docs/reference/admin/node/admin.appCheck.AppCheck-1 - - title: "AppCheckToken" - path: /docs/reference/admin/node/admin.appCheck.AppCheckToken - - title: "AppCheckTokenOptions" - path: /docs/reference/admin/node/admin.appCheck.AppCheckTokenOptions - - title: "DecodedAppCheckToken" - path: /docs/reference/admin/node/admin.appCheck.DecodedAppCheckToken - - title: "VerifyAppCheckTokenResponse" - path: /docs/reference/admin/node/admin.appCheck.VerifyAppCheckTokenResponse - -- title: "admin.auth" - path: /docs/reference/admin/node/admin.auth - section: - - title: "Auth" - path: /docs/reference/admin/node/admin.auth.Auth-1 - - title: "ActionCodeSettings" - path: /docs/reference/admin/node/admin.auth.ActionCodeSettings - - title: "AuthProviderConfigFilter" - path: /docs/reference/admin/node/admin.auth.AuthProviderConfigFilter - - title: "BaseAuthProviderConfig" - path: /docs/reference/admin/node/admin.auth.BaseAuthProviderConfig - - title: "BaseCreateMultiFactorInfoRequest" - path: /docs/reference/admin/node/admin.auth.BaseCreateMultiFactorInfoRequest - - title: "BaseUpdateMultiFactorInfoRequest" - path: /docs/reference/admin/node/admin.auth.BaseUpdateMultiFactorInfoRequest - - title: "CreatePhoneMultiFactorInfoRequest" - path: /docs/reference/admin/node/admin.auth.CreatePhoneMultiFactorInfoRequest - - title: "CreateRequest" - path: /docs/reference/admin/node/admin.auth.CreateRequest - - title: "EmailSignInProviderConfig" - path: /docs/reference/admin/node/admin.auth.EmailSignInProviderConfig - - title: "ListProviderConfigResults" - path: /docs/reference/admin/node/admin.auth.ListProviderConfigResults - - title: "ListTenantsResult" - path: /docs/reference/admin/node/admin.auth.ListTenantsResult - - title: "MultiFactorConfig" - path: /docs/reference/admin/node/admin.auth.MultiFactorConfig - - title: "MultiFactorCreateSettings" - path: /docs/reference/admin/node/admin.auth.MultiFactorCreateSettings - - title: "MultiFactorInfo" - path: /docs/reference/admin/node/admin.auth.MultiFactorInfo - - title: "MultiFactorSettings" - path: /docs/reference/admin/node/admin.auth.MultiFactorSettings - - title: "MultiFactorUpdateSettings" - path: /docs/reference/admin/node/admin.auth.MultiFactorUpdateSettings - - title: "OAuthResponseType" - path: /docs/reference/admin/node/admin.auth.OAuthResponseType - - title: "OIDCAuthProviderConfig" - path: /docs/reference/admin/node/admin.auth.OIDCAuthProviderConfig - - title: "OIDCUpdateAuthProviderRequest" - path: /docs/reference/admin/node/admin.auth.OIDCUpdateAuthProviderRequest - - title: "PhoneMultiFactorInfo" - path: /docs/reference/admin/node/admin.auth.PhoneMultiFactorInfo - - title: "SAMLAuthProviderConfig" - path: /docs/reference/admin/node/admin.auth.SAMLAuthProviderConfig - - title: "SAMLUpdateAuthProviderRequest" - path: /docs/reference/admin/node/admin.auth.SAMLUpdateAuthProviderRequest - - title: "Tenant" - path: /docs/reference/admin/node/admin.auth.Tenant - - title: "TenantAwareAuth" - path: /docs/reference/admin/node/admin.auth.TenantAwareAuth - - title: "TenantManager" - path: /docs/reference/admin/node/admin.auth.TenantManager - - title: "UpdatePhoneMultiFactorInfoRequest" - path: /docs/reference/admin/node/admin.auth.UpdatePhoneMultiFactorInfoRequest - - title: "UpdateRequest" - path: /docs/reference/admin/node/admin.auth.UpdateRequest - - title: "UpdateTenantRequest" - path: /docs/reference/admin/node/admin.auth.UpdateTenantRequest - - title: "UserImportOptions" - path: /docs/reference/admin/node/admin.auth.UserImportOptions - - title: "UserImportRecord" - path: /docs/reference/admin/node/admin.auth.UserImportRecord - - title: "UserImportResult" - path: /docs/reference/admin/node/admin.auth.UserImportResult - - title: "DecodedIdToken" - path: /docs/reference/admin/node/admin.auth.DecodedIdToken - - title: "UserInfo" - path: /docs/reference/admin/node/admin.auth.UserInfo - - title: "UserMetadata" - path: /docs/reference/admin/node/admin.auth.UserMetadata - - title: "UserMetadataRequest" - path: /docs/reference/admin/node/admin.auth.UserMetadataRequest - - title: "UserProviderRequest" - path: /docs/reference/admin/node/admin.auth.UserProviderRequest - - title: "UserRecord" - path: /docs/reference/admin/node/admin.auth.UserRecord - - title: "UserProvider" - path: /docs/reference/admin/node/admin.auth.UserProvider - - title: "SessionCookieOptions" - path: /docs/reference/admin/node/admin.auth.SessionCookieOptions - - title: "BaseAuth" - path: /docs/reference/admin/node/admin.auth.BaseAuth - - title: "ListUsersResult" - path: /docs/reference/admin/node/admin.auth.ListUsersResult - - title: "GetUsersResult" - path: /docs/reference/admin/node/admin.auth.GetUsersResult - - title: "DeleteUsersResult" - path: /docs/reference/admin/node/admin.auth.DeleteUsersResult - - title: "UidIdentifier" - path: /docs/reference/admin/node/admin.auth.UidIdentifier - - title: "EmailIdentifier" - path: /docs/reference/admin/node/admin.auth.EmailIdentifier - - title: "PhoneIdentifier" - path: /docs/reference/admin/node/admin.auth.PhoneIdentifier - - title: "ProviderIdentifier" - path: /docs/reference/admin/node/admin.auth.ProviderIdentifier - -- title: "admin.credential" - path: /docs/reference/admin/node/admin.credential - section: - - title: "Credential" - path: /docs/reference/admin/node/admin.credential.Credential-1 - -- title: "admin.database" - path: /docs/reference/admin/node/admin.database - section: - - title: "Database" - path: /docs/reference/admin/node/admin.database.Database-1 - -- title: "admin.firestore" - path: /docs/reference/admin/node/admin.firestore - -- title: "admin.installations" - path: /docs/reference/admin/node/admin.installations - section: - - title: "Installations" - path: /docs/reference/admin/node/admin.installations.Installations-1 - -- title: "admin.instanceId" - path: /docs/reference/admin/node/admin.instanceId - section: - - title: "InstanceId" - path: /docs/reference/admin/node/admin.instanceId.InstanceId-1 - -- title: "admin.machineLearning" - path: /docs/reference/admin/node/admin.machineLearning - section: - - title: "ListModelsOptions" - path: /docs/reference/admin/node/admin.machineLearning.ListModelsOptions - - title: "ListModelsResult" - path: /docs/reference/admin/node/admin.machineLearning.ListModelsResult - - title: "MachineLearning" - path: /docs/reference/admin/node/admin.machineLearning.MachineLearning-1 - - title: "Model" - path: /docs/reference/admin/node/admin.machineLearning.Model - - title: "ModelOptionsBase" - path: /docs/reference/admin/node/admin.machineLearning.ModelOptionsBase - - title: "GcsTfliteModelOptions" - path: /docs/reference/admin/node/admin.machineLearning.GcsTfliteModelOptions - - title: "AutoMLTfliteModelOptions" - path: /docs/reference/admin/node/admin.machineLearning.AutoMLTfliteModelOptions - - title: "TFLiteModel" - path: /docs/reference/admin/node/admin.machineLearning.TFLiteModel - -- title: "admin.messaging" - path: /docs/reference/admin/node/admin.messaging - section: - - title: "BaseMessage" - path: /docs/reference/admin/node/admin.messaging.BaseMessage - - title: "TopicMessage" - path: /docs/reference/admin/node/admin.messaging.TopicMessage - - title: "TokenMessage" - path: /docs/reference/admin/node/admin.messaging.TokenMessage - - title: "ConditionMessage" - path: /docs/reference/admin/node/admin.messaging.ConditionMessage - - title: "AndroidConfig" - path: /docs/reference/admin/node/admin.messaging.AndroidConfig - - title: "AndroidFcmOptions" - path: /docs/reference/admin/node/admin.messaging.AndroidFcmOptions - - title: "AndroidNotification" - path: /docs/reference/admin/node/admin.messaging.AndroidNotification - - title: "FcmOptions" - path: /docs/reference/admin/node/admin.messaging.FcmOptions - - title: "LightSettings" - path: /docs/reference/admin/node/admin.messaging.LightSettings - - title: "Messaging" - path: /docs/reference/admin/node/admin.messaging.Messaging-1 - - title: "MessagingConditionResponse" - path: /docs/reference/admin/node/admin.messaging.MessagingConditionResponse - - title: "MessagingDeviceGroupResponse" - path: /docs/reference/admin/node/admin.messaging.MessagingDeviceGroupResponse - - title: "MessagingDeviceResult" - path: /docs/reference/admin/node/admin.messaging.MessagingDeviceResult - - title: "MessagingDevicesResponse" - path: /docs/reference/admin/node/admin.messaging.MessagingDevicesResponse - - title: "MessagingOptions" - path: /docs/reference/admin/node/admin.messaging.MessagingOptions - - title: "MessagingPayload" - path: /docs/reference/admin/node/admin.messaging.MessagingPayload - - title: "MessagingTopicResponse" - path: /docs/reference/admin/node/admin.messaging.MessagingTopicResponse - - title: "MessagingTopicManagementResponse" - path: /docs/reference/admin/node/admin.messaging.MessagingTopicManagementResponse - - title: "NotificationMessagePayload" - path: /docs/reference/admin/node/admin.messaging.NotificationMessagePayload - - title: "MulticastMessage" - path: /docs/reference/admin/node/admin.messaging.MulticastMessage - - title: "WebpushNotification" - path: /docs/reference/admin/node/admin.messaging.WebpushNotification - - title: "WebpushFcmOptions" - path: /docs/reference/admin/node/admin.messaging.WebpushFcmOptions - - title: "DataMessagePayload" - path: /docs/reference/admin/node/admin.messaging.DataMessagePayload - - title: "BatchResponse" - path: /docs/reference/admin/node/admin.messaging.BatchResponse - - title: "SendResponse" - path: /docs/reference/admin/node/admin.messaging.SendResponse - - title: "ApnsConfig" - path: /docs/reference/admin/node/admin.messaging.ApnsConfig - - title: "ApnsFcmOptions" - path: /docs/reference/admin/node/admin.messaging.ApnsFcmOptions - - title: "ApnsPayload" - path: /docs/reference/admin/node/admin.messaging.ApnsPayload - - title: "Aps" - path: /docs/reference/admin/node/admin.messaging.Aps - - title: "ApsAlert" - path: /docs/reference/admin/node/admin.messaging.ApsAlert - - title: "CriticalSound" - path: /docs/reference/admin/node/admin.messaging.CriticalSound - - title: "Notification" - path: /docs/reference/admin/node/admin.messaging.Notification - - title: "WebpushConfig" - path: /docs/reference/admin/node/admin.messaging.WebpushConfig - -- title: "admin.projectManagement" - path: /docs/reference/admin/node/admin.projectManagement - section: - - title: "AndroidApp" - path: /docs/reference/admin/node/admin.projectManagement.AndroidApp - - title: "AndroidAppMetadata" - path: /docs/reference/admin/node/admin.projectManagement.AndroidAppMetadata - - title: "AppMetadata" - path: /docs/reference/admin/node/admin.projectManagement.AppMetadata - - title: "AppPlatform" - path: /docs/reference/admin/node/admin.projectManagement.AppPlatform - - title: "IosApp" - path: /docs/reference/admin/node/admin.projectManagement.IosApp - - title: "IosAppMetadata" - path: /docs/reference/admin/node/admin.projectManagement.IosAppMetadata - - title: "ProjectManagement" - path: /docs/reference/admin/node/admin.projectManagement.ProjectManagement-1 - - title: "ShaCertificate" - path: /docs/reference/admin/node/admin.projectManagement.ShaCertificate - -- title: "admin.securityRules" - path: /docs/reference/admin/node/admin.securityRules - section: - - title: "RulesFile" - path: /docs/reference/admin/node/admin.securityRules.RulesFile - - title: "Ruleset" - path: /docs/reference/admin/node/admin.securityRules.Ruleset - - title: "RulesetMetadata" - path: /docs/reference/admin/node/admin.securityRules.RulesetMetadata - - title: "RulesetMetadataList" - path: /docs/reference/admin/node/admin.securityRules.RulesetMetadataList - - title: "SecurityRules" - path: /docs/reference/admin/node/admin.securityRules.SecurityRules-1 - -- title: "admin.storage" - path: /docs/reference/admin/node/admin.storage - section: - - title: "Storage" - path: /docs/reference/admin/node/admin.storage.Storage-1 - -- title: "admin.remoteConfig" - path: /docs/reference/admin/node/admin.remoteConfig - section: - - title: "RemoteConfig" - path: /docs/reference/admin/node/admin.remoteConfig.RemoteConfig-1 - - title: "RemoteConfigTemplate" - path: /docs/reference/admin/node/admin.remoteConfig.RemoteConfigTemplate - - title: "RemoteConfigParameter" - path: /docs/reference/admin/node/admin.remoteConfig.RemoteConfigParameter - - title: "RemoteConfigParameterGroup" - path: /docs/reference/admin/node/admin.remoteConfig.RemoteConfigParameterGroup - - title: "RemoteConfigCondition" - path: /docs/reference/admin/node/admin.remoteConfig.RemoteConfigCondition - - title: "ExplicitParameterValue" - path: /docs/reference/admin/node/admin.remoteConfig.ExplicitParameterValue - - title: "InAppDefaultValue" - path: /docs/reference/admin/node/admin.remoteConfig.InAppDefaultValue - - title: "Version" - path: /docs/reference/admin/node/admin.remoteConfig.Version - - title: "ListVersionsResult" - path: /docs/reference/admin/node/admin.remoteConfig.ListVersionsResult - - title: "ListVersionsOptions" - path: /docs/reference/admin/node/admin.remoteConfig.ListVersionsOptions - - title: "RemoteConfigUser" - path: /docs/reference/admin/node/admin.remoteConfig.RemoteConfigUser diff --git a/docgen/extras/firebase-admin.database.md b/docgen/extras/firebase-admin.database.md new file mode 100644 index 0000000000..0f33392cf0 --- /dev/null +++ b/docgen/extras/firebase-admin.database.md @@ -0,0 +1,12 @@ +## External API Re-exports + +The following externally defined APIs are re-exported from this module entry point for convenience. + +| Symbol | Description | +| --- | --- | +| [DataSnapshot](https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot) | `DataSnapshot` type from the `@firebase/database` package. | +| [EventType](https://firebase.google.com/docs/reference/js/firebase.database#eventtype) | `EventType` type from the `@firebase/database` package. | +| [OnDisconnect](https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect) | `OnDisconnect` type from the `@firebase/database` package. | +| [Query](https://firebase.google.com/docs/reference/js/firebase.database.Query) | `Query` type from the `@firebase/database` package. | +| [Reference](https://firebase.google.com/docs/reference/js/firebase.database.Reference) | `Reference` type from the `@firebase/database` package. | +| [ThenableReference](https://firebase.google.com/docs/reference/js/firebase.database.ThenableReference) | `ThenableReference` type from the `@firebase/database` package. | diff --git a/docgen/extras/firebase-admin.firestore.md b/docgen/extras/firebase-admin.firestore.md new file mode 100644 index 0000000000..9281e20c2b --- /dev/null +++ b/docgen/extras/firebase-admin.firestore.md @@ -0,0 +1,27 @@ +## External API Re-exports + +The following externally defined APIs are re-exported from this module entry point for convenience. + +| Symbol | Description | +| --- | --- | +| [BulkWriter](https://googleapis.dev/nodejs/firestore/latest/BulkWriter.html) | `BulkWriter` type from the `@google-cloud/firestore` package. | +| [BulkWriterOptions](https://googleapis.dev/nodejs/firestore/latest/global.html#BulkWriterOptions) | `BulkWriterOptions` type from the `@google-cloud/firestore` package. | +| [CollectionGroup](https://googleapis.dev/nodejs/firestore/latest/CollectionGroup.html) | `CollectionGroup` type from the `@google-cloud/firestore` package. | +| [CollectionReference](https://googleapis.dev/nodejs/firestore/latest/CollectionReference.html) | `CollectionReference` type from the `@google-cloud/firestore` package. | +| [DocumentData](https://googleapis.dev/nodejs/firestore/latest/global.html#DocumentData) | `DocumentData` type from the `@google-cloud/firestore` package. | +| [DocumentReference](https://googleapis.dev/nodejs/firestore/latest/DocumentReference.html) | `DocumentReference` type from the `@google-cloud/firestore` package. | +| [DocumentSnapshot](https://googleapis.dev/nodejs/firestore/latest/DocumentSnapshot.html) | `DocumentSnapshot` type from the `@google-cloud/firestore` package. | +| [FieldPath](https://googleapis.dev/nodejs/firestore/latest/FieldPath.html) | `FieldPath` type from the `@google-cloud/firestore` package. | +| [FieldValue](https://googleapis.dev/nodejs/firestore/latest/FieldValue.html) | `FieldValue` type from the `@google-cloud/firestore` package. | +| [Firestore](https://googleapis.dev/nodejs/firestore/latest/Firestore.html) | `Firestore` type from the `@google-cloud/firestore` package. | +| [FirestoreDataConverter](https://googleapis.dev/nodejs/firestore/latest/global.html#FirestoreDataConverter) | `FirestoreDataConverter` type from the `@google-cloud/firestore` package. | +| [GeoPoint](https://googleapis.dev/nodejs/firestore/latest/GeoPoint.html) | `GeoPoint` type from the `@google-cloud/firestore` package. | +| [Query](https://googleapis.dev/nodejs/firestore/latest/Query.html) | `Query` type from the `@google-cloud/firestore` package. | +| [QueryDocumentSnapshot](https://googleapis.dev/nodejs/firestore/latest/QueryDocumentSnapshot.html) | `QueryDocumentSnapshot` type from the `@google-cloud/firestore` package. | +| [QueryPartition](https://googleapis.dev/nodejs/firestore/latest/QueryPartition.html) | `QueryPartition` type from the `@google-cloud/firestore` package. | +| [QuerySnapshot](https://googleapis.dev/nodejs/firestore/latest/QuerySnapshot.html) | `QuerySnapshot` type from the `@google-cloud/firestore` package. | +| [Timestamp](https://googleapis.dev/nodejs/firestore/latest/Timestamp.html) | `Timestamp` type from the `@google-cloud/firestore` package. | +| [Transaction](https://googleapis.dev/nodejs/firestore/latest/Transaction.html) | `Transaction` type from the `@google-cloud/firestore` package. | +| [WriteBatch](https://googleapis.dev/nodejs/firestore/latest/WriteBatch.html) | `WriteBatch` type from the `@google-cloud/firestore` package. | +| [WriteResult](https://googleapis.dev/nodejs/firestore/latest/WriteResult.html) | `WriteResult` type from the `@google-cloud/firestore` package. | +| [setLogFunction](https://googleapis.dev/nodejs/firestore/latest/global.html#setLogFunction) | `setLogFunction` function from the `@google-cloud/firestore` package. | diff --git a/docgen/generate-docs.js b/docgen/generate-docs.js deleted file mode 100644 index 619526dff8..0000000000 --- a/docgen/generate-docs.js +++ /dev/null @@ -1,466 +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. - */ - -const { exec } = require('child-process-promise'); -const fs = require('mz/fs'); -const jsdom = require('jsdom'); -const path = require('path'); -const readline = require('readline'); -const yargs = require('yargs'); -const yaml = require('js-yaml'); - -const repoPath = path.resolve(`${__dirname}/..`); - -const defaultSources = [ - `${repoPath}/lib/firebase-namespace.d.ts`, - `${repoPath}/lib/firebase-namespace-api.d.ts`, - `${repoPath}/lib/**/*.d.ts`, -]; - -// Command-line options. -const { source: sourceFile } = yargs - .option('source', { - default: defaultSources.join(' '), - describe: 'Typescript source file(s)', - type: 'string' - }) - .version(false) - .help().argv; - -const docPath = path.resolve(`${__dirname}/html/node`); -const contentPath = path.resolve(`${__dirname}/content-sources/node`); -const tempHomePath = path.resolve(`${contentPath}/HOME_TEMP.md`); -const devsitePath = `/docs/reference/admin/node/`; - -const firestoreExcludes = [ - 'v1', 'v1beta1', 'setLogFunction','DocumentData', - 'BulkWriterOptions', 'DocumentChangeType', 'FirestoreDataConverter', - 'GrpcStatus', 'Precondition', 'ReadOptions', 'UpdateData', 'Settings', -]; -const firestoreHtmlPath = `${docPath}/admin.firestore.html`; -const firestoreHeader = `
-

Type aliases

-
-

Following types are defined in the @google-cloud/firestore package - and re-exported from this namespace for convenience.

-
-
    `; -const firestoreFooter = '\n
\n
\n'; - -const databaseExcludes = ['enableLogging']; -const databaseHtmlPath = `${docPath}/admin.database.html`; -const databaseHeader = `
-

Type aliases

-
-

Following types are defined in the @firebase/database package - and re-exported from this namespace for convenience.

-
-
    `; -const databaseFooter = '\n
\n
\n'; - -/** - * Strips path prefix and returns only filename. - * @param {string} path - */ -function stripPath(path) { - const parts = path.split('/'); - return parts[parts.length - 1]; -} - -/** - * Runs Typedoc command. - * - * Additional config options come from ./typedoc.js - */ -function runTypedoc() { - const command = `${repoPath}/node_modules/.bin/typedoc ${sourceFile} \ - --out ${docPath} \ - --readme ${tempHomePath} \ - --options ${__dirname}/typedoc.js \ - --theme ${__dirname}/theme`; - - console.log('Running command:\n', command); - return exec(command); -} - -/** - * Moves files from subdir to root. - * @param {string} subdir Subdir to move files out of. - */ -function moveFilesToRoot(subdir) { - return exec(`mv ${docPath}/${subdir}/* ${docPath}`) - .then(() => { - exec(`rmdir ${docPath}/${subdir}`); - }) - .catch(e => console.error(e)); -} - -/** - * Reformat links to match flat structure. - * @param {string} file File to fix links in. - */ -function fixLinks(file) { - return fs.readFile(file, 'utf8').then(data => { - const flattenedLinks = data - .replace(/\.\.\//g, '') - .replace(/globals\.html/g, 'admin.html') - .replace(/(modules|interfaces|classes|enums)\//g, ''); - let caseFixedLinks = flattenedLinks; - for (const lower in lowerToUpperLookup) { - const re = new RegExp('\\b' + lower, 'g'); - caseFixedLinks = caseFixedLinks.replace(re, lowerToUpperLookup[lower]); - } - return fs.writeFile(file, caseFixedLinks); - }); -} - -let tocText = ''; - -/** - * Generates temporary markdown file that will be sourced by Typedoc to - * create index.html. - * - * @param {string} tocRaw - * @param {string} homeRaw - */ -function generateTempHomeMdFile(tocRaw, homeRaw) { - const { toc } = yaml.safeLoad(tocRaw); - let tocPageLines = [homeRaw, '# API Reference']; - toc.forEach(group => { - tocPageLines.push(`\n## [${group.title}](${stripPath(group.path)}.html)`); - const section = group.section || []; - section.forEach(item => { - tocPageLines.push(`- [${item.title}](${stripPath(item.path)}.html)`); - }); - }); - return fs.writeFile(tempHomePath, tocPageLines.join('\n')); -} - -/** - * Mapping between lowercase file name and correctly cased name. - * Used to update links when filenames are capitalized. - */ -const lowerToUpperLookup = {}; - -/** - * Checks to see if any files listed in toc.yaml were not generated. - * If files exist, fixes filename case to match toc.yaml version. - */ -function checkForMissingFilesAndFixFilenameCase() { - // Get filenames from toc.yaml. - const filenames = tocText - .split('\n') - .filter(line => line.includes('path:')) - .map(line => line.split(devsitePath)[1]); - // Logs warning to console if a file from TOC is not found. - const fileCheckPromises = filenames.map(filename => { - // Warns if file does not exist, fixes filename case if it does. - // Preferred filename for devsite should be capitalized and taken from - // toc.yaml. - const tocFilePath = `${docPath}/${filename}.html`; - // Generated filename from Typedoc will be lowercase and won't have the admin prefix. - const generatedFilePath = `${docPath}/${filename.toLowerCase().replace('admin.', '')}.html`; - return fs.exists(generatedFilePath).then(exists => { - if (exists) { - // Store in a lookup table for link fixing. - lowerToUpperLookup[ - `${filename.toLowerCase().replace('admin.', '')}.html` - ] = `${filename}.html`; - return fs.rename(generatedFilePath, tocFilePath); - } else { - console.warn( - `Missing file: ${filename}.html requested ` + - `in toc.yaml but not found in ${docPath}` - ); - } - }); - }); - - return Promise.all(fileCheckPromises).then(() => filenames); -} - -/** - * Gets a list of html files in generated dir and checks if any are not - * found in toc.yaml. - * Option to remove the file if not found (used for node docs). - * - * @param {Array} filenamesFromToc Filenames pulled from toc.yaml - * @param {boolean} shouldRemove Should just remove the file - */ -function checkForUnlistedFiles(filenamesFromToc, shouldRemove) { - return fs.readdir(docPath).then(files => { - const htmlFiles = files - .filter(filename => filename.slice(-4) === 'html') - .map(filename => filename.slice(0, -5)); - const removePromises = []; - htmlFiles.forEach(filename => { - if ( - !filenamesFromToc.includes(filename) && - filename !== 'index' && - filename !== 'globals' - ) { - if (shouldRemove) { - console.log( - `REMOVING ${docPath}/${filename}.html - not listed in toc.yaml.` - ); - removePromises.push(fs.unlink(`${docPath}/${filename}.html`)); - } else { - // This is just a warning, it doesn't need to finish before - // the process continues. - console.warn( - `Unlisted file: ${filename} generated ` + - `but not listed in toc.yaml.` - ); - } - } - }); - if (shouldRemove) { - return Promise.all(removePromises).then(() => - htmlFiles.filter(filename => filenamesFromToc.includes(filename)) - ); - } else { - return htmlFiles; - } - }); -} - -/** - * Writes a _toc_autogenerated.yaml as a record of all files that were - * autogenerated. Helpful to tech writers. - * - * @param {Array} htmlFiles List of html files found in generated dir. - */ -function writeGeneratedFileList(htmlFiles) { - const fileList = htmlFiles.map(filename => { - return { - title: filename, - path: `${devsitePath}${filename}` - }; - }); - const generatedTocYAML = yaml.safeDump({ toc: fileList }); - return fs - .writeFile(`${docPath}/_toc_autogenerated.yaml`, generatedTocYAML) - .then(() => htmlFiles); -} - -/** - * Fix all links in generated files to other generated files to point to top - * level of generated docs dir. - * - * @param {Array} htmlFiles List of html files found in generated dir. - */ -function fixAllLinks(htmlFiles) { - const writePromises = []; - htmlFiles.forEach(file => { - // Update links in each html file to match flattened file structure. - writePromises.push(fixLinks(`${docPath}/${file}.html`)); - }); - return Promise.all(writePromises); -} - -/** - * Updates the auto-generated Firestore API references page, by appending - * the specified HTML content block. - * - * @param {string} htmlPath Path of the HTML file to update. - * @param {string} contentBlock The HTML content block to be added to the Firestore docs. - */ -function updateHtml(htmlPath, contentBlock) { - const dom = new jsdom.JSDOM(fs.readFileSync(htmlPath)); - const contentNode = dom.window.document.body.querySelector('.col-12'); - - // Recent versions of Typedoc generates an additional index section and a variables - // section for namespaces with re-exports. We iterate through these nodes and remove - // them from the output. - const sections = []; - contentNode.childNodes.forEach((child) => { - if (child.nodeName === 'SECTION') { - sections.push(child); - } - }); - contentNode.removeChild(sections[1]); - contentNode.removeChild(sections[2]); - - const newSection = new jsdom.JSDOM(contentBlock); - contentNode.appendChild(newSection.window.document.body.firstChild); - fs.writeFileSync(htmlPath, dom.window.document.documentElement.outerHTML); -} - -/** - * Adds Firestore type aliases to the auto-generated API docs. These are the - * types that are imported from the @google-cloud/firestore package, and - * then re-exported from the admin.firestore namespace. Typedoc currently - * does not handle these correctly, so we need this solution instead. - */ -function addFirestoreTypeAliases() { - return new Promise((resolve, reject) => { - const fileStream = fs.createReadStream(`${repoPath}/lib/firestore/index.d.ts`); - fileStream.on('error', (err) => { - reject(err); - }); - const lineReader = readline.createInterface({ - input: fileStream, - }); - - let contentBlock = firestoreHeader; - lineReader.on('line', (line) => { - line = line.trim(); - if (line.startsWith('export import') && line.indexOf('_firestore.') >= 0) { - const typeName = line.split(' ')[2]; - if (firestoreExcludes.indexOf(typeName) === -1) { - contentBlock += ` -
  • - ${typeName} -
  • `; - } - } - }); - - lineReader.on('close', () => { - try { - contentBlock += firestoreFooter; - updateHtml(firestoreHtmlPath, contentBlock); - resolve(); - } catch (err) { - reject(err); - } - }); - }); -} - -/** - * Adds RTDB type aliases to the auto-generated API docs. These are the - * types that are imported from the @firebase/database package, and - * then re-exported from the admin.database namespace. Typedoc currently - * does not handle these correctly, so we need this solution instead. - */ -function addDatabaseTypeAliases() { - return new Promise((resolve, reject) => { - const fileStream = fs.createReadStream(`${repoPath}/lib/database/index.d.ts`); - fileStream.on('error', (err) => { - reject(err); - }); - const lineReader = readline.createInterface({ - input: fileStream, - }); - - let contentBlock = databaseHeader; - lineReader.on('line', (line) => { - line = line.trim(); - if (line.startsWith('export import') && line.indexOf('rtdb.') >= 0) { - const typeName = line.split(' ')[2]; - if (databaseExcludes.indexOf(typeName) === -1) { - contentBlock += ` -
  • - ${typeName} -
  • `; - } - } - }); - - lineReader.on('close', () => { - try { - contentBlock += databaseFooter; - updateHtml(databaseHtmlPath, contentBlock); - resolve(); - } catch (err) { - reject(err); - } - }); - }); -} - -/** - * Main document generation process. - * - * Steps for generating documentation: - * 1) Create temporary md file as source of homepage. - * 2) Run Typedoc, sourcing index.d.ts for API content and temporary md file - * for index.html content. - * 3) Write table of contents file. - * 4) Flatten file structure by moving all items up to root dir and fixing - * links as needed. - * 5) Check for mismatches between TOC list and generated file list. - */ -Promise.all([ - fs.readFile(`${contentPath}/toc.yaml`, 'utf8'), - fs.readFile(`${contentPath}/HOME.md`, 'utf8') -]) - // Read TOC and homepage text and assemble a homepage markdown file. - // This file will be sourced by Typedoc to generate index.html. - .then(([tocRaw, homeRaw]) => { - tocText = tocRaw; - return generateTempHomeMdFile(tocRaw, homeRaw); - }) - // Run main Typedoc process (uses index.d.ts and generated temp file above). - .then(runTypedoc) - .then(output => { - // Typedoc output. - console.log(output.stdout); - // Clean up temp home markdown file. (Nothing needs to wait for this.) - return fs.unlink(tempHomePath); - }) - // Write out TOC file. Do this after Typedoc step to prevent Typedoc - // erroring when it finds an unexpected file in the target dir. - .then(() => fs.writeFile(`${docPath}/_toc.yaml`, tocText)) - // Flatten file structure. These categories don't matter to us and it makes - // it easier to manage the docs directory. - .then(() => { - return Promise.all([ - // moveFilesToRoot('classes'), - moveFilesToRoot('modules'), - moveFilesToRoot('interfaces'), - moveFilesToRoot('enums'), - ]); - }) - // Rename the globals file to be the top-level admin doc. - .then(() => fs.rename(`${docPath}/globals.html`, `${docPath}/admin.html`)) - // Check for files listed in TOC that are missing and warn if so. - // Not blocking. - .then(checkForMissingFilesAndFixFilenameCase) - // Check for files that exist but aren't listed in the TOC and warn. - // (If API is node, actually remove the file.) - // Removal is blocking, warnings aren't. - .then(filenamesFromToc => - checkForUnlistedFiles(filenamesFromToc, true) - ) - // Write a _toc_autogenerated.yaml to record what files were created. - .then(htmlFiles => writeGeneratedFileList(htmlFiles)) - // Correct the links in all the generated html files now that files have - // all been moved to top level. - .then(fixAllLinks) - // Add local variable include line to index.html (to access current SDK - // version number). - .then(addFirestoreTypeAliases) - .then(addDatabaseTypeAliases) - .then(() => { - fs.readFile(`${docPath}/index.html`, 'utf8').then(data => { - // String to include devsite local variables. - const localVariablesIncludeString = `{% include "docs/web/_local_variables.html" %}\n`; - return fs.writeFile( - `${docPath}/index.html`, - localVariablesIncludeString + data - ); - }); - }) - .catch(e => { - if (e.stdout) { - console.error(e.stdout); - } else { - console.error(e); - } - }); diff --git a/docgen/post-process.js b/docgen/post-process.js new file mode 100644 index 0000000000..9c0a79bdcd --- /dev/null +++ b/docgen/post-process.js @@ -0,0 +1,181 @@ +/** + * @license + * Copyright 2021 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. + */ + +const fs = require('mz/fs'); +const path = require('path'); +const readline = require('readline'); + +async function main() { + await applyExtras(); + await fixHomePage(); + await fixTitles(); +} + +/** + * Adds extra content to the generated markdown files. Content in each file in the `extras/` + * directory is added/merged to the top of the corresponding markdown file in the `markdown/` + * directory. + */ +async function applyExtras() { + const extras = await getExtraFiles(); + for (const source of extras) { + await applyExtraContentFrom(source); + } +} + +/** + * Replace dotted module names in the home page with the correct slash-separated names. For + * example, `firebase-admin.foo` becomes `firebase-admin/foo`. Also replaces the term "Package" + * with "Module" for accuracy. + */ +async function fixHomePage() { + const homePage = path.join(__dirname, 'markdown', 'index.md'); + const content = await fs.readFile(homePage); + const updatedText = content.toString() + .replace(/\[firebase-admin\./g, '[firebase-admin/') + .replace(/_package/g, '_module') + .replace(/Package/g, 'Module'); + console.log(`Updating module listings in ${homePage}`); + await fs.writeFile(homePage, updatedText); +} + +/** + * Replaces dotted module names and the term "package" in page titles. For example, the title text + * `firebase-admin.foo package` becomes `firebase-admin/foo module`. + */ +async function fixTitles() { + const markdownDir = path.join(__dirname, 'markdown'); + const files = await fs.readdir(markdownDir); + for (const file of files) { + await fixTitleOf(path.join(markdownDir, file)); + } + + const tocFile = path.join(markdownDir, 'toc.yaml'); + await fixTocTitles(tocFile); +} + +async function fixTitleOf(file) { + const reader = readline.createInterface({ + input: fs.createReadStream(file), + }); + + const buffer = []; + let updated = false; + for await (let line of reader) { + if (line.startsWith('{% block title %}')) { + if (line.match(/firebase-admin\./)) { + line = line.replace(/firebase-admin\./, 'firebase-admin/').replace('package', 'module'); + updated = true; + } else { + break; + } + } + + buffer.push(line); + } + + if (updated) { + console.log(`Updating title in ${file}`); + const content = Buffer.from(buffer.join('\n')); + await fs.writeFile(file, content); + } +} + +async function fixTocTitles(file) { + const reader = readline.createInterface({ + input: fs.createReadStream(file), + }); + + const buffer = []; + for await (let line of reader) { + if (line.includes('- title: firebase-admin.')) { + line = line.replace(/firebase-admin\./, 'firebase-admin/'); + } + + buffer.push(line); + } + + console.log(`Updating titles in ${file}`); + const content = Buffer.from(buffer.join('\n')); + await fs.writeFile(file, content); +} + +async function getExtraFiles() { + const extrasPath = path.join(__dirname, 'extras'); + const files = await fs.readdir(extrasPath); + return files + .filter((name) => name.endsWith('.md')) + .map((name) => path.join(__dirname, 'extras', name)); +} + +async function applyExtraContentFrom(source) { + const target = path.join(__dirname, 'markdown', path.basename(source)); + if (!await fs.exists(target)) { + console.log(`Target path not found: ${target}`); + return; + } + + const extra = await readExtraContentFrom(source); + await writeExtraContentTo(target, extra); +} + +async function readExtraContentFrom(source) { + const reader = readline.createInterface({ + input: fs.createReadStream(source), + }); + const content = []; + for await (const line of reader) { + content.push(line); + } + + return content; +} + +async function writeExtraContentTo(target, extra) { + const output = []; + const reader = readline.createInterface({ + input: fs.createReadStream(target), + }); + + let firstHeadingSeen = false; + for await (const line of reader) { + // Insert extra content just before the first markdown heading. + if (line.match(/^\#+ /)) { + if (!firstHeadingSeen) { + output.push(...extra); + output.push(''); + } + + firstHeadingSeen = true; + } + + output.push(line); + } + + const outputBuffer = Buffer.from(output.join('\n')); + console.log(`Writing extra content to ${target}`); + await fs.writeFile(target, outputBuffer); +} + +(async () => { + try { + await main(); + } catch (err) { + console.log(err); + process.exit(1); + } +})(); diff --git a/docgen/theme/assets/css/firebase.css b/docgen/theme/assets/css/firebase.css deleted file mode 100644 index 86c5928201..0000000000 --- a/docgen/theme/assets/css/firebase.css +++ /dev/null @@ -1,40 +0,0 @@ -.firebase-docs .project-name { - color: #333; - display: inline-block; - font-size: 20px; - font-weight: normal; - margin-left: 130px; -} - -.firebase-docs aside.tsd-sources { - padding: 8px; -} - -.firebase-docs .tsd-panel li { - margin: 0; -} - -.firebase-docs aside.tsd-sources:before { - content: unset; -} - -.firebase-docs dl.tsd-comment-tags dt.tag-example { - float: none; - text-transform: capitalize; - color: #000; - font-size: 1.1em; - padding: 5px; - border: none; -} - -.firebase-docs dl.tsd-comment-tags dd.tag-body-example { - padding-left: 0; -} - -.firebase-docs .tsd-breadcrumb .breadcrumb-name a { - color: #039be5; -} - -.firebase-docs .tsd-breadcrumb .model-name { - color: #333; -} \ No newline at end of file diff --git a/docgen/theme/assets/css/main.css b/docgen/theme/assets/css/main.css deleted file mode 100644 index 12f3d05d96..0000000000 --- a/docgen/theme/assets/css/main.css +++ /dev/null @@ -1,552 +0,0 @@ -/*! normalize.css v1.1.3 | MIT License | git.io/normalize */ -/* ========================================================================== HTML5 display definitions ========================================================================== */ -/** Correct `block` display not defined in IE 6/7/8/9 and Firefox 3. */ -article, aside, details, figcaption, figure, footer, header, hgroup, main, nav, section, summary { display: block; } - -/** Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3. */ -audio, canvas, video { display: inline-block; *display: inline; *zoom: 1; } - -/** Prevent modern browsers from displaying `audio` without controls. Remove excess height in iOS 5 devices. */ -audio:not([controls]) { display: none; height: 0; } - -/** Address styling not present in IE 7/8/9, Firefox 3, and Safari 4. Known issue: no IE 6 support. */ -[hidden] { display: none; } - -/* ========================================================================== Base ========================================================================== */ -/** 1. Correct text resizing oddly in IE 6/7 when body `font-size` is set using `em` units. 2. Prevent iOS text size adjust after orientation change, without disabling user zoom. */ -html { font-size: 100%; /* 1 */ -ms-text-size-adjust: 100%; /* 2 */ -webkit-text-size-adjust: 100%; /* 2 */ font-family: sans-serif; } - -/** Address `font-family` inconsistency between `textarea` and other form elements. */ -button, input, select, textarea { font-family: sans-serif; } - -/** Address margins handled incorrectly in IE 6/7. */ -body { margin: 0; } - -/* ========================================================================== Links ========================================================================== */ -/** Address `outline` inconsistency between Chrome and other browsers. */ -a:focus { outline: thin dotted; } -a:active, a:hover { outline: 0; } - -/** Improve readability when focused and also mouse hovered in all browsers. */ -/* ========================================================================== Typography ========================================================================== */ -/** Address font sizes and margins set differently in IE 6/7. Address font sizes within `section` and `article` in Firefox 4+, Safari 5, and Chrome. */ -h1 { font-size: 2em; margin: 0.67em 0; } - -h2 { font-size: 1.5em; margin: 0.83em 0; } - -h3 { font-size: 1.17em; margin: 1em 0; } - -h4, .tsd-index-panel h3 { font-size: 1em; margin: 1.33em 0; } - -h5 { font-size: 0.83em; margin: 1.67em 0; } - -h6 { font-size: 0.67em; margin: 2.33em 0; } - -/** Address styling not present in IE 7/8/9, Safari 5, and Chrome. */ -abbr[title] { border-bottom: 1px dotted; } - -/** Address style set to `bolder` in Firefox 3+, Safari 4/5, and Chrome. */ -b, strong { font-weight: bold; } - -blockquote { margin: 1em 40px; } - -/** Address styling not present in Safari 5 and Chrome. */ -dfn { font-style: italic; } - -/** Address differences between Firefox and other browsers. Known issue: no IE 6/7 normalization. */ -hr { box-sizing: content-box; height: 0; } - -/** Address styling not present in IE 6/7/8/9. */ -mark { background: #ff0; color: #000; } - -/** Address margins set differently in IE 6/7. */ -p, pre { margin: 1em 0; } - -/** Improve readability of pre-formatted text in all browsers. */ -pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; } - -/** Address CSS quotes not supported in IE 6/7. */ -q { quotes: none; } -q:before, q:after { content: ""; content: none; } - -/** Address `quotes` property not supported in Safari 4. */ -/** Address inconsistent and variable font size in all browsers. */ -small { font-size: 80%; } - -/** Prevent `sub` and `sup` affecting `line-height` in all browsers. */ -sub { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } - -sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; top: -0.5em; } - -sub { bottom: -0.25em; } - -/* ========================================================================== Lists ========================================================================== */ -dd { margin: 0 0 0 40px; } - -/** Address paddings set differently in IE 6/7. */ -menu, ol, ul { padding: 0 0 0 40px; } - -/** Correct list images handled incorrectly in IE 7. */ -nav ul, nav ol { list-style: none; list-style-image: none; } - -/* ========================================================================== Embedded content ========================================================================== */ -/** 1. Remove border when inside `a` element in IE 6/7/8/9 and Firefox 3. 2. Improve image quality when scaled in IE 7. */ -img { border: 0; /* 1 */ -ms-interpolation-mode: bicubic; } - -/* 2 */ -/** Correct overflow displayed oddly in IE 9. */ -svg:not(:root) { overflow: hidden; } - -/* ========================================================================== Figures ========================================================================== */ -/** Address margin not present in IE 6/7/8/9, Safari 5, and Opera 11. */ -figure, form { margin: 0; } - -/* ========================================================================== Forms ========================================================================== */ -/** Correct margin displayed oddly in IE 6/7. */ -/** Define consistent border, margin, and padding. */ -fieldset { border: 1px solid #c0c0c0; margin: 0 2px; padding: 0.35em 0.625em 0.75em; } - -/** 1. Correct color not being inherited in IE 6/7/8/9. 2. Correct text not wrapping in Firefox 3. 3. Correct alignment displayed oddly in IE 6/7. */ -legend { border: 0; /* 1 */ padding: 0; white-space: normal; /* 2 */ *margin-left: -7px; } - -/* 3 */ -/** 1. Correct font size not being inherited in all browsers. 2. Address margins set differently in IE 6/7, Firefox 3+, Safari 5, and Chrome. 3. Improve appearance and consistency in all browsers. */ -button, input, select, textarea { font-size: 100%; /* 1 */ margin: 0; /* 2 */ vertical-align: baseline; /* 3 */ *vertical-align: middle; } - -/* 3 */ -/** Address Firefox 3+ setting `line-height` on `input` using `!important` in the UA stylesheet. */ -button, input { line-height: normal; } - -/** Address inconsistent `text-transform` inheritance for `button` and `select`. All other form control elements do not inherit `text-transform` values. Correct `button` style inheritance in Chrome, Safari 5+, and IE 6+. Correct `select` style inheritance in Firefox 4+ and Opera. */ -button, select { text-transform: none; } - -/** 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` and `video` controls. 2. Correct inability to style clickable `input` types in iOS. 3. Improve usability and consistency of cursor style between image-type `input` and others. 4. Remove inner spacing in IE 7 without affecting normal text inputs. Known issue: inner spacing remains in IE 6. */ -button, html input[type="button"] { -webkit-appearance: button; /* 2 */ cursor: pointer; /* 3 */ *overflow: visible; } - -/* 4 */ -input[type="reset"], input[type="submit"] { -webkit-appearance: button; /* 2 */ cursor: pointer; /* 3 */ *overflow: visible; } - -/* 4 */ -/** Re-set default cursor for disabled elements. */ -button[disabled], html input[disabled] { cursor: default; } - -/** 1. Address box sizing set to content-box in IE 8/9. 2. Remove excess padding in IE 8/9. 3. Remove excess padding in IE 7. Known issue: excess padding remains in IE 6. */ -input { /* 3 */ } -input[type="checkbox"], input[type="radio"] { box-sizing: border-box; /* 1 */ padding: 0; /* 2 */ *height: 13px; /* 3 */ *width: 13px; } -input[type="search"] { -webkit-appearance: textfield; /* 1 */ /* 2 */ box-sizing: content-box; } -input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } - -/** 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome (include `-moz` to future-proof). */ -/** Remove inner padding and search cancel button in Safari 5 and Chrome on OS X. */ -/** Remove inner padding and border in Firefox 3+. */ -button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } - -/** 1. Remove default vertical scrollbar in IE 6/7/8/9. 2. Improve readability and alignment in all browsers. */ -textarea { overflow: auto; /* 1 */ vertical-align: top; } - -/* 2 */ -/* ========================================================================== Tables ========================================================================== */ -/** Remove most spacing between table cells. */ -table { border-collapse: collapse; border-spacing: 0; } - -.hljs { display: inline-block; padding: 0.5em; background: white; color: #37474f; } - -.hljs-comment, -.hljs-annotation, -.hljs-template_comment, -.diff .hljs-header, -.hljs-chunk, -.apache .hljs-cbracket { color: #d81b60; } - -.hljs-keyword, -.hljs-id, -.hljs-built_in, -.css .smalltalk .hljs-class, -.hljs-winutils, -.bash .hljs-variable, -.tex .hljs-command, -.hljs-request, -.hljs-status, -.hljs-meta, -.nginx .hljs-title { color: #3b78e7; } - -.xml .hljs-tag { color: #3b78e7; } -.xml .hljs-tag .hljs-value { color: #3b78e7; } - -.hljs-string, -.hljs-title, -.hljs-parent, -.hljs-tag .hljs-value, -.hljs-rules .hljs-value { color: #0d904f; } - -.devsite-dark-code .hljs { display: inline-block; padding: 0.5em; background: white; color: #eceff1; } - -.devsite-dark-code .hljs-comment, -.devsite-dark-code .hljs-annotation, -.devsite-dark-code .hljs-template_comment, -.devsite-dark-code .diff .hljs-header, -.devsite-dark-code .hljs-chunk { color: #f06292; } - -.devsite-dark-code .hljs-keyword, -.devsite-dark-code .hljs-id, -.devsite-dark-code .hljs-built_in, -.devsite-dark-code .hljs-winutils, -.devsite-dark-code .hljs-request, -.devsite-dark-code .hljs-status, -.devsite-dark-code .hljs-meta { color: #4dd0e1; } - -.devsite-dark-code .hljs-string, -.devsite-dark-code .hljs-title, -.devsite-dark-code .hljs-parent, -.devsite-dark-code .hljs-tag .hljs-value, -.devsite-dark-code .hljs-rules .hljs-value { color: #9ccc65; } - -.col > :first-child, .col-1 > :first-child, .col-2 > :first-child, .col-3 > :first-child, .col-4 > :first-child, .col-5 > :first-child, .col-6 > :first-child, .col-7 > :first-child, .col-8 > :first-child, .col-9 > :first-child, .col-10 > :first-child, .col-11 > :first-child, .tsd-panel > :first-child, ul.tsd-descriptions > li > :first-child, .col > :first-child > :first-child, .col-1 > :first-child > :first-child, .col-2 > :first-child > :first-child, .col-3 > :first-child > :first-child, .col-4 > :first-child > :first-child, .col-5 > :first-child > :first-child, .col-6 > :first-child > :first-child, .col-7 > :first-child > :first-child, .col-8 > :first-child > :first-child, .col-9 > :first-child > :first-child, .col-10 > :first-child > :first-child, .col-11 > :first-child > :first-child, .tsd-panel > :first-child > :first-child, ul.tsd-descriptions > li > :first-child > :first-child, .col > :first-child > :first-child > :first-child, .col-1 > :first-child > :first-child > :first-child, .col-2 > :first-child > :first-child > :first-child, .col-3 > :first-child > :first-child > :first-child, .col-4 > :first-child > :first-child > :first-child, .col-5 > :first-child > :first-child > :first-child, .col-6 > :first-child > :first-child > :first-child, .col-7 > :first-child > :first-child > :first-child, .col-8 > :first-child > :first-child > :first-child, .col-9 > :first-child > :first-child > :first-child, .col-10 > :first-child > :first-child > :first-child, .col-11 > :first-child > :first-child > :first-child, .tsd-panel > :first-child > :first-child > :first-child, ul.tsd-descriptions > li > :first-child > :first-child > :first-child { margin-top: 0; } -.col > :last-child, .col-1 > :last-child, .col-2 > :last-child, .col-3 > :last-child, .col-4 > :last-child, .col-5 > :last-child, .col-6 > :last-child, .col-7 > :last-child, .col-8 > :last-child, .col-9 > :last-child, .col-10 > :last-child, .col-11 > :last-child, .tsd-panel > :last-child, ul.tsd-descriptions > li > :last-child, .col > :last-child > :last-child, .col-1 > :last-child > :last-child, .col-2 > :last-child > :last-child, .col-3 > :last-child > :last-child, .col-4 > :last-child > :last-child, .col-5 > :last-child > :last-child, .col-6 > :last-child > :last-child, .col-7 > :last-child > :last-child, .col-8 > :last-child > :last-child, .col-9 > :last-child > :last-child, .col-10 > :last-child > :last-child, .col-11 > :last-child > :last-child, .tsd-panel > :last-child > :last-child, ul.tsd-descriptions > li > :last-child > :last-child, .col > :last-child > :last-child > :last-child, .col-1 > :last-child > :last-child > :last-child, .col-2 > :last-child > :last-child > :last-child, .col-3 > :last-child > :last-child > :last-child, .col-4 > :last-child > :last-child > :last-child, .col-5 > :last-child > :last-child > :last-child, .col-6 > :last-child > :last-child > :last-child, .col-7 > :last-child > :last-child > :last-child, .col-8 > :last-child > :last-child > :last-child, .col-9 > :last-child > :last-child > :last-child, .col-10 > :last-child > :last-child > :last-child, .col-11 > :last-child > :last-child > :last-child, .tsd-panel > :last-child > :last-child > :last-child, ul.tsd-descriptions > li > :last-child > :last-child > :last-child { margin-bottom: 0; } - -.container { max-width: 1200px; margin: 0 auto; padding: 0 40px; } -@media (max-width: 640px) { .container { padding: 0 20px; } } - -.container-main { padding-bottom: 200px; } - -.row { position: relative; margin: 0 -10px; } -.row:after { visibility: hidden; display: block; content: ""; clear: both; height: 0; } - -.col, .col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11 { box-sizing: border-box; float: left; padding: 0 10px; } - -.col-1 { width: 8.33333%; } - -.offset-1 { margin-left: 8.33333%; } - -.col-2 { width: 16.66667%; } - -.offset-2 { margin-left: 16.66667%; } - -.col-3 { width: 25%; } - -.offset-3 { margin-left: 25%; } - -.col-4 { width: 33.33333%; } - -.offset-4 { margin-left: 33.33333%; } - -.col-5 { width: 41.66667%; } - -.offset-5 { margin-left: 41.66667%; } - -.col-6 { width: 50%; } - -.offset-6 { margin-left: 50%; } - -.col-7 { width: 58.33333%; } - -.offset-7 { margin-left: 58.33333%; } - -.col-8 { width: 66.66667%; } - -.offset-8 { margin-left: 66.66667%; } - -.col-9 { width: 75%; } - -.offset-9 { margin-left: 75%; } - -.col-10 { width: 83.33333%; } - -.offset-10 { margin-left: 83.33333%; } - -.col-11 { width: 91.66667%; } - -.offset-11 { margin-left: 91.66667%; } - -.tsd-kind-icon { display: block; position: relative; padding-left: 20px; text-indent: -20px; } - -.no-transition { transition: none !important; } - -@-webkit-keyframes fade-in { from { opacity: 0; } - to { opacity: 1; } } - -@keyframes fade-in { from { opacity: 0; } - to { opacity: 1; } } -@-webkit-keyframes fade-out { from { opacity: 1; visibility: visible; } - to { opacity: 0; } } -@keyframes fade-out { from { opacity: 1; visibility: visible; } - to { opacity: 0; } } -@-webkit-keyframes fade-in-delayed { 0% { opacity: 0; } - 33% { opacity: 0; } - 100% { opacity: 1; } } -@keyframes fade-in-delayed { 0% { opacity: 0; } - 33% { opacity: 0; } - 100% { opacity: 1; } } -@-webkit-keyframes fade-out-delayed { 0% { opacity: 1; visibility: visible; } - 66% { opacity: 0; } - 100% { opacity: 0; } } -@keyframes fade-out-delayed { 0% { opacity: 1; visibility: visible; } - 66% { opacity: 0; } - 100% { opacity: 0; } } -@-webkit-keyframes shift-to-left { from { -webkit-transform: translate(0, 0); transform: translate(0, 0); } - to { -webkit-transform: translate(-25%, 0); transform: translate(-25%, 0); } } -@keyframes shift-to-left { from { -webkit-transform: translate(0, 0); transform: translate(0, 0); } - to { -webkit-transform: translate(-25%, 0); transform: translate(-25%, 0); } } -@-webkit-keyframes unshift-to-left { from { -webkit-transform: translate(-25%, 0); transform: translate(-25%, 0); } - to { -webkit-transform: translate(0, 0); transform: translate(0, 0); } } -@keyframes unshift-to-left { from { -webkit-transform: translate(-25%, 0); transform: translate(-25%, 0); } - to { -webkit-transform: translate(0, 0); transform: translate(0, 0); } } -@-webkit-keyframes pop-in-from-right { from { -webkit-transform: translate(100%, 0); transform: translate(100%, 0); } - to { -webkit-transform: translate(0, 0); transform: translate(0, 0); } } -@keyframes pop-in-from-right { from { -webkit-transform: translate(100%, 0); transform: translate(100%, 0); } - to { -webkit-transform: translate(0, 0); transform: translate(0, 0); } } -@-webkit-keyframes pop-out-to-right { from { -webkit-transform: translate(0, 0); transform: translate(0, 0); visibility: visible; } - to { -webkit-transform: translate(100%, 0); transform: translate(100%, 0); } } -@keyframes pop-out-to-right { from { -webkit-transform: translate(0, 0); transform: translate(0, 0); visibility: visible; } - to { -webkit-transform: translate(100%, 0); transform: translate(100%, 0); } } - -a { color: #4da6ff; text-decoration: none; } -a:hover { text-decoration: underline; } - -pre { padding: 10px; } -pre code { padding: 0; font-size: 100%; background-color: transparent; } - -.tsd-typography ul { list-style: square; padding: 0 0 0 20px; margin: 0; } -.tsd-typography h4, .tsd-typography .tsd-index-panel h3, .tsd-index-panel .tsd-typography h3, .tsd-typography h5, .tsd-typography h6 { font-size: 1em; margin: 0; } -.tsd-typography h5, .tsd-typography h6 { font-weight: normal; } -.tsd-typography p, .tsd-typography ul, .tsd-typography ol { margin: 1em 0; } - -@media (min-width: 901px) and (max-width: 1024px) { html.default .col-content { width: 72%; } - html.default .col-menu { width: 28%; } - html.default .tsd-navigation { padding-left: 10px; } } -@media (max-width: 900px) { html.default .col-content { float: none; width: 100%; } - html.default .col-menu { position: fixed !important; overflow: auto; -webkit-overflow-scrolling: touch; overflow-scrolling: touch; z-index: 1024; top: 0 !important; bottom: 0 !important; left: auto !important; right: 0 !important; width: 100%; padding: 20px 20px 0 0; max-width: 450px; visibility: hidden; background-color: #fff; -webkit-transform: translate(100%, 0); transform: translate(100%, 0); } - html.default .col-menu > *:last-child { padding-bottom: 20px; } - html.default .overlay { content: ""; display: block; position: fixed; z-index: 1023; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.75); visibility: hidden; } - html.default.to-has-menu .overlay { -webkit-animation: fade-in 0.4s; animation: fade-in 0.4s; } - html.default.to-has-menu header, html.default.to-has-menu footer, html.default.to-has-menu .col-content { -webkit-animation: shift-to-left 0.4s; animation: shift-to-left 0.4s; } - html.default.to-has-menu .col-menu { -webkit-animation: pop-in-from-right 0.4s; animation: pop-in-from-right 0.4s; } - html.default.from-has-menu .overlay { -webkit-animation: fade-out 0.4s; animation: fade-out 0.4s; } - html.default.from-has-menu header, html.default.from-has-menu footer, html.default.from-has-menu .col-content { -webkit-animation: unshift-to-left 0.4s; animation: unshift-to-left 0.4s; } - html.default.from-has-menu .col-menu { -webkit-animation: pop-out-to-right 0.4s; animation: pop-out-to-right 0.4s; } - html.default.has-menu body { overflow: hidden; } - html.default.has-menu .overlay { visibility: visible; } - html.default.has-menu header, html.default.has-menu footer, html.default.has-menu .col-content { -webkit-transform: translate(-25%, 0); transform: translate(-25%, 0); } - html.default.has-menu .col-menu { visibility: visible; -webkit-transform: translate(0, 0); transform: translate(0, 0); } } - -.tsd-page-title { padding: 0; margin: 0; background: #fff; } -.tsd-page-title h1 { font-weight: normal; margin: 0; } - -.tsd-breadcrumb { margin: 0; padding: 0; color: #808080; } -.tsd-breadcrumb a { color: #808080; text-decoration: none; } -.tsd-breadcrumb a:hover { text-decoration: underline; } -.tsd-breadcrumb li { display: inline; margin-right: -0.25em; } - -html.minimal .container { margin: 0; } -html.minimal .container-main { padding-top: 50px; padding-bottom: 0; } -html.minimal .content-wrap { padding-left: 300px; } -html.minimal .tsd-navigation { position: fixed !important; overflow: auto; -webkit-overflow-scrolling: touch; overflow-scrolling: touch; box-sizing: border-box; z-index: 1; left: 0; top: 40px; bottom: 0; width: 300px; padding: 20px; margin: 0; } -html.minimal .tsd-member .tsd-member { margin-left: 0; } -html.minimal .tsd-page-toolbar { position: fixed; z-index: 2; } -html.minimal #tsd-filter .tsd-filter-group { right: 0; -webkit-transform: none; transform: none; } -html.minimal footer { background-color: transparent; } -html.minimal footer .container { padding: 0; } -html.minimal .tsd-generator { padding: 0; } -@media (max-width: 900px) { html.minimal .tsd-navigation { display: none; } - html.minimal .content-wrap { padding-left: 0; } } - -dl.tsd-comment-tags { overflow: hidden; } -dl.tsd-comment-tags dt { clear: both; float: left; padding: 1px 5px; margin: 0 10px 0 0; border-radius: 4px; border: 1px solid #808080; color: #808080; font-size: 0.8em; font-weight: normal; } -dl.tsd-comment-tags dd { margin: 0 0 10px 0; } -dl.tsd-comment-tags p { margin: 0; } - -.tsd-panel.tsd-comment .lead { font-size: 1.1em; line-height: 1.333em; margin-bottom: 2em; } -.tsd-panel.tsd-comment .lead:last-child { margin-bottom: 0; } - -.toggle-protected .tsd-is-private { display: none; } - -.toggle-public .tsd-is-private, .toggle-public .tsd-is-protected, .toggle-public .tsd-is-private-protected { display: none; } - -.toggle-inherited .tsd-is-inherited { display: none; } - -.toggle-only-exported .tsd-is-not-exported { display: none; } - -.toggle-externals .tsd-is-external { display: none; } - -#tsd-filter { position: relative; display: inline-block; height: 40px; vertical-align: bottom; } -.no-filter #tsd-filter { display: none; } -#tsd-filter .tsd-filter-group { display: inline-block; height: 40px; vertical-align: bottom; white-space: nowrap; } -#tsd-filter input { display: none; } -@media (max-width: 900px) { #tsd-filter .tsd-filter-group { display: block; position: absolute; top: 40px; right: 20px; height: auto; background-color: #fff; visibility: hidden; -webkit-transform: translate(50%, 0); transform: translate(50%, 0); box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); } - .has-options #tsd-filter .tsd-filter-group { visibility: visible; } - .to-has-options #tsd-filter .tsd-filter-group { -webkit-animation: fade-in 0.2s; animation: fade-in 0.2s; } - .from-has-options #tsd-filter .tsd-filter-group { -webkit-animation: fade-out 0.2s; animation: fade-out 0.2s; } - #tsd-filter label, #tsd-filter .tsd-select { display: block; padding-right: 20px; } } - -footer { border-top: 1px solid #eee; background-color: #fff; } -footer.with-border-bottom { border-bottom: 1px solid #eee; } -footer .tsd-legend-group { font-size: 0; } -footer .tsd-legend { display: inline-block; width: 25%; padding: 0; font-size: 16px; list-style: none; line-height: 1.333em; vertical-align: top; } -@media (max-width: 900px) { footer .tsd-legend { width: 50%; } } - -.tsd-hierarchy { list-style: square; padding: 0 0 0 20px; margin: 0; } -.tsd-hierarchy .target { font-weight: bold; } - -.tsd-index-panel .tsd-index-content { margin-bottom: -30px !important; } -.tsd-index-panel .tsd-index-section { margin-bottom: 30px !important; } -.tsd-index-panel h3 { margin: 0 -20px 10px -20px; padding: 0 20px 10px 20px; border-bottom: 1px solid #eee; } -.tsd-index-panel ul.tsd-index-list { -webkit-column-count: 3; -moz-column-count: 3; -ms-column-count: 3; -o-column-count: 3; column-count: 3; -webkit-column-gap: 20px; -moz-column-gap: 20px; -ms-column-gap: 20px; -o-column-gap: 20px; column-gap: 20px; padding: 0; list-style: none; line-height: 1.333em; } -@media (max-width: 900px) { .tsd-index-panel ul.tsd-index-list { -webkit-column-count: 1; -moz-column-count: 1; -ms-column-count: 1; -o-column-count: 1; column-count: 1; } } -@media (min-width: 901px) and (max-width: 1024px) { .tsd-index-panel ul.tsd-index-list { -webkit-column-count: 2; -moz-column-count: 2; -ms-column-count: 2; -o-column-count: 2; column-count: 2; } } -.tsd-index-panel ul.tsd-index-list li { -webkit-column-break-inside: avoid; -moz-column-break-inside: avoid; -ms-column-break-inside: avoid; -o-column-break-inside: avoid; column-break-inside: avoid; -webkit-page-break-inside: avoid; -moz-page-break-inside: avoid; -ms-page-break-inside: avoid; -o-page-break-inside: avoid; page-break-inside: avoid; } - -.tsd-flag { display: inline-block; padding: 1px 5px; border-radius: 4px; color: #fff; background-color: #808080; text-indent: 0; font-size: 14px; font-weight: normal; line-height: 1.5em; } - -.tsd-anchor { position: absolute; top: -100px; } - -.tsd-member { position: relative; } -.tsd-member .tsd-anchor + h3 { margin-top: 0; margin-bottom: 0; border-bottom: none; } - -.tsd-navigation { padding: 0 0 0 40px; } -.tsd-navigation a { display: block; padding-top: 2px; padding-bottom: 2px; border-left: 2px solid transparent; color: #222; text-decoration: none; transition: border-left-color 0.1s; } -.tsd-navigation a:hover { text-decoration: underline; } -.tsd-navigation ul { margin: 0; padding: 0; list-style: none; } -.tsd-navigation li { padding: 0; } - -.tsd-navigation.primary { padding-bottom: 40px; } -.tsd-navigation.primary a { display: block; padding-top: 6px; padding-bottom: 6px; } -.tsd-navigation.primary ul li a { padding-left: 5px; } -.tsd-navigation.primary ul li li a { padding-left: 25px; } -.tsd-navigation.primary ul li li li a { padding-left: 45px; } -.tsd-navigation.primary ul li li li li a { padding-left: 65px; } -.tsd-navigation.primary ul li li li li li a { padding-left: 85px; } -.tsd-navigation.primary ul li li li li li li a { padding-left: 105px; } -.tsd-navigation.primary > ul { border-bottom: 1px solid #eee; } -.tsd-navigation.primary li { border-top: 1px solid #eee; } -.tsd-navigation.primary li.current > a { font-weight: bold; } -.tsd-navigation.primary li.label span { display: block; padding: 20px 0 6px 5px; color: #808080; } -.tsd-navigation.primary li.globals + li > span, .tsd-navigation.primary li.globals + li > a { padding-top: 20px; } - -.tsd-navigation.secondary ul { transition: opacity 0.2s; } -.tsd-navigation.secondary ul li a { padding-left: 25px; } -.tsd-navigation.secondary ul li li a { padding-left: 45px; } -.tsd-navigation.secondary ul li li li a { padding-left: 65px; } -.tsd-navigation.secondary ul li li li li a { padding-left: 85px; } -.tsd-navigation.secondary ul li li li li li a { padding-left: 105px; } -.tsd-navigation.secondary ul li li li li li li a { padding-left: 125px; } -.tsd-navigation.secondary ul.current a { border-left-color: #eee; } -.tsd-navigation.secondary li.focus > a, .tsd-navigation.secondary ul.current li.focus > a { border-left-color: #000; } -.tsd-navigation.secondary li.current { margin-top: 20px; margin-bottom: 20px; border-left-color: #eee; } -.tsd-navigation.secondary li.current > a { font-weight: bold; } - -@media (min-width: 901px) { .menu-sticky-wrap { position: static; } - .no-csspositionsticky .menu-sticky-wrap.sticky { position: fixed; } - .no-csspositionsticky .menu-sticky-wrap.sticky-current { position: fixed; } - .no-csspositionsticky .menu-sticky-wrap.sticky-current ul.before-current, .no-csspositionsticky .menu-sticky-wrap.sticky-current ul.after-current { opacity: 0; } - .no-csspositionsticky .menu-sticky-wrap.sticky-bottom { position: absolute; top: auto !important; left: auto !important; bottom: 0; right: 0; } - .csspositionsticky .menu-sticky-wrap.sticky { position: -webkit-sticky; position: sticky; } - .csspositionsticky .menu-sticky-wrap.sticky-current { position: -webkit-sticky; position: sticky; } } - -.tsd-panel { margin: 20px 0; padding: 20px; background-color: #fff; } -.tsd-panel:empty { display: none; } -.tsd-panel > h1, .tsd-panel > h2, .tsd-panel > h3 { margin: 1.5em -20px 10px -20px; padding: 0 20px 10px 20px; border-bottom: 1px solid #ebebeb; } -.tsd-panel > h1.tsd-before-signature, .tsd-panel > h2.tsd-before-signature, .tsd-panel > h3.tsd-before-signature { margin-bottom: 0; border-bottom: 0; } -.tsd-panel table { display: block; width: 100%; overflow: auto; margin-top: 10px; word-break: normal; word-break: keep-all; } -.tsd-panel table th { font-weight: bold; } -.tsd-panel table th, .tsd-panel table td { padding: 6px 13px; border: 1px solid #ddd; } -.tsd-panel table tr { background-color: #fff; border-top: 1px solid #ccc; } -.tsd-panel table tr:nth-child(2n) { background-color: #f8f8f8; } - -.tsd-panel-group { margin: 60px 0; } -.tsd-panel-group > h1, .tsd-panel-group > h2, .tsd-panel-group > h3 { padding-left: 20px; padding-right: 20px; } - -#tsd-search { transition: background-color 0.2s; } -#tsd-search .title { position: relative; z-index: 2; } -#tsd-search .field { position: absolute; left: 0; top: 0; right: 40px; height: 40px; } -#tsd-search .field input { box-sizing: border-box; position: relative; top: -50px; z-index: 1; width: 100%; padding: 0 10px; opacity: 0; outline: 0; border: 0; background: transparent; color: #222; } -#tsd-search .field label { position: absolute; overflow: hidden; right: -40px; } -#tsd-search .field input, #tsd-search .title { transition: opacity 0.2s; } -#tsd-search .results { position: absolute; visibility: hidden; top: 40px; width: 100%; margin: 0; padding: 0; list-style: none; box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); } -#tsd-search .results li { padding: 0 10px; background-color: #fdfdfd; } -#tsd-search .results li:nth-child(even) { background-color: #fff; } -#tsd-search .results li.state { display: none; } -#tsd-search .results li.current, #tsd-search .results li:hover { background-color: #eee; } -#tsd-search .results a { display: block; } -#tsd-search .results a:before { top: 10px; } -#tsd-search .results span.parent { color: #808080; font-weight: normal; } -#tsd-search.has-focus { background-color: #eee; } -#tsd-search.has-focus .field input { top: 0; opacity: 1; } -#tsd-search.has-focus .title { z-index: 0; opacity: 0; } -#tsd-search.has-focus .results { visibility: visible; } -#tsd-search.loading .results li.state.loading { display: block; } -#tsd-search.failure .results li.state.failure { display: block; } - -.tsd-signature { margin: 0 0 1em 0; padding: 10px; border: 1px solid #eee; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 14px; } -.tsd-signature.tsd-kind-icon { padding-left: 30px; } -.tsd-panel > .tsd-signature { margin-left: -20px; margin-right: -20px; border-width: 1px 0; } -.tsd-panel > .tsd-signature.tsd-kind-icon { padding-left: 40px; } - -.tsd-signature-symbol { color: #808080; font-weight: normal; } - -.tsd-signature-type { font-style: italic; font-weight: normal; } - -.tsd-signatures { padding: 0; margin: 0 0 1em 0; border: 1px solid #eee; } -.tsd-signatures .tsd-signature { margin: 0; border-width: 1px 0 0 0; transition: background-color 0.1s; } -.tsd-signatures .tsd-signature:first-child { border-top-width: 0; } -.tsd-signatures .tsd-signature.current { background-color: #eee; } -.tsd-signatures.active > .tsd-signature { cursor: pointer; } -.tsd-panel > .tsd-signatures { margin-left: -20px; margin-right: -20px; border-width: 1px 0; } -.tsd-panel > .tsd-signatures .tsd-signature.tsd-kind-icon { padding-left: 40px; } -.tsd-panel > a.anchor + .tsd-signatures { border-top-width: 0; margin-top: -20px; } - -ul.tsd-descriptions { position: relative; overflow: hidden; transition: height 0.3s; padding: 0; list-style: none; } -ul.tsd-descriptions.active > .tsd-description { display: none; } -ul.tsd-descriptions.active > .tsd-description.current { display: block; } -ul.tsd-descriptions.active > .tsd-description.fade-in { -webkit-animation: fade-in-delayed 0.3s; animation: fade-in-delayed 0.3s; } -ul.tsd-descriptions.active > .tsd-description.fade-out { -webkit-animation: fade-out-delayed 0.3s; animation: fade-out-delayed 0.3s; position: absolute; display: block; top: 0; left: 0; right: 0; opacity: 0; visibility: hidden; } -ul.tsd-descriptions h4, ul.tsd-descriptions .tsd-index-panel h3, .tsd-index-panel ul.tsd-descriptions h3 { font-size: 16px; margin: 1em 0 0.5em 0; } - -ul.tsd-parameters, ul.tsd-type-parameters { list-style: square; margin: 0; padding-left: 20px; } -ul.tsd-parameters > li.tsd-parameter-siganture, ul.tsd-type-parameters > li.tsd-parameter-siganture { list-style: none; margin-left: -20px; } -ul.tsd-parameters h5, ul.tsd-type-parameters h5 { font-size: 16px; margin: 1em 0 0.5em 0; } -ul.tsd-parameters .tsd-comment, ul.tsd-type-parameters .tsd-comment { margin-top: -0.5em; } - -.tsd-sources { font-size: 14px; color: #808080; } -.tsd-sources a { color: #808080; text-decoration: underline; } -.tsd-sources ul, .tsd-sources p { margin: 0 !important; } -.tsd-sources ul { list-style: none; padding: 0; } - -.tsd-page-toolbar { position: absolute; z-index: 1; top: 0; left: 0; width: 100%; height: 40px; color: #333; background: #fff; border-bottom: 1px solid #eee; } -.tsd-page-toolbar a { color: #333; text-decoration: none; } -.tsd-page-toolbar a.title { font-weight: bold; } -.tsd-page-toolbar a.title:hover { text-decoration: underline; } -.tsd-page-toolbar .table-wrap { display: table; width: 100%; height: 40px; } -.tsd-page-toolbar .table-cell { display: table-cell; position: relative; white-space: nowrap; line-height: 40px; } -.tsd-page-toolbar .table-cell:first-child { width: 100%; } - -.tsd-widget:before, .tsd-select .tsd-select-label:before, .tsd-select .tsd-select-list li:before { content: ""; display: inline-block; width: 40px; height: 40px; margin: 0 -8px 0 0; background-image: url(../images/widgets.png); background-repeat: no-repeat; text-indent: -1024px; vertical-align: bottom; } -@media (-webkit-min-device-pixel-ratio: 1.5), (min-device-pixel-ratio: 1.5), (min-resolution: 144dpi) { .tsd-widget:before, .tsd-select .tsd-select-label:before, .tsd-select .tsd-select-list li:before { background-image: url(../images/widgets@2x.png); background-size: 320px 40px; } } - -.tsd-widget { display: inline-block; overflow: hidden; opacity: 0.6; height: 40px; transition: opacity 0.1s, background-color 0.2s; vertical-align: bottom; cursor: pointer; } -.tsd-widget:hover { opacity: 0.8; } -.tsd-widget.active { opacity: 1; background-color: #eee; } -.tsd-widget.no-caption { width: 40px; } -.tsd-widget.no-caption:before { margin: 0; } -.tsd-widget.search:before { background-position: 0 0; } -.tsd-widget.menu:before { background-position: -40px 0; } -.tsd-widget.options:before { background-position: -80px 0; } -.tsd-widget.options, .tsd-widget.menu { display: none; } -@media (max-width: 900px) { .tsd-widget.options, .tsd-widget.menu { display: inline-block; } } -input[type=checkbox] + .tsd-widget:before { background-position: -120px 0; } -input[type=checkbox]:checked + .tsd-widget:before { background-position: -160px 0; } - -.tsd-select { position: relative; display: inline-block; height: 40px; transition: opacity 0.1s, background-color 0.2s; vertical-align: bottom; cursor: pointer; } -.tsd-select .tsd-select-label { opacity: 0.6; transition: opacity 0.2s; } -.tsd-select .tsd-select-label:before { background-position: -240px 0; } -.tsd-select.active .tsd-select-label { opacity: 0.8; } -.tsd-select.active .tsd-select-list { visibility: visible; opacity: 1; transition-delay: 0s; } -.tsd-select .tsd-select-list { position: absolute; visibility: hidden; top: 40px; left: 0; margin: 0; padding: 0; opacity: 0; list-style: none; box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); transition: visibility 0s 0.2s, opacity 0.2s; } -.tsd-select .tsd-select-list li { padding: 0 20px 0 0; background-color: #fdfdfd; } -.tsd-select .tsd-select-list li:before { background-position: 40px 0; } -.tsd-select .tsd-select-list li:nth-child(even) { background-color: #fff; } -.tsd-select .tsd-select-list li:hover { background-color: #eee; } -.tsd-select .tsd-select-list li.selected:before { background-position: -200px 0; } -@media (max-width: 900px) { .tsd-select .tsd-select-list { top: 0; left: auto; right: 100%; margin-right: -5px; } - .tsd-select .tsd-select-label:before { background-position: -280px 0; } } - -img { max-width: 100%; } diff --git a/docgen/theme/assets/images/lockup.png b/docgen/theme/assets/images/lockup.png deleted file mode 100644 index f4cdcf24a4..0000000000 Binary files a/docgen/theme/assets/images/lockup.png and /dev/null differ diff --git a/docgen/theme/layouts/default.hbs b/docgen/theme/layouts/default.hbs deleted file mode 100644 index 485d831d71..0000000000 --- a/docgen/theme/layouts/default.hbs +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - {{#ifCond model.name '==' project.name}}{{project.name}}{{else}}{{model.name}} | {{project.name}}{{/ifCond}} - - - - - - - -{{> header}} - -
    -
    -
    - {{{contents}}} -
    -
    -
    - -
    - -{{> analytics}} - - - diff --git a/docgen/theme/partials/breadcrumb.hbs b/docgen/theme/partials/breadcrumb.hbs deleted file mode 100644 index db115163f7..0000000000 --- a/docgen/theme/partials/breadcrumb.hbs +++ /dev/null @@ -1,11 +0,0 @@ - -{{#if parent}} - {{#with parent}}{{> breadcrumb}}{{/with}} - -{{/if}} \ No newline at end of file diff --git a/docgen/theme/partials/comment.hbs b/docgen/theme/partials/comment.hbs deleted file mode 100644 index f92e54301a..0000000000 --- a/docgen/theme/partials/comment.hbs +++ /dev/null @@ -1,22 +0,0 @@ -{{#with comment}} - {{#if hasVisibleComponent}} -
    - {{#if shortText}} -
    - {{#markdown}}{{{shortText}}}{{/markdown}} -
    - {{/if}} - {{#if text}} - {{#markdown}}{{{text}}}{{/markdown}} - {{/if}} - {{#if tags}} -
    - {{#each tags}} -
    {{tagName}}
    -
    {{#markdown}}{{{text}}}{{/markdown}}
    - {{/each}} -
    - {{/if}} -
    - {{/if}} -{{/with}} \ No newline at end of file diff --git a/docgen/theme/partials/header.hbs b/docgen/theme/partials/header.hbs deleted file mode 100644 index 4aee65a6b3..0000000000 --- a/docgen/theme/partials/header.hbs +++ /dev/null @@ -1,23 +0,0 @@ -
    -
    -
    -

    - {{#ifCond model.name '==' project.name}} - {{else}} -
      - {{#with model.parent}}{{> breadcrumb}}{{/with}} -
    • {{model.name}}
    • - {{#if model.typeParameters}} - < - {{#each model.typeParameters}} - {{#if @index}}, {{/if}} - {{name}} - {{/each}} - > - {{/if}} -
    - {{/ifCond}} -

    -
    -
    -
    \ No newline at end of file diff --git a/docgen/theme/partials/member.sources.hbs b/docgen/theme/partials/member.sources.hbs deleted file mode 100644 index 5a0e186f01..0000000000 --- a/docgen/theme/partials/member.sources.hbs +++ /dev/null @@ -1,15 +0,0 @@ -{{#if implementationOf}} - -{{/if}} -{{#if inheritedFrom}} - -{{/if}} -{{#if overwrites}} - -{{/if}} \ No newline at end of file diff --git a/docgen/theme/partials/navigation.hbs b/docgen/theme/partials/navigation.hbs deleted file mode 100644 index 54704739a8..0000000000 --- a/docgen/theme/partials/navigation.hbs +++ /dev/null @@ -1,22 +0,0 @@ -{{#if isVisible}} - {{#if isLabel}} -
  • - {{{wbr title}}} -
  • - {{else}} - {{#unless isGlobals}} -
  • - {{{wbr title}}} - {{#if isInPath}} - {{#if children}} -
      - {{#each children}} - {{> navigation}} - {{/each}} -
    - {{/if}} - {{/if}} -
  • - {{/unless}} - {{/if}} -{{/if}} diff --git a/docgen/theme/templates/reflection.hbs b/docgen/theme/templates/reflection.hbs deleted file mode 100644 index 53cd2879a4..0000000000 --- a/docgen/theme/templates/reflection.hbs +++ /dev/null @@ -1,72 +0,0 @@ -{{#with model}} - {{#if hasComment}} -
    - {{> comment}} -
    - {{/if}} -{{/with}} - -{{#if model.typeParameters}} -
    -

    Type parameters

    - {{#with model}}{{> typeParameters}}{{/with}} -
    -{{/if}} - -{{#if model.implementedTypes}} -
    -

    Implements

    -
      - {{#each model.implementedTypes}} -
    • {{> type}}
    • - {{/each}} -
    -
    -{{/if}} - -{{#if model.implementedBy}} -
    -

    Implemented by

    -
      - {{#each model.implementedBy}} -
    • {{> type}}
    • - {{/each}} -
    -
    -{{/if}} - -{{#if model.signatures}} -
    -

    Callable

    - {{#with model}}{{> member.signatures}}{{/with}} -
    -{{/if}} - -{{#if model.indexSignature}} -
    -

    Indexable

    -
    {{#compact}} - [ - {{#each model.indexSignature.parameters}} - {{name}}: {{#with type}}{{>type}}{{/with}} - {{/each}} - ]:  - {{#with model.indexSignature.type}}{{>type}}{{/with}} - {{/compact}}
    - - {{#with model.indexSignature}} - {{> comment}} - {{/with}} - - {{#if model.indexSignature.type.declaration}} - {{#with model.indexSignature.type.declaration}} - {{> parameter}} - {{/with}} - {{/if}} -
    -{{/if}} - -{{#with model}} - {{> index}} - {{> members}} -{{/with}} \ No newline at end of file diff --git a/docgen/tsconfig.json b/docgen/tsconfig.json deleted file mode 100644 index 3c43903cfd..0000000000 --- a/docgen/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../tsconfig.json" -} diff --git a/entrypoints.json b/entrypoints.json new file mode 100644 index 0000000000..975db81888 --- /dev/null +++ b/entrypoints.json @@ -0,0 +1,59 @@ +{ + "firebase-admin": { + "legacy": true, + "typings": "./lib/default-namespace.d.ts", + "dist": "./lib/index.js" + }, + "firebase-admin/app": { + "typings": "./lib/app/index.d.ts", + "dist": "./lib/app/index.js" + }, + "firebase-admin/app-check": { + "typings": "./lib/app-check/index.d.ts", + "dist": "./lib/app-check/index.js" + }, + "firebase-admin/auth": { + "typings": "./lib/auth/index.d.ts", + "dist": "./lib/auth/index.js" + }, + "firebase-admin/database": { + "typings": "./lib/database/index.d.ts", + "dist": "./lib/database/index.js" + }, + "firebase-admin/firestore": { + "typings": "./lib/firestore/index.d.ts", + "dist": "./lib/firestore/index.js" + }, + "firebase-admin/installations": { + "typings": "./lib/installations/index.d.ts", + "dist": "./lib/installations/index.js" + }, + "firebase-admin/instance-id": { + "typings": "./lib/instance-id/index.d.ts", + "dist": "./lib/instance-id/index.js" + }, + "firebase-admin/messaging": { + "typings": "./lib/messaging/index.d.ts", + "dist": "./lib/messaging/index.js" + }, + "firebase-admin/machine-learning": { + "typings": "./lib/machine-learning/index.d.ts", + "dist": "./lib/machine-learning/index.js" + }, + "firebase-admin/project-management": { + "typings": "./lib/project-management/index.d.ts", + "dist": "./lib/project-management/index.js" + }, + "firebase-admin/security-rules": { + "typings": "./lib/security-rules/index.d.ts", + "dist": "./lib/security-rules/index.js" + }, + "firebase-admin/storage": { + "typings": "./lib/storage/index.d.ts", + "dist": "./lib/storage/index.js" + }, + "firebase-admin/remote-config": { + "typings": "./lib/remote-config/index.d.ts", + "dist": "./lib/remote-config/index.js" + } +} diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index 1a07fc3111..af4bef9361 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -8,6 +8,7 @@ import { Agent } from 'http'; import { Bucket } from '@google-cloud/storage'; +import { FirebaseDatabase } from '@firebase/database-types'; import * as _firestore from '@google-cloud/firestore'; import * as rtdb from '@firebase/database-types'; @@ -16,7 +17,8 @@ export function app(name?: string): app.App; // @public (undocumented) export namespace app { - export interface App { + // Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point default-namespace.d.ts + export interface App extends App { // (undocumented) appCheck(): appCheck.AppCheck; // (undocumented) @@ -34,8 +36,6 @@ export namespace app { machineLearning(): machineLearning.MachineLearning; // (undocumented) messaging(): messaging.Messaging; - name: string; - options: AppOptions; // (undocumented) projectManagement(): projectManagement.ProjectManagement; // (undocumented) @@ -48,42 +48,28 @@ export namespace app { } // @public -export function appCheck(app?: app.App): appCheck.AppCheck; +export function appCheck(app?: App): appCheck.AppCheck; // @public (undocumented) export namespace appCheck { - export interface AppCheck { - // (undocumented) - app: app.App; - createToken(appId: string, options?: AppCheckTokenOptions): Promise; - verifyToken(appCheckToken: string): Promise; - } - export interface AppCheckToken { - token: string; - ttlMillis: number; - } - export interface AppCheckTokenOptions { - ttlMillis?: number; - } - export interface DecodedAppCheckToken { - // (undocumented) - [key: string]: any; - app_id: string; - aud: string[]; - exp: number; - iat: number; - iss: string; - sub: string; - } - export interface VerifyAppCheckTokenResponse { - appId: string; - token: appCheck.DecodedAppCheckToken; - } + // Warning: (ae-forgotten-export) The symbol "AppCheck" needs to be exported by the entry point default-namespace.d.ts + export type AppCheck = AppCheck; + // Warning: (ae-forgotten-export) The symbol "AppCheckToken" needs to be exported by the entry point default-namespace.d.ts + export type AppCheckToken = AppCheckToken; + // Warning: (ae-forgotten-export) The symbol "AppCheckTokenOptions" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type AppCheckTokenOptions = AppCheckTokenOptions; + // Warning: (ae-forgotten-export) The symbol "DecodedAppCheckToken" needs to be exported by the entry point default-namespace.d.ts + export type DecodedAppCheckToken = DecodedAppCheckToken; + // Warning: (ae-forgotten-export) The symbol "VerifyAppCheckTokenResponse" needs to be exported by the entry point default-namespace.d.ts + export type VerifyAppCheckTokenResponse = VerifyAppCheckTokenResponse; } // @public export interface AppOptions { - credential?: credential.Credential; + // Warning: (ae-forgotten-export) The symbol "Credential" needs to be exported by the entry point default-namespace.d.ts + credential?: Credential; databaseAuthVariableOverride?: object | null; databaseURL?: string; httpAgent?: Agent; @@ -96,394 +82,139 @@ export interface AppOptions { export const apps: (app.App | null)[]; // @public -export function auth(app?: app.App): auth.Auth; +export function auth(app?: App): auth.Auth; // @public (undocumented) export namespace auth { - export interface ActionCodeSettings { - android?: { - packageName: string; - installApp?: boolean; - minimumVersion?: string; - }; - dynamicLinkDomain?: string; - handleCodeInApp?: boolean; - iOS?: { - bundleId: string; - }; - url: string; - } - // (undocumented) - export interface Auth extends BaseAuth { - // (undocumented) - app: app.App; - tenantManager(): TenantManager; - } - export type AuthFactorType = 'phone'; - export type AuthProviderConfig = SAMLAuthProviderConfig | OIDCAuthProviderConfig; - export interface AuthProviderConfigFilter { - maxResults?: number; - pageToken?: string; - type: 'saml' | 'oidc'; - } - // (undocumented) - export interface BaseAuth { - createCustomToken(uid: string, developerClaims?: object): Promise; - createProviderConfig(config: AuthProviderConfig): Promise; - createSessionCookie(idToken: string, sessionCookieOptions: SessionCookieOptions): Promise; - createUser(properties: CreateRequest): Promise; - deleteProviderConfig(providerId: string): Promise; - deleteUser(uid: string): Promise; - deleteUsers(uids: string[]): Promise; - generateEmailVerificationLink(email: string, actionCodeSettings?: ActionCodeSettings): Promise; - generatePasswordResetLink(email: string, actionCodeSettings?: ActionCodeSettings): Promise; - generateSignInWithEmailLink(email: string, actionCodeSettings: ActionCodeSettings): Promise; - getProviderConfig(providerId: string): Promise; - getUser(uid: string): Promise; - getUserByEmail(email: string): Promise; - getUserByPhoneNumber(phoneNumber: string): Promise; - getUserByProviderUid(providerId: string, uid: string): Promise; - getUsers(identifiers: UserIdentifier[]): Promise; - importUsers(users: UserImportRecord[], options?: UserImportOptions): Promise; - listProviderConfigs(options: AuthProviderConfigFilter): Promise; - listUsers(maxResults?: number, pageToken?: string): Promise; - revokeRefreshTokens(uid: string): Promise; - setCustomUserClaims(uid: string, customUserClaims: object | null): Promise; - updateProviderConfig(providerId: string, updatedConfig: UpdateAuthProviderRequest): Promise; - updateUser(uid: string, properties: UpdateRequest): Promise; - verifyIdToken(idToken: string, checkRevoked?: boolean): Promise; - verifySessionCookie(sessionCookie: string, checkRevoked?: boolean): Promise; - } - export interface BaseAuthProviderConfig { - displayName?: string; - enabled: boolean; - providerId: string; - } - export interface BaseCreateMultiFactorInfoRequest { - displayName?: string; - factorId: string; - } - export interface BaseUpdateMultiFactorInfoRequest { - displayName?: string; - enrollmentTime?: string; - factorId: string; - uid?: string; - } - export type CreateMultiFactorInfoRequest = CreatePhoneMultiFactorInfoRequest; - export interface CreatePhoneMultiFactorInfoRequest extends BaseCreateMultiFactorInfoRequest { - phoneNumber: string; - } - export interface CreateRequest extends UpdateRequest { - multiFactor?: MultiFactorCreateSettings; - uid?: string; - } - export type CreateTenantRequest = UpdateTenantRequest; - export interface DecodedIdToken { - // (undocumented) - [key: string]: any; - aud: string; - auth_time: number; - email?: string; - email_verified?: boolean; - exp: number; - firebase: { - identities: { - [key: string]: any; - }; - sign_in_provider: string; - sign_in_second_factor?: string; - second_factor_identifier?: string; - tenant?: string; - [key: string]: any; - }; - iat: number; - iss: string; - phone_number?: string; - picture?: string; - sub: string; - uid: string; - } - export interface DeleteUsersResult { - errors: FirebaseArrayIndexError[]; - failureCount: number; - successCount: number; - } - export interface EmailIdentifier { - // (undocumented) - email: string; - } - export interface EmailSignInProviderConfig { - enabled: boolean; - passwordRequired?: boolean; - } - export interface GetUsersResult { - notFound: UserIdentifier[]; - users: UserRecord[]; - } - // (undocumented) - export type HashAlgorithmType = 'SCRYPT' | 'STANDARD_SCRYPT' | 'HMAC_SHA512' | 'HMAC_SHA256' | 'HMAC_SHA1' | 'HMAC_MD5' | 'MD5' | 'PBKDF_SHA1' | 'BCRYPT' | 'PBKDF2_SHA256' | 'SHA512' | 'SHA256' | 'SHA1'; - export interface ListProviderConfigResults { - pageToken?: string; - providerConfigs: AuthProviderConfig[]; - } - export interface ListTenantsResult { - pageToken?: string; - tenants: Tenant[]; - } - export interface ListUsersResult { - pageToken?: string; - users: UserRecord[]; - } - export interface MultiFactorConfig { - factorIds?: AuthFactorType[]; - state: MultiFactorConfigState; - } - export type MultiFactorConfigState = 'ENABLED' | 'DISABLED'; - export interface MultiFactorCreateSettings { - enrolledFactors: CreateMultiFactorInfoRequest[]; - } - export interface MultiFactorInfo { - displayName?: string; - enrollmentTime?: string; - factorId: string; - toJSON(): object; - uid: string; - } - export interface MultiFactorSettings { - enrolledFactors: MultiFactorInfo[]; - toJSON(): object; - } - export interface MultiFactorUpdateSettings { - enrolledFactors: UpdateMultiFactorInfoRequest[] | null; - } - export interface OAuthResponseType { - code?: boolean; - idToken?: boolean; - } - export interface OIDCAuthProviderConfig extends BaseAuthProviderConfig { - clientId: string; - clientSecret?: string; - issuer: string; - responseType?: OAuthResponseType; - } - export interface OIDCUpdateAuthProviderRequest { - clientId?: string; - clientSecret?: string; - displayName?: string; - enabled?: boolean; - issuer?: string; - responseType?: OAuthResponseType; - } - export interface PhoneIdentifier { - // (undocumented) - phoneNumber: string; - } - export interface PhoneMultiFactorInfo extends MultiFactorInfo { - phoneNumber: string; - } - export interface ProviderIdentifier { - // (undocumented) - providerId: string; - // (undocumented) - providerUid: string; - } - export interface SAMLAuthProviderConfig extends BaseAuthProviderConfig { - callbackURL?: string; - idpEntityId: string; - rpEntityId: string; - ssoURL: string; - x509Certificates: string[]; - } - export interface SAMLUpdateAuthProviderRequest { - callbackURL?: string; - displayName?: string; - enabled?: boolean; - idpEntityId?: string; - rpEntityId?: string; - ssoURL?: string; - x509Certificates?: string[]; - } - export interface SessionCookieOptions { - expiresIn: number; - } - export interface Tenant { - anonymousSignInEnabled: boolean; - displayName?: string; - emailSignInConfig?: { - enabled: boolean; - passwordRequired?: boolean; - }; - multiFactorConfig?: MultiFactorConfig; - tenantId: string; - testPhoneNumbers?: { - [phoneNumber: string]: string; - }; - toJSON(): object; - } - export interface TenantAwareAuth extends BaseAuth { - tenantId: string; - } - export interface TenantManager { - // (undocumented) - authForTenant(tenantId: string): TenantAwareAuth; - createTenant(tenantOptions: CreateTenantRequest): Promise; - deleteTenant(tenantId: string): Promise; - getTenant(tenantId: string): Promise; - listTenants(maxResults?: number, pageToken?: string): Promise; - updateTenant(tenantId: string, tenantOptions: UpdateTenantRequest): Promise; - } - export interface UidIdentifier { - // (undocumented) - uid: string; - } - // (undocumented) - export type UpdateAuthProviderRequest = SAMLUpdateAuthProviderRequest | OIDCUpdateAuthProviderRequest; - export type UpdateMultiFactorInfoRequest = UpdatePhoneMultiFactorInfoRequest; - export interface UpdatePhoneMultiFactorInfoRequest extends BaseUpdateMultiFactorInfoRequest { - phoneNumber: string; - } - export interface UpdateRequest { - disabled?: boolean; - displayName?: string | null; - email?: string; - emailVerified?: boolean; - multiFactor?: MultiFactorUpdateSettings; - password?: string; - phoneNumber?: string | null; - photoURL?: string | null; - providersToUnlink?: string[]; - providerToLink?: UserProvider; - } - export interface UpdateTenantRequest { - anonymousSignInEnabled?: boolean; - displayName?: string; - emailSignInConfig?: EmailSignInProviderConfig; - multiFactorConfig?: MultiFactorConfig; - testPhoneNumbers?: { - [phoneNumber: string]: string; - } | null; - } - export type UserIdentifier = UidIdentifier | EmailIdentifier | PhoneIdentifier | ProviderIdentifier; - export interface UserImportOptions { - hash: { - algorithm: HashAlgorithmType; - key?: Buffer; - saltSeparator?: Buffer; - rounds?: number; - memoryCost?: number; - parallelization?: number; - blockSize?: number; - derivedKeyLength?: number; - }; - } - export interface UserImportRecord { - customClaims?: { - [key: string]: any; - }; - disabled?: boolean; - displayName?: string; - email?: string; - emailVerified?: boolean; - metadata?: UserMetadataRequest; - multiFactor?: MultiFactorUpdateSettings; - passwordHash?: Buffer; - passwordSalt?: Buffer; - phoneNumber?: string; - photoURL?: string; - providerData?: UserProviderRequest[]; - tenantId?: string; - uid: string; - } - export interface UserImportResult { - errors: FirebaseArrayIndexError[]; - failureCount: number; - successCount: number; - } - export interface UserInfo { - displayName: string; - email: string; - phoneNumber: string; - photoURL: string; - providerId: string; - toJSON(): object; - uid: string; - } - export interface UserMetadata { - creationTime: string; - lastRefreshTime?: string | null; - lastSignInTime: string; - toJSON(): object; - } - export interface UserMetadataRequest { - creationTime?: string; - lastSignInTime?: string; - } - export interface UserProvider { - displayName?: string; - email?: string; - phoneNumber?: string; - photoURL?: string; - providerId?: string; - uid?: string; - } - export interface UserProviderRequest { - displayName?: string; - email?: string; - phoneNumber?: string; - photoURL?: string; - providerId: string; - uid: string; - } - export interface UserRecord { - customClaims?: { - [key: string]: any; - }; - disabled: boolean; - displayName?: string; - email?: string; - emailVerified: boolean; - metadata: UserMetadata; - multiFactor?: MultiFactorSettings; - passwordHash?: string; - passwordSalt?: string; - phoneNumber?: string; - photoURL?: string; - providerData: UserInfo[]; - tenantId?: string | null; - toJSON(): object; - tokensValidAfterTime?: string; - uid: string; - } + // Warning: (ae-forgotten-export) The symbol "ActionCodeSettings" needs to be exported by the entry point default-namespace.d.ts + export type ActionCodeSettings = ActionCodeSettings; + // Warning: (ae-forgotten-export) The symbol "Auth" needs to be exported by the entry point default-namespace.d.ts + export type Auth = Auth; + // Warning: (ae-forgotten-export) The symbol "AuthFactorType" needs to be exported by the entry point default-namespace.d.ts + export type AuthFactorType = AuthFactorType; + // Warning: (ae-forgotten-export) The symbol "AuthProviderConfig" needs to be exported by the entry point default-namespace.d.ts + export type AuthProviderConfig = AuthProviderConfig; + // Warning: (ae-forgotten-export) The symbol "AuthProviderConfigFilter" needs to be exported by the entry point default-namespace.d.ts + export type AuthProviderConfigFilter = AuthProviderConfigFilter; + // Warning: (ae-forgotten-export) The symbol "BaseAuth" needs to be exported by the entry point default-namespace.d.ts + export type BaseAuth = BaseAuth; + // Warning: (ae-forgotten-export) The symbol "CreateMultiFactorInfoRequest" needs to be exported by the entry point default-namespace.d.ts + export type CreateMultiFactorInfoRequest = CreateMultiFactorInfoRequest; + // Warning: (ae-forgotten-export) The symbol "CreatePhoneMultiFactorInfoRequest" needs to be exported by the entry point default-namespace.d.ts + export type CreatePhoneMultiFactorInfoRequest = CreatePhoneMultiFactorInfoRequest; + // Warning: (ae-forgotten-export) The symbol "CreateRequest" needs to be exported by the entry point default-namespace.d.ts + export type CreateRequest = CreateRequest; + // Warning: (ae-forgotten-export) The symbol "CreateTenantRequest" needs to be exported by the entry point default-namespace.d.ts + export type CreateTenantRequest = CreateTenantRequest; + // Warning: (ae-forgotten-export) The symbol "DecodedIdToken" needs to be exported by the entry point default-namespace.d.ts + export type DecodedIdToken = DecodedIdToken; + // Warning: (ae-forgotten-export) The symbol "DeleteUsersResult" needs to be exported by the entry point default-namespace.d.ts + export type DeleteUsersResult = DeleteUsersResult; + // Warning: (ae-forgotten-export) The symbol "EmailIdentifier" needs to be exported by the entry point default-namespace.d.ts + export type EmailIdentifier = EmailIdentifier; + // Warning: (ae-forgotten-export) The symbol "EmailSignInProviderConfig" needs to be exported by the entry point default-namespace.d.ts + export type EmailSignInProviderConfig = EmailSignInProviderConfig; + // Warning: (ae-forgotten-export) The symbol "GetUsersResult" needs to be exported by the entry point default-namespace.d.ts + export type GetUsersResult = GetUsersResult; + // Warning: (ae-forgotten-export) The symbol "HashAlgorithmType" needs to be exported by the entry point default-namespace.d.ts + export type HashAlgorithmType = HashAlgorithmType; + // Warning: (ae-forgotten-export) The symbol "ListProviderConfigResults" needs to be exported by the entry point default-namespace.d.ts + export type ListProviderConfigResults = ListProviderConfigResults; + // Warning: (ae-forgotten-export) The symbol "ListTenantsResult" needs to be exported by the entry point default-namespace.d.ts + export type ListTenantsResult = ListTenantsResult; + // Warning: (ae-forgotten-export) The symbol "ListUsersResult" needs to be exported by the entry point default-namespace.d.ts + export type ListUsersResult = ListUsersResult; + // Warning: (ae-forgotten-export) The symbol "MultiFactorConfig" needs to be exported by the entry point default-namespace.d.ts + export type MultiFactorConfig = MultiFactorConfig; + // Warning: (ae-forgotten-export) The symbol "MultiFactorConfigState" needs to be exported by the entry point default-namespace.d.ts + export type MultiFactorConfigState = MultiFactorConfigState; + // Warning: (ae-forgotten-export) The symbol "MultiFactorCreateSettings" needs to be exported by the entry point default-namespace.d.ts + export type MultiFactorCreateSettings = MultiFactorCreateSettings; + // Warning: (ae-forgotten-export) The symbol "MultiFactorInfo" needs to be exported by the entry point default-namespace.d.ts + export type MultiFactorInfo = MultiFactorInfo; + // Warning: (ae-forgotten-export) The symbol "MultiFactorSettings" needs to be exported by the entry point default-namespace.d.ts + export type MultiFactorSettings = MultiFactorSettings; + // Warning: (ae-forgotten-export) The symbol "MultiFactorUpdateSettings" needs to be exported by the entry point default-namespace.d.ts + export type MultiFactorUpdateSettings = MultiFactorUpdateSettings; + // Warning: (ae-forgotten-export) The symbol "OIDCAuthProviderConfig" needs to be exported by the entry point default-namespace.d.ts + export type OIDCAuthProviderConfig = OIDCAuthProviderConfig; + // Warning: (ae-forgotten-export) The symbol "OIDCUpdateAuthProviderRequest" needs to be exported by the entry point default-namespace.d.ts + export type OIDCUpdateAuthProviderRequest = OIDCUpdateAuthProviderRequest; + // Warning: (ae-forgotten-export) The symbol "PhoneIdentifier" needs to be exported by the entry point default-namespace.d.ts + export type PhoneIdentifier = PhoneIdentifier; + // Warning: (ae-forgotten-export) The symbol "PhoneMultiFactorInfo" needs to be exported by the entry point default-namespace.d.ts + export type PhoneMultiFactorInfo = PhoneMultiFactorInfo; + // Warning: (ae-forgotten-export) The symbol "ProviderIdentifier" needs to be exported by the entry point default-namespace.d.ts + export type ProviderIdentifier = ProviderIdentifier; + // Warning: (ae-forgotten-export) The symbol "SAMLAuthProviderConfig" needs to be exported by the entry point default-namespace.d.ts + export type SAMLAuthProviderConfig = SAMLAuthProviderConfig; + // Warning: (ae-forgotten-export) The symbol "SAMLUpdateAuthProviderRequest" needs to be exported by the entry point default-namespace.d.ts + export type SAMLUpdateAuthProviderRequest = SAMLUpdateAuthProviderRequest; + // Warning: (ae-forgotten-export) The symbol "SessionCookieOptions" needs to be exported by the entry point default-namespace.d.ts + export type SessionCookieOptions = SessionCookieOptions; + // Warning: (ae-forgotten-export) The symbol "Tenant" needs to be exported by the entry point default-namespace.d.ts + export type Tenant = Tenant; + // Warning: (ae-forgotten-export) The symbol "TenantAwareAuth" needs to be exported by the entry point default-namespace.d.ts + export type TenantAwareAuth = TenantAwareAuth; + // Warning: (ae-forgotten-export) The symbol "TenantManager" needs to be exported by the entry point default-namespace.d.ts + export type TenantManager = TenantManager; + // Warning: (ae-forgotten-export) The symbol "UidIdentifier" needs to be exported by the entry point default-namespace.d.ts + export type UidIdentifier = UidIdentifier; + // Warning: (ae-forgotten-export) The symbol "UpdateAuthProviderRequest" needs to be exported by the entry point default-namespace.d.ts + export type UpdateAuthProviderRequest = UpdateAuthProviderRequest; + // Warning: (ae-forgotten-export) The symbol "UpdateMultiFactorInfoRequest" needs to be exported by the entry point default-namespace.d.ts + export type UpdateMultiFactorInfoRequest = UpdateMultiFactorInfoRequest; + // Warning: (ae-forgotten-export) The symbol "UpdatePhoneMultiFactorInfoRequest" needs to be exported by the entry point default-namespace.d.ts + export type UpdatePhoneMultiFactorInfoRequest = UpdatePhoneMultiFactorInfoRequest; + // Warning: (ae-forgotten-export) The symbol "UpdateRequest" needs to be exported by the entry point default-namespace.d.ts + export type UpdateRequest = UpdateRequest; + // Warning: (ae-forgotten-export) The symbol "UpdateTenantRequest" needs to be exported by the entry point default-namespace.d.ts + export type UpdateTenantRequest = UpdateTenantRequest; + // Warning: (ae-forgotten-export) The symbol "UserIdentifier" needs to be exported by the entry point default-namespace.d.ts + export type UserIdentifier = UserIdentifier; + // Warning: (ae-forgotten-export) The symbol "UserImportOptions" needs to be exported by the entry point default-namespace.d.ts + export type UserImportOptions = UserImportOptions; + // Warning: (ae-forgotten-export) The symbol "UserImportRecord" needs to be exported by the entry point default-namespace.d.ts + export type UserImportRecord = UserImportRecord; + // Warning: (ae-forgotten-export) The symbol "UserImportResult" needs to be exported by the entry point default-namespace.d.ts + export type UserImportResult = UserImportResult; + // Warning: (ae-forgotten-export) The symbol "UserInfo" needs to be exported by the entry point default-namespace.d.ts + export type UserInfo = UserInfo; + // Warning: (ae-forgotten-export) The symbol "UserMetadata" needs to be exported by the entry point default-namespace.d.ts + export type UserMetadata = UserMetadata; + // Warning: (ae-forgotten-export) The symbol "UserMetadataRequest" needs to be exported by the entry point default-namespace.d.ts + export type UserMetadataRequest = UserMetadataRequest; + // Warning: (ae-forgotten-export) The symbol "UserProviderRequest" needs to be exported by the entry point default-namespace.d.ts + export type UserProviderRequest = UserProviderRequest; + // Warning: (ae-forgotten-export) The symbol "UserRecord" needs to be exported by the entry point default-namespace.d.ts + export type UserRecord = UserRecord; } // @public (undocumented) export namespace credential { - export function applicationDefault(httpAgent?: Agent): Credential; - export function cert(serviceAccountPathOrObject: string | ServiceAccount, httpAgent?: Agent): Credential; - export interface Credential { - getAccessToken(): Promise; - } - export function refreshToken(refreshTokenPathOrObject: string | object, httpAgent?: Agent): Credential; + export type Credential = Credential; + const // Warning: (ae-forgotten-export) The symbol "applicationDefault" needs to be exported by the entry point default-namespace.d.ts + applicationDefault: typeof applicationDefault; + const // Warning: (ae-forgotten-export) The symbol "cert" needs to be exported by the entry point default-namespace.d.ts + cert: typeof cert; + const // Warning: (ae-forgotten-export) The symbol "refreshToken" needs to be exported by the entry point default-namespace.d.ts + refreshToken: typeof refreshToken; } // @public -export function database(app?: app.App): database.Database; +export function database(app?: App): database.Database; // @public (undocumented) export namespace database { - // (undocumented) - export interface Database extends rtdb.FirebaseDatabase { - getRules(): Promise; - getRulesJSON(): Promise; - setRules(source: string | Buffer | object): Promise; - } - import DataSnapshot = rtdb.DataSnapshot; - import EventType = rtdb.EventType; - import OnDisconnect = rtdb.OnDisconnect; - import Query = rtdb.Query; - import Reference = rtdb.Reference; - import ThenableReference = rtdb.ThenableReference; - import enableLogging = rtdb.enableLogging; + // Warning: (ae-forgotten-export) The symbol "Database" needs to be exported by the entry point default-namespace.d.ts + export type Database = Database; + export type DataSnapshot = rtdb.DataSnapshot; + export type EventType = rtdb.EventType; + export type OnDisconnect = rtdb.OnDisconnect; + export type Query = rtdb.Query; + export type Reference = rtdb.Reference; + export type ThenableReference = rtdb.ThenableReference; + const enableLogging: typeof rtdb.enableLogging; const ServerValue: rtdb.ServerValue; } @@ -502,7 +233,7 @@ export interface FirebaseError { } // @public (undocumented) -export function firestore(app?: app.App): _firestore.Firestore; +export function firestore(app?: App): _firestore.Firestore; // @public (undocumented) export namespace firestore { @@ -549,575 +280,197 @@ export interface GoogleOAuthAccessToken { export function initializeApp(options?: AppOptions, name?: string): app.App; // @public -export function installations(app?: app.App): installations.Installations; +export function installations(app?: App): installations.Installations; // @public (undocumented) export namespace installations { - export interface Installations { - // (undocumented) - app: app.App; - deleteInstallation(fid: string): Promise; - } + // Warning: (ae-forgotten-export) The symbol "Installations" needs to be exported by the entry point default-namespace.d.ts + export type Installations = Installations; } -// @public @deprecated -export function instanceId(app?: app.App): instanceId.InstanceId; +// @public +export function instanceId(app?: App): instanceId.InstanceId; // @public (undocumented) export namespace instanceId { - // @deprecated - export interface InstanceId { - // (undocumented) - app: app.App; - // @deprecated - deleteInstanceId(instanceId: string): Promise; - } + // Warning: (ae-forgotten-export) The symbol "InstanceId" needs to be exported by the entry point default-namespace.d.ts + export type InstanceId = InstanceId; } // @public -export function machineLearning(app?: app.App): machineLearning.MachineLearning; +export function machineLearning(app?: App): machineLearning.MachineLearning; // @public (undocumented) export namespace machineLearning { - // (undocumented) - export interface AutoMLTfliteModelOptions extends ModelOptionsBase { - // (undocumented) - tfliteModel: { - automlModel: string; - }; - } - // (undocumented) - export interface GcsTfliteModelOptions extends ModelOptionsBase { - // (undocumented) - tfliteModel: { - gcsTfliteUri: string; - }; - } - export interface ListModelsOptions { - filter?: string; - pageSize?: number; - pageToken?: string; - } - export interface ListModelsResult { - readonly models: Model[]; - readonly pageToken?: string; - } - export interface MachineLearning { - app: app.App; - createModel(model: ModelOptions): Promise; - deleteModel(modelId: string): Promise; - getModel(modelId: string): Promise; - listModels(options?: ListModelsOptions): Promise; - publishModel(modelId: string): Promise; - unpublishModel(modelId: string): Promise; - updateModel(modelId: string, model: ModelOptions): Promise; - } - export interface Model { - readonly createTime: string; - readonly displayName: string; - readonly etag: string; - readonly locked: boolean; - readonly modelHash?: string; - readonly modelId: string; - readonly published: boolean; - readonly tags?: string[]; - readonly tfliteModel?: TFLiteModel; - toJSON(): { - [key: string]: any; - }; - readonly updateTime: string; - readonly validationError?: string; - waitForUnlocked(maxTimeMillis?: number): Promise; - } - // (undocumented) - export type ModelOptions = ModelOptionsBase | GcsTfliteModelOptions | AutoMLTfliteModelOptions; - export interface ModelOptionsBase { - // (undocumented) - displayName?: string; - // (undocumented) - tags?: string[]; - } - export interface TFLiteModel { - readonly automlModel?: string; - readonly gcsTfliteUri?: string; - readonly sizeBytes: number; - } + // Warning: (ae-forgotten-export) The symbol "AutoMLTfliteModelOptions" needs to be exported by the entry point default-namespace.d.ts + export type AutoMLTfliteModelOptions = AutoMLTfliteModelOptions; + // Warning: (ae-forgotten-export) The symbol "GcsTfliteModelOptions" needs to be exported by the entry point default-namespace.d.ts + export type GcsTfliteModelOptions = GcsTfliteModelOptions; + // Warning: (ae-forgotten-export) The symbol "ListModelsOptions" needs to be exported by the entry point default-namespace.d.ts + export type ListModelsOptions = ListModelsOptions; + // Warning: (ae-forgotten-export) The symbol "ListModelsResult" needs to be exported by the entry point default-namespace.d.ts + export type ListModelsResult = ListModelsResult; + // Warning: (ae-forgotten-export) The symbol "MachineLearning" needs to be exported by the entry point default-namespace.d.ts + export type MachineLearning = MachineLearning; + // Warning: (ae-forgotten-export) The symbol "Model" needs to be exported by the entry point default-namespace.d.ts + export type Model = Model; + // Warning: (ae-forgotten-export) The symbol "ModelOptions" needs to be exported by the entry point default-namespace.d.ts + export type ModelOptions = ModelOptions; + // Warning: (ae-forgotten-export) The symbol "ModelOptionsBase" needs to be exported by the entry point default-namespace.d.ts + export type ModelOptionsBase = ModelOptionsBase; + // Warning: (ae-forgotten-export) The symbol "TFLiteModel" needs to be exported by the entry point default-namespace.d.ts + export type TFLiteModel = TFLiteModel; } // @public -export function messaging(app?: app.App): messaging.Messaging; +export function messaging(app?: App): messaging.Messaging; // @public (undocumented) export namespace messaging { - export interface AndroidConfig { - collapseKey?: string; - data?: { - [key: string]: string; - }; - fcmOptions?: AndroidFcmOptions; - notification?: AndroidNotification; - priority?: ('high' | 'normal'); - restrictedPackageName?: string; - ttl?: number; - } - export interface AndroidFcmOptions { - analyticsLabel?: string; - } - export interface AndroidNotification { - body?: string; - bodyLocArgs?: string[]; - bodyLocKey?: string; - channelId?: string; - clickAction?: string; - color?: string; - defaultLightSettings?: boolean; - defaultSound?: boolean; - defaultVibrateTimings?: boolean; - eventTimestamp?: Date; - icon?: string; - imageUrl?: string; - lightSettings?: LightSettings; - localOnly?: boolean; - notificationCount?: number; - priority?: ('min' | 'low' | 'default' | 'high' | 'max'); - sound?: string; - sticky?: boolean; - tag?: string; - ticker?: string; - title?: string; - titleLocArgs?: string[]; - titleLocKey?: string; - vibrateTimingsMillis?: number[]; - visibility?: ('private' | 'public' | 'secret'); - } - export interface ApnsConfig { - fcmOptions?: ApnsFcmOptions; - headers?: { - [key: string]: string; - }; - payload?: ApnsPayload; - } - export interface ApnsFcmOptions { - analyticsLabel?: string; - imageUrl?: string; - } - export interface ApnsPayload { - // (undocumented) - [customData: string]: any; - aps: Aps; - } - export interface Aps { - // (undocumented) - [customData: string]: any; - alert?: string | ApsAlert; - badge?: number; - category?: string; - contentAvailable?: boolean; - mutableContent?: boolean; - sound?: string | CriticalSound; - threadId?: string; - } - // (undocumented) - export interface ApsAlert { - // (undocumented) - actionLocKey?: string; - // (undocumented) - body?: string; - // (undocumented) - launchImage?: string; - // (undocumented) - locArgs?: string[]; - // (undocumented) - locKey?: string; - // (undocumented) - subtitle?: string; - // (undocumented) - subtitleLocArgs?: string[]; - // (undocumented) - subtitleLocKey?: string; - // (undocumented) - title?: string; - // (undocumented) - titleLocArgs?: string[]; - // (undocumented) - titleLocKey?: string; - } - // (undocumented) - export interface BaseMessage { - // (undocumented) - android?: AndroidConfig; - // (undocumented) - apns?: ApnsConfig; - // (undocumented) - data?: { - [key: string]: string; - }; - // (undocumented) - fcmOptions?: FcmOptions; - // (undocumented) - notification?: Notification; - // (undocumented) - webpush?: WebpushConfig; - } - export interface BatchResponse { - failureCount: number; - responses: SendResponse[]; - successCount: number; - } - // (undocumented) - export interface ConditionMessage extends BaseMessage { - // (undocumented) - condition: string; - } - export interface CriticalSound { - critical?: boolean; - name: string; - volume?: number; - } - export interface DataMessagePayload { - // (undocumented) - [key: string]: string; - } - export interface FcmOptions { - analyticsLabel?: string; - } - export interface LightSettings { - color: string; - lightOffDurationMillis: number; - lightOnDurationMillis: number; - } - export type Message = TokenMessage | TopicMessage | ConditionMessage; - // (undocumented) - export interface Messaging { - app: app.App; - send(message: Message, dryRun?: boolean): Promise; - sendAll(messages: Array, dryRun?: boolean): Promise; - sendMulticast(message: MulticastMessage, dryRun?: boolean): Promise; - sendToCondition(condition: string, payload: MessagingPayload, options?: MessagingOptions): Promise; - sendToDevice(registrationToken: string | string[], payload: MessagingPayload, options?: MessagingOptions): Promise; - sendToDeviceGroup(notificationKey: string, payload: MessagingPayload, options?: MessagingOptions): Promise; - sendToTopic(topic: string, payload: MessagingPayload, options?: MessagingOptions): Promise; - subscribeToTopic(registrationTokens: string | string[], topic: string): Promise; - unsubscribeFromTopic(registrationTokens: string | string[], topic: string): Promise; - } - export interface MessagingConditionResponse { - messageId: number; - } - export interface MessagingDeviceGroupResponse { - failedRegistrationTokens: string[]; - failureCount: number; - successCount: number; - } - // (undocumented) - export interface MessagingDeviceResult { - canonicalRegistrationToken?: string; - error?: FirebaseError; - messageId?: string; - } - export interface MessagingDevicesResponse { - // (undocumented) - canonicalRegistrationTokenCount: number; - // (undocumented) - failureCount: number; - // (undocumented) - multicastId: number; - // (undocumented) - results: MessagingDeviceResult[]; - // (undocumented) - successCount: number; - } - export interface MessagingOptions { - // (undocumented) - [key: string]: any | undefined; - collapseKey?: string; - contentAvailable?: boolean; - dryRun?: boolean; - mutableContent?: boolean; - priority?: string; - restrictedPackageName?: string; - timeToLive?: number; - } - export interface MessagingPayload { - data?: DataMessagePayload; - notification?: NotificationMessagePayload; - } - export interface MessagingTopicManagementResponse { - errors: FirebaseArrayIndexError[]; - failureCount: number; - successCount: number; - } - export interface MessagingTopicResponse { - messageId: number; - } - export interface MulticastMessage extends BaseMessage { - // (undocumented) - tokens: string[]; - } - export interface Notification { - body?: string; - imageUrl?: string; - title?: string; - } - export interface NotificationMessagePayload { - // (undocumented) - [key: string]: string | undefined; - badge?: string; - body?: string; - bodyLocArgs?: string; - bodyLocKey?: string; - clickAction?: string; - color?: string; - icon?: string; - sound?: string; - tag?: string; - title?: string; - titleLocArgs?: string; - titleLocKey?: string; - } - export interface SendResponse { - error?: FirebaseError; - messageId?: string; - success: boolean; - } - // (undocumented) - export interface TokenMessage extends BaseMessage { - // (undocumented) - token: string; - } - // (undocumented) - export interface TopicMessage extends BaseMessage { - // (undocumented) - topic: string; - } - export interface WebpushConfig { - data?: { - [key: string]: string; - }; - fcmOptions?: WebpushFcmOptions; - headers?: { - [key: string]: string; - }; - notification?: WebpushNotification; - } - export interface WebpushFcmOptions { - link?: string; - } - export interface WebpushNotification { - // (undocumented) - [key: string]: any; - actions?: Array<{ - action: string; - icon?: string; - title: string; - }>; - badge?: string; - body?: string; - data?: any; - dir?: 'auto' | 'ltr' | 'rtl'; - icon?: string; - image?: string; - lang?: string; - renotify?: boolean; - requireInteraction?: boolean; - silent?: boolean; - tag?: string; - timestamp?: number; - title?: string; - vibrate?: number | number[]; - } - {}; + // Warning: (ae-forgotten-export) The symbol "AndroidConfig" needs to be exported by the entry point default-namespace.d.ts + export type AndroidConfig = AndroidConfig; + // Warning: (ae-forgotten-export) The symbol "AndroidFcmOptions" needs to be exported by the entry point default-namespace.d.ts + export type AndroidFcmOptions = AndroidFcmOptions; + // Warning: (ae-forgotten-export) The symbol "AndroidNotification" needs to be exported by the entry point default-namespace.d.ts + export type AndroidNotification = AndroidNotification; + // Warning: (ae-forgotten-export) The symbol "ApnsConfig" needs to be exported by the entry point default-namespace.d.ts + export type ApnsConfig = ApnsConfig; + // Warning: (ae-forgotten-export) The symbol "ApnsFcmOptions" needs to be exported by the entry point default-namespace.d.ts + export type ApnsFcmOptions = ApnsFcmOptions; + // Warning: (ae-forgotten-export) The symbol "ApnsPayload" needs to be exported by the entry point default-namespace.d.ts + export type ApnsPayload = ApnsPayload; + // Warning: (ae-forgotten-export) The symbol "Aps" needs to be exported by the entry point default-namespace.d.ts + export type Aps = Aps; + // Warning: (ae-forgotten-export) The symbol "ApsAlert" needs to be exported by the entry point default-namespace.d.ts + export type ApsAlert = ApsAlert; + // Warning: (ae-forgotten-export) The symbol "BatchResponse" needs to be exported by the entry point default-namespace.d.ts + export type BatchResponse = BatchResponse; + // Warning: (ae-forgotten-export) The symbol "ConditionMessage" needs to be exported by the entry point default-namespace.d.ts + export type ConditionMessage = ConditionMessage; + // Warning: (ae-forgotten-export) The symbol "CriticalSound" needs to be exported by the entry point default-namespace.d.ts + export type CriticalSound = CriticalSound; + // Warning: (ae-forgotten-export) The symbol "DataMessagePayload" needs to be exported by the entry point default-namespace.d.ts + export type DataMessagePayload = DataMessagePayload; + // Warning: (ae-forgotten-export) The symbol "FcmOptions" needs to be exported by the entry point default-namespace.d.ts + export type FcmOptions = FcmOptions; + // Warning: (ae-forgotten-export) The symbol "LightSettings" needs to be exported by the entry point default-namespace.d.ts + export type LightSettings = LightSettings; + // Warning: (ae-forgotten-export) The symbol "Message" needs to be exported by the entry point default-namespace.d.ts + export type Message = Message; + // Warning: (ae-forgotten-export) The symbol "Messaging" needs to be exported by the entry point default-namespace.d.ts + export type Messaging = Messaging; + // Warning: (ae-forgotten-export) The symbol "MessagingConditionResponse" needs to be exported by the entry point default-namespace.d.ts + export type MessagingConditionResponse = MessagingConditionResponse; + // Warning: (ae-forgotten-export) The symbol "MessagingDeviceGroupResponse" needs to be exported by the entry point default-namespace.d.ts + export type MessagingDeviceGroupResponse = MessagingDeviceGroupResponse; + // Warning: (ae-forgotten-export) The symbol "MessagingDeviceResult" needs to be exported by the entry point default-namespace.d.ts + export type MessagingDeviceResult = MessagingDeviceResult; + // Warning: (ae-forgotten-export) The symbol "MessagingDevicesResponse" needs to be exported by the entry point default-namespace.d.ts + export type MessagingDevicesResponse = MessagingDevicesResponse; + // Warning: (ae-forgotten-export) The symbol "MessagingOptions" needs to be exported by the entry point default-namespace.d.ts + export type MessagingOptions = MessagingOptions; + // Warning: (ae-forgotten-export) The symbol "MessagingPayload" needs to be exported by the entry point default-namespace.d.ts + export type MessagingPayload = MessagingPayload; + // Warning: (ae-forgotten-export) The symbol "MessagingTopicManagementResponse" needs to be exported by the entry point default-namespace.d.ts + export type MessagingTopicManagementResponse = MessagingTopicManagementResponse; + // Warning: (ae-forgotten-export) The symbol "MessagingTopicResponse" needs to be exported by the entry point default-namespace.d.ts + export type MessagingTopicResponse = MessagingTopicResponse; + // Warning: (ae-forgotten-export) The symbol "MulticastMessage" needs to be exported by the entry point default-namespace.d.ts + export type MulticastMessage = MulticastMessage; + // Warning: (ae-forgotten-export) The symbol "Notification" needs to be exported by the entry point default-namespace.d.ts + export type Notification = Notification; + // Warning: (ae-forgotten-export) The symbol "NotificationMessagePayload" needs to be exported by the entry point default-namespace.d.ts + export type NotificationMessagePayload = NotificationMessagePayload; + // Warning: (ae-forgotten-export) The symbol "SendResponse" needs to be exported by the entry point default-namespace.d.ts + export type SendResponse = SendResponse; + // Warning: (ae-forgotten-export) The symbol "TokenMessage" needs to be exported by the entry point default-namespace.d.ts + export type TokenMessage = TokenMessage; + // Warning: (ae-forgotten-export) The symbol "TopicMessage" needs to be exported by the entry point default-namespace.d.ts + export type TopicMessage = TopicMessage; + // Warning: (ae-forgotten-export) The symbol "WebpushConfig" needs to be exported by the entry point default-namespace.d.ts + export type WebpushConfig = WebpushConfig; + // Warning: (ae-forgotten-export) The symbol "WebpushFcmOptions" needs to be exported by the entry point default-namespace.d.ts + export type WebpushFcmOptions = WebpushFcmOptions; + // Warning: (ae-forgotten-export) The symbol "WebpushNotification" needs to be exported by the entry point default-namespace.d.ts + export type WebpushNotification = WebpushNotification; } // @public -export function projectManagement(app?: app.App): projectManagement.ProjectManagement; +export function projectManagement(app?: App): projectManagement.ProjectManagement; // @public (undocumented) export namespace projectManagement { - export interface AndroidApp { - addShaCertificate(certificateToAdd: ShaCertificate): Promise; - // (undocumented) - appId: string; - deleteShaCertificate(certificateToRemove: ShaCertificate): Promise; - getConfig(): Promise; - getMetadata(): Promise; - getShaCertificates(): Promise; - setDisplayName(newDisplayName: string): Promise; - } - export interface AndroidAppMetadata extends AppMetadata { - packageName: string; - // (undocumented) - platform: AppPlatform.ANDROID; - } - export interface AppMetadata { - appId: string; - displayName?: string; - platform: AppPlatform; - projectId: string; - resourceName: string; - } - export enum AppPlatform { - ANDROID = "ANDROID", - IOS = "IOS", - PLATFORM_UNKNOWN = "PLATFORM_UNKNOWN" - } - export interface IosApp { - // (undocumented) - appId: string; - getConfig(): Promise; - getMetadata(): Promise; - setDisplayName(newDisplayName: string): Promise; - } - export interface IosAppMetadata extends AppMetadata { - bundleId: string; - // (undocumented) - platform: AppPlatform.IOS; - } - export interface ProjectManagement { - androidApp(appId: string): AndroidApp; - // (undocumented) - app: app.App; - createAndroidApp(packageName: string, displayName?: string): Promise; - createIosApp(bundleId: string, displayName?: string): Promise; - iosApp(appId: string): IosApp; - listAndroidApps(): Promise; - listAppMetadata(): Promise; - listIosApps(): Promise; - setDisplayName(newDisplayName: string): Promise; - shaCertificate(shaHash: string): ShaCertificate; - } - export interface ShaCertificate { - certType: ('sha1' | 'sha256'); - resourceName?: string; - shaHash: string; - } + // Warning: (ae-forgotten-export) The symbol "AndroidApp" needs to be exported by the entry point default-namespace.d.ts + export type AndroidApp = AndroidApp; + // Warning: (ae-forgotten-export) The symbol "AndroidAppMetadata" needs to be exported by the entry point default-namespace.d.ts + export type AndroidAppMetadata = AndroidAppMetadata; + // Warning: (ae-forgotten-export) The symbol "AppMetadata" needs to be exported by the entry point default-namespace.d.ts + export type AppMetadata = AppMetadata; + // Warning: (ae-forgotten-export) The symbol "AppPlatform" needs to be exported by the entry point default-namespace.d.ts + export type AppPlatform = AppPlatform; + // Warning: (ae-forgotten-export) The symbol "IosApp" needs to be exported by the entry point default-namespace.d.ts + export type IosApp = IosApp; + // Warning: (ae-forgotten-export) The symbol "IosAppMetadata" needs to be exported by the entry point default-namespace.d.ts + export type IosAppMetadata = IosAppMetadata; + // Warning: (ae-forgotten-export) The symbol "ProjectManagement" needs to be exported by the entry point default-namespace.d.ts + export type ProjectManagement = ProjectManagement; + // Warning: (ae-forgotten-export) The symbol "ShaCertificate" needs to be exported by the entry point default-namespace.d.ts + export type ShaCertificate = ShaCertificate; } // @public -export function remoteConfig(app?: app.App): remoteConfig.RemoteConfig; +export function remoteConfig(app?: App): remoteConfig.RemoteConfig; // @public (undocumented) export namespace remoteConfig { - export interface ExplicitParameterValue { - value: string; - } - export interface InAppDefaultValue { - useInAppDefault: boolean; - } - export interface ListVersionsOptions { - endTime?: Date | string; - endVersionNumber?: string | number; - pageSize?: number; - pageToken?: string; - startTime?: Date | string; - } - export interface ListVersionsResult { - nextPageToken?: string; - versions: Version[]; - } - export type ParameterValueType = 'STRING' | 'BOOLEAN' | 'NUMBER' | 'JSON'; - export interface RemoteConfig { - // (undocumented) - app: app.App; - createTemplateFromJSON(json: string): RemoteConfigTemplate; - getTemplate(): Promise; - getTemplateAtVersion(versionNumber: number | string): Promise; - listVersions(options?: ListVersionsOptions): Promise; - publishTemplate(template: RemoteConfigTemplate, options?: { - force: boolean; - }): Promise; - rollback(versionNumber: string | number): Promise; - validateTemplate(template: RemoteConfigTemplate): Promise; - } - export interface RemoteConfigCondition { - expression: string; - name: string; - tagColor?: TagColor; - } - export interface RemoteConfigParameter { - conditionalValues?: { - [key: string]: RemoteConfigParameterValue; - }; - defaultValue?: RemoteConfigParameterValue; - description?: string; - valueType?: ParameterValueType; - } - export interface RemoteConfigParameterGroup { - description?: string; - parameters: { - [key: string]: RemoteConfigParameter; - }; - } - export type RemoteConfigParameterValue = ExplicitParameterValue | InAppDefaultValue; - export interface RemoteConfigTemplate { - conditions: RemoteConfigCondition[]; - readonly etag: string; - parameterGroups: { - [key: string]: RemoteConfigParameterGroup; - }; - parameters: { - [key: string]: RemoteConfigParameter; - }; - version?: Version; - } - export interface RemoteConfigUser { - email: string; - imageUrl?: string; - name?: string; - } - export type TagColor = 'BLUE' | 'BROWN' | 'CYAN' | 'DEEP_ORANGE' | 'GREEN' | 'INDIGO' | 'LIME' | 'ORANGE' | 'PINK' | 'PURPLE' | 'TEAL'; - export interface Version { - description?: string; - isLegacy?: boolean; - rollbackSource?: string; - updateOrigin?: ('REMOTE_CONFIG_UPDATE_ORIGIN_UNSPECIFIED' | 'CONSOLE' | 'REST_API' | 'ADMIN_SDK_NODE'); - updateTime?: string; - updateType?: ('REMOTE_CONFIG_UPDATE_TYPE_UNSPECIFIED' | 'INCREMENTAL_UPDATE' | 'FORCED_UPDATE' | 'ROLLBACK'); - updateUser?: RemoteConfigUser; - versionNumber?: string; - } + // Warning: (ae-forgotten-export) The symbol "ExplicitParameterValue" needs to be exported by the entry point default-namespace.d.ts + export type ExplicitParameterValue = ExplicitParameterValue; + // Warning: (ae-forgotten-export) The symbol "InAppDefaultValue" needs to be exported by the entry point default-namespace.d.ts + export type InAppDefaultValue = InAppDefaultValue; + // Warning: (ae-forgotten-export) The symbol "ListVersionsOptions" needs to be exported by the entry point default-namespace.d.ts + export type ListVersionsOptions = ListVersionsOptions; + // Warning: (ae-forgotten-export) The symbol "ListVersionsResult" needs to be exported by the entry point default-namespace.d.ts + export type ListVersionsResult = ListVersionsResult; + // Warning: (ae-forgotten-export) The symbol "ParameterValueType" needs to be exported by the entry point default-namespace.d.ts + export type ParameterValueType = ParameterValueType; + // Warning: (ae-forgotten-export) The symbol "RemoteConfig" needs to be exported by the entry point default-namespace.d.ts + export type RemoteConfig = RemoteConfig; + // Warning: (ae-forgotten-export) The symbol "RemoteConfigCondition" needs to be exported by the entry point default-namespace.d.ts + export type RemoteConfigCondition = RemoteConfigCondition; + // Warning: (ae-forgotten-export) The symbol "RemoteConfigParameter" needs to be exported by the entry point default-namespace.d.ts + export type RemoteConfigParameter = RemoteConfigParameter; + // Warning: (ae-forgotten-export) The symbol "RemoteConfigParameterGroup" needs to be exported by the entry point default-namespace.d.ts + export type RemoteConfigParameterGroup = RemoteConfigParameterGroup; + // Warning: (ae-forgotten-export) The symbol "RemoteConfigParameterValue" needs to be exported by the entry point default-namespace.d.ts + export type RemoteConfigParameterValue = RemoteConfigParameterValue; + // Warning: (ae-forgotten-export) The symbol "RemoteConfigTemplate" needs to be exported by the entry point default-namespace.d.ts + export type RemoteConfigTemplate = RemoteConfigTemplate; + // Warning: (ae-forgotten-export) The symbol "RemoteConfigUser" needs to be exported by the entry point default-namespace.d.ts + export type RemoteConfigUser = RemoteConfigUser; + // Warning: (ae-forgotten-export) The symbol "TagColor" needs to be exported by the entry point default-namespace.d.ts + export type TagColor = TagColor; + // Warning: (ae-forgotten-export) The symbol "Version" needs to be exported by the entry point default-namespace.d.ts + export type Version = Version; } // @public (undocumented) export const SDK_VERSION: string; // @public -export function securityRules(app?: app.App): securityRules.SecurityRules; +export function securityRules(app?: App): securityRules.SecurityRules; // @public (undocumented) export namespace securityRules { - export interface Ruleset extends RulesetMetadata { - // (undocumented) - readonly source: RulesFile[]; - } - export interface RulesetMetadata { - readonly createTime: string; - readonly name: string; - } - export interface RulesetMetadataList { - readonly nextPageToken?: string; - readonly rulesets: RulesetMetadata[]; - } - export interface RulesFile { - // (undocumented) - readonly content: string; - // (undocumented) - readonly name: string; - } - export interface SecurityRules { - // (undocumented) - app: app.App; - createRuleset(file: RulesFile): Promise; - createRulesFileFromSource(name: string, source: string | Buffer): RulesFile; - deleteRuleset(name: string): Promise; - getFirestoreRuleset(): Promise; - getRuleset(name: string): Promise; - getStorageRuleset(bucket?: string): Promise; - listRulesetMetadata(pageSize?: number, nextPageToken?: string): Promise; - releaseFirestoreRuleset(ruleset: string | RulesetMetadata): Promise; - releaseFirestoreRulesetFromSource(source: string | Buffer): Promise; - releaseStorageRuleset(ruleset: string | RulesetMetadata, bucket?: string): Promise; - releaseStorageRulesetFromSource(source: string | Buffer, bucket?: string): Promise; - } + // Warning: (ae-forgotten-export) The symbol "Ruleset" needs to be exported by the entry point default-namespace.d.ts + export type Ruleset = Ruleset; + // Warning: (ae-forgotten-export) The symbol "RulesetMetadata" needs to be exported by the entry point default-namespace.d.ts + export type RulesetMetadata = RulesetMetadata; + // Warning: (ae-forgotten-export) The symbol "RulesetMetadataList" needs to be exported by the entry point default-namespace.d.ts + export type RulesetMetadataList = RulesetMetadataList; + // Warning: (ae-forgotten-export) The symbol "RulesFile" needs to be exported by the entry point default-namespace.d.ts + export type RulesFile = RulesFile; + // Warning: (ae-forgotten-export) The symbol "SecurityRules" needs to be exported by the entry point default-namespace.d.ts + export type SecurityRules = SecurityRules; } // @public (undocumented) @@ -1131,17 +484,12 @@ export interface ServiceAccount { } // @public -export function storage(app?: app.App): storage.Storage; +export function storage(app?: App): storage.Storage; // @public (undocumented) export namespace storage { - export interface Storage { - app: app.App; - // (undocumented) - bucket(name?: string): Bucket; - } + // Warning: (ae-forgotten-export) The symbol "Storage" needs to be exported by the entry point default-namespace.d.ts + export type Storage = Storage; } -// (No @packageDocumentation comment for this package) - ``` diff --git a/etc/firebase-admin.app-check.api.md b/etc/firebase-admin.app-check.api.md new file mode 100644 index 0000000000..fb4e10ff64 --- /dev/null +++ b/etc/firebase-admin.app-check.api.md @@ -0,0 +1,53 @@ +## API Report File for "firebase-admin.app-check" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +/// + +import { Agent } from 'http'; + +// @public +export class AppCheck { + // Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts + // + // (undocumented) + readonly app: App; + createToken(appId: string, options?: AppCheckTokenOptions): Promise; + verifyToken(appCheckToken: string): Promise; +} + +// @public +export interface AppCheckToken { + token: string; + ttlMillis: number; +} + +// @public +export interface AppCheckTokenOptions { + ttlMillis?: number; +} + +// @public +export interface DecodedAppCheckToken { + // (undocumented) + [key: string]: any; + app_id: string; + aud: string[]; + exp: number; + iat: number; + iss: string; + sub: string; +} + +// @public +export function getAppCheck(app?: App): AppCheck; + +// @public +export interface VerifyAppCheckTokenResponse { + appId: string; + token: DecodedAppCheckToken; +} + +``` diff --git a/etc/firebase-admin.app.api.md b/etc/firebase-admin.app.api.md new file mode 100644 index 0000000000..a6ad65cd57 --- /dev/null +++ b/etc/firebase-admin.app.api.md @@ -0,0 +1,89 @@ +## API Report File for "firebase-admin.app" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +/// + +import { Agent } from 'http'; + +// @public +export interface App { + name: string; + options: AppOptions; +} + +// @public +export function applicationDefault(httpAgent?: Agent): Credential; + +// @public +export interface AppOptions { + credential?: Credential; + databaseAuthVariableOverride?: object | null; + databaseURL?: string; + httpAgent?: Agent; + projectId?: string; + serviceAccountId?: string; + storageBucket?: string; +} + +// @public +export function cert(serviceAccountPathOrObject: string | ServiceAccount, httpAgent?: Agent): Credential; + +// @public +export interface Credential { + getAccessToken(): Promise; +} + +// @public +export function deleteApp(app: App): Promise; + +// @public +export interface FirebaseArrayIndexError { + error: FirebaseError; + index: number; +} + +// @public +export interface FirebaseError { + code: string; + message: string; + stack?: string; + toJSON(): object; +} + +// @public (undocumented) +export function getApp(appName?: string): App; + +// @public (undocumented) +export function getApps(): App[]; + +// @public +export interface GoogleOAuthAccessToken { + // (undocumented) + access_token: string; + // (undocumented) + expires_in: number; +} + +// @public (undocumented) +export function initializeApp(options?: AppOptions, appName?: string): App; + +// @public +export function refreshToken(refreshTokenPathOrObject: string | object, httpAgent?: Agent): Credential; + +// @public (undocumented) +export const SDK_VERSION: string; + +// @public (undocumented) +export interface ServiceAccount { + // (undocumented) + clientEmail?: string; + // (undocumented) + privateKey?: string; + // (undocumented) + projectId?: string; +} + +``` diff --git a/etc/firebase-admin.auth.api.md b/etc/firebase-admin.auth.api.md new file mode 100644 index 0000000000..36b2dcf686 --- /dev/null +++ b/etc/firebase-admin.auth.api.md @@ -0,0 +1,477 @@ +## API Report File for "firebase-admin.auth" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +/// + +import { Agent } from 'http'; + +// @public +export interface ActionCodeSettings { + android?: { + packageName: string; + installApp?: boolean; + minimumVersion?: string; + }; + dynamicLinkDomain?: string; + handleCodeInApp?: boolean; + iOS?: { + bundleId: string; + }; + url: string; +} + +// @public +export class Auth extends BaseAuth { + // Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts + get app(): App; + tenantManager(): TenantManager; +} + +// @public +export type AuthFactorType = 'phone'; + +// @public +export type AuthProviderConfig = SAMLAuthProviderConfig | OIDCAuthProviderConfig; + +// @public +export interface AuthProviderConfigFilter { + maxResults?: number; + pageToken?: string; + type: 'saml' | 'oidc'; +} + +// @public +export abstract class BaseAuth { + createCustomToken(uid: string, developerClaims?: object): Promise; + createProviderConfig(config: AuthProviderConfig): Promise; + createSessionCookie(idToken: string, sessionCookieOptions: SessionCookieOptions): Promise; + createUser(properties: CreateRequest): Promise; + deleteProviderConfig(providerId: string): Promise; + deleteUser(uid: string): Promise; + deleteUsers(uids: string[]): Promise; + generateEmailVerificationLink(email: string, actionCodeSettings?: ActionCodeSettings): Promise; + generatePasswordResetLink(email: string, actionCodeSettings?: ActionCodeSettings): Promise; + generateSignInWithEmailLink(email: string, actionCodeSettings: ActionCodeSettings): Promise; + getProviderConfig(providerId: string): Promise; + getUser(uid: string): Promise; + getUserByEmail(email: string): Promise; + getUserByPhoneNumber(phoneNumber: string): Promise; + getUserByProviderUid(providerId: string, uid: string): Promise; + getUsers(identifiers: UserIdentifier[]): Promise; + importUsers(users: UserImportRecord[], options?: UserImportOptions): Promise; + listProviderConfigs(options: AuthProviderConfigFilter): Promise; + listUsers(maxResults?: number, pageToken?: string): Promise; + revokeRefreshTokens(uid: string): Promise; + setCustomUserClaims(uid: string, customUserClaims: object | null): Promise; + updateProviderConfig(providerId: string, updatedConfig: UpdateAuthProviderRequest): Promise; + updateUser(uid: string, properties: UpdateRequest): Promise; + verifyIdToken(idToken: string, checkRevoked?: boolean): Promise; + verifySessionCookie(sessionCookie: string, checkRevoked?: boolean): Promise; +} + +// @public +export interface BaseAuthProviderConfig { + displayName?: string; + enabled: boolean; + providerId: string; +} + +// @public +export interface BaseCreateMultiFactorInfoRequest { + displayName?: string; + factorId: string; +} + +// @public +export interface BaseUpdateMultiFactorInfoRequest { + displayName?: string; + enrollmentTime?: string; + factorId: string; + uid?: string; +} + +// @public +export type CreateMultiFactorInfoRequest = CreatePhoneMultiFactorInfoRequest; + +// @public +export interface CreatePhoneMultiFactorInfoRequest extends BaseCreateMultiFactorInfoRequest { + phoneNumber: string; +} + +// @public +export interface CreateRequest extends UpdateRequest { + multiFactor?: MultiFactorCreateSettings; + uid?: string; +} + +// @public +export type CreateTenantRequest = UpdateTenantRequest; + +// @public +export interface DecodedIdToken { + [key: string]: any; + aud: string; + auth_time: number; + email?: string; + email_verified?: boolean; + exp: number; + firebase: { + identities: { + [key: string]: any; + }; + sign_in_provider: string; + sign_in_second_factor?: string; + second_factor_identifier?: string; + tenant?: string; + [key: string]: any; + }; + iat: number; + iss: string; + phone_number?: string; + picture?: string; + sub: string; + uid: string; +} + +// @public +export interface DeleteUsersResult { + // Warning: (ae-forgotten-export) The symbol "FirebaseArrayIndexError" needs to be exported by the entry point index.d.ts + errors: FirebaseArrayIndexError[]; + failureCount: number; + successCount: number; +} + +// @public +export interface EmailIdentifier { + // (undocumented) + email: string; +} + +// @public +export interface EmailSignInProviderConfig { + enabled: boolean; + passwordRequired?: boolean; +} + +// @public +export function getAuth(app?: App): Auth; + +// @public +export interface GetUsersResult { + notFound: UserIdentifier[]; + users: UserRecord[]; +} + +// @public (undocumented) +export type HashAlgorithmType = 'SCRYPT' | 'STANDARD_SCRYPT' | 'HMAC_SHA512' | 'HMAC_SHA256' | 'HMAC_SHA1' | 'HMAC_MD5' | 'MD5' | 'PBKDF_SHA1' | 'BCRYPT' | 'PBKDF2_SHA256' | 'SHA512' | 'SHA256' | 'SHA1'; + +// @public +export interface ListProviderConfigResults { + pageToken?: string; + providerConfigs: AuthProviderConfig[]; +} + +// @public +export interface ListTenantsResult { + pageToken?: string; + tenants: Tenant[]; +} + +// @public +export interface ListUsersResult { + pageToken?: string; + users: UserRecord[]; +} + +// @public +export interface MultiFactorConfig { + factorIds?: AuthFactorType[]; + state: MultiFactorConfigState; +} + +// @public +export type MultiFactorConfigState = 'ENABLED' | 'DISABLED'; + +// @public +export interface MultiFactorCreateSettings { + enrolledFactors: CreateMultiFactorInfoRequest[]; +} + +// @public +export abstract class MultiFactorInfo { + readonly displayName?: string; + readonly enrollmentTime?: string; + readonly factorId: string; + toJSON(): object; + readonly uid: string; +} + +// @public +export class MultiFactorSettings { + enrolledFactors: MultiFactorInfo[]; + toJSON(): object; +} + +// @public +export interface MultiFactorUpdateSettings { + enrolledFactors: UpdateMultiFactorInfoRequest[] | null; +} + +// @public +export interface OAuthResponseType { + code?: boolean; + idToken?: boolean; +} + +// @public +export interface OIDCAuthProviderConfig extends BaseAuthProviderConfig { + clientId: string; + clientSecret?: string; + issuer: string; + responseType?: OAuthResponseType; +} + +// @public +export interface OIDCUpdateAuthProviderRequest { + clientId?: string; + clientSecret?: string; + displayName?: string; + enabled?: boolean; + issuer?: string; + responseType?: OAuthResponseType; +} + +// @public +export interface PhoneIdentifier { + // (undocumented) + phoneNumber: string; +} + +// @public +export class PhoneMultiFactorInfo extends MultiFactorInfo { + readonly phoneNumber: string; + toJSON(): object; +} + +// @public +export interface ProviderIdentifier { + // (undocumented) + providerId: string; + // (undocumented) + providerUid: string; +} + +// @public +export interface SAMLAuthProviderConfig extends BaseAuthProviderConfig { + callbackURL?: string; + idpEntityId: string; + rpEntityId: string; + ssoURL: string; + x509Certificates: string[]; +} + +// @public +export interface SAMLUpdateAuthProviderRequest { + callbackURL?: string; + displayName?: string; + enabled?: boolean; + idpEntityId?: string; + rpEntityId?: string; + ssoURL?: string; + x509Certificates?: string[]; +} + +// @public +export interface SessionCookieOptions { + expiresIn: number; +} + +// @public +export class Tenant { + // (undocumented) + readonly anonymousSignInEnabled: boolean; + readonly displayName?: string; + get emailSignInConfig(): EmailSignInProviderConfig | undefined; + get multiFactorConfig(): MultiFactorConfig | undefined; + readonly tenantId: string; + readonly testPhoneNumbers?: { + [phoneNumber: string]: string; + }; + toJSON(): object; +} + +// @public +export class TenantAwareAuth extends BaseAuth { + createSessionCookie(idToken: string, sessionCookieOptions: SessionCookieOptions): Promise; + readonly tenantId: string; + verifyIdToken(idToken: string, checkRevoked?: boolean): Promise; + verifySessionCookie(sessionCookie: string, checkRevoked?: boolean): Promise; +} + +// @public +export class TenantManager { + authForTenant(tenantId: string): TenantAwareAuth; + createTenant(tenantOptions: CreateTenantRequest): Promise; + deleteTenant(tenantId: string): Promise; + getTenant(tenantId: string): Promise; + listTenants(maxResults?: number, pageToken?: string): Promise; + updateTenant(tenantId: string, tenantOptions: UpdateTenantRequest): Promise; +} + +// @public +export interface UidIdentifier { + // (undocumented) + uid: string; +} + +// @public (undocumented) +export type UpdateAuthProviderRequest = SAMLUpdateAuthProviderRequest | OIDCUpdateAuthProviderRequest; + +// @public +export type UpdateMultiFactorInfoRequest = UpdatePhoneMultiFactorInfoRequest; + +// @public +export interface UpdatePhoneMultiFactorInfoRequest extends BaseUpdateMultiFactorInfoRequest { + phoneNumber: string; +} + +// @public +export interface UpdateRequest { + disabled?: boolean; + displayName?: string | null; + email?: string; + emailVerified?: boolean; + multiFactor?: MultiFactorUpdateSettings; + password?: string; + phoneNumber?: string | null; + photoURL?: string | null; + providersToUnlink?: string[]; + providerToLink?: UserProvider; +} + +// @public +export interface UpdateTenantRequest { + anonymousSignInEnabled?: boolean; + displayName?: string; + emailSignInConfig?: EmailSignInProviderConfig; + multiFactorConfig?: MultiFactorConfig; + testPhoneNumbers?: { + [phoneNumber: string]: string; + } | null; +} + +// @public +export type UserIdentifier = UidIdentifier | EmailIdentifier | PhoneIdentifier | ProviderIdentifier; + +// @public +export interface UserImportOptions { + hash: { + algorithm: HashAlgorithmType; + key?: Buffer; + saltSeparator?: Buffer; + rounds?: number; + memoryCost?: number; + parallelization?: number; + blockSize?: number; + derivedKeyLength?: number; + }; +} + +// @public +export interface UserImportRecord { + customClaims?: { + [key: string]: any; + }; + disabled?: boolean; + displayName?: string; + email?: string; + emailVerified?: boolean; + metadata?: UserMetadataRequest; + multiFactor?: MultiFactorUpdateSettings; + passwordHash?: Buffer; + passwordSalt?: Buffer; + phoneNumber?: string; + photoURL?: string; + providerData?: UserProviderRequest[]; + tenantId?: string; + uid: string; +} + +// @public +export interface UserImportResult { + errors: FirebaseArrayIndexError[]; + failureCount: number; + successCount: number; +} + +// @public +export class UserInfo { + readonly displayName: string; + readonly email: string; + readonly phoneNumber: string; + readonly photoURL: string; + readonly providerId: string; + toJSON(): object; + readonly uid: string; +} + +// @public +export class UserMetadata { + readonly creationTime: string; + readonly lastRefreshTime?: string | null; + readonly lastSignInTime: string; + toJSON(): object; +} + +// @public +export interface UserMetadataRequest { + creationTime?: string; + lastSignInTime?: string; +} + +// @public +export interface UserProvider { + displayName?: string; + email?: string; + phoneNumber?: string; + photoURL?: string; + providerId?: string; + uid?: string; +} + +// @public +export interface UserProviderRequest { + displayName?: string; + email?: string; + phoneNumber?: string; + photoURL?: string; + providerId: string; + uid: string; +} + +// @public +export class UserRecord { + readonly customClaims?: { + [key: string]: any; + }; + readonly disabled: boolean; + readonly displayName?: string; + readonly email?: string; + readonly emailVerified: boolean; + readonly metadata: UserMetadata; + readonly multiFactor?: MultiFactorSettings; + readonly passwordHash?: string; + readonly passwordSalt?: string; + readonly phoneNumber?: string; + readonly photoURL?: string; + readonly providerData: UserInfo[]; + readonly tenantId?: string | null; + toJSON(): object; + readonly tokensValidAfterTime?: string; + readonly uid: string; +} + +``` diff --git a/etc/firebase-admin.database.api.md b/etc/firebase-admin.database.api.md new file mode 100644 index 0000000000..e2411ce4be --- /dev/null +++ b/etc/firebase-admin.database.api.md @@ -0,0 +1,52 @@ +## API Report File for "firebase-admin.database" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +/// + +import { Agent } from 'http'; +import { DataSnapshot } from '@firebase/database-types'; +import { EventType } from '@firebase/database-types'; +import { FirebaseDatabase } from '@firebase/database-types'; +import { OnDisconnect } from '@firebase/database-types'; +import { Query } from '@firebase/database-types'; +import { Reference } from '@firebase/database-types'; +import * as rtdb from '@firebase/database-types'; +import { ThenableReference } from '@firebase/database-types'; + +// @public +export interface Database extends FirebaseDatabase { + getRules(): Promise; + getRulesJSON(): Promise; + setRules(source: string | Buffer | object): Promise; +} + +export { DataSnapshot } + +// @public +export const enableLogging: typeof rtdb.enableLogging; + +export { EventType } + +// Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts +// +// @public +export function getDatabase(app?: App): Database; + +// @public +export function getDatabaseWithUrl(url: string, app?: App): Database; + +export { OnDisconnect } + +export { Query } + +export { Reference } + +// @public +export const ServerValue: rtdb.ServerValue; + +export { ThenableReference } + +``` diff --git a/etc/firebase-admin.firestore.api.md b/etc/firebase-admin.firestore.api.md new file mode 100644 index 0000000000..48373f54b6 --- /dev/null +++ b/etc/firebase-admin.firestore.api.md @@ -0,0 +1,100 @@ +## API Report File for "firebase-admin.firestore" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +/// + +import { Agent } from 'http'; +import { BulkWriter } from '@google-cloud/firestore'; +import { BulkWriterOptions } from '@google-cloud/firestore'; +import { CollectionGroup } from '@google-cloud/firestore'; +import { CollectionReference } from '@google-cloud/firestore'; +import { DocumentChangeType } from '@google-cloud/firestore'; +import { DocumentData } from '@google-cloud/firestore'; +import { DocumentReference } from '@google-cloud/firestore'; +import { DocumentSnapshot } from '@google-cloud/firestore'; +import { FieldPath } from '@google-cloud/firestore'; +import { FieldValue } from '@google-cloud/firestore'; +import { Firestore } from '@google-cloud/firestore'; +import { FirestoreDataConverter } from '@google-cloud/firestore'; +import { GeoPoint } from '@google-cloud/firestore'; +import { GrpcStatus } from '@google-cloud/firestore'; +import { Precondition } from '@google-cloud/firestore'; +import { Query } from '@google-cloud/firestore'; +import { QueryDocumentSnapshot } from '@google-cloud/firestore'; +import { QueryPartition } from '@google-cloud/firestore'; +import { QuerySnapshot } from '@google-cloud/firestore'; +import { ReadOptions } from '@google-cloud/firestore'; +import { setLogFunction } from '@google-cloud/firestore'; +import { Settings } from '@google-cloud/firestore'; +import { Timestamp } from '@google-cloud/firestore'; +import { Transaction } from '@google-cloud/firestore'; +import { UpdateData } from '@google-cloud/firestore'; +import { v1 } from '@google-cloud/firestore'; +import { WriteBatch } from '@google-cloud/firestore'; +import { WriteResult } from '@google-cloud/firestore'; + +export { BulkWriter } + +export { BulkWriterOptions } + +export { CollectionGroup } + +export { CollectionReference } + +export { DocumentChangeType } + +export { DocumentData } + +export { DocumentReference } + +export { DocumentSnapshot } + +export { FieldPath } + +export { FieldValue } + +export { Firestore } + +export { FirestoreDataConverter } + +export { GeoPoint } + +// Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts +// +// @public +export function getFirestore(app?: App): Firestore; + +export { GrpcStatus } + +export { Precondition } + +export { Query } + +export { QueryDocumentSnapshot } + +export { QueryPartition } + +export { QuerySnapshot } + +export { ReadOptions } + +export { setLogFunction } + +export { Settings } + +export { Timestamp } + +export { Transaction } + +export { UpdateData } + +export { v1 } + +export { WriteBatch } + +export { WriteResult } + +``` diff --git a/etc/firebase-admin.installations.api.md b/etc/firebase-admin.installations.api.md new file mode 100644 index 0000000000..4a4d11e06f --- /dev/null +++ b/etc/firebase-admin.installations.api.md @@ -0,0 +1,22 @@ +## API Report File for "firebase-admin.installations" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +/// + +import { Agent } from 'http'; + +// Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts +// +// @public +export function getInstallations(app?: App): Installations; + +// @public +export class Installations { + get app(): App; + deleteInstallation(fid: string): Promise; +} + +``` diff --git a/etc/firebase-admin.instance-id.api.md b/etc/firebase-admin.instance-id.api.md new file mode 100644 index 0000000000..2e2a43f642 --- /dev/null +++ b/etc/firebase-admin.instance-id.api.md @@ -0,0 +1,22 @@ +## API Report File for "firebase-admin.instance-id" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +/// + +import { Agent } from 'http'; + +// Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts +// +// @public @deprecated +export function getInstanceId(app?: App): InstanceId; + +// @public @deprecated +export class InstanceId { + get app(): App; + deleteInstanceId(instanceId: string): Promise; +} + +``` diff --git a/etc/firebase-admin.machine-learning.api.md b/etc/firebase-admin.machine-learning.api.md new file mode 100644 index 0000000000..80fa32bcc5 --- /dev/null +++ b/etc/firebase-admin.machine-learning.api.md @@ -0,0 +1,94 @@ +## API Report File for "firebase-admin.machine-learning" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +/// + +import { Agent } from 'http'; + +// @public (undocumented) +export interface AutoMLTfliteModelOptions extends ModelOptionsBase { + // (undocumented) + tfliteModel: { + automlModel: string; + }; +} + +// @public (undocumented) +export interface GcsTfliteModelOptions extends ModelOptionsBase { + // (undocumented) + tfliteModel: { + gcsTfliteUri: string; + }; +} + +// Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts +// +// @public +export function getMachineLearning(app?: App): MachineLearning; + +// @public +export interface ListModelsOptions { + filter?: string; + pageSize?: number; + pageToken?: string; +} + +// @public +export interface ListModelsResult { + readonly models: Model[]; + readonly pageToken?: string; +} + +// @public +export class MachineLearning { + get app(): App; + createModel(model: ModelOptions): Promise; + deleteModel(modelId: string): Promise; + getModel(modelId: string): Promise; + listModels(options?: ListModelsOptions): Promise; + publishModel(modelId: string): Promise; + unpublishModel(modelId: string): Promise; + updateModel(modelId: string, model: ModelOptions): Promise; +} + +// @public +export class Model { + get createTime(): string; + get displayName(): string; + get etag(): string; + get locked(): boolean; + get modelHash(): string | undefined; + get modelId(): string; + get published(): boolean; + get tags(): string[]; + get tfliteModel(): TFLiteModel | undefined; + toJSON(): { + [key: string]: any; + }; + get updateTime(): string; + get validationError(): string | undefined; + waitForUnlocked(maxTimeMillis?: number): Promise; +} + +// @public (undocumented) +export type ModelOptions = ModelOptionsBase | GcsTfliteModelOptions | AutoMLTfliteModelOptions; + +// @public +export interface ModelOptionsBase { + // (undocumented) + displayName?: string; + // (undocumented) + tags?: string[]; +} + +// @public +export interface TFLiteModel { + readonly automlModel?: string; + readonly gcsTfliteUri?: string; + readonly sizeBytes: number; +} + +``` diff --git a/etc/firebase-admin.messaging.api.md b/etc/firebase-admin.messaging.api.md new file mode 100644 index 0000000000..c37466734c --- /dev/null +++ b/etc/firebase-admin.messaging.api.md @@ -0,0 +1,355 @@ +## API Report File for "firebase-admin.messaging" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +/// + +import { Agent } from 'http'; + +// @public +export interface AndroidConfig { + collapseKey?: string; + data?: { + [key: string]: string; + }; + fcmOptions?: AndroidFcmOptions; + notification?: AndroidNotification; + priority?: ('high' | 'normal'); + restrictedPackageName?: string; + ttl?: number; +} + +// @public +export interface AndroidFcmOptions { + analyticsLabel?: string; +} + +// @public +export interface AndroidNotification { + body?: string; + bodyLocArgs?: string[]; + bodyLocKey?: string; + channelId?: string; + clickAction?: string; + color?: string; + defaultLightSettings?: boolean; + defaultSound?: boolean; + defaultVibrateTimings?: boolean; + eventTimestamp?: Date; + icon?: string; + imageUrl?: string; + lightSettings?: LightSettings; + localOnly?: boolean; + notificationCount?: number; + priority?: ('min' | 'low' | 'default' | 'high' | 'max'); + sound?: string; + sticky?: boolean; + tag?: string; + ticker?: string; + title?: string; + titleLocArgs?: string[]; + titleLocKey?: string; + vibrateTimingsMillis?: number[]; + visibility?: ('private' | 'public' | 'secret'); +} + +// @public +export interface ApnsConfig { + fcmOptions?: ApnsFcmOptions; + headers?: { + [key: string]: string; + }; + payload?: ApnsPayload; +} + +// @public +export interface ApnsFcmOptions { + analyticsLabel?: string; + imageUrl?: string; +} + +// @public +export interface ApnsPayload { + // (undocumented) + [customData: string]: any; + aps: Aps; +} + +// @public +export interface Aps { + // (undocumented) + [customData: string]: any; + alert?: string | ApsAlert; + badge?: number; + category?: string; + contentAvailable?: boolean; + mutableContent?: boolean; + sound?: string | CriticalSound; + threadId?: string; +} + +// @public (undocumented) +export interface ApsAlert { + // (undocumented) + actionLocKey?: string; + // (undocumented) + body?: string; + // (undocumented) + launchImage?: string; + // (undocumented) + locArgs?: string[]; + // (undocumented) + locKey?: string; + // (undocumented) + subtitle?: string; + // (undocumented) + subtitleLocArgs?: string[]; + // (undocumented) + subtitleLocKey?: string; + // (undocumented) + title?: string; + // (undocumented) + titleLocArgs?: string[]; + // (undocumented) + titleLocKey?: string; +} + +// @public (undocumented) +export interface BaseMessage { + // (undocumented) + android?: AndroidConfig; + // (undocumented) + apns?: ApnsConfig; + // (undocumented) + data?: { + [key: string]: string; + }; + // (undocumented) + fcmOptions?: FcmOptions; + // (undocumented) + notification?: Notification; + // (undocumented) + webpush?: WebpushConfig; +} + +// @public +export interface BatchResponse { + failureCount: number; + responses: SendResponse[]; + successCount: number; +} + +// @public (undocumented) +export interface ConditionMessage extends BaseMessage { + // (undocumented) + condition: string; +} + +// @public +export interface CriticalSound { + critical?: boolean; + name: string; + volume?: number; +} + +// @public +export interface DataMessagePayload { + // (undocumented) + [key: string]: string; +} + +// @public +export interface FcmOptions { + analyticsLabel?: string; +} + +// Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts +// +// @public +export function getMessaging(app?: App): Messaging; + +// @public +export interface LightSettings { + color: string; + lightOffDurationMillis: number; + lightOnDurationMillis: number; +} + +// @public +export type Message = TokenMessage | TopicMessage | ConditionMessage; + +// @public +export class Messaging { + get app(): App; + send(message: Message, dryRun?: boolean): Promise; + sendAll(messages: Message[], dryRun?: boolean): Promise; + sendMulticast(message: MulticastMessage, dryRun?: boolean): Promise; + sendToCondition(condition: string, payload: MessagingPayload, options?: MessagingOptions): Promise; + sendToDevice(registrationTokenOrTokens: string | string[], payload: MessagingPayload, options?: MessagingOptions): Promise; + sendToDeviceGroup(notificationKey: string, payload: MessagingPayload, options?: MessagingOptions): Promise; + sendToTopic(topic: string, payload: MessagingPayload, options?: MessagingOptions): Promise; + subscribeToTopic(registrationTokenOrTokens: string | string[], topic: string): Promise; + unsubscribeFromTopic(registrationTokenOrTokens: string | string[], topic: string): Promise; +} + +// @public +export interface MessagingConditionResponse { + messageId: number; +} + +// @public +export interface MessagingDeviceGroupResponse { + failedRegistrationTokens: string[]; + failureCount: number; + successCount: number; +} + +// @public (undocumented) +export interface MessagingDeviceResult { + canonicalRegistrationToken?: string; + // Warning: (ae-forgotten-export) The symbol "FirebaseError" needs to be exported by the entry point index.d.ts + error?: FirebaseError; + messageId?: string; +} + +// @public +export interface MessagingDevicesResponse { + // (undocumented) + canonicalRegistrationTokenCount: number; + // (undocumented) + failureCount: number; + // (undocumented) + multicastId: number; + // (undocumented) + results: MessagingDeviceResult[]; + // (undocumented) + successCount: number; +} + +// @public +export interface MessagingOptions { + // (undocumented) + [key: string]: any | undefined; + collapseKey?: string; + contentAvailable?: boolean; + dryRun?: boolean; + mutableContent?: boolean; + priority?: string; + restrictedPackageName?: string; + timeToLive?: number; +} + +// @public +export interface MessagingPayload { + data?: DataMessagePayload; + notification?: NotificationMessagePayload; +} + +// @public +export interface MessagingTopicManagementResponse { + // Warning: (ae-forgotten-export) The symbol "FirebaseArrayIndexError" needs to be exported by the entry point index.d.ts + errors: FirebaseArrayIndexError[]; + failureCount: number; + successCount: number; +} + +// @public +export interface MessagingTopicResponse { + messageId: number; +} + +// @public +export interface MulticastMessage extends BaseMessage { + // (undocumented) + tokens: string[]; +} + +// @public +export interface Notification { + body?: string; + imageUrl?: string; + title?: string; +} + +// @public +export interface NotificationMessagePayload { + // (undocumented) + [key: string]: string | undefined; + badge?: string; + body?: string; + bodyLocArgs?: string; + bodyLocKey?: string; + clickAction?: string; + color?: string; + icon?: string; + sound?: string; + tag?: string; + title?: string; + titleLocArgs?: string; + titleLocKey?: string; +} + +// @public +export interface SendResponse { + error?: FirebaseError; + messageId?: string; + success: boolean; +} + +// @public (undocumented) +export interface TokenMessage extends BaseMessage { + // (undocumented) + token: string; +} + +// @public (undocumented) +export interface TopicMessage extends BaseMessage { + // (undocumented) + topic: string; +} + +// @public +export interface WebpushConfig { + data?: { + [key: string]: string; + }; + fcmOptions?: WebpushFcmOptions; + headers?: { + [key: string]: string; + }; + notification?: WebpushNotification; +} + +// @public +export interface WebpushFcmOptions { + link?: string; +} + +// @public +export interface WebpushNotification { + // (undocumented) + [key: string]: any; + actions?: Array<{ + action: string; + icon?: string; + title: string; + }>; + badge?: string; + body?: string; + data?: any; + dir?: 'auto' | 'ltr' | 'rtl'; + icon?: string; + image?: string; + lang?: string; + renotify?: boolean; + requireInteraction?: boolean; + silent?: boolean; + tag?: string; + timestamp?: number; + title?: string; + vibrate?: number | number[]; +} + +``` diff --git a/etc/firebase-admin.project-management.api.md b/etc/firebase-admin.project-management.api.md new file mode 100644 index 0000000000..2b8d28297c --- /dev/null +++ b/etc/firebase-admin.project-management.api.md @@ -0,0 +1,91 @@ +## API Report File for "firebase-admin.project-management" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +/// + +import { Agent } from 'http'; + +// @public +export class AndroidApp { + addShaCertificate(certificateToAdd: ShaCertificate): Promise; + // (undocumented) + readonly appId: string; + deleteShaCertificate(certificateToDelete: ShaCertificate): Promise; + getConfig(): Promise; + getMetadata(): Promise; + getShaCertificates(): Promise; + setDisplayName(newDisplayName: string): Promise; +} + +// @public +export interface AndroidAppMetadata extends AppMetadata { + packageName: string; + // (undocumented) + platform: AppPlatform.ANDROID; +} + +// @public +export interface AppMetadata { + appId: string; + displayName?: string; + platform: AppPlatform; + projectId: string; + resourceName: string; +} + +// @public +export enum AppPlatform { + ANDROID = "ANDROID", + IOS = "IOS", + PLATFORM_UNKNOWN = "PLATFORM_UNKNOWN" +} + +// Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts +// +// @public +export function getProjectManagement(app?: App): ProjectManagement; + +// @public +export class IosApp { + // (undocumented) + readonly appId: string; + getConfig(): Promise; + getMetadata(): Promise; + setDisplayName(newDisplayName: string): Promise; +} + +// @public +export interface IosAppMetadata extends AppMetadata { + bundleId: string; + // (undocumented) + platform: AppPlatform.IOS; +} + +// @public +export class ProjectManagement { + androidApp(appId: string): AndroidApp; + // (undocumented) + readonly app: App; + createAndroidApp(packageName: string, displayName?: string): Promise; + createIosApp(bundleId: string, displayName?: string): Promise; + iosApp(appId: string): IosApp; + listAndroidApps(): Promise; + listAppMetadata(): Promise; + listIosApps(): Promise; + setDisplayName(newDisplayName: string): Promise; + shaCertificate(shaHash: string): ShaCertificate; +} + +// @public +export class ShaCertificate { + readonly certType: ('sha1' | 'sha256'); + // (undocumented) + readonly resourceName?: string | undefined; + // (undocumented) + readonly shaHash: string; +} + +``` diff --git a/etc/firebase-admin.remote-config.api.md b/etc/firebase-admin.remote-config.api.md new file mode 100644 index 0000000000..fb07bfad76 --- /dev/null +++ b/etc/firebase-admin.remote-config.api.md @@ -0,0 +1,122 @@ +## API Report File for "firebase-admin.remote-config" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +/// + +import { Agent } from 'http'; + +// @public +export interface ExplicitParameterValue { + value: string; +} + +// Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts +// +// @public +export function getRemoteConfig(app?: App): RemoteConfig; + +// @public +export interface InAppDefaultValue { + useInAppDefault: boolean; +} + +// @public +export interface ListVersionsOptions { + endTime?: Date | string; + endVersionNumber?: string | number; + pageSize?: number; + pageToken?: string; + startTime?: Date | string; +} + +// @public +export interface ListVersionsResult { + nextPageToken?: string; + versions: Version[]; +} + +// @public +export type ParameterValueType = 'STRING' | 'BOOLEAN' | 'NUMBER' | 'JSON'; + +// @public +export class RemoteConfig { + // (undocumented) + readonly app: App; + createTemplateFromJSON(json: string): RemoteConfigTemplate; + getTemplate(): Promise; + getTemplateAtVersion(versionNumber: number | string): Promise; + listVersions(options?: ListVersionsOptions): Promise; + publishTemplate(template: RemoteConfigTemplate, options?: { + force: boolean; + }): Promise; + rollback(versionNumber: number | string): Promise; + validateTemplate(template: RemoteConfigTemplate): Promise; +} + +// @public +export interface RemoteConfigCondition { + expression: string; + name: string; + tagColor?: TagColor; +} + +// @public +export interface RemoteConfigParameter { + conditionalValues?: { + [key: string]: RemoteConfigParameterValue; + }; + defaultValue?: RemoteConfigParameterValue; + description?: string; + valueType?: ParameterValueType; +} + +// @public +export interface RemoteConfigParameterGroup { + description?: string; + parameters: { + [key: string]: RemoteConfigParameter; + }; +} + +// @public +export type RemoteConfigParameterValue = ExplicitParameterValue | InAppDefaultValue; + +// @public +export interface RemoteConfigTemplate { + conditions: RemoteConfigCondition[]; + readonly etag: string; + parameterGroups: { + [key: string]: RemoteConfigParameterGroup; + }; + parameters: { + [key: string]: RemoteConfigParameter; + }; + version?: Version; +} + +// @public +export interface RemoteConfigUser { + email: string; + imageUrl?: string; + name?: string; +} + +// @public +export type TagColor = 'BLUE' | 'BROWN' | 'CYAN' | 'DEEP_ORANGE' | 'GREEN' | 'INDIGO' | 'LIME' | 'ORANGE' | 'PINK' | 'PURPLE' | 'TEAL'; + +// @public +export interface Version { + description?: string; + isLegacy?: boolean; + rollbackSource?: string; + updateOrigin?: ('REMOTE_CONFIG_UPDATE_ORIGIN_UNSPECIFIED' | 'CONSOLE' | 'REST_API' | 'ADMIN_SDK_NODE'); + updateTime?: string; + updateType?: ('REMOTE_CONFIG_UPDATE_TYPE_UNSPECIFIED' | 'INCREMENTAL_UPDATE' | 'FORCED_UPDATE' | 'ROLLBACK'); + updateUser?: RemoteConfigUser; + versionNumber?: string; +} + +``` diff --git a/etc/firebase-admin.security-rules.api.md b/etc/firebase-admin.security-rules.api.md new file mode 100644 index 0000000000..890da538d1 --- /dev/null +++ b/etc/firebase-admin.security-rules.api.md @@ -0,0 +1,61 @@ +## API Report File for "firebase-admin.security-rules" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +/// + +import { Agent } from 'http'; + +// Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts +// +// @public +export function getSecurityRules(app?: App): SecurityRules; + +// @public +export class Ruleset implements RulesetMetadata { + readonly createTime: string; + readonly name: string; + // (undocumented) + readonly source: RulesFile[]; +} + +// @public +export interface RulesetMetadata { + readonly createTime: string; + readonly name: string; +} + +// @public +export class RulesetMetadataList { + readonly nextPageToken?: string; + readonly rulesets: RulesetMetadata[]; +} + +// @public +export interface RulesFile { + // (undocumented) + readonly content: string; + // (undocumented) + readonly name: string; +} + +// @public +export class SecurityRules { + // (undocumented) + readonly app: App; + createRuleset(file: RulesFile): Promise; + createRulesFileFromSource(name: string, source: string | Buffer): RulesFile; + deleteRuleset(name: string): Promise; + getFirestoreRuleset(): Promise; + getRuleset(name: string): Promise; + getStorageRuleset(bucket?: string): Promise; + listRulesetMetadata(pageSize?: number, nextPageToken?: string): Promise; + releaseFirestoreRuleset(ruleset: string | RulesetMetadata): Promise; + releaseFirestoreRulesetFromSource(source: string | Buffer): Promise; + releaseStorageRuleset(ruleset: string | RulesetMetadata, bucket?: string): Promise; + releaseStorageRulesetFromSource(source: string | Buffer, bucket?: string): Promise; +} + +``` diff --git a/etc/firebase-admin.storage.api.md b/etc/firebase-admin.storage.api.md new file mode 100644 index 0000000000..204c033a6b --- /dev/null +++ b/etc/firebase-admin.storage.api.md @@ -0,0 +1,23 @@ +## API Report File for "firebase-admin.storage" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +/// + +import { Agent } from 'http'; +import { Bucket } from '@google-cloud/storage'; + +// Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts +// +// @public +export function getStorage(app?: App): Storage; + +// @public +export class Storage { + get app(): App; + bucket(name?: string): Bucket; +} + +``` diff --git a/generate-esm-wrapper.js b/generate-esm-wrapper.js new file mode 100644 index 0000000000..3d47c709d1 --- /dev/null +++ b/generate-esm-wrapper.js @@ -0,0 +1,82 @@ +/** + * @license + * Copyright 2021 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. + */ + +const path = require('path'); +const fs = require('mz/fs'); + +async function main() { + const entryPoints = require('./entrypoints.json'); + for (const entryPoint in entryPoints) { + const info = entryPoints[entryPoint]; + if (info.legacy) { + continue; + } + + await generateEsmWrapper(entryPoint, info.dist); + } +} + +async function generateEsmWrapper(entryPoint, source) { + console.log(`Generating ESM wrapper for ${entryPoint}`); + const target = getTarget(entryPoint); + const output = getEsmOutput(source, target); + await fs.mkdir(path.dirname(target), { recursive: true }); + await fs.writeFile(target, output); + await fs.writeFile('./lib/esm/package.json', JSON.stringify({type: 'module'})); +} + +function getTarget(entryPoint) { + const child = entryPoint.replace('firebase-admin/', ''); + return `./lib/esm/${child}/index.js`; +} + +function getEsmOutput(source, target) { + const sourcePath = path.resolve(source); + const cjsSource = require.resolve(sourcePath); + const keys = getExports(cjsSource); + const targetPath = path.resolve(target); + const importPath = getImportPath(targetPath, cjsSource); + + let output = `import mod from ${JSON.stringify(importPath)};`; + output += '\n\n'; + for (const key of keys) { + output += `export const ${key} = mod.${key};\n`; + } + + return output; +} + +function getImportPath(from, to) { + const fromDir = path.dirname(from); + return path.relative(fromDir, to).replace(/\\/g, '/'); +} + +function getExports(cjsSource) { + const mod = require(cjsSource); + const keys = new Set(Object.getOwnPropertyNames(mod)); + keys.delete('__esModule'); + return [...keys].sort(); +} + +(async () => { + try { + await main(); + } catch (err) { + console.log(err); + process.exit(1); + } +})(); diff --git a/generate-reports.js b/generate-reports.js new file mode 100644 index 0000000000..129c06e542 --- /dev/null +++ b/generate-reports.js @@ -0,0 +1,88 @@ +/** + * @license + * Copyright 2021 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. + */ + +const path = require('path'); +const fs = require('mz/fs'); +const yargs = require('yargs'); +const { Extractor, ExtractorConfig } = require('@microsoft/api-extractor'); + +const { local: localMode } = yargs + .option('local', { + boolean: true, + description: 'Run API Extractor with --local flag', + }) + .version(false) + .help().argv; + +// API Extractor configuration file. +const config = require('./api-extractor.json'); + +const tempConfigFile = 'api-extractor.tmp'; + +async function generateReports() { + const entryPoints = require('./entrypoints.json'); + for (const entryPoint in entryPoints) { + const filePath = entryPoints[entryPoint].typings; + await generateReportForEntryPoint(entryPoint, filePath); + } +} + +async function generateReportForEntryPoint(entryPoint, filePath) { + console.log(`\nGenerating API report for ${entryPoint}`) + console.log('========================================================\n'); + + const safeName = entryPoint.replace('/', '.'); + console.log('Updating configuration for entry point...'); + config.apiReport.reportFileName = `${safeName}.api.md`; + config.mainEntryPointFilePath = filePath; + console.log(`Report file name: ${config.apiReport.reportFileName}`); + console.log(`Entry point declaration: ${config.mainEntryPointFilePath}`); + await fs.writeFile(tempConfigFile, JSON.stringify(config)); + + try { + const configFile = ExtractorConfig.loadFile(tempConfigFile); + const extractorConfig = ExtractorConfig.prepare({ + configObject: configFile, + configObjectFullPath: path.resolve(tempConfigFile), + packageJson: { + name: safeName, + }, + packageJsonFullPath: path.resolve('package.json'), + }); + const extractorResult = Extractor.invoke(extractorConfig, { + localBuild: localMode, + showVerboseMessages: true + }); + if (!extractorResult.succeeded) { + throw new Error(`API Extractor completed with ${extractorResult.errorCount} errors` + + ` and ${extractorResult.warningCount} warnings`); + } + + console.error(`API Extractor completed successfully`); + } finally { + await fs.unlink(tempConfigFile); + } +} + +(async () => { + try { + await generateReports(); + } catch (err) { + console.log(err); + process.exit(1); + } +})(); diff --git a/gulpfile.js b/gulpfile.js index da33386589..749b6ff517 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -41,7 +41,7 @@ var paths = { test: [ 'test/**/*.ts', - '!test/integration/typescript/src/example*.ts', + '!test/integration/postcheck/typescript/*.ts', ], build: 'lib/', @@ -86,9 +86,7 @@ gulp.task('compile', function() { const configuration = [ 'lib/**/*.js', - 'lib/**/index.d.ts', - 'lib/firebase-namespace-api.d.ts', - '!lib/utils/index.d.ts', + 'lib/**/*.d.ts', ]; workflow = workflow.pipe(filter(configuration)); @@ -108,7 +106,7 @@ gulp.task('compile_test', function() { }); gulp.task('copyTypings', function() { - return gulp.src(['src/index.d.ts', 'src/firebase-namespace.d.ts']) + return gulp.src(['src/index.d.ts', 'src/default-namespace.d.ts']) // Add header .pipe(header(banner)) .pipe(gulp.dest(paths.build)) diff --git a/package-lock.json b/package-lock.json index 103a3c79e3..7d0614a2aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "firebase-admin", - "version": "9.11.1", + "version": "9.12.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -336,10 +336,26 @@ "@cspotcode/source-map-consumer": "0.8.0" } }, + "@firebase/api-documenter": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@firebase/api-documenter/-/api-documenter-0.1.2.tgz", + "integrity": "sha512-aDofRZebqbMzrbo5WAi9f21qUTzhIub7yOszirik3AwujqOzcUr1F7lIFrI41686JD1Zw56lLL/B5EWZTwvVjA==", + "dev": true, + "requires": { + "@microsoft/tsdoc": "0.12.24", + "@rushstack/node-core-library": "3.36.0", + "@rushstack/ts-command-line": "4.7.8", + "api-extractor-model-me": "0.1.1", + "colors": "~1.2.1", + "js-yaml": "4.0.0", + "resolve": "~1.17.0", + "tslib": "^2.1.0" + } + }, "@firebase/app": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.7.1.tgz", - "integrity": "sha512-B4z6E1EPQc0mOjF35IPKdDRCFnT/fNQIHfM+v7F9obB7ItPhGILK3LxaQfuampSQpF6GG6TPFDbrWK6myXAq+g==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.7.2.tgz", + "integrity": "sha512-xKO3KWxVqCLijJToaBGvBnXCaVGvIw+rT2Dtd9B2iyOFJieQQ+xx8/zRWgoSqbMBIZ2crQVr0KdsoyP9D2nQfg==", "dev": true, "requires": { "@firebase/component": "0.5.7", @@ -349,12 +365,12 @@ } }, "@firebase/app-compat": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.1.2.tgz", - "integrity": "sha512-kF1maoqA8bZqJ4v/ojVvA7kIyyXEPkJmL48otGrC8LIgdcen7xCx3JFDe0DGeQywg+qujvdkJz/TptFN1cvAgw==", + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.1.3.tgz", + "integrity": "sha512-+/U2RgRLfLznPuluIMW3bsAehTBTVWKxA6l6jjk9noozPuP99xOulReMqf5kCrXVdW1aMHdRuKfntjbTAR8+aw==", "dev": true, "requires": { - "@firebase/app": "0.7.1", + "@firebase/app": "0.7.2", "@firebase/component": "0.5.7", "@firebase/logger": "0.3.0", "@firebase/util": "1.4.0", @@ -367,38 +383,44 @@ "integrity": "sha512-6fbHQwDv2jp/v6bXhBw2eSRbNBpxHcd1NBF864UksSMVIqIyri9qpJB1Mn6sGZE+bnDsSQBC5j2TbMxYsJQkQg==" }, "@firebase/auth": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.18.0.tgz", - "integrity": "sha512-iK+VXkdDkum8SmJNgz9ZcOboRLrUN1VW7AHHkpZb76VJvoYRoCPD+A9O/v/ziI0LpwIZJwi1GFes9XjZTlfLiA==", + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.18.1.tgz", + "integrity": "sha512-q455ls7Hjug3yGp7htLL/LABqySoXGXL/ADLJPyiSnVl22a5oQWuTKUL6N5PAXHc5LwygFfHYiHrNhpQDaGm3w==", "dev": true, "requires": { "@firebase/component": "0.5.7", "@firebase/logger": "0.3.0", "@firebase/util": "1.4.0", - "node-fetch": "2.6.2", + "node-fetch": "2.6.5", "selenium-webdriver": "4.0.0-rc-1", "tslib": "^2.1.0" }, "dependencies": { - "node-fetch": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.2.tgz", - "integrity": "sha512-aLoxToI6RfZ+0NOjmWAgn9+LEd30YCkJKFSyWacNZdEKTit/ZMcKjGkTRo8uWEsnIb/hfKecNPEbln02PdWbcA==", - "dev": true + "selenium-webdriver": { + "version": "4.0.0-rc-1", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.0.0-rc-1.tgz", + "integrity": "sha512-bcrwFPRax8fifRP60p7xkWDGSJJoMkPAzufMlk5K2NyLPht/YZzR2WcIk1+3gR8VOCLlst1P2PI+MXACaFzpIw==", + "dev": true, + "requires": { + "jszip": "^3.6.0", + "rimraf": "^3.0.2", + "tmp": "^0.2.1", + "ws": ">=7.4.6" + } } } }, "@firebase/auth-compat": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.1.3.tgz", - "integrity": "sha512-eDDtY5If+ERJxalt+plvX6avZspuwo4/kPXssvV+csm414awhDzQBtSDPDajgbH3YB9V+O3LAFHeWcP3rrHS5w==", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.1.4.tgz", + "integrity": "sha512-Vn7Dsxa7B50ihgDAMQAVb/IxU9tcQyR1JDbWjZzf2b1212hBuuwEs1V1u01xoKunMXMSg+P8ztbG7IRxOj2FdQ==", "dev": true, "requires": { - "@firebase/auth": "0.18.0", + "@firebase/auth": "0.18.1", "@firebase/auth-types": "0.11.0", "@firebase/component": "0.5.7", "@firebase/util": "1.4.0", - "node-fetch": "2.6.2", + "node-fetch": "2.6.5", "selenium-webdriver": "^4.0.0-beta.2", "tslib": "^2.1.0" }, @@ -408,12 +430,6 @@ "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.11.0.tgz", "integrity": "sha512-q7Bt6cx+ySj9elQHTsKulwk3+qDezhzRBFC9zlQ1BjgMueUOnGMcvqmU0zuKlQ4RhLSH7MNAdBV2znVaoN3Vxw==", "dev": true - }, - "node-fetch": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.2.tgz", - "integrity": "sha512-aLoxToI6RfZ+0NOjmWAgn9+LEd30YCkJKFSyWacNZdEKTit/ZMcKjGkTRo8uWEsnIb/hfKecNPEbln02PdWbcA==", - "dev": true } } }, @@ -557,9 +573,9 @@ "optional": true }, "@google-cloud/storage": { - "version": "5.14.3", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.14.3.tgz", - "integrity": "sha512-XwB+W/WcQMPQ8dH6i/d4hGnHsmb03k5IDtCG9HwwHszLWWCvzK7qX2G0fo+9nM4Ph8XIQrPNeHpRX4CwoQ3r2A==", + "version": "5.14.4", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.14.4.tgz", + "integrity": "sha512-CjpGuk+ZZB7b3yMXPQrPb0TMIhXqbDzrGxngeSl2S2fItFp2pZDnYhvFuB0/8S73cA2T/4x3g1tl6PB1OuuaoQ==", "optional": true, "requires": { "@google-cloud/common": "^3.7.0", @@ -651,6 +667,16 @@ "path-exists": "^4.0.0" } }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -727,15 +753,15 @@ } }, "@microsoft/api-extractor": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.18.10.tgz", - "integrity": "sha512-FrXFniXYVG8YS55uwJ+lxHsHuy7SFmne0yClF0k8up2+CXw1zqOGuWJE66QzH4JITirTfny7E8x3i/3WlK53xg==", + "version": "7.18.11", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.18.11.tgz", + "integrity": "sha512-WfN5MZry4TrF60OOcGadFDsGECF9JNKNT+8P/8crYAumAYQRitI2cUiQRlCWrgmFgCWNezsNZeI/2BggdnUqcg==", "dev": true, "requires": { - "@microsoft/api-extractor-model": "7.13.8", + "@microsoft/api-extractor-model": "7.13.9", "@microsoft/tsdoc": "0.13.2", "@microsoft/tsdoc-config": "~0.15.2", - "@rushstack/node-core-library": "3.40.3", + "@rushstack/node-core-library": "3.41.0", "@rushstack/rig-package": "0.3.1", "@rushstack/ts-command-line": "4.9.1", "colors": "~1.2.1", @@ -746,6 +772,47 @@ "typescript": "~4.4.2" }, "dependencies": { + "@microsoft/tsdoc": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.13.2.tgz", + "integrity": "sha512-WrHvO8PDL8wd8T2+zBGKrMwVL5IyzR3ryWUsl0PXgEV0QHup4mTLi0QcATefGI6Gx9Anu7vthPyyyLpY0EpiQg==", + "dev": true + }, + "@rushstack/node-core-library": { + "version": "3.41.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.41.0.tgz", + "integrity": "sha512-JxdmqR+SHU04jTDaZhltMZL3/XTz2ZZM47DTN+FSPUGUVp6WmxLlvJnT5FoHrOZWUjL/FoIlZUdUPTSXjTjIcg==", + "dev": true, + "requires": { + "@types/node": "12.20.24", + "colors": "~1.2.1", + "fs-extra": "~7.0.1", + "import-lazy": "~4.0.0", + "jju": "~1.4.0", + "resolve": "~1.17.0", + "semver": "~7.3.0", + "timsort": "~0.3.0", + "z-schema": "~3.18.3" + } + }, + "@rushstack/ts-command-line": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.9.1.tgz", + "integrity": "sha512-zzoWB6OqVbMjnxlxbAUqbZqDWITUSHqwFCx7JbH5CVrjR9kcsB4NeWkN1I8GcR92beiOGvO3yPlB2NRo5Ugh+A==", + "dev": true, + "requires": { + "@types/argparse": "1.0.38", + "argparse": "~1.0.9", + "colors": "~1.2.1", + "string-argv": "~0.3.1" + } + }, + "@types/node": { + "version": "12.20.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.24.tgz", + "integrity": "sha512-yxDeaQIAJlMav7fH5AQqPH1u8YIuhYJXYBzxaQ4PifsU0GDO38MSdmEDeRlIxrKbC6NbEaaEHDanWb+y30U8SQ==", + "dev": true + }, "semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -764,20 +831,60 @@ } }, "@microsoft/api-extractor-model": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.13.8.tgz", - "integrity": "sha512-tC/Mbc7vOEkinVmhXyGS4RvPD3cesE0UvE0RmgazDfLHOpefLwoakdoocZqUp+mL5hMUep/ymIW7IbfZlwWxnQ==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.13.9.tgz", + "integrity": "sha512-t/XKTr8MlHRWgDr1fkyCzTQRR5XICf/WzIFs8yw1JLU8Olw99M3by4/dtpOZNskfqoW+J8NwOxovduU2csi4Ww==", "dev": true, "requires": { "@microsoft/tsdoc": "0.13.2", "@microsoft/tsdoc-config": "~0.15.2", - "@rushstack/node-core-library": "3.40.3" + "@rushstack/node-core-library": "3.41.0" + }, + "dependencies": { + "@microsoft/tsdoc": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.13.2.tgz", + "integrity": "sha512-WrHvO8PDL8wd8T2+zBGKrMwVL5IyzR3ryWUsl0PXgEV0QHup4mTLi0QcATefGI6Gx9Anu7vthPyyyLpY0EpiQg==", + "dev": true + }, + "@rushstack/node-core-library": { + "version": "3.41.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.41.0.tgz", + "integrity": "sha512-JxdmqR+SHU04jTDaZhltMZL3/XTz2ZZM47DTN+FSPUGUVp6WmxLlvJnT5FoHrOZWUjL/FoIlZUdUPTSXjTjIcg==", + "dev": true, + "requires": { + "@types/node": "12.20.24", + "colors": "~1.2.1", + "fs-extra": "~7.0.1", + "import-lazy": "~4.0.0", + "jju": "~1.4.0", + "resolve": "~1.17.0", + "semver": "~7.3.0", + "timsort": "~0.3.0", + "z-schema": "~3.18.3" + } + }, + "@types/node": { + "version": "12.20.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.24.tgz", + "integrity": "sha512-yxDeaQIAJlMav7fH5AQqPH1u8YIuhYJXYBzxaQ4PifsU0GDO38MSdmEDeRlIxrKbC6NbEaaEHDanWb+y30U8SQ==", + "dev": true + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } } }, "@microsoft/tsdoc": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.13.2.tgz", - "integrity": "sha512-WrHvO8PDL8wd8T2+zBGKrMwVL5IyzR3ryWUsl0PXgEV0QHup4mTLi0QcATefGI6Gx9Anu7vthPyyyLpY0EpiQg==", + "version": "0.12.24", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.12.24.tgz", + "integrity": "sha512-Mfmij13RUTmHEMi9vRUhMXD7rnGR2VvxeNYtaGtaJ4redwwjT4UXYJ+nzmVJF7hhd4pn/Fx5sncDKxMVFJSWPg==", "dev": true }, "@microsoft/tsdoc-config": { @@ -792,6 +899,12 @@ "resolve": "~1.19.0" }, "dependencies": { + "@microsoft/tsdoc": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.13.2.tgz", + "integrity": "sha512-WrHvO8PDL8wd8T2+zBGKrMwVL5IyzR3ryWUsl0PXgEV0QHup4mTLi0QcATefGI6Gx9Anu7vthPyyyLpY0EpiQg==", + "dev": true + }, "resolve": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", @@ -900,12 +1013,12 @@ "optional": true }, "@rushstack/node-core-library": { - "version": "3.40.3", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.40.3.tgz", - "integrity": "sha512-yWM84xgLVy1p3pQJw8EQYui5IgAFzB0MUpdGXCVKl3/qt25ucsqKA/I50DSPhvLf/Gpsvc8bGv+kx5PKgAseZg==", + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.36.0.tgz", + "integrity": "sha512-bID2vzXpg8zweXdXgQkKToEdZwVrVCN9vE9viTRk58gqzYaTlz4fMId6V3ZfpXN6H0d319uGi2KDlm+lUEeqCg==", "dev": true, "requires": { - "@types/node": "12.20.24", + "@types/node": "10.17.13", "colors": "~1.2.1", "fs-extra": "~7.0.1", "import-lazy": "~4.0.0", @@ -917,9 +1030,9 @@ }, "dependencies": { "@types/node": { - "version": "12.20.24", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.24.tgz", - "integrity": "sha512-yxDeaQIAJlMav7fH5AQqPH1u8YIuhYJXYBzxaQ4PifsU0GDO38MSdmEDeRlIxrKbC6NbEaaEHDanWb+y30U8SQ==", + "version": "10.17.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.13.tgz", + "integrity": "sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg==", "dev": true }, "semver": { @@ -944,9 +1057,9 @@ } }, "@rushstack/ts-command-line": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.9.1.tgz", - "integrity": "sha512-zzoWB6OqVbMjnxlxbAUqbZqDWITUSHqwFCx7JbH5CVrjR9kcsB4NeWkN1I8GcR92beiOGvO3yPlB2NRo5Ugh+A==", + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.7.8.tgz", + "integrity": "sha512-8ghIWhkph7NnLCMDJtthpsb7TMOsVGXVDvmxjE/CeklTqjbbUFBjGXizJfpbEkRQTELuZQ2+vGn7sGwIWKN2uA==", "dev": true, "requires": { "@types/argparse": "1.0.38", @@ -991,9 +1104,9 @@ "dev": true }, "@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", "optional": true }, "@tsconfig/node10": { @@ -1145,9 +1258,9 @@ } }, "@types/lodash": { - "version": "4.14.173", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.173.tgz", - "integrity": "sha512-vv0CAYoaEjCw/mLy96GBTnRoZrSxkGE0BKzKimdR8P3OzrNYNvBgtW7p055A+E8C31vXNUhWKoFCbhq7gbyhFg==", + "version": "4.14.175", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.175.tgz", + "integrity": "sha512-XmdEOrKQ8a1Y/yxQFOMbC47G/V2VDO1GvMRnl4O75M4GW/abC5tnfzadQYkqEveqRM1dEJGFFegfPNA2vvx2iw==", "dev": true }, "@types/long": { @@ -1189,9 +1302,9 @@ } }, "@types/node": { - "version": "16.9.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.6.tgz", - "integrity": "sha512-YHUZhBOMTM3mjFkXVcK+WwAcYmyhe1wL4lfqNtzI0b3qAy7yuSetnM7QJazgE5PFmgVTNGiLOgRFfJMqW7XpSQ==" + "version": "16.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.2.tgz", + "integrity": "sha512-zCclL4/rx+W5SQTzFs9wyvvyCwoK9QtBpratqz2IYJ3O8Umrn0m3nsTv0wQBk9sRGpvUe9CwPDrQFB10f1FIjQ==" }, "@types/qs": { "version": "6.9.7", @@ -1235,9 +1348,9 @@ } }, "@types/sinon": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.3.tgz", - "integrity": "sha512-XUaFuUOQ3A/r6gS1qCU/USMleascaqGeQpGR1AZ5JdRtBPlzijRzKsik1TuGzvdtPA0mdq42JqaJmJ+Afg1LJg==", + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.4.tgz", + "integrity": "sha512-fOYjrxQv8zJsqOY6V6ecP4eZhQBxtY80X0er1VVnUIAIZo74jHm8e1vguG5Yt4Iv8W2Wr7TgibB8MfRe32k9pA==", "dev": true, "requires": { "@sinonjs/fake-timers": "^7.1.0" @@ -1327,12 +1440,6 @@ "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", "dev": true }, - "abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", - "dev": true - }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -1354,24 +1461,6 @@ "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true }, - "acorn-globals": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", - "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==", - "dev": true, - "requires": { - "acorn": "^6.0.1", - "acorn-walk": "^6.0.1" - }, - "dependencies": { - "acorn": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", - "dev": true - } - } - }, "acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -1379,9 +1468,9 @@ "dev": true }, "acorn-walk": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", - "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "dev": true }, "agent-base": { @@ -1616,6 +1705,16 @@ } } }, + "api-extractor-model-me": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/api-extractor-model-me/-/api-extractor-model-me-0.1.1.tgz", + "integrity": "sha512-Ez801ZMADfkseOWNRFquvyQYDm3D9McpxfkKMWL6JFCGcpub0miJ+TFNphIR1nSZbrsxz3kIeOovNMY4VlL6Bw==", + "dev": true, + "requires": { + "@microsoft/tsdoc": "0.12.24", + "@rushstack/node-core-library": "3.36.0" + } + }, "append-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", @@ -1751,12 +1850,6 @@ "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", "dev": true }, - "array-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", - "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", - "dev": true - }, "array-initial": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", @@ -1909,12 +2002,6 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, - "at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true - }, "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", @@ -2082,12 +2169,6 @@ "fill-range": "^7.0.1" } }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, "browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", @@ -2095,15 +2176,15 @@ "dev": true }, "browserslist": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.1.tgz", - "integrity": "sha512-aLD0ZMDSnF4lUt4ZDNgqi5BUn9BZ7YdQdI/cYlILrhdSSZJLU9aNZoD5/NBmM4SK34APB2e83MOsRt1EnkuyaQ==", + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.2.tgz", + "integrity": "sha512-jSDZyqJmkKMEMi7SZAgX5UltFdR5NAO43vY0AwTpu4X3sGH7GLLQ83KiUomgrnvZRCeW0yPPnKqnxPqQOER9zQ==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001259", - "electron-to-chromium": "^1.3.846", + "caniuse-lite": "^1.0.30001261", + "electron-to-chromium": "^1.3.854", "escalade": "^3.1.1", - "nanocolors": "^0.1.5", + "nanocolors": "^0.2.12", "node-releases": "^1.1.76" } }, @@ -2187,13 +2268,10 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001260", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001260.tgz", - "integrity": "sha512-Fhjc/k8725ItmrvW5QomzxLeojewxvqiYCKeFcfFEhut28IVLdpHU19dneOmltZQIE5HNbawj1HYD+1f2bM1Dg==", - "dev": true, - "requires": { - "nanocolors": "^0.1.0" - } + "version": "1.0.30001263", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001263.tgz", + "integrity": "sha512-doiV5dft6yzWO1WwU19kt8Qz8R0/8DgEziz6/9n2FxUasteZNwNNYSmJO3GLBH8lCVE73AB1RPDPAeYbcO5Cvw==", + "dev": true }, "caseless": { "version": "0.12.0", @@ -2743,29 +2821,6 @@ "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", "optional": true }, - "cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true - }, - "cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "requires": { - "cssom": "~0.3.6" - }, - "dependencies": { - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - } - } - }, "d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", @@ -2785,45 +2840,6 @@ "assert-plus": "^1.0.0" } }, - "data-urls": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", - "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", - "dev": true, - "requires": { - "abab": "^2.0.0", - "whatwg-mimetype": "^2.2.0", - "whatwg-url": "^7.0.0" - }, - "dependencies": { - "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true - }, - "whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - } - } - }, "date-and-time": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-2.0.0.tgz", @@ -3027,23 +3043,6 @@ "esutils": "^2.0.2" } }, - "domexception": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", - "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", - "dev": true, - "requires": { - "webidl-conversions": "^4.0.2" - }, - "dependencies": { - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true - } - } - }, "dot-prop": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", @@ -3094,9 +3093,9 @@ } }, "electron-to-chromium": { - "version": "1.3.848", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.848.tgz", - "integrity": "sha512-wchRyBcdcmibioggdO7CbMT5QQ4lXlN/g7Mkpf1K2zINidnqij6EVu94UIZ+h5nB2S9XD4bykqFv9LonAWLFyw==", + "version": "1.3.856", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.856.tgz", + "integrity": "sha512-lSezYIe1/p5qkEswAfaQUseOBiwGwuCvRl/MKzOEVe++DcmQ92+43dznDl4rFJ4Zpu+kevhwyIf7KjJevyDA/A==", "dev": true }, "emoji-regex": { @@ -3128,9 +3127,9 @@ } }, "es-abstract": { - "version": "1.18.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.6.tgz", - "integrity": "sha512-kAeIT4cku5eNLNuUKhlmtuk1/TRZvQoYccn6TO0cSVdf1kzB0T7+dYuVK9MWM7l+/53W2Q8M7N2c6MQvhXFcUQ==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.0.tgz", + "integrity": "sha512-oWPrF+7P1nGv/rw9oIInwdkmI1qediEJSvVfHFryBd8mWllCKB5tke3aKyf51J6chgyKmi6mODqdnin2yb88Nw==", "dev": true, "requires": { "call-bind": "^1.0.2", @@ -3144,7 +3143,9 @@ "is-callable": "^1.2.4", "is-negative-zero": "^2.0.1", "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.1", "is-string": "^1.0.7", + "is-weakref": "^1.0.1", "object-inspect": "^1.11.0", "object-keys": "^1.1.1", "object.assign": "^4.1.2", @@ -3225,19 +3226,6 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, - "escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "dev": true, - "requires": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - } - }, "eslint": { "version": "6.8.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", @@ -3366,6 +3354,16 @@ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, "mkdirp": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", @@ -3555,9 +3553,9 @@ } }, "ext": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.5.0.tgz", - "integrity": "sha512-+ONcYoWj/SoQwUofMr94aGu05Ou4FepKi7N7b+O8T4jVfyIsZQV1/xeS8jpaBzF0csAk0KLXoHCxU7cKYZjo1Q==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", + "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==", "dev": true, "requires": { "type": "^2.5.0" @@ -4545,9 +4543,9 @@ } }, "google-auth-library": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.9.2.tgz", - "integrity": "sha512-HjxbJt660a+YUTYAgYor87JCuBZvjUSNBExk4bXTEaMuCn8IHSDeHmFxKqThuDPrLCiKJp8blk/Ze8f7SI4N6g==", + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.10.0.tgz", + "integrity": "sha512-ICsqaU+lxMHVlDUzMrfVIEqnARw2AwBiZ/2KnNM6BcTf9Nott+Af87DTIzmlnW865p3REUP2MVL0xkPC3a61aQ==", "optional": true, "requires": { "arrify": "^2.0.0", @@ -4562,9 +4560,9 @@ } }, "google-gax": { - "version": "2.25.4", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.25.4.tgz", - "integrity": "sha512-+Jd0FFOWyb8ieX53e6Sl5OYvHXoA1sWKfQ24ykR502NKgBTvPAh/RFcITihGePBJZ1E8pfh4MKWU0Sf+f1CK+A==", + "version": "2.27.0", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.27.0.tgz", + "integrity": "sha512-xcLCeNKCqNm/w0At7/vdZHV/zol/iRS+PSAZTu7i6xNGBra/kWI3cfn4M6ZLQXeUEGbTVLJ4zGm53TVc4lvbDA==", "optional": true, "requires": { "@grpc/grpc-js": "~1.3.0", @@ -4823,19 +4821,6 @@ "glogg": "^1.0.0" } }, - "handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", - "dev": true, - "requires": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4", - "wordwrap": "^1.0.0" - } - }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -4985,12 +4970,6 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, - "highlight.js": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", - "dev": true - }, "homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -5006,15 +4985,6 @@ "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, - "html-encoding-sniffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", - "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", - "dev": true, - "requires": { - "whatwg-encoding": "^1.0.1" - } - }, "html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -5040,12 +5010,12 @@ "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==" }, "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", "optional": true, "requires": { - "@tootallnate/once": "1", + "@tootallnate/once": "2", "agent-base": "6", "debug": "4" } @@ -5189,12 +5159,6 @@ "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", "dev": true }, - "ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", - "dev": true - }, "is-absolute": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", @@ -5272,9 +5236,9 @@ "dev": true }, "is-core-module": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.6.0.tgz", - "integrity": "sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.7.0.tgz", + "integrity": "sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ==", "dev": true, "requires": { "has": "^1.0.3" @@ -5346,9 +5310,9 @@ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "requires": { "is-extglob": "^2.1.1" @@ -5433,6 +5397,12 @@ "is-unc-path": "^1.0.0" } }, + "is-shared-array-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", + "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", + "dev": true + }, "is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -5488,6 +5458,15 @@ "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", "dev": true }, + "is-weakref": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.1.tgz", + "integrity": "sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0" + } + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -5671,13 +5650,20 @@ "dev": true }, "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", + "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", "dev": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + } } }, "jsbn": { @@ -5686,74 +5672,6 @@ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", "dev": true }, - "jsdom": { - "version": "15.2.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-15.2.1.tgz", - "integrity": "sha512-fAl1W0/7T2G5vURSyxBzrJ1LSdQn6Tr5UX/xD4PXDx/PDgwygedfW6El/KIj3xJ7FU61TTYnc/l/B7P49Eqt6g==", - "dev": true, - "requires": { - "abab": "^2.0.0", - "acorn": "^7.1.0", - "acorn-globals": "^4.3.2", - "array-equal": "^1.0.0", - "cssom": "^0.4.1", - "cssstyle": "^2.0.0", - "data-urls": "^1.1.0", - "domexception": "^1.0.1", - "escodegen": "^1.11.1", - "html-encoding-sniffer": "^1.0.2", - "nwsapi": "^2.2.0", - "parse5": "5.1.0", - "pn": "^1.1.0", - "request": "^2.88.0", - "request-promise-native": "^1.0.7", - "saxes": "^3.1.9", - "symbol-tree": "^3.2.2", - "tough-cookie": "^3.0.1", - "w3c-hr-time": "^1.0.1", - "w3c-xmlserializer": "^1.1.2", - "webidl-conversions": "^4.0.2", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^7.0.0", - "ws": "^7.0.0", - "xml-name-validator": "^3.0.0" - }, - "dependencies": { - "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true - }, - "whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, - "ws": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz", - "integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==", - "dev": true - } - } - }, "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -6180,12 +6098,6 @@ "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=", "dev": true }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true - }, "lodash.template": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", @@ -6253,12 +6165,6 @@ } } }, - "lunr": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", - "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", - "dev": true - }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -6303,12 +6209,6 @@ "object-visit": "^1.0.0" } }, - "marked": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/marked/-/marked-1.2.9.tgz", - "integrity": "sha512-H8lIX2SvyitGX+TRdtS06m1jHMijKN/XjfH6Ooii9fvxMlh8QdqBfBDkGUpMWH2kQNrtixjzYUa3SH8ROTgRRw==", - "dev": true - }, "matchdep": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", @@ -6478,22 +6378,14 @@ "mime-db": { "version": "1.50.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz", - "integrity": "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==", - "optional": true + "integrity": "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==" }, "mime-types": { - "version": "2.1.32", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", - "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", + "version": "2.1.33", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.33.tgz", + "integrity": "sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==", "requires": { - "mime-db": "1.49.0" - }, - "dependencies": { - "mime-db": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", - "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==" - } + "mime-db": "1.50.0" } }, "mimic-fn": { @@ -6612,12 +6504,6 @@ "picomatch": "^2.0.4" } }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -6703,15 +6589,6 @@ "binary-extensions": "^2.0.0" } }, - "js-yaml": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", - "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -6823,9 +6700,9 @@ "optional": true }, "nanocolors": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/nanocolors/-/nanocolors-0.1.12.tgz", - "integrity": "sha512-2nMHqg1x5PU+unxX7PGY7AuYxl2qDx7PSrTRjizr8sxdd3l/3hBuWWaki62qmtYm2U5i4Z5E7GbjlyDFhs9/EQ==", + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/nanocolors/-/nanocolors-0.2.12.tgz", + "integrity": "sha512-SFNdALvzW+rVlzqexid6epYdt8H9Zol7xDoQarioEFcFN0JHo4CYNztAxmtfgGTVRCmFlEOqqhBpoFGKqSAMug==", "dev": true }, "nanoid": { @@ -6859,12 +6736,6 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, "next-tick": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", @@ -7157,12 +7028,6 @@ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, - "nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", - "dev": true - }, "nyc": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", @@ -7634,12 +7499,6 @@ "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", "dev": true }, - "parse5": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", - "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==", - "dev": true - }, "pascalcase": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", @@ -7828,12 +7687,6 @@ "extend-shallow": "^3.0.2" } }, - "pn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", - "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", - "dev": true - }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -8290,16 +8143,6 @@ "mime-types": "^2.1.12" } }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, "uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", @@ -8318,18 +8161,6 @@ "request-promise-core": "1.1.4", "stealthy-require": "^1.1.1", "tough-cookie": "^2.3.3" - }, - "dependencies": { - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - } } }, "request-promise-core": { @@ -8341,29 +8172,6 @@ "lodash": "^4.17.19" } }, - "request-promise-native": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", - "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", - "dev": true, - "requires": { - "request-promise-core": "1.1.4", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - }, - "dependencies": { - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - } - } - }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -8617,19 +8425,10 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, - "saxes": { - "version": "3.1.11", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-3.1.11.tgz", - "integrity": "sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g==", - "dev": true, - "requires": { - "xmlchars": "^2.1.1" - } - }, "selenium-webdriver": { - "version": "4.0.0-rc-1", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.0.0-rc-1.tgz", - "integrity": "sha512-bcrwFPRax8fifRP60p7xkWDGSJJoMkPAzufMlk5K2NyLPht/YZzR2WcIk1+3gR8VOCLlst1P2PI+MXACaFzpIw==", + "version": "4.0.0-rc-2", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.0.0-rc-2.tgz", + "integrity": "sha512-HT974l00r7wdZL+SPS0f8lBLVYe/aKGAFONMvVroL7z9mHm3PC30IirsYqrvSkw51Pom3XJiN5gjXBRkxuHAdw==", "dev": true, "requires": { "jszip": "^3.6.0", @@ -8717,17 +8516,6 @@ "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==", "dev": true }, - "shelljs": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", - "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", - "dev": true, - "requires": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - } - }, "side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -8740,9 +8528,9 @@ } }, "signal-exit": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.4.tgz", - "integrity": "sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q==" + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz", + "integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==" }, "sinon": { "version": "9.2.4", @@ -9260,12 +9048,6 @@ "es6-symbol": "^3.1.1" } }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, "table": { "version": "5.4.6", "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", @@ -9333,12 +9115,12 @@ } }, "teeny-request": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.1.2.tgz", - "integrity": "sha512-Mr4NYZuniKDpgcLxdBkDE1CcWy98Aw1ennn6oNofen+XWUvDs+ZZzBAujy6XOAVwwLLZMwEQSfdljUI+ebs4Ww==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.1.3.tgz", + "integrity": "sha512-Ew3aoFzgQEatLA5OBIjdr1DWJUaC1xardG+qbPPo5k/y/3fMwXLxpjh5UB5dVfElktLaQbbMs80chkz53ByvSg==", "optional": true, "requires": { - "http-proxy-agent": "^4.0.0", + "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "node-fetch": "^2.6.1", "stream-events": "^1.0.5", @@ -9526,12 +9308,11 @@ } }, "tough-cookie": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", - "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", "dev": true, "requires": { - "ip-regex": "^2.1.0", "psl": "^1.1.28", "punycode": "^2.1.1" } @@ -9567,12 +9348,6 @@ "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", "dev": true }, - "acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true - }, "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -9659,83 +9434,12 @@ "is-typedarray": "^1.0.0" } }, - "typedoc": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.19.2.tgz", - "integrity": "sha512-oDEg1BLEzi1qvgdQXc658EYgJ5qJLVSeZ0hQ57Eq4JXy6Vj2VX4RVo18qYxRWz75ifAaYuYNBUCnbhjd37TfOg==", - "dev": true, - "requires": { - "fs-extra": "^9.0.1", - "handlebars": "^4.7.6", - "highlight.js": "^10.2.0", - "lodash": "^4.17.20", - "lunr": "^2.3.9", - "marked": "^1.1.1", - "minimatch": "^3.0.0", - "progress": "^2.0.3", - "semver": "^7.3.2", - "shelljs": "^0.8.4", - "typedoc-default-themes": "^0.11.4" - }, - "dependencies": { - "fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "requires": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - } - } - }, - "typedoc-default-themes": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/typedoc-default-themes/-/typedoc-default-themes-0.11.4.tgz", - "integrity": "sha512-Y4Lf+qIb9NTydrexlazAM46SSLrmrQRqWiD52593g53SsmUFioAsMWt8m834J6qsp+7wHRjxCXSZeiiW5cMUdw==", - "dev": true - }, "typescript": { "version": "3.9.10", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==", "dev": true }, - "uglify-js": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.14.2.tgz", - "integrity": "sha512-rtPMlmcO4agTUfz10CbgJ1k6UAoXM2gWb3GoMPPZB/+/Ackf8lNWk11K4rYi2D0apgoFRLtQOZhb+/iGNJq26A==", - "dev": true, - "optional": true - }, "unbox-primitive": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", @@ -10087,34 +9791,6 @@ } } }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, - "w3c-xmlserializer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz", - "integrity": "sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg==", - "dev": true, - "requires": { - "domexception": "^1.0.1", - "webidl-conversions": "^4.0.2", - "xml-name-validator": "^3.0.0" - }, - "dependencies": { - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true - } - } - }, "webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -10135,21 +9811,6 @@ "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" }, - "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "requires": { - "iconv-lite": "0.4.24" - } - }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, "whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -10235,12 +9896,6 @@ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, "workerpool": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", @@ -10305,18 +9960,6 @@ "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", "optional": true }, - "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -10334,9 +9977,9 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yargs": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.2.0.tgz", - "integrity": "sha512-UPeZv4h9Xv510ibpt5rdsUNzgD78nMa1rhxxCgvkKiq06hlKCEHJLiJ6Ub8zDg/wR6hedEI6ovnd2vCvJ4nusA==", + "version": "17.2.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.2.1.tgz", + "integrity": "sha512-XfR8du6ua4K6uLGm5S6fA+FIJom/MdJcFNVY8geLlp2v8GYbOXD4EB1tPNZsRn4vBzKGMgb5DRZMeWuFc2GO8Q==", "dev": true, "requires": { "cliui": "^7.0.2", diff --git a/package.json b/package.json index 47037009b6..6648fbf991 100644 --- a/package.json +++ b/package.json @@ -6,12 +6,12 @@ "license": "Apache-2.0", "homepage": "https://firebase.google.com/", "engines": { - "node": ">=10.13.0" + "node": ">=12.7.0" }, "scripts": { "build": "gulp build", "build:tests": "gulp compile_test", - "prepare": "npm run build", + "prepare": "npm run build && npm run esm-wrap", "lint": "run-p lint:src lint:test", "test": "run-s lint test:unit", "integration": "run-s build test:integration", @@ -20,9 +20,14 @@ "test:coverage": "nyc npm run test:unit", "lint:src": "eslint src/ --ext .ts", "lint:test": "eslint test/ --ext .ts", - "apidocs": "node docgen/generate-docs.js --api node", - "api-extractor": "api-extractor run", - "api-extractor:local": "api-extractor run --local" + "apidocs": "run-s api-extractor:local api-documenter", + "api-extractor": "node generate-reports.js", + "api-extractor:local": "npm run build && node generate-reports.js --local", + "esm-wrap": "node generate-esm-wrapper.js", + "api-documenter": "run-s api-documenter:markdown api-documenter:toc api-documenter:post", + "api-documenter:markdown": "api-documenter-fire markdown --input temp --output docgen/markdown -s", + "api-documenter:toc": "api-documenter-fire toc --input temp --output docgen/markdown -p /docs/reference/admin/node -s", + "api-documenter:post": "node docgen/post-process.js" }, "nyc": { "extension": [ @@ -55,6 +60,104 @@ "package.json" ], "types": "./lib/index.d.ts", + "typesVersions": { + "*": { + "app": [ + "lib/app" + ], + "app-check": [ + "lib/app-check" + ], + "auth": [ + "lib/auth" + ], + "database": [ + "lib/database" + ], + "firestore": [ + "lib/firestore" + ], + "installations": [ + "lib/installations" + ], + "instance-id": [ + "lib/instance-id" + ], + "machine-learning": [ + "lib/machine-learning" + ], + "messaging": [ + "lib/messaging" + ], + "project-management": [ + "lib/project-management" + ], + "remote-config": [ + "lib/remote-config" + ], + "security-rules": [ + "lib/security-rules" + ], + "storage": [ + "lib/storage" + ] + } + }, + "exports": { + ".": "./lib/index.js", + "./app": { + "require": "./lib/app/index.js", + "import": "./lib/esm/app/index.js" + }, + "./app-check": { + "require": "./lib/app-check/index.js", + "import": "./lib/esm/app-check/index.js" + }, + "./auth": { + "require": "./lib/auth/index.js", + "import": "./lib/esm/auth/index.js" + }, + "./database": { + "require": "./lib/database/index.js", + "import": "./lib/esm/database/index.js" + }, + "./firestore": { + "require": "./lib/firestore/index.js", + "import": "./lib/esm/firestore/index.js" + }, + "./installations": { + "require": "./lib/installations/index.js", + "import": "./lib/esm/installations/index.js" + }, + "./instance-id": { + "require": "./lib/instance-id/index.js", + "import": "./lib/esm/instance-id/index.js" + }, + "./machine-learning": { + "require": "./lib/machine-learning/index.js", + "import": "./lib/esm/machine-learning/index.js" + }, + "./messaging": { + "require": "./lib/messaging/index.js", + "import": "./lib/esm/messaging/index.js" + }, + "./project-management": { + "require": "./lib/project-management/index.js", + "import": "./lib/esm/project-management/index.js" + }, + "./remote-config": { + "require": "./lib/remote-config/index.js", + "import": "./lib/esm/remote-config/index.js" + }, + "./security-rules": { + "require": "./lib/security-rules/index.js", + "import": "./lib/esm/security-rules/index.js" + }, + "./storage": { + "require": "./lib/storage/index.js", + "import": "./lib/esm/storage/index.js" + } + }, "dependencies": { "@firebase/database-compat": "^0.1.1", "@firebase/database-types": "^0.7.2", @@ -69,6 +172,7 @@ "@google-cloud/storage": "^5.3.0" }, "devDependencies": { + "@firebase/api-documenter": "^0.1.2", "@firebase/app-compat": "^0.1.2", "@firebase/auth-compat": "^0.1.3", "@firebase/auth-types": "^0.10.3", @@ -101,7 +205,6 @@ "gulp-header": "^2.0.9", "gulp-typescript": "^5.0.1", "http-message-parser": "^0.0.34", - "jsdom": "^15.0.0", "lodash": "^4.17.15", "minimist": "^1.2.0", "mocha": "^8.0.0", @@ -115,7 +218,6 @@ "sinon": "^9.0.0", "sinon-chai": "^3.0.0", "ts-node": "^10.2.0", - "typedoc": "^0.19.2", "typescript": "^3.7.3", "yargs": "^17.0.1" } diff --git a/src/app-check/app-check-api-client-internal.ts b/src/app-check/app-check-api-client-internal.ts index 8d25e23cf7..e7427f838a 100644 --- a/src/app-check/app-check-api-client-internal.ts +++ b/src/app-check/app-check-api-client-internal.ts @@ -15,17 +15,15 @@ * limitations under the License. */ -import { appCheck } from './index'; +import { App } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; import { HttpRequestConfig, HttpClient, HttpError, AuthorizedHttpClient, HttpResponse } from '../utils/api-request'; -import { FirebaseApp } from '../firebase-app'; import { PrefixedFirebaseError } from '../utils/error'; - import * as utils from '../utils/index'; import * as validator from '../utils/validator'; - -import AppCheckToken = appCheck.AppCheckToken; +import { AppCheckToken } from './app-check-api' // App Check backend constants const FIREBASE_APP_CHECK_V1_API_URL_FORMAT = 'https://firebaseappcheck.googleapis.com/v1beta/projects/{projectId}/apps/{appId}:exchangeCustomToken'; @@ -43,21 +41,21 @@ export class AppCheckApiClient { private readonly httpClient: HttpClient; private projectId?: string; - constructor(private readonly app: FirebaseApp) { + constructor(private readonly app: App) { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseAppCheckError( 'invalid-argument', 'First argument passed to admin.appCheck() must be a valid Firebase app instance.'); } - this.httpClient = new AuthorizedHttpClient(app); + this.httpClient = new AuthorizedHttpClient(app as FirebaseApp); } /** * Exchange a signed custom token to App Check token - * + * * @param customToken The custom token to be exchanged. * @param appId The mobile App ID. - * @return A promise that fulfills with a `AppCheckToken`. + * @returns A promise that fulfills with a `AppCheckToken`. */ public exchangeToken(customToken: string, appId: string): Promise { if (!validator.isNonEmptyString(appId)) { @@ -143,7 +141,7 @@ export class AppCheckApiClient { * Creates an AppCheckToken from the API response. * * @param resp API response object. - * @return An AppCheckToken instance. + * @returns An AppCheckToken instance. */ private toAppCheckToken(resp: HttpResponse): AppCheckToken { const token = resp.data.attestationToken; @@ -161,10 +159,10 @@ export class AppCheckApiClient { * * @param duration The duration as a string with the suffix "s" preceded by the * number of seconds, with fractional seconds. For example, 3 seconds with 0 nanoseconds - * is expressed as "3s", while 3 seconds and 1 nanosecond is expressed as "3.000000001s", + * is expressed as "3s", while 3 seconds and 1 nanosecond is expressed as "3.000000001s", * and 3 seconds and 1 microsecond is expressed as "3.000001s". - * - * @return The duration in milliseconds. + * + * @returns The duration in milliseconds. */ private stringToMilliseconds(duration: string): number { if (!validator.isNonEmptyString(duration) || !duration.endsWith('s')) { @@ -211,8 +209,8 @@ export type AppCheckErrorCode = /** * Firebase App Check error code structure. This extends PrefixedFirebaseError. * - * @param {AppCheckErrorCode} code The error code. - * @param {string} message The error message. + * @param code The error code. + * @param message The error message. * @constructor */ export class FirebaseAppCheckError extends PrefixedFirebaseError { diff --git a/src/app-check/app-check-api.ts b/src/app-check/app-check-api.ts new file mode 100644 index 0000000000..ab959af04d --- /dev/null +++ b/src/app-check/app-check-api.ts @@ -0,0 +1,105 @@ +/*! + * @license + * Copyright 2021 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. + */ + +/** + * Interface representing an App Check token. + */ +export interface AppCheckToken { + /** + * The Firebase App Check token. + */ + token: string; + + /** + * The time-to-live duration of the token in milliseconds. + */ + ttlMillis: number; +} + +/** + * Interface representing App Check token options. + */ +export interface AppCheckTokenOptions { + /** + * The length of time, in milliseconds, for which the App Check token will + * be valid. This value must be between 30 minutes and 7 days, inclusive. + */ + ttlMillis?: number; +} + +/** + * Interface representing a decoded Firebase App Check token, returned from the + * {@link AppCheck.verifyToken} method. + */ +export interface DecodedAppCheckToken { + /** + * The issuer identifier for the issuer of the response. + * This value is a URL with the format + * `https://firebaseappcheck.googleapis.com/`, where `` is the + * same project number specified in the {@link DecodedAppCheckToken.aud | aud} property. + */ + iss: string; + + /** + * The Firebase App ID corresponding to the app the token belonged to. + * As a convenience, this value is copied over to the {@link DecodedAppCheckToken.app_id | app_id} property. + */ + sub: string; + + /** + * The audience for which this token is intended. + * This value is a JSON array of two strings, the first is the project number of your + * Firebase project, and the second is the project ID of the same project. + */ + aud: string[]; + + /** + * The App Check token's expiration time, in seconds since the Unix epoch. That is, the + * time at which this App Check token expires and should no longer be considered valid. + */ + exp: number; + + /** + * The App Check token's issued-at time, in seconds since the Unix epoch. That is, the + * time at which this App Check token was issued and should start to be considered + * valid. + */ + iat: number; + + /** + * The App ID corresponding to the App the App Check token belonged to. + * This value is not actually one of the JWT token claims. It is added as a + * convenience, and is set as the value of the {@link DecodedAppCheckToken.sub | sub} property. + */ + app_id: string; + [key: string]: any; +} + +/** + * Interface representing a verified App Check token response. + */ +export interface VerifyAppCheckTokenResponse { + /** + * The App ID corresponding to the App the App Check token belonged to. + */ + appId: string; + + /** + * The decoded Firebase App Check token. + */ + token: DecodedAppCheckToken; +} diff --git a/src/app-check/app-check-namespace.ts b/src/app-check/app-check-namespace.ts new file mode 100644 index 0000000000..128cedd474 --- /dev/null +++ b/src/app-check/app-check-namespace.ts @@ -0,0 +1,77 @@ +/*! + * Copyright 2021 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 { App } from '../app'; +import { + AppCheckToken as TAppCheckToken, + AppCheckTokenOptions as TAppCheckTokenOptions, + DecodedAppCheckToken as TDecodedAppCheckToken, + VerifyAppCheckTokenResponse as TVerifyAppCheckTokenResponse, +} from './app-check-api'; +import { AppCheck as TAppCheck } from './app-check'; + +/** + * Gets the {@link firebase-admin.app-check#AppCheck} service for the default app or a given app. + * + * `admin.appCheck()` can be called with no arguments to access the default + * app's `AppCheck` service or as `admin.appCheck(app)` to access the + * `AppCheck` service associated with a specific app. + * + * @example + * ```javascript + * // Get the `AppCheck` service for the default app + * var defaultAppCheck = admin.appCheck(); + * ``` + * + * @example + * ```javascript + * // Get the `AppCheck` service for a given app + * var otherAppCheck = admin.appCheck(otherApp); + * ``` + * + * @param app Optional app for which to return the `AppCheck` service. + * If not provided, the default `AppCheck` service is returned. + * + * @returns The default `AppCheck` service if no + * app is provided, or the `AppCheck` service associated with the provided + * app. + */ +export declare function appCheck(app?: App): appCheck.AppCheck; + +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace appCheck { + /** + * Type alias to {@link firebase-admin.app-check#AppCheck}. + */ + export type AppCheck = TAppCheck; + + /** + * Type alias to {@link firebase-admin.app-check#AppCheckToken}. + */ + export type AppCheckToken = TAppCheckToken; + + /** + * Type alias to {@link firebase-admin.app-check#DecodedAppCheckToken}. + */ + export type DecodedAppCheckToken = TDecodedAppCheckToken; + + /** + * Type alias to {@link firebase-admin.app-check#VerifyAppCheckTokenResponse}. + */ + export type VerifyAppCheckTokenResponse = TVerifyAppCheckTokenResponse; + + export type AppCheckTokenOptions = TAppCheckTokenOptions; +} diff --git a/src/app-check/app-check.ts b/src/app-check/app-check.ts index 175d023419..97fc24f933 100644 --- a/src/app-check/app-check.ts +++ b/src/app-check/app-check.ts @@ -15,24 +15,24 @@ * limitations under the License. */ -import { FirebaseApp } from '../firebase-app'; -import { appCheck } from './index'; +import { App } from '../app'; import { AppCheckApiClient } from './app-check-api-client-internal'; import { - appCheckErrorFromCryptoSignerError, AppCheckTokenGenerator + appCheckErrorFromCryptoSignerError, AppCheckTokenGenerator, } from './token-generator'; import { AppCheckTokenVerifier } from './token-verifier'; import { cryptoSignerFromApp } from '../utils/crypto-signer'; -import AppCheckInterface = appCheck.AppCheck; -import AppCheckToken = appCheck.AppCheckToken; -import AppCheckTokenOptions = appCheck.AppCheckTokenOptions; -import VerifyAppCheckTokenResponse = appCheck.VerifyAppCheckTokenResponse; +import { + AppCheckToken, + AppCheckTokenOptions, + VerifyAppCheckTokenResponse, +} from './app-check-api'; /** - * AppCheck service bound to the provided app. + * The Firebase `AppCheck` service interface. */ -export class AppCheck implements AppCheckInterface { +export class AppCheck { private readonly client: AppCheckApiClient; private readonly tokenGenerator: AppCheckTokenGenerator; @@ -41,8 +41,9 @@ export class AppCheck implements AppCheckInterface { /** * @param app The app for this AppCheck service. * @constructor + * @internal */ - constructor(readonly app: FirebaseApp) { + constructor(readonly app: App) { this.client = new AppCheckApiClient(app); try { this.tokenGenerator = new AppCheckTokenGenerator(cryptoSignerFromApp(app)); @@ -69,12 +70,14 @@ export class AppCheck implements AppCheckInterface { } /** - * Verifies an App Check token. + * Verifies a Firebase App Check token (JWT). If the token is valid, the promise is + * fulfilled with the token's decoded claims; otherwise, the promise is + * rejected. * * @param appCheckToken The App Check token to verify. * - * @return A promise that fulfills with a `VerifyAppCheckTokenResponse` on successful - * verification. + * @returns A promise fulfilled with the token's decoded claims + * if the App Check token is valid; otherwise, a rejected promise. */ public verifyToken(appCheckToken: string): Promise { return this.appCheckTokenVerifier.verifyToken(appCheckToken) diff --git a/src/app-check/index.ts b/src/app-check/index.ts index 295f49e4be..3ff1ae302d 100644 --- a/src/app-check/index.ts +++ b/src/app-check/index.ts @@ -15,162 +15,55 @@ * limitations under the License. */ -import { app } from '../firebase-namespace-api'; +/** + * Firebase App Check. + * + * @packageDocumentation + */ + +import { App, getApp } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; +import { AppCheck } from './app-check'; + +export { + AppCheckToken, + AppCheckTokenOptions, + DecodedAppCheckToken, + VerifyAppCheckTokenResponse, +} from './app-check-api'; +export { AppCheck } from './app-check'; /** - * Gets the {@link appCheck.AppCheck `AppCheck`} service for the - * default app or a given app. + * Gets the {@link AppCheck} service for the default app or a given app. * - * You can call `admin.appCheck()` with no arguments to access the default - * app's {@link appCheck.AppCheck `AppCheck`} service or as - * `admin.appCheck(app)` to access the - * {@link appCheck.AppCheck `AppCheck`} service associated with a - * specific app. + * `getAppCheck()` can be called with no arguments to access the default + * app's `AppCheck` service or as `getAppCheck(app)` to access the + * `AppCheck` service associated with a specific app. * * @example * ```javascript * // Get the `AppCheck` service for the default app - * var defaultAppCheck = admin.appCheck(); + * const defaultAppCheck = getAppCheck(); * ``` * * @example * ```javascript * // Get the `AppCheck` service for a given app - * var otherAppCheck = admin.appCheck(otherApp); + * const otherAppCheck = getAppCheck(otherApp); * ``` * * @param app Optional app for which to return the `AppCheck` service. * If not provided, the default `AppCheck` service is returned. * - * @return The default `AppCheck` service if no + * @returns The default `AppCheck` service if no * app is provided, or the `AppCheck` service associated with the provided * app. */ -export declare function appCheck(app?: app.App): appCheck.AppCheck; - -/* eslint-disable @typescript-eslint/no-namespace */ -export namespace appCheck { - /** - * The Firebase `AppCheck` service interface. - */ - export interface AppCheck { - app: app.App; - - /** - * Creates a new {@link appCheck.AppCheckToken `AppCheckToken`} that can be sent - * back to a client. - * - * @param appId The App ID of the Firebase App the token belongs to. - * @param options Optional options object when creating a new App Check Token. - * - * @returns A promise that fulfills with a `AppCheckToken`. - */ - createToken(appId: string, options?: AppCheckTokenOptions): Promise; - - /** - * Verifies a Firebase App Check token (JWT). If the token is valid, the promise is - * fulfilled with the token's decoded claims; otherwise, the promise is - * rejected. - * - * @param appCheckToken The App Check token to verify. - * - * @return A promise fulfilled with the - * token's decoded claims if the App Check token is valid; otherwise, a rejected - * promise. - */ - verifyToken(appCheckToken: string): Promise; - } - - /** - * Interface representing an App Check token. - */ - export interface AppCheckToken { - /** - * The Firebase App Check token. - */ - token: string; - - /** - * The time-to-live duration of the token in milliseconds. - */ - ttlMillis: number; +export function getAppCheck(app?: App): AppCheck { + if (typeof app === 'undefined') { + app = getApp(); } - /** - * Interface representing App Check token options. - */ - export interface AppCheckTokenOptions { - /** - * The length of time, in milliseconds, for which the App Check token will - * be valid. This value must be between 30 minutes and 7 days, inclusive. - */ - ttlMillis?: number; - } - - /** - * Interface representing a decoded Firebase App Check token, returned from the - * {@link appCheck.AppCheck.verifyToken `verifyToken()`} method. - */ - export interface DecodedAppCheckToken { - /** - * The issuer identifier for the issuer of the response. - * - * This value is a URL with the format - * `https://firebaseappcheck.googleapis.com/`, where `` is the - * same project number specified in the [`aud`](#aud) property. - */ - iss: string; - - /** - * The Firebase App ID corresponding to the app the token belonged to. - * - * As a convenience, this value is copied over to the [`app_id`](#app_id) property. - */ - sub: string; - - /** - * The audience for which this token is intended. - * - * This value is a JSON array of two strings, the first is the project number of your - * Firebase project, and the second is the project ID of the same project. - */ - aud: string[]; - - /** - * The App Check token's expiration time, in seconds since the Unix epoch. That is, the - * time at which this App Check token expires and should no longer be considered valid. - */ - exp: number; - - /** - * The App Check token's issued-at time, in seconds since the Unix epoch. That is, the - * time at which this App Check token was issued and should start to be considered - * valid. - */ - iat: number; - - /** - * The App ID corresponding to the App the App Check token belonged to. - * - * This value is not actually one of the JWT token claims. It is added as a - * convenience, and is set as the value of the [`sub`](#sub) property. - */ - app_id: string; - [key: string]: any; - } - - /** - * Interface representing a verified App Check token response. - */ - export interface VerifyAppCheckTokenResponse { - /** - * The App ID corresponding to the App the App Check token belonged to. - */ - appId: string; - - /** - * The decoded Firebase App Check token. - */ - token: appCheck.DecodedAppCheckToken; - } + const firebaseApp: FirebaseApp = app as FirebaseApp; + return firebaseApp.getOrInitService('appCheck', (app) => new AppCheck(app)); } diff --git a/src/app-check/token-generator.ts b/src/app-check/token-generator.ts index 86745b793c..adc6898786 100644 --- a/src/app-check/token-generator.ts +++ b/src/app-check/token-generator.ts @@ -15,21 +15,17 @@ * limitations under the License. */ -import { appCheck } from './index'; - import * as validator from '../utils/validator'; import { toWebSafeBase64, transformMillisecondsToSecondsString } from '../utils'; - import { CryptoSigner, CryptoSignerError, CryptoSignerErrorCode } from '../utils/crypto-signer'; -import { +import { FirebaseAppCheckError, AppCheckErrorCode, - APP_CHECK_ERROR_CODE_MAPPING, + APP_CHECK_ERROR_CODE_MAPPING, } from './app-check-api-client-internal'; +import { AppCheckTokenOptions } from './app-check-api'; import { HttpError } from '../utils/api-request'; -import AppCheckTokenOptions = appCheck.AppCheckTokenOptions; - const ONE_MINUTE_IN_SECONDS = 60; const ONE_MINUTE_IN_MILLIS = ONE_MINUTE_IN_SECONDS * 1000; const ONE_DAY_IN_MILLIS = 24 * 60 * 60 * 1000; @@ -39,7 +35,7 @@ const FIREBASE_APP_CHECK_AUDIENCE = 'https://firebaseappcheck.googleapis.com/goo /** * Class for generating Firebase App Check tokens. - * + * * @internal */ export class AppCheckTokenGenerator { @@ -65,8 +61,8 @@ export class AppCheckTokenGenerator { * Creates a new custom token that can be exchanged to an App Check token. * * @param appId The Application ID to use for the generated token. - * - * @return A Promise fulfilled with a custom token signed with a service account key + * + * @returns A Promise fulfilled with a custom token signed with a service account key * that can be exchanged to an App Check token. */ public createCustomToken(appId: string, options?: AppCheckTokenOptions): Promise { @@ -113,7 +109,7 @@ export class AppCheckTokenGenerator { /** * Checks if a given `AppCheckTokenOptions` object is valid. If successful, returns an object with * custom properties. - * + * * @param options An options object to be validated. * @returns A custom object with ttl converted to protobuf Duration string format. */ @@ -134,7 +130,7 @@ export class AppCheckTokenGenerator { 'invalid-argument', 'ttlMillis must be a duration in milliseconds between 30 minutes and 7 days (inclusive).'); } - return { ttl: transformMillisecondsToSecondsString(options.ttlMillis) }; + return { ttl: transformMillisecondsToSecondsString(options.ttlMillis) }; } return {}; } @@ -145,7 +141,7 @@ export class AppCheckTokenGenerator { * details from a CryptoSignerError. * * @param err The Error to convert into a FirebaseAppCheckError error - * @return A Firebase App Check error that can be returned to the user. + * @returns A Firebase App Check error that can be returned to the user. */ export function appCheckErrorFromCryptoSignerError(err: Error): Error { if (!(err instanceof CryptoSignerError)) { diff --git a/src/app-check/token-verifier.ts b/src/app-check/token-verifier.ts index 318a1fd10b..661b805597 100644 --- a/src/app-check/token-verifier.ts +++ b/src/app-check/token-verifier.ts @@ -14,30 +14,29 @@ * limitations under the License. */ -import { appCheck } from '.'; import * as validator from '../utils/validator'; import * as util from '../utils/index'; import { FirebaseAppCheckError } from './app-check-api-client-internal'; -import { FirebaseApp } from '../firebase-app'; import { ALGORITHM_RS256, DecodedToken, decodeJwt, JwtError, JwtErrorCode, PublicKeySignatureVerifier, SignatureVerifier } from '../utils/jwt'; -import DecodedAppCheckToken = appCheck.DecodedAppCheckToken; +import { DecodedAppCheckToken } from './app-check-api' +import { App } from '../app'; const APP_CHECK_ISSUER = 'https://firebaseappcheck.googleapis.com/'; const JWKS_URL = 'https://firebaseappcheck.googleapis.com/v1beta/jwks'; /** * Class for verifying Firebase App Check tokens. - * + * * @internal */ export class AppCheckTokenVerifier { private readonly signatureVerifier: SignatureVerifier; - constructor(private readonly app: FirebaseApp) { + constructor(private readonly app: App) { this.signatureVerifier = PublicKeySignatureVerifier.withJwksUrl(JWKS_URL); } @@ -45,7 +44,7 @@ export class AppCheckTokenVerifier { * Verifies the format and signature of a Firebase App Check token. * * @param token The Firebase Auth JWT token to verify. - * @return A promise fulfilled with the decoded claims of the Firebase App Check token. + * @returns A promise fulfilled with the decoded claims of the Firebase App Check token. */ public verifyToken(token: string): Promise { if (!validator.isString(token)) { @@ -142,7 +141,7 @@ export class AppCheckTokenVerifier { /** * Maps JwtError to FirebaseAppCheckError - * + * * @param error JwtError to be mapped. * @returns FirebaseAppCheckError instance. */ diff --git a/src/app/core.ts b/src/app/core.ts new file mode 100644 index 0000000000..9434ab6244 --- /dev/null +++ b/src/app/core.ts @@ -0,0 +1,208 @@ +/*! + * @license + * Copyright 2021 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 { Agent } from 'http'; + +import { Credential } from './credential'; + +/** + * Available options to pass to {@link firebase-admin.app#initializeApp}. + */ +export interface AppOptions { + + /** + * A {@link Credential `Credential`} object used to + * authenticate the Admin SDK. + * + * See {@link https://firebase.google.com/docs/admin/setup#initialize_the_sdk | Initialize the SDK} + * for detailed documentation and code samples. + */ + credential?: Credential; + + /** + * The object to use as the {@link https://firebase.google.com/docs/reference/security/database/#auth | auth} + * variable in your Realtime Database Rules when the Admin SDK reads from or + * writes to the Realtime Database. This allows you to downscope the Admin SDK + * from its default full read and write privileges. + * + * You can pass `null` to act as an unauthenticated client. + * + * See + * {@link https://firebase.google.com/docs/database/admin/start#authenticate-with-limited-privileges | + * Authenticate with limited privileges} + * for detailed documentation and code samples. + */ + databaseAuthVariableOverride?: object | null; + + /** + * The URL of the Realtime Database from which to read and write data. + */ + databaseURL?: string; + + /** + * The ID of the service account to be used for signing custom tokens. This + * can be found in the `client_email` field of a service account JSON file. + */ + serviceAccountId?: string; + + /** + * The name of the Google Cloud Storage bucket used for storing application data. + * Use only the bucket name without any prefixes or additions (do *not* prefix + * the name with "gs://"). + */ + storageBucket?: string; + + /** + * The ID of the Google Cloud project associated with the App. + */ + projectId?: string; + + /** + * An {@link https://nodejs.org/api/http.html#http_class_http_agent | HTTP Agent} + * to be used when making outgoing HTTP calls. This Agent instance is used + * by all services that make REST calls (e.g. `auth`, `messaging`, + * `projectManagement`). + * + * Realtime Database and Firestore use other means of communicating with + * the backend servers, so they do not use this HTTP Agent. `Credential` + * instances also do not use this HTTP Agent, but instead support + * specifying an HTTP Agent in the corresponding factory methods. + */ + httpAgent?: Agent; +} + +/** + * A Firebase app holds the initialization information for a collection of + * services. + */ +export interface App { + + /** + * The (read-only) name for this app. + * + * The default app's name is `"[DEFAULT]"`. + * + * @example + * ```javascript + * // The default app's name is "[DEFAULT]" + * initializeApp(defaultAppConfig); + * console.log(admin.app().name); // "[DEFAULT]" + * ``` + * + * @example + * ```javascript + * // A named app's name is what you provide to initializeApp() + * const otherApp = 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-admin.app#initializeApp}. + * + * @example + * ```javascript + * const app = initializeApp(config); + * console.log(app.options.credential === config.credential); // true + * console.log(app.options.databaseURL === config.databaseURL); // true + * ``` + */ + options: AppOptions; +} + +/** + * `FirebaseError` is a subclass of the standard JavaScript `Error` object. In + * addition to a message string and stack trace, it contains a string code. + */ +export interface FirebaseError { + + /** + * Error codes are strings using the following format: `"service/string-code"`. + * Some examples include `"auth/invalid-uid"` and + * `"messaging/invalid-recipient"`. + * + * 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; + + /** + * A string value containing the execution backtrace when the error originally + * occurred. + * + * 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. + */ + stack?: string; + + /** + * Returns a JSON-serializable object representation of this error. + * + * @returns A JSON-serializable representation of this object. + */ + toJSON(): object; +} + +/** + * Composite type which includes both a `FirebaseError` object and an index + * which can be used to get the errored item. + * + * @example + * ```javascript + * var registrationTokens = [token1, token2, token3]; + * admin.messaging().subscribeToTopic(registrationTokens, 'topic-name') + * .then(function(response) { + * if (response.failureCount > 0) { + * console.log("Following devices unsucessfully subscribed to topic:"); + * response.errors.forEach(function(error) { + * var invalidToken = registrationTokens[error.index]; + * console.log(invalidToken, error.error); + * }); + * } else { + * console.log("All devices successfully subscribed to topic:", response); + * } + * }) + * .catch(function(error) { + * console.log("Error subscribing to topic:", error); + * }); + *``` + */ +export interface FirebaseArrayIndexError { + + /** + * The index of the errored item within the original array passed as part of the + * called API method. + */ + index: number; + + /** + * The error object. + */ + error: FirebaseError; +} diff --git a/src/app/credential-factory.ts b/src/app/credential-factory.ts new file mode 100644 index 0000000000..4ad5ea5cf9 --- /dev/null +++ b/src/app/credential-factory.ts @@ -0,0 +1,155 @@ +/*! + * @license + * Copyright 2021 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 { Agent } from 'http'; + +import { Credential, ServiceAccount } from './credential'; +import { + ServiceAccountCredential, RefreshTokenCredential, getApplicationDefault +} from './credential-internal'; + +let globalAppDefaultCred: Credential | undefined; +const globalCertCreds: { [key: string]: ServiceAccountCredential } = {}; +const globalRefreshTokenCreds: { [key: string]: RefreshTokenCredential } = {}; + +/** + * Returns a credential created from the + * {@link https://developers.google.com/identity/protocols/application-default-credentials | + * Google Application Default Credentials} + * that grants admin access to Firebase services. This credential can be used + * in the call to {@link firebase-admin.app#initializeApp}. + * + * Google Application Default Credentials are available on any Google + * infrastructure, such as Google App Engine and Google Compute Engine. + * + * See + * {@link https://firebase.google.com/docs/admin/setup#initialize_the_sdk | Initialize the SDK} + * for more details. + * + * @example + * ```javascript + * initializeApp({ + * credential: applicationDefault(), + * databaseURL: "https://.firebaseio.com" + * }); + * ``` + * + * @param httpAgent Optional {@link https://nodejs.org/api/http.html#http_class_http_agent | HTTP Agent} + * to be used when retrieving access tokens from Google token servers. + * + * @returns A credential authenticated via Google + * Application Default Credentials that can be used to initialize an app. + */ +export function applicationDefault(httpAgent?: Agent): Credential { + if (typeof globalAppDefaultCred === 'undefined') { + globalAppDefaultCred = getApplicationDefault(httpAgent); + } + return globalAppDefaultCred; +} + +/** + * Returns a credential created from the provided service account that grants + * admin access to Firebase services. This credential can be used in the call + * to {@link firebase-admin.app#initializeApp}. + * + * See + * {@link https://firebase.google.com/docs/admin/setup#initialize_the_sdk | Initialize the SDK} + * for more details. + * + * @example + * ```javascript + * // Providing a path to a service account key JSON file + * const serviceAccount = require("path/to/serviceAccountKey.json"); + * initializeApp({ + * credential: cert(serviceAccount), + * databaseURL: "https://.firebaseio.com" + * }); + * ``` + * + * @example + * ```javascript + * // Providing a service account object inline + * initializeApp({ + * credential: cert({ + * projectId: "", + * clientEmail: "foo@.iam.gserviceaccount.com", + * privateKey: "-----BEGIN PRIVATE KEY----------END PRIVATE KEY-----\n" + * }), + * databaseURL: "https://.firebaseio.com" + * }); + * ``` + * + * @param serviceAccountPathOrObject The path to a service + * account key JSON file or an object representing a service account key. + * @param httpAgent Optional {@link https://nodejs.org/api/http.html#http_class_http_agent | HTTP Agent} + * to be used when retrieving access tokens from Google token servers. + * + * @returns A credential authenticated via the + * provided service account that can be used to initialize an app. + */ +export function cert(serviceAccountPathOrObject: string | ServiceAccount, httpAgent?: Agent): Credential { + const stringifiedServiceAccount = JSON.stringify(serviceAccountPathOrObject); + if (!(stringifiedServiceAccount in globalCertCreds)) { + globalCertCreds[stringifiedServiceAccount] = new ServiceAccountCredential( + serviceAccountPathOrObject, httpAgent); + } + return globalCertCreds[stringifiedServiceAccount]; +} + +/** + * Returns a credential created from the provided refresh token that grants + * admin access to Firebase services. This credential can be used in the call + * to {@link firebase-admin.app#initializeApp}. + * + * See + * {@link https://firebase.google.com/docs/admin/setup#initialize_the_sdk | Initialize the SDK} + * for more details. + * + * @example + * ```javascript + * // Providing a path to a refresh token JSON file + * const refreshToken = require("path/to/refreshToken.json"); + * initializeApp({ + * credential: refreshToken(refreshToken), + * databaseURL: "https://.firebaseio.com" + * }); + * ``` + * + * @param refreshTokenPathOrObject The path to a Google + * OAuth2 refresh token JSON file or an object representing a Google OAuth2 + * refresh token. + * @param httpAgent Optional {@link https://nodejs.org/api/http.html#http_class_http_agent | HTTP Agent} + * to be used when retrieving access tokens from Google token servers. + * + * @returns A credential authenticated via the + * provided service account that can be used to initialize an app. + */ +export function refreshToken(refreshTokenPathOrObject: string | object, httpAgent?: Agent): Credential { + const stringifiedRefreshToken = JSON.stringify(refreshTokenPathOrObject); + if (!(stringifiedRefreshToken in globalRefreshTokenCreds)) { + globalRefreshTokenCreds[stringifiedRefreshToken] = new RefreshTokenCredential( + refreshTokenPathOrObject, httpAgent); + } + return globalRefreshTokenCreds[stringifiedRefreshToken]; +} + +/** + * Clears the global ADC cache. Exported for testing. + */ +export function clearGlobalAppDefaultCred(): void { + globalAppDefaultCred = undefined; +} diff --git a/src/credential/credential-internal.ts b/src/app/credential-internal.ts similarity index 99% rename from src/credential/credential-internal.ts rename to src/app/credential-internal.ts index a266a42ceb..28dcf7f6b6 100644 --- a/src/credential/credential-internal.ts +++ b/src/app/credential-internal.ts @@ -1,4 +1,5 @@ /*! + * @license * Copyright 2020 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,13 +20,11 @@ import os = require('os'); import path = require('path'); import { Agent } from 'http'; -import { credential, GoogleOAuthAccessToken } from './index'; +import { Credential, GoogleOAuthAccessToken } from './credential'; import { AppErrorCodes, FirebaseAppError } from '../utils/error'; import { HttpClient, HttpRequestConfig, HttpError, HttpResponse } from '../utils/api-request'; import * as util from '../utils/validator'; -import Credential = credential.Credential; - const GOOGLE_TOKEN_AUDIENCE = 'https://accounts.google.com/o/oauth2/token'; const GOOGLE_AUTH_TOKEN_HOST = 'accounts.google.com'; const GOOGLE_AUTH_TOKEN_PATH = '/o/oauth2/token'; diff --git a/src/app/credential.ts b/src/app/credential.ts new file mode 100644 index 0000000000..2453a97242 --- /dev/null +++ b/src/app/credential.ts @@ -0,0 +1,48 @@ +/*! + * @license + * Copyright 2021 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. + */ + +export interface ServiceAccount { + projectId?: string; + clientEmail?: string; + privateKey?: string; +} + +/** + * Interface for Google OAuth 2.0 access tokens. + */ +export interface GoogleOAuthAccessToken { + access_token: string; + expires_in: number; +} + +/** + * Interface that provides Google OAuth2 access tokens used to authenticate + * with Firebase services. + * + * In most cases, you will not need to implement this yourself and can instead + * use the default implementations provided by + * {@link credential `admin.credential`}. + */ +export interface Credential { + /** + * Returns a Google OAuth2 access token object used to authenticate with + * Firebase services. + * + * @returns A Google OAuth2 access token object. + */ + getAccessToken(): Promise; +} \ No newline at end of file diff --git a/src/firebase-app.ts b/src/app/firebase-app.ts similarity index 53% rename from src/firebase-app.ts rename to src/app/firebase-app.ts index 29afbb75d2..42ce2fd6dc 100644 --- a/src/firebase-app.ts +++ b/src/app/firebase-app.ts @@ -15,39 +15,16 @@ * limitations under the License. */ -import { AppOptions, app } from './firebase-namespace-api'; -import { credential } from './credential/index'; -import { getApplicationDefault } from './credential/credential-internal'; -import * as validator from './utils/validator'; -import { deepCopy } from './utils/deep-copy'; -import { FirebaseNamespaceInternals } from './firebase-namespace'; -import { AppErrorCodes, FirebaseAppError } from './utils/error'; - -import { Auth } from './auth/auth'; -import { MachineLearning } from './machine-learning/machine-learning'; -import { Messaging } from './messaging/messaging'; -import { Storage } from './storage/storage'; -import { database } from './database/index'; -import { DatabaseService } from './database/database-internal'; -import { Firestore } from '@google-cloud/firestore'; -import { FirestoreService } from './firestore/firestore-internal'; -import { Installations } from './installations/installations'; -import { InstanceId } from './instance-id/instance-id'; -import { ProjectManagement } from './project-management/project-management'; -import { SecurityRules } from './security-rules/security-rules'; -import { RemoteConfig } from './remote-config/remote-config'; -import { AppCheck } from './app-check/app-check'; - -import Credential = credential.Credential; -import Database = database.Database; +import { AppOptions, App } from './core'; +import { AppStore } from './lifecycle'; +import { Credential } from './credential'; +import { getApplicationDefault } from './credential-internal'; +import * as validator from '../utils/validator'; +import { deepCopy } from '../utils/deep-copy'; +import { AppErrorCodes, FirebaseAppError } from '../utils/error'; const TOKEN_EXPIRY_THRESHOLD_MILLIS = 5 * 60 * 1000; -/** - * Type representing a callback which is called every time an app lifecycle event occurs. - */ -export type AppHook = (event: string, app: app.App) => void; - /** * Type representing a Firebase OAuth access token (derived from a Google OAuth2 access token) which * can be used to authenticate to Firebase services such as the Realtime Database and Auth. @@ -161,8 +138,11 @@ export class FirebaseAppInternals { /** * Global context object for a collection of services using a shared authentication state. + * + * @internal */ -export class FirebaseApp implements app.App { +export class FirebaseApp implements App { + public INTERNAL: FirebaseAppInternals; private name_: string; @@ -170,7 +150,7 @@ export class FirebaseApp implements app.App { private services_: {[name: string]: unknown} = {}; private isDeleted_ = false; - constructor(options: AppOptions, name: string, private firebaseInternals_: FirebaseNamespaceInternals) { + constructor(options: AppOptions, name: string, private readonly appStore?: AppStore) { this.name_ = name; this.options_ = deepCopy(options); @@ -200,156 +180,10 @@ export class FirebaseApp implements app.App { this.INTERNAL = new FirebaseAppInternals(credential); } - /** - * Returns the Auth service instance associated with this app. - * - * @return The Auth service instance of this app. - */ - public auth(): Auth { - return this.ensureService_('auth', () => { - const authService: typeof Auth = require('./auth/auth').Auth; - return new authService(this); - }); - } - - /** - * Returns the Database service for the specified URL, and the current app. - * - * @return The Database service instance of this app. - */ - public database(url?: string): Database { - const service: DatabaseService = this.ensureService_('database', () => { - const dbService: typeof DatabaseService = require('./database/database-internal').DatabaseService; - return new dbService(this); - }); - return service.getDatabase(url); - } - - /** - * Returns the Messaging service instance associated with this app. - * - * @return The Messaging service instance of this app. - */ - public messaging(): Messaging { - return this.ensureService_('messaging', () => { - const messagingService: typeof Messaging = require('./messaging/messaging').Messaging; - return new messagingService(this); - }); - } - - /** - * Returns the Storage service instance associated with this app. - * - * @return The Storage service instance of this app. - */ - public storage(): Storage { - return this.ensureService_('storage', () => { - const storageService: typeof Storage = require('./storage/storage').Storage; - return new storageService(this); - }); - } - - public firestore(): Firestore { - const service: FirestoreService = this.ensureService_('firestore', () => { - const firestoreService: typeof FirestoreService = require('./firestore/firestore-internal').FirestoreService; - return new firestoreService(this); - }); - return service.client; - } - - /** - * Returns the `Installations` service instance associated with this app. - * - * @return The `Installations` service instance of this app. - */ - public installations(): Installations { - return this.ensureService_('installations', () => { - const fisService: typeof Installations = require('./installations/installations').Installations; - return new fisService(this); - }); - } - - /** - * Returns the InstanceId service instance associated with this app. - * - * This API is deprecated. Use the `installations()` API instead. - * - * @return The InstanceId service instance of this app. - */ - public instanceId(): InstanceId { - return this.ensureService_('iid', () => { - const iidService: typeof InstanceId = require('./instance-id/instance-id').InstanceId; - return new iidService(this); - }); - } - - /** - * Returns the MachineLearning service instance associated with this app. - * - * @return The Machine Learning service instance of this app - */ - public machineLearning(): MachineLearning { - return this.ensureService_('machine-learning', () => { - const machineLearningService: typeof MachineLearning = - require('./machine-learning/machine-learning').MachineLearning; - return new machineLearningService(this); - }); - } - - /** - * Returns the ProjectManagement service instance associated with this app. - * - * @return The ProjectManagement service instance of this app. - */ - public projectManagement(): ProjectManagement { - return this.ensureService_('project-management', () => { - const projectManagementService: typeof ProjectManagement = - require('./project-management/project-management').ProjectManagement; - return new projectManagementService(this); - }); - } - - /** - * Returns the SecurityRules service instance associated with this app. - * - * @return The SecurityRules service instance of this app. - */ - public securityRules(): SecurityRules { - return this.ensureService_('security-rules', () => { - const securityRulesService: typeof SecurityRules = - require('./security-rules/security-rules').SecurityRules; - return new securityRulesService(this); - }); - } - - /** - * Returns the RemoteConfig service instance associated with this app. - * - * @return The RemoteConfig service instance of this app. - */ - public remoteConfig(): RemoteConfig { - return this.ensureService_('remoteConfig', () => { - const remoteConfigService: typeof RemoteConfig = require('./remote-config/remote-config').RemoteConfig; - return new remoteConfigService(this); - }); - } - - /** - * Returns the AppCheck service instance associated with this app. - * - * @return The AppCheck service instance of this app. - */ - public appCheck(): AppCheck { - return this.ensureService_('appCheck', () => { - const appCheckService: typeof AppCheck = require('./app-check/app-check').AppCheck; - return new appCheckService(this); - }); - } - /** * Returns the name of the FirebaseApp instance. * - * @return The name of the FirebaseApp instance. + * @returns The name of the FirebaseApp instance. */ get name(): string { this.checkDestroyed_(); @@ -359,21 +193,32 @@ export class FirebaseApp implements app.App { /** * Returns the options for the FirebaseApp instance. * - * @return The options for the FirebaseApp instance. + * @returns The options for the FirebaseApp instance. */ get options(): AppOptions { this.checkDestroyed_(); return deepCopy(this.options_); } + /** + * @internal + */ + public getOrInitService(name: string, init: (app: FirebaseApp) => T): T { + return this.ensureService_(name, () => init(this)); + } + /** * Deletes the FirebaseApp instance. * - * @return An empty Promise fulfilled once the FirebaseApp instance is deleted. + * @returns An empty Promise fulfilled once the FirebaseApp instance is deleted. */ public delete(): Promise { this.checkDestroyed_(); - this.firebaseInternals_.removeApp(this.name_); + + // Also remove the instance from the AppStore. This is needed to support the existing + // app.delete() use case. In the future we can remove this API, and deleteApp() will + // become the only way to tear down an App. + this.appStore?.removeApp(this.name); return Promise.all(Object.keys(this.services_).map((serviceName) => { const service = this.services_[serviceName]; diff --git a/src/firebase-namespace.ts b/src/app/firebase-namespace.ts similarity index 62% rename from src/firebase-namespace.ts rename to src/app/firebase-namespace.ts index 1a04716771..642d5ad23c 100644 --- a/src/firebase-namespace.ts +++ b/src/app/firebase-namespace.ts @@ -15,29 +15,14 @@ * limitations under the License. */ -import fs = require('fs'); - -import { AppErrorCodes, FirebaseAppError } from './utils/error'; -import { AppOptions, app } from './firebase-namespace-api'; -import { FirebaseApp } from './firebase-app'; -import { cert, refreshToken, applicationDefault } from './credential/credential'; -import { getApplicationDefault } from './credential/credential-internal'; - -import { appCheck } from './app-check/index'; -import { auth } from './auth/index'; -import { database } from './database/index'; -import { firestore } from './firestore/index'; -import { installations } from './installations/index'; -import { instanceId } from './instance-id/index'; -import { machineLearning } from './machine-learning/index'; -import { messaging } from './messaging/index'; -import { projectManagement } from './project-management/index'; -import { remoteConfig } from './remote-config/index'; -import { securityRules } from './security-rules/index'; -import { storage } from './storage/index'; - -import * as validator from './utils/validator'; -import { getSdkVersion } from './utils/index'; +import { App as AppCore } from './core'; +import { AppStore, defaultAppStore } from './lifecycle'; +import { + app, appCheck, auth, messaging, machineLearning, storage, firestore, database, + instanceId, installations, projectManagement, securityRules , remoteConfig, AppOptions, +} from '../firebase-namespace-api'; +import { cert, refreshToken, applicationDefault } from './credential-factory'; +import { getSdkVersion } from '../utils/index'; import App = app.App; import AppCheck = appCheck.AppCheck; @@ -53,15 +38,6 @@ import RemoteConfig = remoteConfig.RemoteConfig; import SecurityRules = securityRules.SecurityRules; import Storage = storage.Storage; -const DEFAULT_APP_NAME = '[DEFAULT]'; - -/** - * Constant holding the environment variable name with the default config. - * If the environment variable contains a string that starts with '{' it will be parsed as JSON, - * otherwise it will be assumed to be pointing to a file. - */ -export const FIREBASE_CONFIG_VAR = 'FIREBASE_CONFIG'; - export interface FirebaseServiceNamespace { (app?: App): T; [key: string]: any; @@ -72,8 +48,7 @@ export interface FirebaseServiceNamespace { */ export class FirebaseNamespaceInternals { - private apps_: {[appName: string]: App} = {}; - constructor(public firebase_: {[key: string]: any}) {} + constructor(private readonly appStore: AppStore) {} /** * Initializes the App instance. @@ -84,41 +59,11 @@ export class FirebaseNamespaceInternals { * to a file. * @param appName Optional name of the FirebaseApp instance. * - * @return A new App instance. + * @returns A new App instance. */ - public initializeApp(options?: AppOptions, appName = DEFAULT_APP_NAME): App { - if (typeof options === 'undefined') { - options = this.loadOptionsFromEnvVar(); - options.credential = getApplicationDefault(); - } - if (typeof appName !== 'string' || appName === '') { - throw new FirebaseAppError( - AppErrorCodes.INVALID_APP_NAME, - `Invalid Firebase app name "${appName}" provided. App name must be a non-empty string.`, - ); - } else if (appName in this.apps_) { - if (appName === DEFAULT_APP_NAME) { - throw new FirebaseAppError( - AppErrorCodes.DUPLICATE_APP, - 'The default Firebase app already exists. This means you called initializeApp() ' + - 'more than once without providing an app name as the second argument. In most cases ' + - 'you only need to call initializeApp() once. But if you do want to initialize ' + - 'multiple apps, pass a second argument to initializeApp() to give each app a unique ' + - 'name.', - ); - } else { - throw new FirebaseAppError( - AppErrorCodes.DUPLICATE_APP, - `Firebase app named "${appName}" already exists. This means you called initializeApp() ` + - 'more than once with the same app name as the second argument. Make sure you provide a ' + - 'unique name every time you call initializeApp().', - ); - } - } - - const app = new FirebaseApp(options, appName, this); - this.apps_[appName] = app; - return app; + public initializeApp(options?: AppOptions, appName?: string): App { + const app = this.appStore.initializeApp(options, appName); + return extendApp(app); } /** @@ -126,69 +71,18 @@ export class FirebaseNamespaceInternals { * if no name is provided). * * @param appName Optional name of the FirebaseApp instance to return. - * @return The App instance which has the provided name. + * @returns The App instance which has the provided name. */ - public app(appName = DEFAULT_APP_NAME): App { - if (typeof appName !== 'string' || appName === '') { - throw new FirebaseAppError( - AppErrorCodes.INVALID_APP_NAME, - `Invalid Firebase app name "${appName}" provided. App name must be a non-empty string.`, - ); - } else if (!(appName in this.apps_)) { - let errorMessage: string = (appName === DEFAULT_APP_NAME) - ? 'The default Firebase app does not exist. ' : `Firebase app named "${appName}" does not exist. `; - errorMessage += 'Make sure you call initializeApp() before using any of the Firebase services.'; - - throw new FirebaseAppError(AppErrorCodes.NO_APP, errorMessage); - } - - return this.apps_[appName]; + public app(appName?: string): App { + const app = this.appStore.getApp(appName); + return extendApp(app); } /* * Returns an array of all the non-deleted App instances. */ public get apps(): App[] { - // Return a copy so the caller cannot mutate the array - return Object.keys(this.apps_).map((appName) => this.apps_[appName]); - } - - /* - * Removes the specified App instance. - */ - public removeApp(appName: string): void { - if (typeof appName === 'undefined') { - throw new FirebaseAppError( - AppErrorCodes.INVALID_APP_NAME, - 'No Firebase app name provided. App name must be a non-empty string.', - ); - } - - const appToRemove = this.app(appName); - delete this.apps_[appToRemove.name]; - } - - /** - * Parse the file pointed to by the FIREBASE_CONFIG_VAR, if it exists. - * Or if the FIREBASE_CONFIG_ENV contains a valid JSON object, parse it directly. - * If the environment variable contains a string that starts with '{' it will be parsed as JSON, - * otherwise it will be assumed to be pointing to a file. - */ - private loadOptionsFromEnvVar(): AppOptions { - const config = process.env[FIREBASE_CONFIG_VAR]; - if (!validator.isNonEmptyString(config)) { - return {}; - } - try { - const contents = config.startsWith('{') ? config : fs.readFileSync(config, 'utf8'); - return JSON.parse(contents) as AppOptions; - } catch (error) { - // Throw a nicely formed error message if the file contents cannot be parsed - throw new FirebaseAppError( - AppErrorCodes.INVALID_APP_OPTIONS, - 'Failed to parse app options file: ' + error, - ); - } + return this.appStore.getApps().map((app) => extendApp(app)); } } @@ -215,8 +109,8 @@ export class FirebaseNamespace { public Promise: any = Promise; /* tslint:enable */ - constructor() { - this.INTERNAL = new FirebaseNamespaceInternals(this); + constructor(appStore?: AppStore) { + this.INTERNAL = new FirebaseNamespaceInternals(appStore ?? new AppStore()); } /** @@ -227,7 +121,7 @@ export class FirebaseNamespace { const fn: FirebaseServiceNamespace = (app?: App) => { return this.ensureApp(app).auth(); }; - const auth = require('./auth/auth').Auth; + const auth = require('../auth/auth').Auth; return Object.assign(fn, { Auth: auth }); } @@ -252,7 +146,7 @@ export class FirebaseNamespace { const fn: FirebaseServiceNamespace = (app?: App) => { return this.ensureApp(app).messaging(); }; - const messaging = require('./messaging/messaging').Messaging; + const messaging = require('../messaging/messaging').Messaging; return Object.assign(fn, { Messaging: messaging }); } @@ -264,7 +158,7 @@ export class FirebaseNamespace { const fn: FirebaseServiceNamespace = (app?: App) => { return this.ensureApp(app).storage(); }; - const storage = require('./storage/storage').Storage; + const storage = require('../storage/storage').Storage; return Object.assign(fn, { Storage: storage }); } @@ -309,7 +203,7 @@ export class FirebaseNamespace { return this.ensureApp(app).machineLearning(); }; const machineLearning = - require('./machine-learning/machine-learning').MachineLearning; + require('../machine-learning/machine-learning').MachineLearning; return Object.assign(fn, { MachineLearning: machineLearning }); } @@ -321,7 +215,7 @@ export class FirebaseNamespace { const fn: FirebaseServiceNamespace = (app?: App) => { return this.ensureApp(app).installations(); }; - const installations = require('./installations/installations').Installations; + const installations = require('../installations/installations').Installations; return Object.assign(fn, { Installations: installations }); } @@ -333,7 +227,7 @@ export class FirebaseNamespace { const fn: FirebaseServiceNamespace = (app?: App) => { return this.ensureApp(app).instanceId(); }; - const instanceId = require('./instance-id/instance-id').InstanceId; + const instanceId = require('../instance-id/instance-id').InstanceId; return Object.assign(fn, { InstanceId: instanceId }); } @@ -345,7 +239,7 @@ export class FirebaseNamespace { const fn: FirebaseServiceNamespace = (app?: App) => { return this.ensureApp(app).projectManagement(); }; - const projectManagement = require('./project-management/project-management').ProjectManagement; + const projectManagement = require('../project-management/project-management').ProjectManagement; return Object.assign(fn, { ProjectManagement: projectManagement }); } @@ -357,7 +251,7 @@ export class FirebaseNamespace { const fn: FirebaseServiceNamespace = (app?: App) => { return this.ensureApp(app).securityRules(); }; - const securityRules = require('./security-rules/security-rules').SecurityRules; + const securityRules = require('../security-rules/security-rules').SecurityRules; return Object.assign(fn, { SecurityRules: securityRules }); } @@ -369,7 +263,7 @@ export class FirebaseNamespace { const fn: FirebaseServiceNamespace = (app?: App) => { return this.ensureApp(app).remoteConfig(); }; - const remoteConfig = require('./remote-config/remote-config').RemoteConfig; + const remoteConfig = require('../remote-config/remote-config').RemoteConfig; return Object.assign(fn, { RemoteConfig: remoteConfig }); } @@ -381,7 +275,7 @@ export class FirebaseNamespace { const fn: FirebaseServiceNamespace = (app?: App) => { return this.ensureApp(app).appCheck(); }; - const appCheck = require('./app-check/app-check').AppCheck; + const appCheck = require('../app-check/app-check').AppCheck; return Object.assign(fn, { AppCheck: appCheck }); } @@ -396,7 +290,7 @@ export class FirebaseNamespace { * otherwise it will be assumed to be pointing to a file. * @param appName Optional name of the FirebaseApp instance. * - * @return A new FirebaseApp instance. + * @returns A new FirebaseApp instance. */ public initializeApp(options?: AppOptions, appName?: string): App { return this.INTERNAL.initializeApp(options, appName); @@ -407,7 +301,7 @@ export class FirebaseNamespace { * if no name is provided). * * @param appName Optional name of the FirebaseApp instance to return. - * @return The FirebaseApp instance which has the provided name. + * @returns The FirebaseApp instance which has the provided name. */ public app(appName?: string): App { return this.INTERNAL.app(appName); @@ -427,3 +321,82 @@ export class FirebaseNamespace { return app; } } + +/** + * In order to maintain backward compatibility, we instantiate a default namespace instance in + * this module, and delegate all app lifecycle operations to it. In a future implementation where + * the old admin namespace is no longer supported, we should remove this. + * + * @internal + */ +export const defaultNamespace = new FirebaseNamespace(defaultAppStore); + +function extendApp(app: AppCore): App { + const result: App = app as App; + if ((result as any).__extended) { + return result; + } + + result.auth = () => { + const fn = require('../auth/index').getAuth; + return fn(app); + }; + + result.appCheck = () => { + const fn = require('../app-check/index').getAppCheck; + return fn(app); + }; + + result.database = (url?: string) => { + const fn = require('../database/index').getDatabaseWithUrl; + return fn(url, app); + }; + + result.messaging = () => { + const fn = require('../messaging/index').getMessaging; + return fn(app); + }; + + result.storage = () => { + const fn = require('../storage/index').getStorage; + return fn(app); + }; + + result.firestore = () => { + const fn = require('../firestore/index').getFirestore; + return fn(app); + }; + + result.instanceId = () => { + const fn = require('../instance-id/index').getInstanceId; + return fn(app); + } + + result.installations = () => { + const fn = require('../installations/index').getInstallations; + return fn(app); + }; + + result.machineLearning = () => { + const fn = require('../machine-learning/index').getMachineLearning; + return fn(app); + } + + result.projectManagement = () => { + const fn = require('../project-management/index').getProjectManagement; + return fn(app); + }; + + result.securityRules = () => { + const fn = require('../security-rules/index').getSecurityRules; + return fn(app); + }; + + result.remoteConfig = () => { + const fn = require('../remote-config/index').getRemoteConfig; + return fn(app); + }; + + (result as any).__extended = true; + return result; +} diff --git a/src/firebase-namespace.d.ts b/src/app/index.ts similarity index 50% rename from src/firebase-namespace.d.ts rename to src/app/index.ts index bff64ccdf3..02a8509653 100644 --- a/src/firebase-namespace.d.ts +++ b/src/app/index.ts @@ -1,5 +1,6 @@ /*! - * Copyright 2020 Google Inc. + * @license + * Copyright 2021 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,17 +15,18 @@ * limitations under the License. */ -export * from './credential/index'; -export * from './firebase-namespace-api'; -export * from './app-check/index'; -export * from './auth/index'; -export * from './database/index'; -export * from './firestore/index'; -export * from './installations/index'; -export * from './instance-id/index'; -export * from './machine-learning/index'; -export * from './messaging/index'; -export * from './project-management/index'; -export * from './remote-config/index'; -export * from './security-rules/index'; -export * from './storage/index'; +import { getSdkVersion } from '../utils'; + +/** + * Firebase App and SDK initialization. + * + * @packageDocumentation + */ + +export { App, AppOptions, FirebaseArrayIndexError, FirebaseError } from './core' +export { initializeApp, getApp, getApps, deleteApp } from './lifecycle'; + +export { Credential, ServiceAccount, GoogleOAuthAccessToken } from './credential'; +export { applicationDefault, cert, refreshToken } from './credential-factory'; + +export const SDK_VERSION = getSdkVersion(); diff --git a/src/app/lifecycle.ts b/src/app/lifecycle.ts new file mode 100644 index 0000000000..9c7cfdd31d --- /dev/null +++ b/src/app/lifecycle.ts @@ -0,0 +1,186 @@ +/*! + * @license + * Copyright 2021 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 fs = require('fs'); + +import * as validator from '../utils/validator'; +import { AppErrorCodes, FirebaseAppError } from '../utils/error'; +import { App, AppOptions } from './core'; +import { getApplicationDefault } from './credential-internal'; +import { FirebaseApp } from './firebase-app'; + +const DEFAULT_APP_NAME = '[DEFAULT]'; + +export class AppStore { + + private readonly appStore = new Map(); + + public initializeApp(options?: AppOptions, appName: string = DEFAULT_APP_NAME): App { + if (typeof options === 'undefined') { + options = loadOptionsFromEnvVar(); + options.credential = getApplicationDefault(); + } + + if (typeof appName !== 'string' || appName === '') { + throw new FirebaseAppError( + AppErrorCodes.INVALID_APP_NAME, + `Invalid Firebase app name "${appName}" provided. App name must be a non-empty string.`, + ); + } else if (this.appStore.has(appName)) { + if (appName === DEFAULT_APP_NAME) { + throw new FirebaseAppError( + AppErrorCodes.DUPLICATE_APP, + 'The default Firebase app already exists. This means you called initializeApp() ' + + 'more than once without providing an app name as the second argument. In most cases ' + + 'you only need to call initializeApp() once. But if you do want to initialize ' + + 'multiple apps, pass a second argument to initializeApp() to give each app a unique ' + + 'name.', + ); + } else { + throw new FirebaseAppError( + AppErrorCodes.DUPLICATE_APP, + `Firebase app named "${appName}" already exists. This means you called initializeApp() ` + + 'more than once with the same app name as the second argument. Make sure you provide a ' + + 'unique name every time you call initializeApp().', + ); + } + } + + const app = new FirebaseApp(options, appName, this); + this.appStore.set(app.name, app); + return app; + } + + public getApp(appName: string = DEFAULT_APP_NAME): App { + if (typeof appName !== 'string' || appName === '') { + throw new FirebaseAppError( + AppErrorCodes.INVALID_APP_NAME, + `Invalid Firebase app name "${appName}" provided. App name must be a non-empty string.`, + ); + } else if (!this.appStore.has(appName)) { + let errorMessage: string = (appName === DEFAULT_APP_NAME) + ? 'The default Firebase app does not exist. ' : `Firebase app named "${appName}" does not exist. `; + errorMessage += 'Make sure you call initializeApp() before using any of the Firebase services.'; + + throw new FirebaseAppError(AppErrorCodes.NO_APP, errorMessage); + } + + return this.appStore.get(appName)!; + } + + public getApps(): App[] { + // Return a copy so the caller cannot mutate the array + return Array.from(this.appStore.values()); + } + + public deleteApp(app: App): Promise { + if (typeof app !== 'object' || app === null || !('options' in app)) { + throw new FirebaseAppError(AppErrorCodes.INVALID_ARGUMENT, 'Invalid app argument.'); + } + + // Make sure the given app already exists. + const existingApp = getApp(app.name); + + // Delegate delete operation to the App instance itself. That will also remove the App + // instance from the AppStore. + return (existingApp as FirebaseApp).delete(); + } + + public clearAllApps(): Promise { + const promises: Array> = []; + this.getApps().forEach((app) => { + promises.push(this.deleteApp(app)); + }) + + return Promise.all(promises).then(); + } + + /** + * Removes the specified App instance from the store. This is currently called by the + * {@link FirebaseApp.delete} method. Can be removed once the app deletion is handled + * entirely by the {@link deleteApp} top-level function. + */ + public removeApp(appName: string): void { + this.appStore.delete(appName); + } +} + +export const defaultAppStore = new AppStore(); + +export function initializeApp(options?: AppOptions, appName: string = DEFAULT_APP_NAME): App { + return defaultAppStore.initializeApp(options, appName); +} + +export function getApp(appName: string = DEFAULT_APP_NAME): App { + return defaultAppStore.getApp(appName); +} + +export function getApps(): App[] { + return defaultAppStore.getApps(); +} + +/** + * Renders this given `App` unusable and frees the resources of + * all associated services (though it does *not* clean up any backend + * resources). When running the SDK locally, this method + * must be called to ensure graceful termination of the process. + * + * @example + * ```javascript + * deleteApp(app) + * .then(function() { + * console.log("App deleted successfully"); + * }) + * .catch(function(error) { + * console.log("Error deleting app:", error); + * }); + * ``` + */ +export function deleteApp(app: App): Promise { + return defaultAppStore.deleteApp(app); +} + +/** + * Constant holding the environment variable name with the default config. + * If the environment variable contains a string that starts with '{' it will be parsed as JSON, + * otherwise it will be assumed to be pointing to a file. + */ +export const FIREBASE_CONFIG_VAR = 'FIREBASE_CONFIG'; + +/** + * Parse the file pointed to by the FIREBASE_CONFIG_VAR, if it exists. + * Or if the FIREBASE_CONFIG_ENV contains a valid JSON object, parse it directly. + * If the environment variable contains a string that starts with '{' it will be parsed as JSON, + * otherwise it will be assumed to be pointing to a file. + */ +function loadOptionsFromEnvVar(): AppOptions { + const config = process.env[FIREBASE_CONFIG_VAR]; + if (!validator.isNonEmptyString(config)) { + return {}; + } + + try { + const contents = config.startsWith('{') ? config : fs.readFileSync(config, 'utf8'); + return JSON.parse(contents) as AppOptions; + } catch (error) { + // Throw a nicely formed error message if the file contents cannot be parsed + throw new FirebaseAppError( + AppErrorCodes.INVALID_APP_OPTIONS, + 'Failed to parse app options file: ' + error, + ); + } +} diff --git a/src/auth/action-code-settings-builder.ts b/src/auth/action-code-settings-builder.ts index 14c212b6fe..4ebc4df19c 100644 --- a/src/auth/action-code-settings-builder.ts +++ b/src/auth/action-code-settings-builder.ts @@ -16,9 +16,87 @@ import * as validator from '../utils/validator'; import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; -import { auth } from './index'; -import ActionCodeSettings = auth.ActionCodeSettings; +/** + * This is the interface that defines the required continue/state URL with + * optional Android and iOS bundle identifiers. + */ +export interface ActionCodeSettings { + + /** + * Defines 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.
    • + *
    + */ + url: string; + + /** + * Whether to open the link via a mobile app or a browser. + * The default is false. When set to true, the action code link is sent + * as a Universal Link or Android App Link and is opened by the app if + * installed. In the false case, the code is sent to the web widget first + * and then redirects to the app if installed. + */ + handleCodeInApp?: boolean; + + /** + * Defines the iOS bundle ID. This will try to open the link in an iOS app if it + * is installed. + */ + iOS?: { + + /** + * Defines the required iOS bundle ID of the app where the link should be + * handled if the application is already installed on the device. + */ + bundleId: string; + }; + + /** + * Defines 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. + */ + android?: { + + /** + * Defines the required Android package name of the app where the link should be + * handled if the Android app is installed. + */ + packageName: string; + + /** + * Whether to install the Android app if the device supports it and the app is + * not already installed. + */ + installApp?: boolean; + + /** + * The Android minimum version if available. If the installed app is an older + * version, the user is taken to the GOogle Play Store to upgrade the app. + */ + minimumVersion?: string; + }; + + /** + * Defines the dynamic link domain to use for the current link if it is to be + * opened using Firebase Dynamic Links, as multiple dynamic link domains can be + * configured per project. This field provides the ability to explicitly choose + * configured per project. This fields provides the ability explicitly choose + * one. If none is provided, the oldest domain is used by default. + */ + dynamicLinkDomain?: string; +} /** Defines the email action code server request. */ interface EmailActionCodeRequest { @@ -34,6 +112,8 @@ interface EmailActionCodeRequest { /** * Defines the ActionCodeSettings builder class used to convert the * ActionCodeSettings object to its corresponding server request. + * + * @internal */ export class ActionCodeSettingsBuilder { private continueUrl?: string; @@ -70,7 +150,7 @@ export class ActionCodeSettingsBuilder { this.continueUrl = actionCodeSettings.url; if (typeof actionCodeSettings.handleCodeInApp !== 'undefined' && - !validator.isBoolean(actionCodeSettings.handleCodeInApp)) { + !validator.isBoolean(actionCodeSettings.handleCodeInApp)) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_ARGUMENT, '"ActionCodeSettings.handleCodeInApp" must be a boolean.', @@ -79,14 +159,14 @@ export class ActionCodeSettingsBuilder { this.canHandleCodeInApp = actionCodeSettings.handleCodeInApp || false; if (typeof actionCodeSettings.dynamicLinkDomain !== 'undefined' && - !validator.isNonEmptyString(actionCodeSettings.dynamicLinkDomain)) { + !validator.isNonEmptyString(actionCodeSettings.dynamicLinkDomain)) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_DYNAMIC_LINK_DOMAIN, ); } this.dynamicLinkDomain = actionCodeSettings.dynamicLinkDomain; - if (typeof actionCodeSettings.iOS !== 'undefined') { + if (typeof actionCodeSettings.iOS !== 'undefined') { if (!validator.isNonNullObject(actionCodeSettings.iOS)) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_ARGUMENT, @@ -105,7 +185,7 @@ export class ActionCodeSettingsBuilder { this.ibi = actionCodeSettings.iOS.bundleId; } - if (typeof actionCodeSettings.android !== 'undefined') { + if (typeof actionCodeSettings.android !== 'undefined') { if (!validator.isNonNullObject(actionCodeSettings.android)) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_ARGUMENT, @@ -121,13 +201,13 @@ export class ActionCodeSettingsBuilder { '"ActionCodeSettings.android.packageName" must be a valid non-empty string.', ); } else if (typeof actionCodeSettings.android.minimumVersion !== 'undefined' && - !validator.isNonEmptyString(actionCodeSettings.android.minimumVersion)) { + !validator.isNonEmptyString(actionCodeSettings.android.minimumVersion)) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_ARGUMENT, '"ActionCodeSettings.android.minimumVersion" must be a valid non-empty string.', ); } else if (typeof actionCodeSettings.android.installApp !== 'undefined' && - !validator.isBoolean(actionCodeSettings.android.installApp)) { + !validator.isBoolean(actionCodeSettings.android.installApp)) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_ARGUMENT, '"ActionCodeSettings.android.installApp" must be a valid boolean.', @@ -143,10 +223,10 @@ export class ActionCodeSettingsBuilder { * Returns the corresponding constructed server request corresponding to the * current ActionCodeSettings. * - * @return {EmailActionCodeRequest} The constructed EmailActionCodeRequest request. + * @returns The constructed EmailActionCodeRequest request. */ public buildRequest(): EmailActionCodeRequest { - const request: {[key: string]: any} = { + const request: { [key: string]: any } = { continueUrl: this.continueUrl, canHandleCodeInApp: this.canHandleCodeInApp, dynamicLinkDomain: this.dynamicLinkDomain, diff --git a/src/auth/auth-api-request.ts b/src/auth/auth-api-request.ts index e7f429f811..e331b648cc 100644 --- a/src/auth/auth-api-request.ts +++ b/src/auth/auth-api-request.ts @@ -17,44 +17,31 @@ import * as validator from '../utils/validator'; +import { App } from '../app/index'; +import { FirebaseApp } from '../app/firebase-app'; import { deepCopy, deepExtend } from '../utils/deep-copy'; -import { - isUidIdentifier, isEmailIdentifier, isPhoneIdentifier, isProviderIdentifier -} from './identifier'; -import { FirebaseApp } from '../firebase-app'; import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; import { ApiSettings, AuthorizedHttpClient, HttpRequestConfig, HttpError, } from '../utils/api-request'; +import * as utils from '../utils/index'; + import { + UserImportOptions, UserImportRecord, UserImportResult, UserImportBuilder, AuthFactorInfo, convertMultiFactorInfoToServerFormat, } from './user-import-builder'; -import * as utils from '../utils/index'; -import { ActionCodeSettingsBuilder } from './action-code-settings-builder'; +import { ActionCodeSettings, ActionCodeSettingsBuilder } from './action-code-settings-builder'; +import { Tenant, TenantServerResponse, CreateTenantRequest, UpdateTenantRequest } from './tenant'; +import { + isUidIdentifier, isEmailIdentifier, isPhoneIdentifier, isProviderIdentifier, + UserIdentifier, UidIdentifier, EmailIdentifier,PhoneIdentifier, ProviderIdentifier, +} from './identifier'; import { SAMLConfig, OIDCConfig, OIDCConfigServerResponse, SAMLConfigServerResponse, - OIDCConfigServerRequest, SAMLConfigServerRequest, + OIDCConfigServerRequest, SAMLConfigServerRequest, CreateRequest, UpdateRequest, + OIDCAuthProviderConfig, SAMLAuthProviderConfig, OIDCUpdateAuthProviderRequest, + SAMLUpdateAuthProviderRequest } from './auth-config'; -import { Tenant, TenantServerResponse } from './tenant'; -import { auth } from './index'; - -import CreateRequest = auth.CreateRequest; -import UpdateRequest = auth.UpdateRequest; -import UserIdentifier = auth.UserIdentifier; -import UidIdentifier = auth.UidIdentifier; -import EmailIdentifier = auth.EmailIdentifier; -import PhoneIdentifier = auth.PhoneIdentifier; -import ProviderIdentifier = auth.ProviderIdentifier; -import UserImportOptions = auth.UserImportOptions; -import UserImportRecord = auth.UserImportRecord; -import UserImportResult = auth.UserImportResult; -import ActionCodeSettings = auth.ActionCodeSettings; -import OIDCAuthProviderConfig = auth.OIDCAuthProviderConfig; -import SAMLAuthProviderConfig = auth.SAMLAuthProviderConfig; -import OIDCUpdateAuthProviderRequest = auth.OIDCUpdateAuthProviderRequest; -import SAMLUpdateAuthProviderRequest = auth.SAMLUpdateAuthProviderRequest; -import CreateTenantRequest = auth.CreateTenantRequest; -import UpdateTenantRequest = auth.UpdateTenantRequest; /** Firebase Auth request header. */ const FIREBASE_AUTH_HEADER = { @@ -139,11 +126,11 @@ class AuthResourceUrlBuilder { /** * The resource URL builder constructor. * - * @param {string} projectId The resource project ID. - * @param {string} version The endpoint API version. + * @param projectId The resource project ID. + * @param version The endpoint API version. * @constructor */ - constructor(protected app: FirebaseApp, protected version: string = 'v1') { + constructor(protected app: App, protected version: string = 'v1') { if (useEmulator()) { this.urlFormat = utils.formatString(FIREBASE_AUTH_EMULATOR_BASE_URL_FORMAT, { host: emulatorHost() @@ -156,10 +143,10 @@ class AuthResourceUrlBuilder { /** * Returns the resource URL corresponding to the provided parameters. * - * @param {string=} api The backend API name. - * @param {object=} params The optional additional parameters to substitute in the + * @param api The backend API name. + * @param params The optional additional parameters to substitute in the * URL path. - * @return {Promise} The corresponding resource URL. + * @returns The corresponding resource URL. */ public getUrl(api?: string, params?: object): Promise { return this.getProjectId() @@ -203,12 +190,12 @@ class TenantAwareAuthResourceUrlBuilder extends AuthResourceUrlBuilder { /** * The tenant aware resource URL builder constructor. * - * @param {string} projectId The resource project ID. - * @param {string} version The endpoint API version. - * @param {string} tenantId The tenant ID. + * @param projectId The resource project ID. + * @param version The endpoint API version. + * @param tenantId The tenant ID. * @constructor */ - constructor(protected app: FirebaseApp, protected version: string, protected tenantId: string) { + constructor(protected app: App, protected version: string, protected tenantId: string) { super(app, version); if (useEmulator()) { this.urlFormat = utils.formatString(FIREBASE_AUTH_EMULATOR_TENANT_URL_FORMAT, { @@ -222,10 +209,10 @@ class TenantAwareAuthResourceUrlBuilder extends AuthResourceUrlBuilder { /** * Returns the resource URL corresponding to the provided parameters. * - * @param {string=} api The backend API name. - * @param {object=} params The optional additional parameters to substitute in the + * @param api The backend API name. + * @param params The optional additional parameters to substitute in the * URL path. - * @return {Promise} The corresponding resource URL. + * @returns The corresponding resource URL. */ public getUrl(api?: string, params?: object): Promise { return super.getUrl(api, params) @@ -321,7 +308,7 @@ function validateAuthFactorInfo(request: AuthFactorInfo): void { * are removed from the original request. If an invalid field is passed * an error is thrown. * - * @param {any} request The providerUserInfo request object. + * @param request The providerUserInfo request object. */ function validateProviderUserInfo(request: any): void { const validKeys = { @@ -578,7 +565,11 @@ function validateCreateEditRequest(request: any, writeOperationType: WriteOperat } -/** Instantiates the createSessionCookie endpoint settings. */ +/** + * Instantiates the createSessionCookie endpoint settings. + * + * @internal + */ export const FIREBASE_AUTH_CREATE_SESSION_COOKIE = new ApiSettings(':createSessionCookie', 'POST') // Set request validator. @@ -603,11 +594,19 @@ export const FIREBASE_AUTH_CREATE_SESSION_COOKIE = }); -/** Instantiates the uploadAccount endpoint settings. */ +/** + * Instantiates the uploadAccount endpoint settings. + * + * @internal + */ export const FIREBASE_AUTH_UPLOAD_ACCOUNT = new ApiSettings('/accounts:batchCreate', 'POST'); -/** Instantiates the downloadAccount endpoint settings. */ +/** + * Instantiates the downloadAccount endpoint settings. + * + * @internal + */ export const FIREBASE_AUTH_DOWNLOAD_ACCOUNT = new ApiSettings('/accounts:batchGet', 'GET') // Set request validator. .setRequestValidator((request: any) => { @@ -638,7 +637,11 @@ interface GetAccountInfoRequest { }>; } -/** Instantiates the getAccountInfo endpoint settings. */ +/** + * Instantiates the getAccountInfo endpoint settings. + * + * @internal + */ export const FIREBASE_AUTH_GET_ACCOUNT_INFO = new ApiSettings('/accounts:lookup', 'POST') // Set request validator. .setRequestValidator((request: GetAccountInfoRequest) => { @@ -658,6 +661,8 @@ export const FIREBASE_AUTH_GET_ACCOUNT_INFO = new ApiSettings('/accounts:lookup' /** * Instantiates the getAccountInfo endpoint settings for use when fetching info * for multiple accounts. + * + * @internal */ export const FIREBASE_AUTH_GET_ACCOUNTS_INFO = new ApiSettings('/accounts:lookup', 'POST') // Set request validator. @@ -670,7 +675,11 @@ export const FIREBASE_AUTH_GET_ACCOUNTS_INFO = new ApiSettings('/accounts:lookup }); -/** Instantiates the deleteAccount endpoint settings. */ +/** + * Instantiates the deleteAccount endpoint settings. + * + * @internal + */ export const FIREBASE_AUTH_DELETE_ACCOUNT = new ApiSettings('/accounts:delete', 'POST') // Set request validator. .setRequestValidator((request: any) => { @@ -696,6 +705,9 @@ export interface BatchDeleteAccountsResponse { errors?: BatchDeleteErrorInfo[]; } +/** + * @internal + */ export const FIREBASE_AUTH_BATCH_DELETE_ACCOUNTS = new ApiSettings('/accounts:batchDelete', 'POST') .setRequestValidator((request: BatchDeleteAccountsRequest) => { if (!request.localIds) { @@ -726,7 +738,11 @@ export const FIREBASE_AUTH_BATCH_DELETE_ACCOUNTS = new ApiSettings('/accounts:ba }); }); -/** Instantiates the setAccountInfo endpoint settings for updating existing accounts. */ +/** + * Instantiates the setAccountInfo endpoint settings for updating existing accounts. + * + * @internal + */ export const FIREBASE_AUTH_SET_ACCOUNT_INFO = new ApiSettings('/accounts:update', 'POST') // Set request validator. .setRequestValidator((request: any) => { @@ -755,6 +771,8 @@ export const FIREBASE_AUTH_SET_ACCOUNT_INFO = new ApiSettings('/accounts:update' /** * Instantiates the signupNewUser endpoint settings for creating a new user with or without * uid being specified. The backend will create a new one if not provided and return it. + * + * @internal */ export const FIREBASE_AUTH_SIGN_UP_NEW_USER = new ApiSettings('/accounts', 'POST') // Set request validator. @@ -816,7 +834,11 @@ const FIREBASE_AUTH_GET_OOB_CODE = new ApiSettings('/accounts:sendOobCode', 'POS } }); -/** Instantiates the retrieve OIDC configuration endpoint settings. */ +/** + * Instantiates the retrieve OIDC configuration endpoint settings. + * + * @internal + */ const GET_OAUTH_IDP_CONFIG = new ApiSettings('/oauthIdpConfigs/{providerId}', 'GET') // Set response validator. .setResponseValidator((response: any) => { @@ -829,10 +851,18 @@ const GET_OAUTH_IDP_CONFIG = new ApiSettings('/oauthIdpConfigs/{providerId}', 'G } }); -/** Instantiates the delete OIDC configuration endpoint settings. */ +/** + * Instantiates the delete OIDC configuration endpoint settings. + * + * @internal + */ const DELETE_OAUTH_IDP_CONFIG = new ApiSettings('/oauthIdpConfigs/{providerId}', 'DELETE'); -/** Instantiates the create OIDC configuration endpoint settings. */ +/** + * Instantiates the create OIDC configuration endpoint settings. + * + * @internal + */ const CREATE_OAUTH_IDP_CONFIG = new ApiSettings('/oauthIdpConfigs?oauthIdpConfigId={providerId}', 'POST') // Set response validator. .setResponseValidator((response: any) => { @@ -845,7 +875,11 @@ const CREATE_OAUTH_IDP_CONFIG = new ApiSettings('/oauthIdpConfigs?oauthIdpConfig } }); -/** Instantiates the update OIDC configuration endpoint settings. */ +/** + * Instantiates the update OIDC configuration endpoint settings. + * + * @internal + */ const UPDATE_OAUTH_IDP_CONFIG = new ApiSettings('/oauthIdpConfigs/{providerId}?updateMask={updateMask}', 'PATCH') // Set response validator. .setResponseValidator((response: any) => { @@ -858,7 +892,11 @@ const UPDATE_OAUTH_IDP_CONFIG = new ApiSettings('/oauthIdpConfigs/{providerId}?u } }); -/** Instantiates the list OIDC configuration endpoint settings. */ +/** + * Instantiates the list OIDC configuration endpoint settings. + * + * @internal + */ const LIST_OAUTH_IDP_CONFIGS = new ApiSettings('/oauthIdpConfigs', 'GET') // Set request validator. .setRequestValidator((request: any) => { @@ -879,7 +917,11 @@ const LIST_OAUTH_IDP_CONFIGS = new ApiSettings('/oauthIdpConfigs', 'GET') } }); -/** Instantiates the retrieve SAML configuration endpoint settings. */ +/** + * Instantiates the retrieve SAML configuration endpoint settings. + * + * @internal + */ const GET_INBOUND_SAML_CONFIG = new ApiSettings('/inboundSamlConfigs/{providerId}', 'GET') // Set response validator. .setResponseValidator((response: any) => { @@ -892,10 +934,18 @@ const GET_INBOUND_SAML_CONFIG = new ApiSettings('/inboundSamlConfigs/{providerId } }); -/** Instantiates the delete SAML configuration endpoint settings. */ +/** + * Instantiates the delete SAML configuration endpoint settings. + * + * @internal + */ const DELETE_INBOUND_SAML_CONFIG = new ApiSettings('/inboundSamlConfigs/{providerId}', 'DELETE'); -/** Instantiates the create SAML configuration endpoint settings. */ +/** + * Instantiates the create SAML configuration endpoint settings. + * + * @internal + */ const CREATE_INBOUND_SAML_CONFIG = new ApiSettings('/inboundSamlConfigs?inboundSamlConfigId={providerId}', 'POST') // Set response validator. .setResponseValidator((response: any) => { @@ -908,7 +958,11 @@ const CREATE_INBOUND_SAML_CONFIG = new ApiSettings('/inboundSamlConfigs?inboundS } }); -/** Instantiates the update SAML configuration endpoint settings. */ +/** + * Instantiates the update SAML configuration endpoint settings. + * + * @internal + */ const UPDATE_INBOUND_SAML_CONFIG = new ApiSettings('/inboundSamlConfigs/{providerId}?updateMask={updateMask}', 'PATCH') // Set response validator. .setResponseValidator((response: any) => { @@ -921,7 +975,11 @@ const UPDATE_INBOUND_SAML_CONFIG = new ApiSettings('/inboundSamlConfigs/{provide } }); -/** Instantiates the list SAML configuration endpoint settings. */ +/** + * Instantiates the list SAML configuration endpoint settings. + * + * @internal + */ const LIST_INBOUND_SAML_CONFIGS = new ApiSettings('/inboundSamlConfigs', 'GET') // Set request validator. .setRequestValidator((request: any) => { @@ -944,6 +1002,8 @@ const LIST_INBOUND_SAML_CONFIGS = new ApiSettings('/inboundSamlConfigs', 'GET') /** * Class that provides the mechanism to send requests to the Firebase Auth backend endpoints. + * + * @internal */ export abstract class AbstractAuthRequestHandler { @@ -952,8 +1012,8 @@ export abstract class AbstractAuthRequestHandler { private projectConfigUrlBuilder: AuthResourceUrlBuilder; /** - * @param {any} response The response to check for errors. - * @return {string|null} The error code if present; null otherwise. + * @param response The response to check for errors. + * @returns The error code if present; null otherwise. */ private static getErrorCode(response: any): string | null { return (validator.isNonNullObject(response) && response.error && response.error.message) || null; @@ -1001,10 +1061,10 @@ export abstract class AbstractAuthRequestHandler { } /** - * @param {FirebaseApp} app The app used to fetch access tokens to sign API requests. + * @param app The app used to fetch access tokens to sign API requests. * @constructor */ - constructor(protected readonly app: FirebaseApp) { + constructor(protected readonly app: App) { if (typeof app !== 'object' || app === null || !('options' in app)) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_ARGUMENT, @@ -1012,7 +1072,7 @@ export abstract class AbstractAuthRequestHandler { ); } - this.httpClient = new AuthHttpClient(app); + this.httpClient = new AuthHttpClient(app as FirebaseApp); } /** @@ -1020,10 +1080,10 @@ export abstract class AbstractAuthRequestHandler { * session management (set as a server side session cookie with custom cookie policy). * The session cookie JWT will have the same payload claims as the provided ID token. * - * @param {string} idToken The Firebase ID token to exchange for a session cookie. - * @param {number} expiresIn The session cookie duration in milliseconds. + * @param idToken The Firebase ID token to exchange for a session cookie. + * @param expiresIn The session cookie duration in milliseconds. * - * @return {Promise} A promise that resolves on success with the created session cookie. + * @returns A promise that resolves on success with the created session cookie. */ public createSessionCookie(idToken: string, expiresIn: number): Promise { const request = { @@ -1038,8 +1098,8 @@ export abstract class AbstractAuthRequestHandler { /** * Looks up a user by uid. * - * @param {string} uid The uid of the user to lookup. - * @return {Promise} A promise that resolves with the user information. + * @param uid The uid of the user to lookup. + * @returns A promise that resolves with the user information. */ public getAccountInfoByUid(uid: string): Promise { if (!validator.isUid(uid)) { @@ -1055,8 +1115,8 @@ export abstract class AbstractAuthRequestHandler { /** * Looks up a user by email. * - * @param {string} email The email of the user to lookup. - * @return {Promise} A promise that resolves with the user information. + * @param email The email of the user to lookup. + * @returns A promise that resolves with the user information. */ public getAccountInfoByEmail(email: string): Promise { if (!validator.isEmail(email)) { @@ -1072,8 +1132,8 @@ export abstract class AbstractAuthRequestHandler { /** * Looks up a user by phone number. * - * @param {string} phoneNumber The phone number of the user to lookup. - * @return {Promise} A promise that resolves with the user information. + * @param phoneNumber The phone number of the user to lookup. + * @returns A promise that resolves with the user information. */ public getAccountInfoByPhoneNumber(phoneNumber: string): Promise { if (!validator.isPhoneNumber(phoneNumber)) { @@ -1104,9 +1164,9 @@ export abstract class AbstractAuthRequestHandler { /** * Looks up multiple users by their identifiers (uid, email, etc). * - * @param {UserIdentifier[]} identifiers The identifiers indicating the users + * @param identifiers The identifiers indicating the users * to be looked up. Must have <= 100 entries. - * @param {Promise} A promise that resolves with the set of successfully + * @param A promise that resolves with the set of successfully * looked up users. Possibly empty if no users were looked up. */ public getAccountInfoByIdentifiers(identifiers: UserIdentifier[]): Promise { @@ -1143,12 +1203,12 @@ export abstract class AbstractAuthRequestHandler { * Exports the users (single batch only) with a size of maxResults and starting from * the offset as specified by pageToken. * - * @param {number=} maxResults The page size, 1000 if undefined. This is also the maximum + * @param maxResults The page size, 1000 if undefined. This is also the maximum * allowed limit. - * @param {string=} pageToken The next page token. If not specified, returns users starting + * @param pageToken The next page token. If not specified, returns users starting * without any offset. Users are returned in the order they were created from oldest to * newest, relative to the page token offset. - * @return {Promise} A promise that resolves with the current batch of downloaded + * @returns A promise that resolves with the current batch of downloaded * users and the next page token if available. For the last page, an empty list of users * and no page token are returned. */ @@ -1180,10 +1240,10 @@ export abstract class AbstractAuthRequestHandler { * At most, 1000 users are allowed to be imported one at a time. * When importing a list of password users, UserImportOptions are required to be specified. * - * @param {UserImportRecord[]} users The list of user records to import to Firebase Auth. - * @param {UserImportOptions=} options The user import options, required when the users provided + * @param users The list of user records to import to Firebase Auth. + * @param options The user import options, required when the users provided * include password credentials. - * @return {Promise} A promise that resolves when the operation completes + * @returns A promise that resolves when the operation completes * with the result of the import. This includes the number of successful imports, the number * of failed uploads and their corresponding errors. */ @@ -1222,8 +1282,8 @@ export abstract class AbstractAuthRequestHandler { /** * Deletes an account identified by a uid. * - * @param {string} uid The uid of the user to delete. - * @return {Promise} A promise that resolves when the user is deleted. + * @param uid The uid of the user to delete. + * @returns A promise that resolves when the user is deleted. */ public deleteAccount(uid: string): Promise { if (!validator.isUid(uid)) { @@ -1263,9 +1323,9 @@ export abstract class AbstractAuthRequestHandler { /** * Sets additional developer claims on an existing user identified by provided UID. * - * @param {string} uid The user to edit. - * @param {object} customUserClaims The developer claims to set. - * @return {Promise} A promise that resolves when the operation completes + * @param uid The user to edit. + * @param customUserClaims The developer claims to set. + * @returns A promise that resolves when the operation completes * with the user id that was edited. */ public setCustomUserClaims(uid: string, customUserClaims: object | null): Promise { @@ -1298,9 +1358,9 @@ export abstract class AbstractAuthRequestHandler { /** * Edits an existing user. * - * @param {string} uid The user to edit. - * @param {object} properties The properties to set on the user. - * @return {Promise} A promise that resolves when the operation completes + * @param uid The user to edit. + * @param properties The properties to set on the user. + * @returns A promise that resolves when the operation completes * with the user id that was edited. */ public updateExistingAccount(uid: string, properties: UpdateRequest): Promise { @@ -1442,8 +1502,8 @@ export abstract class AbstractAuthRequestHandler { * the same second as the revocation will still be valid. If there is a chance that a token * was minted in the last second, delay for 1 second before revoking. * - * @param {string} uid The user whose tokens are to be revoked. - * @return {Promise} A promise that resolves when the operation completes + * @param uid The user whose tokens are to be revoked. + * @returns A promise that resolves when the operation completes * successfully with the user id of the corresponding user. */ public revokeRefreshTokens(uid: string): Promise { @@ -1465,8 +1525,8 @@ export abstract class AbstractAuthRequestHandler { /** * Create a new user with the properties supplied. * - * @param {object} properties The properties to set on the user. - * @return {Promise} A promise that resolves when the operation completes + * @param properties The properties to set on the user. + * @returns A promise that resolves when the operation completes * with the user id that was created. */ public createNewAccount(properties: CreateRequest): Promise { @@ -1533,13 +1593,13 @@ export abstract class AbstractAuthRequestHandler { * Generates the out of band email action link for the email specified using the action code settings provided. * Returns a promise that resolves with the generated link. * - * @param {string} requestType The request type. This could be either used for password reset, + * @param requestType The request type. This could be either used for password reset, * email verification, email link sign-in. - * @param {string} email The email of the user the link is being sent to. - * @param {ActionCodeSettings=} actionCodeSettings The optional action code setings which defines whether + * @param email The email of the user the link is being sent to. + * @param actionCodeSettings The optional action code setings which defines whether * the link is to be handled by a mobile app and the additional state information to be passed in the * deep link, etc. Required when requestType == 'EMAIL_SIGNIN' - * @return {Promise} A promise that resolves with the email action link. + * @returns A promise that resolves with the email action link. */ public getEmailActionLink( requestType: string, email: string, @@ -1573,8 +1633,8 @@ export abstract class AbstractAuthRequestHandler { /** * Looks up an OIDC provider configuration by provider ID. * - * @param {string} providerId The provider identifier of the configuration to lookup. - * @return {Promise} A promise that resolves with the provider configuration information. + * @param providerId The provider identifier of the configuration to lookup. + * @returns A promise that resolves with the provider configuration information. */ public getOAuthIdpConfig(providerId: string): Promise { if (!OIDCConfig.isProviderId(providerId)) { @@ -1587,12 +1647,12 @@ export abstract class AbstractAuthRequestHandler { * Lists the OIDC configurations (single batch only) with a size of maxResults and starting from * the offset as specified by pageToken. * - * @param {number=} maxResults The page size, 100 if undefined. This is also the maximum + * @param maxResults The page size, 100 if undefined. This is also the maximum * allowed limit. - * @param {string=} pageToken The next page token. If not specified, returns OIDC configurations + * @param pageToken The next page token. If not specified, returns OIDC configurations * without any offset. Configurations are returned in the order they were created from oldest to * newest, relative to the page token offset. - * @return {Promise} A promise that resolves with the current batch of downloaded + * @returns A promise that resolves with the current batch of downloaded * OIDC configurations and the next page token if available. For the last page, an empty list of provider * configuration and no page token are returned. */ @@ -1619,8 +1679,8 @@ export abstract class AbstractAuthRequestHandler { /** * Deletes an OIDC configuration identified by a providerId. * - * @param {string} providerId The identifier of the OIDC configuration to delete. - * @return {Promise} A promise that resolves when the OIDC provider is deleted. + * @param providerId The identifier of the OIDC configuration to delete. + * @returns A promise that resolves when the OIDC provider is deleted. */ public deleteOAuthIdpConfig(providerId: string): Promise { if (!OIDCConfig.isProviderId(providerId)) { @@ -1635,8 +1695,8 @@ export abstract class AbstractAuthRequestHandler { /** * Creates a new OIDC provider configuration with the properties provided. * - * @param {AuthProviderConfig} options The properties to set on the new OIDC provider configuration to be created. - * @return {Promise} A promise that resolves with the newly created OIDC + * @param options The properties to set on the new OIDC provider configuration to be created. + * @returns A promise that resolves with the newly created OIDC * configuration. */ public createOAuthIdpConfig(options: OIDCAuthProviderConfig): Promise { @@ -1663,9 +1723,9 @@ export abstract class AbstractAuthRequestHandler { /** * Updates an existing OIDC provider configuration with the properties provided. * - * @param {string} providerId The provider identifier of the OIDC configuration to update. - * @param {OIDCUpdateAuthProviderRequest} options The properties to update on the existing configuration. - * @return {Promise} A promise that resolves with the modified provider + * @param providerId The provider identifier of the OIDC configuration to update. + * @param options The properties to update on the existing configuration. + * @returns A promise that resolves with the modified provider * configuration. */ public updateOAuthIdpConfig( @@ -1696,8 +1756,8 @@ export abstract class AbstractAuthRequestHandler { /** * Looks up an SAML provider configuration by provider ID. * - * @param {string} providerId The provider identifier of the configuration to lookup. - * @return {Promise} A promise that resolves with the provider configuration information. + * @param providerId The provider identifier of the configuration to lookup. + * @returns A promise that resolves with the provider configuration information. */ public getInboundSamlConfig(providerId: string): Promise { if (!SAMLConfig.isProviderId(providerId)) { @@ -1710,12 +1770,12 @@ export abstract class AbstractAuthRequestHandler { * Lists the SAML configurations (single batch only) with a size of maxResults and starting from * the offset as specified by pageToken. * - * @param {number=} maxResults The page size, 100 if undefined. This is also the maximum + * @param maxResults The page size, 100 if undefined. This is also the maximum * allowed limit. - * @param {string=} pageToken The next page token. If not specified, returns SAML configurations starting + * @param pageToken The next page token. If not specified, returns SAML configurations starting * without any offset. Configurations are returned in the order they were created from oldest to * newest, relative to the page token offset. - * @return {Promise} A promise that resolves with the current batch of downloaded + * @returns A promise that resolves with the current batch of downloaded * SAML configurations and the next page token if available. For the last page, an empty list of provider * configuration and no page token are returned. */ @@ -1742,8 +1802,8 @@ export abstract class AbstractAuthRequestHandler { /** * Deletes a SAML configuration identified by a providerId. * - * @param {string} providerId The identifier of the SAML configuration to delete. - * @return {Promise} A promise that resolves when the SAML provider is deleted. + * @param providerId The identifier of the SAML configuration to delete. + * @returns A promise that resolves when the SAML provider is deleted. */ public deleteInboundSamlConfig(providerId: string): Promise { if (!SAMLConfig.isProviderId(providerId)) { @@ -1758,8 +1818,8 @@ export abstract class AbstractAuthRequestHandler { /** * Creates a new SAML provider configuration with the properties provided. * - * @param {AuthProviderConfig} options The properties to set on the new SAML provider configuration to be created. - * @return {Promise} A promise that resolves with the newly created SAML + * @param options The properties to set on the new SAML provider configuration to be created. + * @returns A promise that resolves with the newly created SAML * configuration. */ public createInboundSamlConfig(options: SAMLAuthProviderConfig): Promise { @@ -1786,9 +1846,9 @@ export abstract class AbstractAuthRequestHandler { /** * Updates an existing SAML provider configuration with the properties provided. * - * @param {string} providerId The provider identifier of the SAML configuration to update. - * @param {SAMLUpdateAuthProviderRequest} options The properties to update on the existing configuration. - * @return {Promise} A promise that resolves with the modified provider + * @param providerId The provider identifier of the SAML configuration to update. + * @param options The properties to update on the existing configuration. + * @returns A promise that resolves with the modified provider * configuration. */ public updateInboundSamlConfig( @@ -1819,11 +1879,11 @@ export abstract class AbstractAuthRequestHandler { /** * Invokes the request handler based on the API settings object passed. * - * @param {AuthResourceUrlBuilder} urlBuilder The URL builder for Auth endpoints. - * @param {ApiSettings} apiSettings The API endpoint settings to apply to request and response. - * @param {object} requestData The request data. - * @param {object=} additionalResourceParams Additional resource related params if needed. - * @return {Promise} A promise that resolves with the response. + * @param urlBuilder The URL builder for Auth endpoints. + * @param apiSettings The API endpoint settings to apply to request and response. + * @param requestData The request data. + * @param additionalResourceParams Additional resource related params if needed. + * @returns A promise that resolves with the response. */ protected invokeRequestHandler( urlBuilder: AuthResourceUrlBuilder, apiSettings: ApiSettings, @@ -1869,17 +1929,17 @@ export abstract class AbstractAuthRequestHandler { } /** - * @return {AuthResourceUrlBuilder} A new Auth user management resource URL builder instance. + * @returns A new Auth user management resource URL builder instance. */ protected abstract newAuthUrlBuilder(): AuthResourceUrlBuilder; /** - * @return {AuthResourceUrlBuilder} A new project config resource URL builder instance. + * @returns A new project config resource URL builder instance. */ protected abstract newProjectConfigUrlBuilder(): AuthResourceUrlBuilder; /** - * @return {AuthResourceUrlBuilder} The current Auth user management resource URL builder. + * @returns The current Auth user management resource URL builder. */ private getAuthUrlBuilder(): AuthResourceUrlBuilder { if (!this.authUrlBuilder) { @@ -1889,7 +1949,7 @@ export abstract class AbstractAuthRequestHandler { } /** - * @return {AuthResourceUrlBuilder} The current project config resource URL builder. + * @returns The current project config resource URL builder. */ private getProjectConfigUrlBuilder(): AuthResourceUrlBuilder { if (!this.projectConfigUrlBuilder) { @@ -1978,23 +2038,23 @@ export class AuthRequestHandler extends AbstractAuthRequestHandler { /** * The FirebaseAuthRequestHandler constructor used to initialize an instance using a FirebaseApp. * - * @param {FirebaseApp} app The app used to fetch access tokens to sign API requests. + * @param app The app used to fetch access tokens to sign API requests. * @constructor. */ - constructor(app: FirebaseApp) { + constructor(app: App) { super(app); this.tenantMgmtResourceBuilder = new AuthResourceUrlBuilder(app, 'v2'); } /** - * @return {AuthResourceUrlBuilder} A new Auth user management resource URL builder instance. + * @returns A new Auth user management resource URL builder instance. */ protected newAuthUrlBuilder(): AuthResourceUrlBuilder { return new AuthResourceUrlBuilder(this.app, 'v1'); } /** - * @return {AuthResourceUrlBuilder} A new project config resource URL builder instance. + * @returns A new project config resource URL builder instance. */ protected newProjectConfigUrlBuilder(): AuthResourceUrlBuilder { return new AuthResourceUrlBuilder(this.app, 'v2'); @@ -2003,8 +2063,8 @@ export class AuthRequestHandler extends AbstractAuthRequestHandler { /** * Looks up a tenant by tenant ID. * - * @param {string} tenantId The tenant identifier of the tenant to lookup. - * @return {Promise} A promise that resolves with the tenant information. + * @param tenantId The tenant identifier of the tenant to lookup. + * @returns A promise that resolves with the tenant information. */ public getTenant(tenantId: string): Promise { if (!validator.isNonEmptyString(tenantId)) { @@ -2020,12 +2080,12 @@ export class AuthRequestHandler extends AbstractAuthRequestHandler { * Exports the tenants (single batch only) with a size of maxResults and starting from * the offset as specified by pageToken. * - * @param {number=} maxResults The page size, 1000 if undefined. This is also the maximum + * @param maxResults The page size, 1000 if undefined. This is also the maximum * allowed limit. - * @param {string=} pageToken The next page token. If not specified, returns tenants starting + * @param pageToken The next page token. If not specified, returns tenants starting * without any offset. Tenants are returned in the order they were created from oldest to * newest, relative to the page token offset. - * @return {Promise} A promise that resolves with the current batch of downloaded + * @returns A promise that resolves with the current batch of downloaded * tenants and the next page token if available. For the last page, an empty list of tenants * and no page token are returned. */ @@ -2053,8 +2113,8 @@ export class AuthRequestHandler extends AbstractAuthRequestHandler { /** * Deletes a tenant identified by a tenantId. * - * @param {string} tenantId The identifier of the tenant to delete. - * @return {Promise} A promise that resolves when the tenant is deleted. + * @param tenantId The identifier of the tenant to delete. + * @returns A promise that resolves when the tenant is deleted. */ public deleteTenant(tenantId: string): Promise { if (!validator.isNonEmptyString(tenantId)) { @@ -2069,8 +2129,8 @@ export class AuthRequestHandler extends AbstractAuthRequestHandler { /** * Creates a new tenant with the properties provided. * - * @param {TenantOptions} tenantOptions The properties to set on the new tenant to be created. - * @return {Promise} A promise that resolves with the newly created tenant object. + * @param tenantOptions The properties to set on the new tenant to be created. + * @returns A promise that resolves with the newly created tenant object. */ public createTenant(tenantOptions: CreateTenantRequest): Promise { try { @@ -2088,9 +2148,9 @@ export class AuthRequestHandler extends AbstractAuthRequestHandler { /** * Updates an existing tenant with the properties provided. * - * @param {string} tenantId The tenant identifier of the tenant to update. - * @param {TenantOptions} tenantOptions The properties to update on the existing tenant. - * @return {Promise} A promise that resolves with the modified tenant object. + * @param tenantId The tenant identifier of the tenant to update. + * @param tenantOptions The properties to update on the existing tenant. + * @returns A promise that resolves with the modified tenant object. */ public updateTenant(tenantId: string, tenantOptions: UpdateTenantRequest): Promise { if (!validator.isNonEmptyString(tenantId)) { @@ -2123,23 +2183,23 @@ export class TenantAwareAuthRequestHandler extends AbstractAuthRequestHandler { * The FirebaseTenantRequestHandler constructor used to initialize an instance using a * FirebaseApp and a tenant ID. * - * @param {FirebaseApp} app The app used to fetch access tokens to sign API requests. - * @param {string} tenantId The request handler's tenant ID. + * @param app The app used to fetch access tokens to sign API requests. + * @param tenantId The request handler's tenant ID. * @constructor */ - constructor(app: FirebaseApp, private readonly tenantId: string) { + constructor(app: App, private readonly tenantId: string) { super(app); } /** - * @return {AuthResourceUrlBuilder} A new Auth user management resource URL builder instance. + * @returns A new Auth user management resource URL builder instance. */ protected newAuthUrlBuilder(): AuthResourceUrlBuilder { return new TenantAwareAuthResourceUrlBuilder(this.app, 'v1', this.tenantId); } /** - * @return {AuthResourceUrlBuilder} A new project config resource URL builder instance. + * @returns A new project config resource URL builder instance. */ protected newProjectConfigUrlBuilder(): AuthResourceUrlBuilder { return new TenantAwareAuthResourceUrlBuilder(this.app, 'v2', this.tenantId); @@ -2154,10 +2214,10 @@ export class TenantAwareAuthRequestHandler extends AbstractAuthRequestHandler { * Overrides the superclass methods by adding an additional check to match tenant IDs of * imported user records if present. * - * @param {UserImportRecord[]} users The list of user records to import to Firebase Auth. - * @param {UserImportOptions=} options The user import options, required when the users provided + * @param users The list of user records to import to Firebase Auth. + * @param options The user import options, required when the users provided * include password credentials. - * @return {Promise} A promise that resolves when the operation completes + * @returns A promise that resolves when the operation completes * with the result of the import. This includes the number of successful imports, the number * of failed uploads and their corresponding errors. */ diff --git a/src/auth/auth-config.ts b/src/auth/auth-config.ts index 059d866574..12909ffb40 100644 --- a/src/auth/auth-config.ts +++ b/src/auth/auth-config.ts @@ -17,15 +17,373 @@ import * as validator from '../utils/validator'; import { deepCopy } from '../utils/deep-copy'; import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; -import { auth } from './index'; -import MultiFactorConfigInterface = auth.MultiFactorConfig; -import MultiFactorConfigState = auth.MultiFactorConfigState; -import AuthFactorType = auth.AuthFactorType; -import EmailSignInProviderConfig = auth.EmailSignInProviderConfig; -import OIDCAuthProviderConfig = auth.OIDCAuthProviderConfig; -import OAuthResponseType = auth.OAuthResponseType; -import SAMLAuthProviderConfig = auth.SAMLAuthProviderConfig; +/** + * Interface representing base properties of a user-enrolled second factor for a + * `CreateRequest`. + */ +export interface BaseCreateMultiFactorInfoRequest { + + /** + * The optional display name for an enrolled second factor. + */ + displayName?: string; + + /** + * The type identifier of the second factor. For SMS second factors, this is `phone`. + */ + factorId: string; +} + +/** + * Interface representing a phone specific user-enrolled second factor for a + * `CreateRequest`. + */ +export interface CreatePhoneMultiFactorInfoRequest extends BaseCreateMultiFactorInfoRequest { + + /** + * The phone number associated with a phone second factor. + */ + phoneNumber: string; +} + +/** + * Type representing the properties of a user-enrolled second factor + * for a `CreateRequest`. + */ +export type CreateMultiFactorInfoRequest = | CreatePhoneMultiFactorInfoRequest; + +/** + * Interface representing common properties of a user-enrolled second factor + * for an `UpdateRequest`. + */ +export interface BaseUpdateMultiFactorInfoRequest { + + /** + * The ID of the enrolled second factor. This ID is unique to the user. When not provided, + * a new one is provisioned by the Auth server. + */ + uid?: string; + + /** + * The optional display name for an enrolled second factor. + */ + displayName?: string; + + /** + * The optional date the second factor was enrolled, formatted as a UTC string. + */ + enrollmentTime?: string; + + /** + * The type identifier of the second factor. For SMS second factors, this is `phone`. + */ + factorId: string; +} + +/** + * Interface representing a phone specific user-enrolled second factor + * for an `UpdateRequest`. + */ +export interface UpdatePhoneMultiFactorInfoRequest extends BaseUpdateMultiFactorInfoRequest { + + /** + * The phone number associated with a phone second factor. + */ + phoneNumber: string; +} + +/** + * Type representing the properties of a user-enrolled second factor + * for an `UpdateRequest`. + */ +export type UpdateMultiFactorInfoRequest = | UpdatePhoneMultiFactorInfoRequest; + +/** + * The multi-factor related user settings for create operations. + */ +export interface MultiFactorCreateSettings { + + /** + * The created user's list of enrolled second factors. + */ + enrolledFactors: CreateMultiFactorInfoRequest[]; +} + +/** + * The multi-factor related user settings for update operations. + */ +export interface MultiFactorUpdateSettings { + + /** + * The updated list of enrolled second factors. The provided list overwrites the user's + * existing list of second factors. + * When null is passed, all of the user's existing second factors are removed. + */ + enrolledFactors: UpdateMultiFactorInfoRequest[] | null; +} + +/** + * Interface representing the properties to update on the provided user. + */ +export interface UpdateRequest { + + /** + * Whether or not the user is disabled: `true` for disabled; + * `false` for enabled. + */ + disabled?: boolean; + + /** + * The user's display name. + */ + displayName?: string | null; + + /** + * The user's primary email. + */ + email?: string; + + /** + * Whether or not the user's primary email is verified. + */ + emailVerified?: boolean; + + /** + * The user's unhashed password. + */ + password?: string; + + /** + * The user's primary phone number. + */ + phoneNumber?: string | null; + + /** + * The user's photo URL. + */ + photoURL?: string | null; + + /** + * The user's updated multi-factor related properties. + */ + multiFactor?: MultiFactorUpdateSettings; + + /** + * Links this user to the specified provider. + * + * Linking a provider to an existing user account does not invalidate the + * refresh token of that account. In other words, the existing account + * would continue to be able to access resources, despite not having used + * the newly linked provider to log in. If you wish to force the user to + * authenticate with this new provider, you need to (a) revoke their + * refresh token (see + * https://firebase.google.com/docs/auth/admin/manage-sessions#revoke_refresh_tokens), + * and (b) ensure no other authentication methods are present on this + * account. + */ + providerToLink?: UserProvider; + + /** + * Unlinks this user from the specified providers. + */ + providersToUnlink?: string[]; +} + +/** + * Represents a user identity provider that can be associated with a Firebase user. + */ +export interface UserProvider { + + /** + * The user identifier for the linked provider. + */ + uid?: string; + + /** + * The display name for the linked provider. + */ + displayName?: string; + + /** + * The email for the linked provider. + */ + email?: string; + + /** + * The phone number for the linked provider. + */ + phoneNumber?: string; + + /** + * The photo URL for the linked provider. + */ + photoURL?: string; + + /** + * The linked provider ID (for example, "google.com" for the Google provider). + */ + providerId?: string; +} + + +/** + * Interface representing the properties to set on a new user record to be + * created. + */ +export interface CreateRequest extends UpdateRequest { + + /** + * The user's `uid`. + */ + uid?: string; + + /** + * The user's multi-factor related properties. + */ + multiFactor?: MultiFactorCreateSettings; +} + +/** + * The response interface for listing provider configs. This is only available + * when listing all identity providers' configurations via + * {@link auth.Auth.listProviderConfigs `listProviderConfigs()`}. + */ +export interface ListProviderConfigResults { + + /** + * The list of providers for the specified type in the current page. + */ + providerConfigs: AuthProviderConfig[]; + + /** + * The next page token, if available. + */ + pageToken?: string; +} + +/** + * The filter interface used for listing provider configurations. This is used + * when specifying how to list configured identity providers via + * {@link auth.Auth.listProviderConfigs `listProviderConfigs()`}. + */ +export interface AuthProviderConfigFilter { + + /** + * The Auth provider configuration filter. This can be either `saml` or `oidc`. + * The former is used to look up SAML providers only, while the latter is used + * for OIDC providers. + */ + type: 'saml' | 'oidc'; + + /** + * The maximum number of results to return per page. The default and maximum is + * 100. + */ + maxResults?: number; + + /** + * The next page token. When not specified, the lookup starts from the beginning + * of the list. + */ + pageToken?: string; +} + +/** + * The request interface for updating a SAML Auth provider. This is used + * when updating a SAML provider's configuration via + * {@link auth.Auth.updateProviderConfig `updateProviderConfig()`}. + */ +export interface SAMLUpdateAuthProviderRequest { + + /** + * The SAML provider's updated display name. If not provided, the existing + * configuration's value is not modified. + */ + displayName?: string; + + /** + * Whether the SAML provider is enabled or not. If not provided, the existing + * configuration's setting is not modified. + */ + enabled?: boolean; + + /** + * The SAML provider's updated IdP entity ID. If not provided, the existing + * configuration's value is not modified. + */ + idpEntityId?: string; + + /** + * The SAML provider's updated SSO URL. If not provided, the existing + * configuration's value is not modified. + */ + ssoURL?: string; + + /** + * The SAML provider's updated list of X.509 certificated. If not provided, the + * existing configuration list is not modified. + */ + x509Certificates?: string[]; + + /** + * The SAML provider's updated RP entity ID. If not provided, the existing + * configuration's value is not modified. + */ + rpEntityId?: string; + + /** + * The SAML provider's callback URL. If not provided, the existing + * configuration's value is not modified. + */ + callbackURL?: string; +} + +/** + * The request interface for updating an OIDC Auth provider. This is used + * when updating an OIDC provider's configuration via + * {@link auth.Auth.updateProviderConfig `updateProviderConfig()`}. + */ +export interface OIDCUpdateAuthProviderRequest { + + /** + * The OIDC provider's updated display name. If not provided, the existing + * configuration's value is not modified. + */ + displayName?: string; + + /** + * Whether the OIDC provider is enabled or not. If not provided, the existing + * configuration's setting is not modified. + */ + enabled?: boolean; + + /** + * The OIDC provider's updated client ID. If not provided, the existing + * configuration's value is not modified. + */ + clientId?: string; + + /** + * The OIDC provider's updated issuer. If not provided, the existing + * configuration's value is not modified. + */ + issuer?: string; + + /** + * The OIDC provider's client secret to enable OIDC code flow. + * If not provided, the existing configuration's value is not modified. + */ + clientSecret?: string; + + /** + * The OIDC provider's response object for OAuth authorization flow. + */ + responseType?: OAuthResponseType; +} + +export type UpdateAuthProviderRequest = + SAMLUpdateAuthProviderRequest | OIDCUpdateAuthProviderRequest; /** A maximum of 10 test phone number / code pairs can be configured. */ export const MAXIMUM_TEST_PHONE_NUMBERS = 10; @@ -122,11 +480,40 @@ export interface MultiFactorAuthServerConfig { enabledProviders?: AuthFactorServerType[]; } +/** + * Identifies a second factor type. + */ +export type AuthFactorType = 'phone'; + +/** + * Identifies a multi-factor configuration state. + */ +export type MultiFactorConfigState = 'ENABLED' | 'DISABLED'; + +/** + * Interface representing a multi-factor configuration. + * This can be used to define whether multi-factor authentication is enabled + * or disabled and the list of second factor challenges that are supported. + */ +export interface MultiFactorConfig { + /** + * The multi-factor config state. + */ + state: MultiFactorConfigState; + + /** + * The list of identifiers for enabled second factors. + * Currently only ‘phone’ is supported. + */ + factorIds?: AuthFactorType[]; +} + /** * Defines the multi-factor config class used to convert client side MultiFactorConfig * to a format that is understood by the Auth server. */ -export class MultiFactorAuthConfig implements MultiFactorConfigInterface { +export class MultiFactorAuthConfig implements MultiFactorConfig { + public readonly state: MultiFactorConfigState; public readonly factorIds: AuthFactorType[]; @@ -135,9 +522,10 @@ export class MultiFactorAuthConfig implements MultiFactorConfigInterface { * Throws an error if validation fails. * * @param options The options object to convert to a server request. - * @return The resulting server request. + * @returns The resulting server request. + * @internal */ - public static buildServerRequest(options: MultiFactorConfigInterface): MultiFactorAuthServerConfig { + public static buildServerRequest(options: MultiFactorConfig): MultiFactorAuthServerConfig { const request: MultiFactorAuthServerConfig = {}; MultiFactorAuthConfig.validate(options); if (Object.prototype.hasOwnProperty.call(options, 'state')) { @@ -163,7 +551,7 @@ export class MultiFactorAuthConfig implements MultiFactorConfigInterface { * * @param options The options object to validate. */ - private static validate(options: MultiFactorConfigInterface): void { + private static validate(options: MultiFactorConfig): void { const validKeys = { state: true, factorIds: true, @@ -219,6 +607,7 @@ export class MultiFactorAuthConfig implements MultiFactorConfigInterface { * @param response The server side response used to initialize the * MultiFactorAuthConfig object. * @constructor + * @internal */ constructor(response: MultiFactorAuthServerConfig) { if (typeof response.state === 'undefined') { @@ -237,7 +626,7 @@ export class MultiFactorAuthConfig implements MultiFactorConfigInterface { }) } - /** @return The plain object representation of the multi-factor config instance. */ + /** @returns The plain object representation of the multi-factor config instance. */ public toJSON(): object { return { state: this.state, @@ -283,10 +672,28 @@ export function validateTestPhoneNumbers( } } +/** + * The email sign in provider configuration. + */ +export interface EmailSignInProviderConfig { + /** + * Whether email provider is enabled. + */ + enabled: boolean; + + /** + * Whether password is required for email sign-in. When not required, + * email sign-in can be performed with password or via email link sign-in. + */ + passwordRequired?: boolean; // In the backend API, default is true if not provided +} + /** * Defines the email sign-in config class used to convert client side EmailSignInConfig * to a format that is understood by the Auth server. + * + * @internal */ export class EmailSignInConfig implements EmailSignInProviderConfig { public readonly enabled: boolean; @@ -296,8 +703,9 @@ export class EmailSignInConfig implements EmailSignInProviderConfig { * Static method to convert a client side request to a EmailSignInConfigServerRequest. * Throws an error if validation fails. * - * @param {any} options The options object to convert to a server request. - * @return {EmailSignInConfigServerRequest} The resulting server request. + * @param options The options object to convert to a server request. + * @returns The resulting server request. + * @internal */ public static buildServerRequest(options: EmailSignInProviderConfig): EmailSignInConfigServerRequest { const request: EmailSignInConfigServerRequest = {}; @@ -314,7 +722,7 @@ export class EmailSignInConfig implements EmailSignInProviderConfig { /** * Validates the EmailSignInConfig options object. Throws an error on failure. * - * @param {any} options The options object to validate. + * @param options The options object to validate. */ private static validate(options: EmailSignInProviderConfig): void { // TODO: Validate the request. @@ -357,7 +765,7 @@ export class EmailSignInConfig implements EmailSignInProviderConfig { /** * The EmailSignInConfig constructor. * - * @param {any} response The server side response used to initialize the + * @param response The server side response used to initialize the * EmailSignInConfig object. * @constructor */ @@ -371,7 +779,7 @@ export class EmailSignInConfig implements EmailSignInProviderConfig { this.passwordRequired = !response.enableEmailLinkSignin; } - /** @return {object} The plain object representation of the email sign-in config. */ + /** @returns The plain object representation of the email sign-in config. */ public toJSON(): object { return { enabled: this.enabled, @@ -380,10 +788,153 @@ export class EmailSignInConfig implements EmailSignInProviderConfig { } } +/** + * The base Auth provider configuration interface. + */ +export interface BaseAuthProviderConfig { + + /** + * The provider ID defined by the developer. + * For a SAML provider, this is always prefixed by `saml.`. + * For an OIDC provider, this is always prefixed by `oidc.`. + */ + providerId: string; + + /** + * The user-friendly display name to the current configuration. This name is + * also used as the provider label in the Cloud Console. + */ + displayName?: string; + + /** + * Whether the provider configuration is enabled or disabled. A user + * cannot sign in using a disabled provider. + */ + enabled: boolean; +} + +/** + * The + * [SAML](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html) + * Auth provider configuration interface. A SAML provider can be created via + * {@link auth.Auth.createProviderConfig `createProviderConfig()`}. + */ +export interface SAMLAuthProviderConfig extends BaseAuthProviderConfig { + + /** + * The SAML IdP entity identifier. + */ + idpEntityId: string; + + /** + * The SAML IdP SSO URL. This must be a valid URL. + */ + ssoURL: string; + + /** + * The list of SAML IdP X.509 certificates issued by CA for this provider. + * Multiple certificates are accepted to prevent outages during + * IdP key rotation (for example ADFS rotates every 10 days). When the Auth + * server receives a SAML response, it will match the SAML response with the + * certificate on record. Otherwise the response is rejected. + * Developers are expected to manage the certificate updates as keys are + * rotated. + */ + x509Certificates: string[]; + + /** + * The SAML relying party (service provider) entity ID. + * This is defined by the developer but needs to be provided to the SAML IdP. + */ + rpEntityId: string; + + /** + * This is fixed and must always be the same as the OAuth redirect URL + * provisioned by Firebase Auth, + * `https://project-id.firebaseapp.com/__/auth/handler` unless a custom + * `authDomain` is used. + * The callback URL should also be provided to the SAML IdP during + * configuration. + */ + callbackURL?: string; +} + +/** + * The interface representing OIDC provider's response object for OAuth + * authorization flow. + * One of the following settings is required: + *
      + *
    • Set code to true for the code flow.
    • + *
    • Set idToken to true for the ID token flow.
    • + *
    + */ +export interface OAuthResponseType { + /** + * Whether ID token is returned from IdP's authorization endpoint. + */ + idToken?: boolean; + + /** + * Whether authorization code is returned from IdP's authorization endpoint. + */ + code?: boolean; +} + +/** + * The [OIDC](https://openid.net/specs/openid-connect-core-1_0-final.html) Auth + * provider configuration interface. An OIDC provider can be created via + * {@link auth.Auth.createProviderConfig `createProviderConfig()`}. + */ +export interface OIDCAuthProviderConfig extends BaseAuthProviderConfig { + + /** + * This is the required client ID used to confirm the audience of an OIDC + * provider's + * [ID token](https://openid.net/specs/openid-connect-core-1_0-final.html#IDToken). + */ + clientId: string; + + /** + * This is the required provider issuer used to match the provider issuer of + * the ID token and to determine the corresponding OIDC discovery document, eg. + * [`/.well-known/openid-configuration`](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig). + * This is needed for the following: + *
      + *
    • To verify the provided issuer.
    • + *
    • Determine the authentication/authorization endpoint during the OAuth + * `id_token` authentication flow.
    • + *
    • To retrieve the public signing keys via `jwks_uri` to verify the OIDC + * provider's ID token's signature.
    • + *
    • To determine the claims_supported to construct the user attributes to be + * returned in the additional user info response.
    • + *
    + * ID token validation will be performed as defined in the + * [spec](https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation). + */ + issuer: string; + + /** + * The OIDC provider's client secret to enable OIDC code flow. + */ + clientSecret?: string; + + /** + * The OIDC provider's response object for OAuth authorization flow. + */ + responseType?: OAuthResponseType; +} + +/** + * The Auth provider configuration type. + * {@link auth.Auth.createProviderConfig `createProviderConfig()`}. + */ +export type AuthProviderConfig = SAMLAuthProviderConfig | OIDCAuthProviderConfig; /** * Defines the SAMLConfig class used to convert a client side configuration to its * server side representation. + * + * @internal */ export class SAMLConfig implements SAMLAuthProviderConfig { public readonly enabled: boolean; @@ -402,9 +953,9 @@ export class SAMLConfig implements SAMLAuthProviderConfig { * Throws an error if validation fails. If the request is not a SAMLConfig request, * returns null. * - * @param {SAMLAuthProviderRequest} options The options object to convert to a server request. - * @param {boolean=} ignoreMissingFields Whether to ignore missing fields. - * @return {?SAMLConfigServerRequest} The resulting server request or null if not valid. + * @param options The options object to convert to a server request. + * @param ignoreMissingFields Whether to ignore missing fields. + * @returns The resulting server request or null if not valid. */ public static buildServerRequest( options: Partial, @@ -446,8 +997,8 @@ export class SAMLConfig implements SAMLAuthProviderConfig { /** * Returns the provider ID corresponding to the resource name if available. * - * @param {string} resourceName The server side resource name. - * @return {?string} The provider ID corresponding to the resource, null otherwise. + * @param resourceName The server side resource name. + * @returns The provider ID corresponding to the resource, null otherwise. */ public static getProviderIdFromResourceName(resourceName: string): string | null { // name is of form projects/project1/inboundSamlConfigs/providerId1 @@ -459,8 +1010,8 @@ export class SAMLConfig implements SAMLAuthProviderConfig { } /** - * @param {any} providerId The provider ID to check. - * @return {boolean} Whether the provider ID corresponds to a SAML provider. + * @param providerId The provider ID to check. + * @returns Whether the provider ID corresponds to a SAML provider. */ public static isProviderId(providerId: any): providerId is string { return validator.isNonEmptyString(providerId) && providerId.indexOf('saml.') === 0; @@ -469,8 +1020,8 @@ export class SAMLConfig implements SAMLAuthProviderConfig { /** * Validates the SAMLConfig options object. Throws an error on failure. * - * @param {SAMLAuthProviderRequest} options The options object to validate. - * @param {boolean=} ignoreMissingFields Whether to ignore missing fields. + * @param options The options object to validate. + * @param ignoreMissingFields Whether to ignore missing fields. */ public static validate(options: Partial, ignoreMissingFields = false): void { const validKeys = { @@ -584,7 +1135,7 @@ export class SAMLConfig implements SAMLAuthProviderConfig { /** * The SAMLConfig constructor. * - * @param {any} response The server side response used to initialize the SAMLConfig object. + * @param response The server side response used to initialize the SAMLConfig object. * @constructor */ constructor(response: SAMLConfigServerResponse) { @@ -629,7 +1180,7 @@ export class SAMLConfig implements SAMLAuthProviderConfig { this.displayName = response.displayName; } - /** @return The plain object representation of the SAMLConfig. */ + /** @returns The plain object representation of the SAMLConfig. */ public toJSON(): object { return { enabled: this.enabled, @@ -648,6 +1199,8 @@ export class SAMLConfig implements SAMLAuthProviderConfig { /** * Defines the OIDCConfig class used to convert a client side configuration to its * server side representation. + * + * @internal */ export class OIDCConfig implements OIDCAuthProviderConfig { public readonly enabled: boolean; @@ -666,7 +1219,7 @@ export class OIDCConfig implements OIDCAuthProviderConfig { * * @param options The options object to convert to a server request. * @param ignoreMissingFields Whether to ignore missing fields. - * @return The resulting server request or null if not valid. + * @returns The resulting server request or null if not valid. */ public static buildServerRequest( options: Partial, @@ -688,15 +1241,15 @@ export class OIDCConfig implements OIDCAuthProviderConfig { } if (typeof options.responseType !== 'undefined') { request.responseType = options.responseType; - } + } return request; } /** * Returns the provider ID corresponding to the resource name if available. * - * @param {string} resourceName The server side resource name - * @return {?string} The provider ID corresponding to the resource, null otherwise. + * @param resourceName The server side resource name + * @returns The provider ID corresponding to the resource, null otherwise. */ public static getProviderIdFromResourceName(resourceName: string): string | null { // name is of form projects/project1/oauthIdpConfigs/providerId1 @@ -708,8 +1261,8 @@ export class OIDCConfig implements OIDCAuthProviderConfig { } /** - * @param {any} providerId The provider ID to check. - * @return {boolean} Whether the provider ID corresponds to an OIDC provider. + * @param providerId The provider ID to check. + * @returns Whether the provider ID corresponds to an OIDC provider. */ public static isProviderId(providerId: any): providerId is string { return validator.isNonEmptyString(providerId) && providerId.indexOf('oidc.') === 0; @@ -805,18 +1358,17 @@ export class OIDCConfig implements OIDCAuthProviderConfig { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_CONFIG, `"${key}" is not a valid OAuthResponseType parameter.`, - ); + ); } }); - + const idToken = options.responseType.idToken; - if (typeof idToken !== 'undefined' && !validator.isBoolean(idToken)) { + if (typeof idToken !== 'undefined' && !validator.isBoolean(idToken)) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_ARGUMENT, '"OIDCAuthProviderConfig.responseType.idToken" must be a boolean.', ); } - const code = options.responseType.code; if (typeof code !== 'undefined') { if (!validator.isBoolean(code)) { @@ -825,16 +1377,15 @@ export class OIDCConfig implements OIDCAuthProviderConfig { '"OIDCAuthProviderConfig.responseType.code" must be a boolean.', ); } - // If code flow is enabled, client secret must be provided. if (code && typeof options.clientSecret === 'undefined') { throw new FirebaseAuthError( AuthClientErrorCode.MISSING_OAUTH_CLIENT_SECRET, 'The OAuth configuration client secret is required to enable OIDC code flow.', - ); + ); } } - + const allKeys = Object.keys(options.responseType).length; const enabledCount = Object.values(options.responseType).filter(Boolean).length; // Only one of OAuth response types can be set to true. @@ -850,7 +1401,7 @@ export class OIDCConfig implements OIDCAuthProviderConfig { /** * The OIDCConfig constructor. * - * @param {any} response The server side response used to initialize the OIDCConfig object. + * @param response The server side response used to initialize the OIDCConfig object. * @constructor */ constructor(response: OIDCConfigServerResponse) { @@ -887,7 +1438,7 @@ export class OIDCConfig implements OIDCAuthProviderConfig { } } - /** @return {OIDCAuthProviderConfig} The plain object representation of the OIDCConfig. */ + /** @returns The plain object representation of the OIDCConfig. */ public toJSON(): OIDCAuthProviderConfig { return { enabled: this.enabled, diff --git a/src/auth/auth-namespace.ts b/src/auth/auth-namespace.ts new file mode 100644 index 0000000000..adf3834a56 --- /dev/null +++ b/src/auth/auth-namespace.ts @@ -0,0 +1,375 @@ +/*! + * Copyright 2021 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 { App } from '../app/index'; + +// Import all public types with aliases, and re-export from the auth namespace. + +import { ActionCodeSettings as TActionCodeSettings } from './action-code-settings-builder'; + +import { Auth as TAuth } from './auth'; + +import { + AuthFactorType as TAuthFactorType, + AuthProviderConfig as TAuthProviderConfig, + AuthProviderConfigFilter as TAuthProviderConfigFilter, + CreateRequest as TCreateRequest, + CreateMultiFactorInfoRequest as TCreateMultiFactorInfoRequest, + CreatePhoneMultiFactorInfoRequest as TCreatePhoneMultiFactorInfoRequest, + EmailSignInProviderConfig as TEmailSignInProviderConfig, + ListProviderConfigResults as TListProviderConfigResults, + MultiFactorCreateSettings as TMultiFactorCreateSettings, + MultiFactorConfig as TMultiFactorConfig, + MultiFactorConfigState as TMultiFactorConfigState, + MultiFactorUpdateSettings as TMultiFactorUpdateSettings, + OIDCAuthProviderConfig as TOIDCAuthProviderConfig, + OIDCUpdateAuthProviderRequest as TOIDCUpdateAuthProviderRequest, + SAMLAuthProviderConfig as TSAMLAuthProviderConfig, + SAMLUpdateAuthProviderRequest as TSAMLUpdateAuthProviderRequest, + UpdateAuthProviderRequest as TUpdateAuthProviderRequest, + UpdateMultiFactorInfoRequest as TUpdateMultiFactorInfoRequest, + UpdatePhoneMultiFactorInfoRequest as TUpdatePhoneMultiFactorInfoRequest, + UpdateRequest as TUpdateRequest, +} from './auth-config'; + +import { + BaseAuth as TBaseAuth, + DeleteUsersResult as TDeleteUsersResult, + GetUsersResult as TGetUsersResult, + ListUsersResult as TListUsersResult, + SessionCookieOptions as TSessionCookieOptions, +} from './base-auth'; + +import { + EmailIdentifier as TEmailIdentifier, + PhoneIdentifier as TPhoneIdentifier, + ProviderIdentifier as TProviderIdentifier, + UserIdentifier as TUserIdentifier, + UidIdentifier as TUidIdentifier, +} from './identifier'; + +import { + CreateTenantRequest as TCreateTenantRequest, + Tenant as TTenant, + UpdateTenantRequest as TUpdateTenantRequest, +} from './tenant'; + +import { + ListTenantsResult as TListTenantsResult, + TenantAwareAuth as TTenantAwareAuth, + TenantManager as TTenantManager, +} from './tenant-manager'; + +import { DecodedIdToken as TDecodedIdToken } from './token-verifier'; + +import { + HashAlgorithmType as THashAlgorithmType, + UserImportOptions as TUserImportOptions, + UserImportRecord as TUserImportRecord, + UserImportResult as TUserImportResult, + UserMetadataRequest as TUserMetadataRequest, + UserProviderRequest as TUserProviderRequest, +} from './user-import-builder'; + +import { + MultiFactorInfo as TMultiFactorInfo, + MultiFactorSettings as TMultiFactorSettings, + PhoneMultiFactorInfo as TPhoneMultiFactorInfo, + UserInfo as TUserInfo, + UserMetadata as TUserMetadata, + UserRecord as TUserRecord, +} from './user-record'; + +/** + * Gets the {@link auth.Auth `Auth`} service for the default app or a + * given app. + * + * `admin.auth()` can be called with no arguments to access the default app's + * {@link auth.Auth `Auth`} service or as `admin.auth(app)` to access the + * {@link auth.Auth `Auth`} service associated with a specific app. + * + * @example + * ```javascript + * // Get the Auth service for the default app + * var defaultAuth = admin.auth(); + * ``` + * + * @example + * ```javascript + * // Get the Auth service for a given app + * var otherAuth = admin.auth(otherApp); + * ``` + * + */ +export declare function auth(app?: App): auth.Auth; + +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace auth { + /** + * Type alias to {@link firebase-admin.auth#ActionCodeSettings}. + */ + export type ActionCodeSettings = TActionCodeSettings; + + /** + * Type alias to {@link firebase-admin.auth#Auth}. + */ + export type Auth = TAuth; + + /** + * Type alias to {@link firebase-admin.auth#AuthFactorType}. + */ + export type AuthFactorType = TAuthFactorType; + + /** + * Type alias to {@link firebase-admin.auth#AuthProviderConfig}. + */ + export type AuthProviderConfig = TAuthProviderConfig; + + /** + * Type alias to {@link firebase-admin.auth#AuthProviderConfigFilter}. + */ + export type AuthProviderConfigFilter = TAuthProviderConfigFilter; + + /** + * Type alias to {@link firebase-admin.auth#BaseAuth}. + */ + export type BaseAuth = TBaseAuth; + + /** + * Type alias to {@link firebase-admin.auth#CreateMultiFactorInfoRequest}. + */ + export type CreateMultiFactorInfoRequest = TCreateMultiFactorInfoRequest; + + /** + * Type alias to {@link firebase-admin.auth#CreatePhoneMultiFactorInfoRequest}. + */ + export type CreatePhoneMultiFactorInfoRequest = TCreatePhoneMultiFactorInfoRequest; + + /** + * Type alias to {@link firebase-admin.auth#CreateRequest}. + */ + export type CreateRequest = TCreateRequest; + + /** + * Type alias to {@link firebase-admin.auth#CreateTenantRequest}. + */ + export type CreateTenantRequest = TCreateTenantRequest; + + /** + * Type alias to {@link firebase-admin.auth#DecodedIdToken}. + */ + export type DecodedIdToken = TDecodedIdToken; + + /** + * Type alias to {@link firebase-admin.auth#DeleteUsersResult}. + */ + export type DeleteUsersResult = TDeleteUsersResult; + + /** + * Type alias to {@link firebase-admin.auth#EmailIdentifier}. + */ + export type EmailIdentifier = TEmailIdentifier; + + /** + * Type alias to {@link firebase-admin.auth#EmailSignInProviderConfig}. + */ + export type EmailSignInProviderConfig = TEmailSignInProviderConfig; + + /** + * Type alias to {@link firebase-admin.auth#GetUsersResult}. + */ + export type GetUsersResult = TGetUsersResult; + + /** + * Type alias to {@link firebase-admin.auth#HashAlgorithmType}. + */ + export type HashAlgorithmType = THashAlgorithmType; + + /** + * Type alias to {@link firebase-admin.auth#ListProviderConfigResults}. + */ + export type ListProviderConfigResults = TListProviderConfigResults; + + /** + * Type alias to {@link firebase-admin.auth#ListTenantsResult}. + */ + export type ListTenantsResult = TListTenantsResult; + + /** + * Type alias to {@link firebase-admin.auth#ListUsersResult}. + */ + export type ListUsersResult = TListUsersResult; + + /** + * Type alias to {@link firebase-admin.auth#MultiFactorCreateSettings}. + */ + export type MultiFactorCreateSettings = TMultiFactorCreateSettings; + + /** + * Type alias to {@link firebase-admin.auth#MultiFactorConfig}. + */ + export type MultiFactorConfig = TMultiFactorConfig; + + /** + * Type alias to {@link firebase-admin.auth#MultiFactorConfigState}. + */ + export type MultiFactorConfigState = TMultiFactorConfigState; + + /** + * Type alias to {@link firebase-admin.auth#MultiFactorInfo}. + */ + export type MultiFactorInfo = TMultiFactorInfo; + + /** + * Type alias to {@link firebase-admin.auth#MultiFactorUpdateSettings}. + */ + export type MultiFactorUpdateSettings = TMultiFactorUpdateSettings; + + /** + * Type alias to {@link firebase-admin.auth#MultiFactorSettings}. + */ + export type MultiFactorSettings = TMultiFactorSettings; + + /** + * Type alias to {@link firebase-admin.auth#OIDCAuthProviderConfig}. + */ + export type OIDCAuthProviderConfig = TOIDCAuthProviderConfig; + + /** + * Type alias to {@link firebase-admin.auth#OIDCUpdateAuthProviderRequest}. + */ + export type OIDCUpdateAuthProviderRequest = TOIDCUpdateAuthProviderRequest; + + /** + * Type alias to {@link firebase-admin.auth#PhoneIdentifier}. + */ + export type PhoneIdentifier = TPhoneIdentifier; + + /** + * Type alias to {@link firebase-admin.auth#PhoneMultiFactorInfo}. + */ + export type PhoneMultiFactorInfo = TPhoneMultiFactorInfo; + + /** + * Type alias to {@link firebase-admin.auth#ProviderIdentifier}. + */ + export type ProviderIdentifier = TProviderIdentifier; + + /** + * Type alias to {@link firebase-admin.auth#SAMLAuthProviderConfig}. + */ + export type SAMLAuthProviderConfig = TSAMLAuthProviderConfig; + + /** + * Type alias to {@link firebase-admin.auth#SAMLUpdateAuthProviderRequest}. + */ + export type SAMLUpdateAuthProviderRequest = TSAMLUpdateAuthProviderRequest; + + /** + * Type alias to {@link firebase-admin.auth#SessionCookieOptions}. + */ + export type SessionCookieOptions = TSessionCookieOptions; + + /** + * Type alias to {@link firebase-admin.auth#Tenant}. + */ + export type Tenant = TTenant; + + /** + * Type alias to {@link firebase-admin.auth#TenantAwareAuth}. + */ + export type TenantAwareAuth = TTenantAwareAuth; + + /** + * Type alias to {@link firebase-admin.auth#TenantManager}. + */ + export type TenantManager = TTenantManager; + + /** + * Type alias to {@link firebase-admin.auth#UidIdentifier}. + */ + export type UidIdentifier = TUidIdentifier; + + /** + * Type alias to {@link firebase-admin.auth#UpdateAuthProviderRequest}. + */ + export type UpdateAuthProviderRequest = TUpdateAuthProviderRequest; + + /** + * Type alias to {@link firebase-admin.auth#UpdateMultiFactorInfoRequest}. + */ + export type UpdateMultiFactorInfoRequest = TUpdateMultiFactorInfoRequest; + + /** + * Type alias to {@link firebase-admin.auth#UpdatePhoneMultiFactorInfoRequest}. + */ + export type UpdatePhoneMultiFactorInfoRequest = TUpdatePhoneMultiFactorInfoRequest; + + /** + * Type alias to {@link firebase-admin.auth#UpdateRequest}. + */ + export type UpdateRequest = TUpdateRequest; + + /** + * Type alias to {@link firebase-admin.auth#UpdateTenantRequest}. + */ + export type UpdateTenantRequest = TUpdateTenantRequest; + + /** + * Type alias to {@link firebase-admin.auth#UserIdentifier}. + */ + export type UserIdentifier = TUserIdentifier; + + /** + * Type alias to {@link firebase-admin.auth#UserImportOptions}. + */ + export type UserImportOptions = TUserImportOptions; + + /** + * Type alias to {@link firebase-admin.auth#UserImportRecord}. + */ + export type UserImportRecord = TUserImportRecord; + + /** + * Type alias to {@link firebase-admin.auth#UserImportResult}. + */ + export type UserImportResult = TUserImportResult; + + /** + * Type alias to {@link firebase-admin.auth#UserInfo}. + */ + export type UserInfo = TUserInfo; + + /** + * Type alias to {@link firebase-admin.auth#UserMetadata}. + */ + export type UserMetadata = TUserMetadata; + + /** + * Type alias to {@link firebase-admin.auth#UserMetadataRequest}. + */ + export type UserMetadataRequest = TUserMetadataRequest; + + /** + * Type alias to {@link firebase-admin.auth#UserProviderRequest}. + */ + export type UserProviderRequest = TUserProviderRequest; + + /** + * Type alias to {@link firebase-admin.auth#UserRecord}. + */ + export type UserRecord = TUserRecord; +} diff --git a/src/auth/auth.ts b/src/auth/auth.ts index 5f881d5ca6..4ad29f6d20 100644 --- a/src/auth/auth.ts +++ b/src/auth/auth.ts @@ -15,874 +15,26 @@ * limitations under the License. */ -import { deepCopy } from '../utils/deep-copy'; -import { UserRecord } from './user-record'; -import { - isUidIdentifier, isEmailIdentifier, isPhoneIdentifier, isProviderIdentifier, -} from './identifier'; -import { FirebaseApp } from '../firebase-app'; -import { FirebaseTokenGenerator, EmulatedSigner, handleCryptoSignerError } from './token-generator'; -import { - AbstractAuthRequestHandler, AuthRequestHandler, TenantAwareAuthRequestHandler, useEmulator, -} from './auth-api-request'; -import { AuthClientErrorCode, FirebaseAuthError, ErrorInfo } from '../utils/error'; -import * as utils from '../utils/index'; -import * as validator from '../utils/validator'; -import { auth } from './index'; -import { - FirebaseTokenVerifier, createSessionCookieVerifier, createIdTokenVerifier -} from './token-verifier'; -import { - SAMLConfig, OIDCConfig, OIDCConfigServerResponse, SAMLConfigServerResponse, -} from './auth-config'; +import { App } from '../app/index'; +import { AuthRequestHandler } from './auth-api-request'; import { TenantManager } from './tenant-manager'; -import { cryptoSignerFromApp } from '../utils/crypto-signer'; - -import UserIdentifier = auth.UserIdentifier; -import CreateRequest = auth.CreateRequest; -import UpdateRequest = auth.UpdateRequest; -import ActionCodeSettings = auth.ActionCodeSettings; -import UserImportOptions = auth.UserImportOptions; -import UserImportRecord = auth.UserImportRecord; -import UserImportResult = auth.UserImportResult; -import AuthProviderConfig = auth.AuthProviderConfig; -import AuthProviderConfigFilter = auth.AuthProviderConfigFilter; -import ListProviderConfigResults = auth.ListProviderConfigResults; -import UpdateAuthProviderRequest = auth.UpdateAuthProviderRequest; -import GetUsersResult = auth.GetUsersResult; -import ListUsersResult = auth.ListUsersResult; -import DeleteUsersResult = auth.DeleteUsersResult; -import DecodedIdToken = auth.DecodedIdToken; -import SessionCookieOptions = auth.SessionCookieOptions; -import OIDCAuthProviderConfig = auth.OIDCAuthProviderConfig; -import SAMLAuthProviderConfig = auth.SAMLAuthProviderConfig; -import BaseAuthInterface = auth.BaseAuth; -import AuthInterface = auth.Auth; -import TenantAwareAuthInterface = auth.TenantAwareAuth; - -/** - * Base Auth class. Mainly used for user management APIs. - */ -export class BaseAuth implements BaseAuthInterface { - - protected readonly tokenGenerator: FirebaseTokenGenerator; - protected readonly idTokenVerifier: FirebaseTokenVerifier; - protected readonly sessionCookieVerifier: FirebaseTokenVerifier; - - /** - * The BaseAuth class constructor. - * - * @param app The FirebaseApp to associate with this Auth instance. - * @param authRequestHandler The RPC request handler for this instance. - * @param tokenGenerator Optional token generator. If not specified, a - * (non-tenant-aware) instance will be created. Use this paramter to - * specify a tenant-aware tokenGenerator. - * @constructor - */ - constructor(app: FirebaseApp, protected readonly authRequestHandler: T, tokenGenerator?: FirebaseTokenGenerator) { - if (tokenGenerator) { - this.tokenGenerator = tokenGenerator; - } else { - this.tokenGenerator = createFirebaseTokenGenerator(app); - } - - this.sessionCookieVerifier = createSessionCookieVerifier(app); - this.idTokenVerifier = createIdTokenVerifier(app); - } - - /** - * Creates a new custom token that can be sent back to a client to use with - * signInWithCustomToken(). - * - * @param {string} uid The uid to use as the JWT subject. - * @param {object=} developerClaims Optional additional claims to include in the JWT payload. - * - * @return {Promise} A JWT for the provided payload. - */ - public createCustomToken(uid: string, developerClaims?: object): Promise { - return this.tokenGenerator.createCustomToken(uid, developerClaims); - } - - /** - * Verifies a JWT auth token. Returns a promise with the token‘s claims. - * Rejects the promise if the token cannot be verified. - * If `checkRevoked` is set to true, first verifies whether the corresponding - * user is disabled. - * If yes, an `auth/user-disabled` error is thrown. - * If no, verifies if the session corresponding to the ID token was revoked. - * If the corresponding user's session was invalidated, an - * `auth/id-token-revoked` error is thrown. - * If not specified the check is not applied. - * - * @param {string} idToken The JWT to verify. - * @param {boolean=} checkRevoked Whether to check if the ID token is revoked. - * @return {Promise} A promise that will be fulfilled after - * a successful verification. - */ - public verifyIdToken(idToken: string, checkRevoked = false): Promise { - const isEmulator = useEmulator(); - return this.idTokenVerifier.verifyJWT(idToken, isEmulator) - .then((decodedIdToken: DecodedIdToken) => { - // Whether to check if the token was revoked. - if (checkRevoked || isEmulator) { - return this.verifyDecodedJWTNotRevokedOrDisabled( - decodedIdToken, - AuthClientErrorCode.ID_TOKEN_REVOKED); - } - return decodedIdToken; - }); - } - - /** - * Looks up the user identified by the provided user id and returns a promise that is - * fulfilled with a user record for the given user if that user is found. - * - * @param {string} uid The uid of the user to look up. - * @return {Promise} A promise that resolves with the corresponding user record. - */ - public getUser(uid: string): Promise { - return this.authRequestHandler.getAccountInfoByUid(uid) - .then((response: any) => { - // Returns the user record populated with server response. - return new UserRecord(response.users[0]); - }); - } - - /** - * Looks up the user identified by the provided email and returns a promise that is - * fulfilled with a user record for the given user if that user is found. - * - * @param {string} email The email of the user to look up. - * @return {Promise} A promise that resolves with the corresponding user record. - */ - public getUserByEmail(email: string): Promise { - return this.authRequestHandler.getAccountInfoByEmail(email) - .then((response: any) => { - // Returns the user record populated with server response. - return new UserRecord(response.users[0]); - }); - } - - /** - * Looks up the user identified by the provided phone number and returns a promise that is - * fulfilled with a user record for the given user if that user is found. - * - * @param {string} phoneNumber The phone number of the user to look up. - * @return {Promise} A promise that resolves with the corresponding user record. - */ - public getUserByPhoneNumber(phoneNumber: string): Promise { - return this.authRequestHandler.getAccountInfoByPhoneNumber(phoneNumber) - .then((response: any) => { - // Returns the user record populated with server response. - return new UserRecord(response.users[0]); - }); - } - - /** - * Gets the user data for the user corresponding to a given provider id. - * - * See [Retrieve user data](/docs/auth/admin/manage-users#retrieve_user_data) - * for code samples and detailed documentation. - * - * @param providerId The provider ID, for example, "google.com" for the - * Google provider. - * @param uid The user identifier for the given provider. - * - * @return A promise fulfilled with the user data corresponding to the - * given provider id. - */ - public getUserByProviderUid(providerId: string, uid: string): Promise { - // Although we don't really advertise it, we want to also handle - // non-federated idps with this call. So if we detect one of them, we'll - // reroute this request appropriately. - if (providerId === 'phone') { - return this.getUserByPhoneNumber(uid); - } else if (providerId === 'email') { - return this.getUserByEmail(uid); - } - - return this.authRequestHandler.getAccountInfoByFederatedUid(providerId, uid) - .then((response: any) => { - // Returns the user record populated with server response. - return new UserRecord(response.users[0]); - }); - } - - /** - * Gets the user data corresponding to the specified identifiers. - * - * There are no ordering guarantees; in particular, the nth entry in the result list is not - * guaranteed to correspond to the nth entry in the input parameters list. - * - * Only a maximum of 100 identifiers may be supplied. If more than 100 identifiers are supplied, - * this method will immediately throw a FirebaseAuthError. - * - * @param identifiers The identifiers used to indicate which user records should be returned. Must - * have <= 100 entries. - * @return {Promise} A promise that resolves to the corresponding user records. - * @throws FirebaseAuthError If any of the identifiers are invalid or if more than 100 - * identifiers are specified. - */ - public getUsers(identifiers: UserIdentifier[]): Promise { - if (!validator.isArray(identifiers)) { - throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, '`identifiers` parameter must be an array'); - } - return this.authRequestHandler - .getAccountInfoByIdentifiers(identifiers) - .then((response: any) => { - /** - * Checks if the specified identifier is within the list of - * UserRecords. - */ - const isUserFound = ((id: UserIdentifier, userRecords: UserRecord[]): boolean => { - return !!userRecords.find((userRecord) => { - if (isUidIdentifier(id)) { - return id.uid === userRecord.uid; - } else if (isEmailIdentifier(id)) { - return id.email === userRecord.email; - } else if (isPhoneIdentifier(id)) { - return id.phoneNumber === userRecord.phoneNumber; - } else if (isProviderIdentifier(id)) { - const matchingUserInfo = userRecord.providerData.find((userInfo) => { - return id.providerId === userInfo.providerId; - }); - return !!matchingUserInfo && id.providerUid === matchingUserInfo.uid; - } else { - throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, - 'Unhandled identifier type'); - } - }); - }); - - const users = response.users ? response.users.map((user: any) => new UserRecord(user)) : []; - const notFound = identifiers.filter((id) => !isUserFound(id, users)); - - return { users, notFound }; - }); - } - - /** - * Exports a batch of user accounts. Batch size is determined by the maxResults argument. - * Starting point of the batch is determined by the pageToken argument. - * - * @param {number=} maxResults The page size, 1000 if undefined. This is also the maximum - * allowed limit. - * @param {string=} pageToken The next page token. If not specified, returns users starting - * without any offset. - * @return {Promise<{users: UserRecord[], pageToken?: string}>} A promise that resolves with - * the current batch of downloaded users and the next page token. For the last page, an - * empty list of users and no page token are returned. - */ - public listUsers(maxResults?: number, pageToken?: string): Promise { - return this.authRequestHandler.downloadAccount(maxResults, pageToken) - .then((response: any) => { - // List of users to return. - const users: UserRecord[] = []; - // Convert each user response to a UserRecord. - response.users.forEach((userResponse: any) => { - users.push(new UserRecord(userResponse)); - }); - // Return list of user records and the next page token if available. - const result = { - users, - pageToken: response.nextPageToken, - }; - // Delete result.pageToken if undefined. - if (typeof result.pageToken === 'undefined') { - delete result.pageToken; - } - return result; - }); - } - - /** - * Creates a new user with the properties provided. - * - * @param {CreateRequest} properties The properties to set on the new user record to be created. - * @return {Promise} A promise that resolves with the newly created user record. - */ - public createUser(properties: CreateRequest): Promise { - return this.authRequestHandler.createNewAccount(properties) - .then((uid) => { - // Return the corresponding user record. - return this.getUser(uid); - }) - .catch((error) => { - if (error.code === 'auth/user-not-found') { - // Something must have happened after creating the user and then retrieving it. - throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, - 'Unable to create the user record provided.'); - } - throw error; - }); - } - - /** - * Deletes the user identified by the provided user id and returns a promise that is - * fulfilled when the user is found and successfully deleted. - * - * @param {string} uid The uid of the user to delete. - * @return {Promise} A promise that resolves when the user is successfully deleted. - */ - public deleteUser(uid: string): Promise { - return this.authRequestHandler.deleteAccount(uid) - .then(() => { - // Return nothing on success. - }); - } - - public deleteUsers(uids: string[]): Promise { - if (!validator.isArray(uids)) { - throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, '`uids` parameter must be an array'); - } - return this.authRequestHandler.deleteAccounts(uids, /*force=*/true) - .then((batchDeleteAccountsResponse) => { - const result: DeleteUsersResult = { - failureCount: 0, - successCount: uids.length, - errors: [], - }; - - if (!validator.isNonEmptyArray(batchDeleteAccountsResponse.errors)) { - return result; - } - - result.failureCount = batchDeleteAccountsResponse.errors.length; - result.successCount = uids.length - batchDeleteAccountsResponse.errors.length; - result.errors = batchDeleteAccountsResponse.errors.map((batchDeleteErrorInfo) => { - if (batchDeleteErrorInfo.index === undefined) { - throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, - 'Corrupt BatchDeleteAccountsResponse detected'); - } - - const errMsgToError = (msg?: string): FirebaseAuthError => { - // We unconditionally set force=true, so the 'NOT_DISABLED' error - // should not be possible. - const code = msg && msg.startsWith('NOT_DISABLED') ? - AuthClientErrorCode.USER_NOT_DISABLED : AuthClientErrorCode.INTERNAL_ERROR; - return new FirebaseAuthError(code, batchDeleteErrorInfo.message); - }; - - return { - index: batchDeleteErrorInfo.index, - error: errMsgToError(batchDeleteErrorInfo.message), - }; - }); - - return result; - }); - } - - /** - * Updates an existing user with the properties provided. - * - * @param {string} uid The uid identifier of the user to update. - * @param {UpdateRequest} properties The properties to update on the existing user. - * @return {Promise} A promise that resolves with the modified user record. - */ - public updateUser(uid: string, properties: UpdateRequest): Promise { - // Although we don't really advertise it, we want to also handle linking of - // non-federated idps with this call. So if we detect one of them, we'll - // adjust the properties parameter appropriately. This *does* imply that a - // conflict could arise, e.g. if the user provides a phoneNumber property, - // but also provides a providerToLink with a 'phone' provider id. In that - // case, we'll throw an error. - properties = deepCopy(properties); - - if (properties?.providerToLink) { - if (properties.providerToLink.providerId === 'email') { - if (typeof properties.email !== 'undefined') { - throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, - "Both UpdateRequest.email and UpdateRequest.providerToLink.providerId='email' were set. To " - + 'link to the email/password provider, only specify the UpdateRequest.email field.'); - } - properties.email = properties.providerToLink.uid; - delete properties.providerToLink; - } else if (properties.providerToLink.providerId === 'phone') { - if (typeof properties.phoneNumber !== 'undefined') { - throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, - "Both UpdateRequest.phoneNumber and UpdateRequest.providerToLink.providerId='phone' were set. To " - + 'link to a phone provider, only specify the UpdateRequest.phoneNumber field.'); - } - properties.phoneNumber = properties.providerToLink.uid; - delete properties.providerToLink; - } - } - if (properties?.providersToUnlink) { - if (properties.providersToUnlink.indexOf('phone') !== -1) { - // If we've been told to unlink the phone provider both via setting - // phoneNumber to null *and* by setting providersToUnlink to include - // 'phone', then we'll reject that. Though it might also be reasonable - // to relax this restriction and just unlink it. - if (properties.phoneNumber === null) { - throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, - "Both UpdateRequest.phoneNumber=null and UpdateRequest.providersToUnlink=['phone'] were set. To " - + 'unlink from a phone provider, only specify the UpdateRequest.phoneNumber=null field.'); - } - } - } - - return this.authRequestHandler.updateExistingAccount(uid, properties) - .then((existingUid) => { - // Return the corresponding user record. - return this.getUser(existingUid); - }); - } - - /** - * Sets additional developer claims on an existing user identified by the provided UID. - * - * @param {string} uid The user to edit. - * @param {object} customUserClaims The developer claims to set. - * @return {Promise} A promise that resolves when the operation completes - * successfully. - */ - public setCustomUserClaims(uid: string, customUserClaims: object | null): Promise { - return this.authRequestHandler.setCustomUserClaims(uid, customUserClaims) - .then(() => { - // Return nothing on success. - }); - } - - /** - * Revokes all refresh tokens for the specified user identified by the provided UID. - * In addition to revoking all refresh tokens for a user, all ID tokens issued before - * revocation will also be revoked on the Auth backend. Any request with an ID token - * generated before revocation will be rejected with a token expired error. - * - * @param {string} uid The user whose tokens are to be revoked. - * @return {Promise} A promise that resolves when the operation completes - * successfully. - */ - public revokeRefreshTokens(uid: string): Promise { - return this.authRequestHandler.revokeRefreshTokens(uid) - .then(() => { - // Return nothing on success. - }); - } - - /** - * Imports the list of users provided to Firebase Auth. This is useful when - * migrating from an external authentication system without having to use the Firebase CLI SDK. - * At most, 1000 users are allowed to be imported one at a time. - * When importing a list of password users, UserImportOptions are required to be specified. - * - * @param {UserImportRecord[]} users The list of user records to import to Firebase Auth. - * @param {UserImportOptions=} options The user import options, required when the users provided - * include password credentials. - * @return {Promise} A promise that resolves when the operation completes - * with the result of the import. This includes the number of successful imports, the number - * of failed uploads and their corresponding errors. - */ - public importUsers( - users: UserImportRecord[], options?: UserImportOptions): Promise { - return this.authRequestHandler.uploadAccount(users, options); - } - - /** - * Creates a new Firebase session cookie with the specified options that can be used for - * session management (set as a server side session cookie with custom cookie policy). - * The session cookie JWT will have the same payload claims as the provided ID token. - * - * @param {string} idToken The Firebase ID token to exchange for a session cookie. - * @param {SessionCookieOptions} sessionCookieOptions The session cookie options which includes - * custom session duration. - * - * @return {Promise} A promise that resolves on success with the created session cookie. - */ - public createSessionCookie( - idToken: string, sessionCookieOptions: SessionCookieOptions): Promise { - // Return rejected promise if expiresIn is not available. - if (!validator.isNonNullObject(sessionCookieOptions) || - !validator.isNumber(sessionCookieOptions.expiresIn)) { - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_SESSION_COOKIE_DURATION)); - } - return this.authRequestHandler.createSessionCookie( - idToken, sessionCookieOptions.expiresIn); - } - - /** - * Verifies a Firebase session cookie. Returns a promise with the token’s claims. - * Rejects the promise if the cookie could not be verified. - * If `checkRevoked` is set to true, first verifies whether the corresponding - * user is disabled: - * If yes, an `auth/user-disabled` error is thrown. - * If no, verifies if the session corresponding to the session cookie was - * revoked. - * If the corresponding user's session was invalidated, an - * `auth/session-cookie-revoked` error is thrown. - * If not specified the check is not performed. - * - * @param {string} sessionCookie The session cookie to verify. - * @param {boolean=} checkRevoked Whether to check if the session cookie is - * revoked. - * @return {Promise} A promise that will be fulfilled after - * a successful verification. - */ - public verifySessionCookie( - sessionCookie: string, checkRevoked = false): Promise { - const isEmulator = useEmulator(); - return this.sessionCookieVerifier.verifyJWT(sessionCookie, isEmulator) - .then((decodedIdToken: DecodedIdToken) => { - // Whether to check if the cookie was revoked. - if (checkRevoked || isEmulator) { - return this.verifyDecodedJWTNotRevokedOrDisabled( - decodedIdToken, - AuthClientErrorCode.SESSION_COOKIE_REVOKED); - } - return decodedIdToken; - }); - } - - /** - * Generates the out of band email action link for password reset flows for the - * email specified using the action code settings provided. - * Returns a promise that resolves with the generated link. - * - * @param {string} email The email of the user whose password is to be reset. - * @param {ActionCodeSettings=} actionCodeSettings The optional action code setings which defines whether - * the link is to be handled by a mobile app and the additional state information to be passed in the - * deep link, etc. - * @return {Promise} A promise that resolves with the password reset link. - */ - public generatePasswordResetLink(email: string, actionCodeSettings?: ActionCodeSettings): Promise { - return this.authRequestHandler.getEmailActionLink('PASSWORD_RESET', email, actionCodeSettings); - } - - /** - * Generates the out of band email action link for email verification flows for the - * email specified using the action code settings provided. - * Returns a promise that resolves with the generated link. - * - * @param {string} email The email of the user to be verified. - * @param {ActionCodeSettings=} actionCodeSettings The optional action code setings which defines whether - * the link is to be handled by a mobile app and the additional state information to be passed in the - * deep link, etc. - * @return {Promise} A promise that resolves with the email verification link. - */ - public generateEmailVerificationLink(email: string, actionCodeSettings?: ActionCodeSettings): Promise { - return this.authRequestHandler.getEmailActionLink('VERIFY_EMAIL', email, actionCodeSettings); - } - - /** - * Generates the out of band email action link for email link sign-in flows for the - * email specified using the action code settings provided. - * Returns a promise that resolves with the generated link. - * - * @param {string} email The email of the user signing in. - * @param {ActionCodeSettings} actionCodeSettings The required action code setings which defines whether - * the link is to be handled by a mobile app and the additional state information to be passed in the - * deep link, etc. - * @return {Promise} A promise that resolves with the email sign-in link. - */ - public generateSignInWithEmailLink(email: string, actionCodeSettings: ActionCodeSettings): Promise { - return this.authRequestHandler.getEmailActionLink('EMAIL_SIGNIN', email, actionCodeSettings); - } - - /** - * Returns the list of existing provider configuation matching the filter provided. - * At most, 100 provider configs are allowed to be imported at a time. - * - * @param {AuthProviderConfigFilter} options The provider config filter to apply. - * @return {Promise} A promise that resolves with the list of provider configs - * meeting the filter requirements. - */ - public listProviderConfigs(options: AuthProviderConfigFilter): Promise { - const processResponse = (response: any, providerConfigs: AuthProviderConfig[]): ListProviderConfigResults => { - // Return list of provider configuration and the next page token if available. - const result: ListProviderConfigResults = { - providerConfigs, - }; - // Delete result.pageToken if undefined. - if (Object.prototype.hasOwnProperty.call(response, 'nextPageToken')) { - result.pageToken = response.nextPageToken; - } - return result; - }; - if (options && options.type === 'oidc') { - return this.authRequestHandler.listOAuthIdpConfigs(options.maxResults, options.pageToken) - .then((response: any) => { - // List of provider configurations to return. - const providerConfigs: OIDCConfig[] = []; - // Convert each provider config response to a OIDCConfig. - response.oauthIdpConfigs.forEach((configResponse: any) => { - providerConfigs.push(new OIDCConfig(configResponse)); - }); - // Return list of provider configuration and the next page token if available. - return processResponse(response, providerConfigs); - }); - } else if (options && options.type === 'saml') { - return this.authRequestHandler.listInboundSamlConfigs(options.maxResults, options.pageToken) - .then((response: any) => { - // List of provider configurations to return. - const providerConfigs: SAMLConfig[] = []; - // Convert each provider config response to a SAMLConfig. - response.inboundSamlConfigs.forEach((configResponse: any) => { - providerConfigs.push(new SAMLConfig(configResponse)); - }); - // Return list of provider configuration and the next page token if available. - return processResponse(response, providerConfigs); - }); - } - return Promise.reject( - new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, - '"AuthProviderConfigFilter.type" must be either "saml" or "oidc"')); - } - - /** - * Looks up an Auth provider configuration by ID. - * Returns a promise that resolves with the provider configuration corresponding to the provider ID specified. - * - * @param {string} providerId The provider ID corresponding to the provider config to return. - * @return {Promise} - */ - public getProviderConfig(providerId: string): Promise { - if (OIDCConfig.isProviderId(providerId)) { - return this.authRequestHandler.getOAuthIdpConfig(providerId) - .then((response: OIDCConfigServerResponse) => { - return new OIDCConfig(response); - }); - } else if (SAMLConfig.isProviderId(providerId)) { - return this.authRequestHandler.getInboundSamlConfig(providerId) - .then((response: SAMLConfigServerResponse) => { - return new SAMLConfig(response); - }); - } - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID)); - } - - /** - * Deletes the provider configuration corresponding to the provider ID passed. - * - * @param {string} providerId The provider ID corresponding to the provider config to delete. - * @return {Promise} A promise that resolves on completion. - */ - public deleteProviderConfig(providerId: string): Promise { - if (OIDCConfig.isProviderId(providerId)) { - return this.authRequestHandler.deleteOAuthIdpConfig(providerId); - } else if (SAMLConfig.isProviderId(providerId)) { - return this.authRequestHandler.deleteInboundSamlConfig(providerId); - } - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID)); - } - - /** - * Returns a promise that resolves with the updated AuthProviderConfig when the provider configuration corresponding - * to the provider ID specified is updated with the specified configuration. - * - * @param {string} providerId The provider ID corresponding to the provider config to update. - * @param {UpdateAuthProviderRequest} updatedConfig The updated configuration. - * @return {Promise} A promise that resolves with the updated provider configuration. - */ - public updateProviderConfig( - providerId: string, updatedConfig: UpdateAuthProviderRequest): Promise { - if (!validator.isNonNullObject(updatedConfig)) { - return Promise.reject(new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, - 'Request is missing "UpdateAuthProviderRequest" configuration.', - )); - } - if (OIDCConfig.isProviderId(providerId)) { - return this.authRequestHandler.updateOAuthIdpConfig(providerId, updatedConfig) - .then((response) => { - return new OIDCConfig(response); - }); - } else if (SAMLConfig.isProviderId(providerId)) { - return this.authRequestHandler.updateInboundSamlConfig(providerId, updatedConfig) - .then((response) => { - return new SAMLConfig(response); - }); - } - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID)); - } - - /** - * Returns a promise that resolves with the newly created AuthProviderConfig when the new provider configuration is - * created. - * @param {AuthProviderConfig} config The provider configuration to create. - * @return {Promise} A promise that resolves with the created provider configuration. - */ - public createProviderConfig(config: AuthProviderConfig): Promise { - if (!validator.isNonNullObject(config)) { - return Promise.reject(new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, - 'Request is missing "AuthProviderConfig" configuration.', - )); - } - if (OIDCConfig.isProviderId(config.providerId)) { - return this.authRequestHandler.createOAuthIdpConfig(config as OIDCAuthProviderConfig) - .then((response) => { - return new OIDCConfig(response); - }); - } else if (SAMLConfig.isProviderId(config.providerId)) { - return this.authRequestHandler.createInboundSamlConfig(config as SAMLAuthProviderConfig) - .then((response) => { - return new SAMLConfig(response); - }); - } - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID)); - } - - /** - * Verifies the decoded Firebase issued JWT is not revoked or disabled. Returns a promise that - * resolves with the decoded claims on success. Rejects the promise with revocation error if revoked - * or user disabled. - * - * @param {DecodedIdToken} decodedIdToken The JWT's decoded claims. - * @param {ErrorInfo} revocationErrorInfo The revocation error info to throw on revocation - * detection. - * @return {Promise} A promise that will be fulfilled after a successful - * verification. - */ - private verifyDecodedJWTNotRevokedOrDisabled( - decodedIdToken: DecodedIdToken, revocationErrorInfo: ErrorInfo): Promise { - // Get tokens valid after time for the corresponding user. - return this.getUser(decodedIdToken.sub) - .then((user: UserRecord) => { - if (user.disabled) { - throw new FirebaseAuthError( - AuthClientErrorCode.USER_DISABLED, - 'The user record is disabled.'); - } - // If no tokens valid after time available, token is not revoked. - if (user.tokensValidAfterTime) { - // Get the ID token authentication time and convert to milliseconds UTC. - const authTimeUtc = decodedIdToken.auth_time * 1000; - // Get user tokens valid after time in milliseconds UTC. - const validSinceUtc = new Date(user.tokensValidAfterTime).getTime(); - // Check if authentication time is older than valid since time. - if (authTimeUtc < validSinceUtc) { - throw new FirebaseAuthError(revocationErrorInfo); - } - } - // All checks above passed. Return the decoded token. - return decodedIdToken; - }); - } -} - - -/** - * The tenant aware Auth class. - */ -export class TenantAwareAuth - extends BaseAuth - implements TenantAwareAuthInterface { - - public readonly tenantId: string; - - /** - * The TenantAwareAuth class constructor. - * - * @param {object} app The app that created this tenant. - * @param tenantId The corresponding tenant ID. - * @constructor - */ - constructor(app: FirebaseApp, tenantId: string) { - super(app, new TenantAwareAuthRequestHandler(app, tenantId), - createFirebaseTokenGenerator(app, tenantId)); - utils.addReadonlyGetter(this, 'tenantId', tenantId); - } - - /** - * Verifies a JWT auth token. Returns a promise with the tokens claims. Rejects - * the promise if the token could not be verified. If checkRevoked is set to true, - * verifies if the session corresponding to the ID token was revoked. If the corresponding - * user's session was invalidated, an `auth/id-token-revoked` error is thrown. If not specified - * the check is not applied. - * - * @param {string} idToken The JWT to verify. - * @param {boolean=} checkRevoked Whether to check if the ID token is revoked. - * @return {Promise} A promise that will be fulfilled after a successful - * verification. - */ - public verifyIdToken(idToken: string, checkRevoked = false): Promise { - return super.verifyIdToken(idToken, checkRevoked) - .then((decodedClaims) => { - // Validate tenant ID. - if (decodedClaims.firebase.tenant !== this.tenantId) { - throw new FirebaseAuthError(AuthClientErrorCode.MISMATCHING_TENANT_ID); - } - return decodedClaims; - }); - } - - /** - * Creates a new Firebase session cookie with the specified options that can be used for - * session management (set as a server side session cookie with custom cookie policy). - * The session cookie JWT will have the same payload claims as the provided ID token. - * - * @param {string} idToken The Firebase ID token to exchange for a session cookie. - * @param {SessionCookieOptions} sessionCookieOptions The session cookie options which includes - * custom session duration. - * - * @return {Promise} A promise that resolves on success with the created session cookie. - */ - public createSessionCookie( - idToken: string, sessionCookieOptions: SessionCookieOptions): Promise { - // Validate arguments before processing. - if (!validator.isNonEmptyString(idToken)) { - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_ID_TOKEN)); - } - if (!validator.isNonNullObject(sessionCookieOptions) || - !validator.isNumber(sessionCookieOptions.expiresIn)) { - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_SESSION_COOKIE_DURATION)); - } - // This will verify the ID token and then match the tenant ID before creating the session cookie. - return this.verifyIdToken(idToken) - .then(() => { - return super.createSessionCookie(idToken, sessionCookieOptions); - }); - } - - /** - * Verifies a Firebase session cookie. Returns a promise with the tokens claims. Rejects - * the promise if the token could not be verified. If checkRevoked is set to true, - * verifies if the session corresponding to the session cookie was revoked. If the corresponding - * user's session was invalidated, an `auth/session-cookie-revoked` error is thrown. If not - * specified the check is not performed. - * - * @param {string} sessionCookie The session cookie to verify. - * @param {boolean=} checkRevoked Whether to check if the session cookie is revoked. - * @return {Promise} A promise that will be fulfilled after a successful - * verification. - */ - public verifySessionCookie( - sessionCookie: string, checkRevoked = false): Promise { - return super.verifySessionCookie(sessionCookie, checkRevoked) - .then((decodedClaims) => { - if (decodedClaims.firebase.tenant !== this.tenantId) { - throw new FirebaseAuthError(AuthClientErrorCode.MISMATCHING_TENANT_ID); - } - return decodedClaims; - }); - } -} - +import { BaseAuth } from './base-auth'; /** * Auth service bound to the provided app. * An Auth instance can have multiple tenants. */ -export class Auth extends BaseAuth implements AuthInterface { +export class Auth extends BaseAuth { private readonly tenantManager_: TenantManager; - private readonly app_: FirebaseApp; + private readonly app_: App; /** - * @param {object} app The app for this Auth service. + * @param app The app for this Auth service. * @constructor + * @internal */ - constructor(app: FirebaseApp) { + constructor(app: App) { super(app, new AuthRequestHandler(app)); this.app_ = app; this.tenantManager_ = new TenantManager(app); @@ -891,24 +43,18 @@ export class Auth extends BaseAuth implements AuthInterface /** * Returns the app associated with this Auth instance. * - * @return {FirebaseApp} The app associated with this Auth instance. + * @returns The app associated with this Auth instance. */ - get app(): FirebaseApp { + get app(): App { return this.app_; } - /** @return The current Auth instance's tenant manager. */ + /** + * Returns the tenant manager instance associated with the current project. + * + * @returns The tenant manager instance associated with the current project. + */ public tenantManager(): TenantManager { return this.tenantManager_; } } - -function createFirebaseTokenGenerator(app: FirebaseApp, - tenantId?: string): FirebaseTokenGenerator { - try { - const signer = useEmulator() ? new EmulatedSigner() : cryptoSignerFromApp(app); - return new FirebaseTokenGenerator(signer, tenantId); - } catch (err) { - throw handleCryptoSignerError(err); - } -} diff --git a/src/auth/base-auth.ts b/src/auth/base-auth.ts new file mode 100644 index 0000000000..0bd67e2b65 --- /dev/null +++ b/src/auth/base-auth.ts @@ -0,0 +1,1093 @@ +/*! + * Copyright 2021 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 { App, FirebaseArrayIndexError } from '../app'; +import { AuthClientErrorCode, ErrorInfo, FirebaseAuthError } from '../utils/error'; +import { deepCopy } from '../utils/deep-copy'; +import * as validator from '../utils/validator'; + +import { AbstractAuthRequestHandler, useEmulator } from './auth-api-request'; +import { FirebaseTokenGenerator, EmulatedSigner, handleCryptoSignerError } from './token-generator'; +import { + FirebaseTokenVerifier, createSessionCookieVerifier, createIdTokenVerifier, + DecodedIdToken, +} from './token-verifier'; +import { + AuthProviderConfig, SAMLAuthProviderConfig, AuthProviderConfigFilter, ListProviderConfigResults, + SAMLConfig, OIDCConfig, OIDCConfigServerResponse, SAMLConfigServerResponse, + UpdateAuthProviderRequest, OIDCAuthProviderConfig, CreateRequest, UpdateRequest, +} from './auth-config'; +import { UserRecord } from './user-record'; +import { + UserIdentifier, isUidIdentifier, isEmailIdentifier, isPhoneIdentifier, isProviderIdentifier, +} from './identifier'; +import { UserImportOptions, UserImportRecord, UserImportResult } from './user-import-builder'; +import { ActionCodeSettings } from './action-code-settings-builder'; +import { cryptoSignerFromApp } from '../utils/crypto-signer'; + +/** Represents the result of the {@link BaseAuth.getUsers} API. */ +export interface GetUsersResult { + /** + * Set of user records, corresponding to the set of users that were + * requested. Only users that were found are listed here. The result set is + * unordered. + */ + users: UserRecord[]; + + /** Set of identifiers that were requested, but not found. */ + notFound: UserIdentifier[]; +} + +/** + * Interface representing the object returned from a + * {@link auth.Auth.listUsers `listUsers()`} operation. Contains the list + * of users for the current batch and the next page token if available. + */ +export interface ListUsersResult { + + /** + * The list of {@link auth.UserRecord `UserRecord`} objects for the + * current downloaded batch. + */ + users: UserRecord[]; + + /** + * The next page token if available. This is needed for the next batch download. + */ + pageToken?: string; +} + +/** + * Represents the result of the {@link BaseAuth.deleteUsers}. + * API. + */ +export interface DeleteUsersResult { + /** + * The number of user records that failed to be deleted (possibly zero). + */ + failureCount: number; + + /** + * The number of users that were deleted successfully (possibly zero). + * Users that did not exist prior to calling `deleteUsers()` are + * considered to be successfully deleted. + */ + successCount: number; + + /** + * A list of `FirebaseArrayIndexError` instances describing the errors that + * were encountered during the deletion. Length of this list is equal to + * the return value of {@link DeleteUsersResult.failureCount}. + */ + errors: FirebaseArrayIndexError[]; +} + +/** + * Interface representing the session cookie options needed for the + * {@link BaseAuth.createSessionCookie} method. + */ +export interface SessionCookieOptions { + + /** + * The session cookie custom expiration in milliseconds. The minimum allowed is + * 5 minutes and the maxium allowed is 2 weeks. + */ + expiresIn: number; +} + +/** + * @internal + */ +export function createFirebaseTokenGenerator(app: App, + tenantId?: string): FirebaseTokenGenerator { + try { + const signer = useEmulator() ? new EmulatedSigner() : cryptoSignerFromApp(app); + return new FirebaseTokenGenerator(signer, tenantId); + } catch (err) { + throw handleCryptoSignerError(err); + } +} + +/** + * Common parent interface for both `Auth` and `TenantAwareAuth` APIs. + */ +export abstract class BaseAuth { + + /** @internal */ + protected readonly tokenGenerator: FirebaseTokenGenerator; + /** @internal */ + protected readonly idTokenVerifier: FirebaseTokenVerifier; + /** @internal */ + protected readonly sessionCookieVerifier: FirebaseTokenVerifier; + + /** + * The BaseAuth class constructor. + * + * @param app The FirebaseApp to associate with this Auth instance. + * @param authRequestHandler The RPC request handler for this instance. + * @param tokenGenerator Optional token generator. If not specified, a + * (non-tenant-aware) instance will be created. Use this paramter to + * specify a tenant-aware tokenGenerator. + * @constructor + * @internal + */ + constructor( + app: App, + /** @internal */ protected readonly authRequestHandler: AbstractAuthRequestHandler, + tokenGenerator?: FirebaseTokenGenerator) { + if (tokenGenerator) { + this.tokenGenerator = tokenGenerator; + } else { + this.tokenGenerator = createFirebaseTokenGenerator(app); + } + + this.sessionCookieVerifier = createSessionCookieVerifier(app); + this.idTokenVerifier = createIdTokenVerifier(app); + } + + /** + * Creates a new Firebase custom token (JWT) that can be sent back to a client + * device to use to sign in with the client SDKs' `signInWithCustomToken()` + * methods. (Tenant-aware instances will also embed the tenant ID in the + * token.) + * + * See {@link https://firebase.google.com/docs/auth/admin/create-custom-tokens | Create Custom Tokens} + * for code samples and detailed documentation. + * + * @param uid The `uid` to use as the custom token's subject. + * @param developerClaims Optional additional claims to include + * in the custom token's payload. + * + * @returns A promise fulfilled with a custom token for the + * provided `uid` and payload. + */ + public createCustomToken(uid: string, developerClaims?: object): Promise { + return this.tokenGenerator.createCustomToken(uid, developerClaims); + } + + /** + * Verifies a Firebase ID token (JWT). If the token is valid, the promise is + * fulfilled with the token's decoded claims; otherwise, the promise is + * rejected. + * + * If `checkRevoked` is set to true, first verifies whether the corresponding + * user is disabled. If yes, an `auth/user-disabled` error is thrown. If no, + * verifies if the session corresponding to the ID token was revoked. If the + * corresponding user's session was invalidated, an `auth/id-token-revoked` + * error is thrown. If not specified the check is not applied. + * + * See {@link https://firebase.google.com/docs/auth/admin/verify-id-tokens | Verify ID Tokens} + * for code samples and detailed documentation. + * + * @param idToken The ID token to verify. + * @param checkRevoked Whether to check if the ID token was revoked. + * This requires an extra request to the Firebase Auth backend to check + * the `tokensValidAfterTime` time for the corresponding user. + * When not specified, this additional check is not applied. + * + * @returns A promise fulfilled with the + * token's decoded claims if the ID token is valid; otherwise, a rejected + * promise. + */ + public verifyIdToken(idToken: string, checkRevoked = false): Promise { + const isEmulator = useEmulator(); + return this.idTokenVerifier.verifyJWT(idToken, isEmulator) + .then((decodedIdToken: DecodedIdToken) => { + // Whether to check if the token was revoked. + if (checkRevoked || isEmulator) { + return this.verifyDecodedJWTNotRevokedOrDisabled( + decodedIdToken, + AuthClientErrorCode.ID_TOKEN_REVOKED); + } + return decodedIdToken; + }); + } + + /** + * Gets the user data for the user corresponding to a given `uid`. + * + * See {@link https://firebase.google.com/docs/auth/admin/manage-users#retrieve_user_data | Retrieve user data} + * for code samples and detailed documentation. + * + * @param uid The `uid` corresponding to the user whose data to fetch. + * + * @returns A promise fulfilled with the user + * data corresponding to the provided `uid`. + */ + public getUser(uid: string): Promise { + return this.authRequestHandler.getAccountInfoByUid(uid) + .then((response: any) => { + // Returns the user record populated with server response. + return new UserRecord(response.users[0]); + }); + } + + /** + * Gets the user data for the user corresponding to a given email. + * + * See {@link https://firebase.google.com/docs/auth/admin/manage-users#retrieve_user_data | Retrieve user data} + * for code samples and detailed documentation. + * + * @param email The email corresponding to the user whose data to + * fetch. + * + * @returns A promise fulfilled with the user + * data corresponding to the provided email. + */ + public getUserByEmail(email: string): Promise { + return this.authRequestHandler.getAccountInfoByEmail(email) + .then((response: any) => { + // Returns the user record populated with server response. + return new UserRecord(response.users[0]); + }); + } + + /** + * Gets the user data for the user corresponding to a given phone number. The + * phone number has to conform to the E.164 specification. + * + * See {@link https://firebase.google.com/docs/auth/admin/manage-users#retrieve_user_data | Retrieve user data} + * for code samples and detailed documentation. + * + * @param phoneNumber The phone number corresponding to the user whose + * data to fetch. + * + * @returns A promise fulfilled with the user + * data corresponding to the provided phone number. + */ + public getUserByPhoneNumber(phoneNumber: string): Promise { + return this.authRequestHandler.getAccountInfoByPhoneNumber(phoneNumber) + .then((response: any) => { + // Returns the user record populated with server response. + return new UserRecord(response.users[0]); + }); + } + + /** + * Gets the user data for the user corresponding to a given provider id. + * + * See {@link https://firebase.google.com/docs/auth/admin/manage-users#retrieve_user_data | Retrieve user data} + * for code samples and detailed documentation. + * + * @param providerId The provider ID, for example, "google.com" for the + * Google provider. + * @param uid The user identifier for the given provider. + * + * @returns A promise fulfilled with the user data corresponding to the + * given provider id. + */ + public getUserByProviderUid(providerId: string, uid: string): Promise { + // Although we don't really advertise it, we want to also handle + // non-federated idps with this call. So if we detect one of them, we'll + // reroute this request appropriately. + if (providerId === 'phone') { + return this.getUserByPhoneNumber(uid); + } else if (providerId === 'email') { + return this.getUserByEmail(uid); + } + + return this.authRequestHandler.getAccountInfoByFederatedUid(providerId, uid) + .then((response: any) => { + // Returns the user record populated with server response. + return new UserRecord(response.users[0]); + }); + } + + /** + * Gets the user data corresponding to the specified identifiers. + * + * There are no ordering guarantees; in particular, the nth entry in the result list is not + * guaranteed to correspond to the nth entry in the input parameters list. + * + * Only a maximum of 100 identifiers may be supplied. If more than 100 identifiers are supplied, + * this method throws a FirebaseAuthError. + * + * @param identifiers The identifiers used to indicate which user records should be returned. + * Must have <= 100 entries. + * @returns A promise that resolves to the corresponding user records. + * @throws FirebaseAuthError If any of the identifiers are invalid or if more than 100 + * identifiers are specified. + */ + public getUsers(identifiers: UserIdentifier[]): Promise { + if (!validator.isArray(identifiers)) { + throw new FirebaseAuthError( + AuthClientErrorCode.INVALID_ARGUMENT, '`identifiers` parameter must be an array'); + } + return this.authRequestHandler + .getAccountInfoByIdentifiers(identifiers) + .then((response: any) => { + /** + * Checks if the specified identifier is within the list of + * UserRecords. + */ + const isUserFound = ((id: UserIdentifier, userRecords: UserRecord[]): boolean => { + return !!userRecords.find((userRecord) => { + if (isUidIdentifier(id)) { + return id.uid === userRecord.uid; + } else if (isEmailIdentifier(id)) { + return id.email === userRecord.email; + } else if (isPhoneIdentifier(id)) { + return id.phoneNumber === userRecord.phoneNumber; + } else if (isProviderIdentifier(id)) { + const matchingUserInfo = userRecord.providerData.find((userInfo) => { + return id.providerId === userInfo.providerId; + }); + return !!matchingUserInfo && id.providerUid === matchingUserInfo.uid; + } else { + throw new FirebaseAuthError( + AuthClientErrorCode.INTERNAL_ERROR, + 'Unhandled identifier type'); + } + }); + }); + + const users = response.users ? response.users.map((user: any) => new UserRecord(user)) : []; + const notFound = identifiers.filter((id) => !isUserFound(id, users)); + + return { users, notFound }; + }); + } + + /** + * Retrieves a list of users (single batch only) with a size of `maxResults` + * starting from the offset as specified by `pageToken`. This is used to + * retrieve all the users of a specified project in batches. + * + * See {@link https://firebase.google.com/docs/auth/admin/manage-users#list_all_users | List all users} + * for code samples and detailed documentation. + * + * @param maxResults The page size, 1000 if undefined. This is also + * the maximum allowed limit. + * @param pageToken The next page token. If not specified, returns + * users starting without any offset. + * @returns A promise that resolves with + * the current batch of downloaded users and the next page token. + */ + public listUsers(maxResults?: number, pageToken?: string): Promise { + return this.authRequestHandler.downloadAccount(maxResults, pageToken) + .then((response: any) => { + // List of users to return. + const users: UserRecord[] = []; + // Convert each user response to a UserRecord. + response.users.forEach((userResponse: any) => { + users.push(new UserRecord(userResponse)); + }); + // Return list of user records and the next page token if available. + const result = { + users, + pageToken: response.nextPageToken, + }; + // Delete result.pageToken if undefined. + if (typeof result.pageToken === 'undefined') { + delete result.pageToken; + } + return result; + }); + } + + /** + * Creates a new user. + * + * See {@link https://firebase.google.com/docs/auth/admin/manage-users#create_a_user | Create a user} + * for code samples and detailed documentation. + * + * @param properties The properties to set on the + * new user record to be created. + * + * @returns A promise fulfilled with the user + * data corresponding to the newly created user. + */ + public createUser(properties: CreateRequest): Promise { + return this.authRequestHandler.createNewAccount(properties) + .then((uid) => { + // Return the corresponding user record. + return this.getUser(uid); + }) + .catch((error) => { + if (error.code === 'auth/user-not-found') { + // Something must have happened after creating the user and then retrieving it. + throw new FirebaseAuthError( + AuthClientErrorCode.INTERNAL_ERROR, + 'Unable to create the user record provided.'); + } + throw error; + }); + } + + /** + * Deletes an existing user. + * + * See {@link https://firebase.google.com/docs/auth/admin/manage-users#delete_a_user | Delete a user} + * for code samples and detailed documentation. + * + * @param uid The `uid` corresponding to the user to delete. + * + * @returns An empty promise fulfilled once the user has been + * deleted. + */ + public deleteUser(uid: string): Promise { + return this.authRequestHandler.deleteAccount(uid) + .then(() => { + // Return nothing on success. + }); + } + + /** + * Deletes the users specified by the given uids. + * + * Deleting a non-existing user won't generate an error (i.e. this method + * is idempotent.) Non-existing users are considered to be successfully + * deleted, and are therefore counted in the + * `DeleteUsersResult.successCount` value. + * + * Only a maximum of 1000 identifiers may be supplied. If more than 1000 + * identifiers are supplied, this method throws a FirebaseAuthError. + * + * This API is currently rate limited at the server to 1 QPS. If you exceed + * this, you may get a quota exceeded error. Therefore, if you want to + * delete more than 1000 users, you may need to add a delay to ensure you + * don't go over this limit. + * + * @param uids The `uids` corresponding to the users to delete. + * + * @returns A Promise that resolves to the total number of successful/failed + * deletions, as well as the array of errors that corresponds to the + * failed deletions. + */ + public deleteUsers(uids: string[]): Promise { + if (!validator.isArray(uids)) { + throw new FirebaseAuthError( + AuthClientErrorCode.INVALID_ARGUMENT, '`uids` parameter must be an array'); + } + return this.authRequestHandler.deleteAccounts(uids, /*force=*/true) + .then((batchDeleteAccountsResponse) => { + const result: DeleteUsersResult = { + failureCount: 0, + successCount: uids.length, + errors: [], + }; + + if (!validator.isNonEmptyArray(batchDeleteAccountsResponse.errors)) { + return result; + } + + result.failureCount = batchDeleteAccountsResponse.errors.length; + result.successCount = uids.length - batchDeleteAccountsResponse.errors.length; + result.errors = batchDeleteAccountsResponse.errors.map((batchDeleteErrorInfo) => { + if (batchDeleteErrorInfo.index === undefined) { + throw new FirebaseAuthError( + AuthClientErrorCode.INTERNAL_ERROR, + 'Corrupt BatchDeleteAccountsResponse detected'); + } + + const errMsgToError = (msg?: string): FirebaseAuthError => { + // We unconditionally set force=true, so the 'NOT_DISABLED' error + // should not be possible. + const code = msg && msg.startsWith('NOT_DISABLED') ? + AuthClientErrorCode.USER_NOT_DISABLED : AuthClientErrorCode.INTERNAL_ERROR; + return new FirebaseAuthError(code, batchDeleteErrorInfo.message); + }; + + return { + index: batchDeleteErrorInfo.index, + error: errMsgToError(batchDeleteErrorInfo.message), + }; + }); + + return result; + }); + } + + /** + * Updates an existing user. + * + * See {@link https://firebsae.google.com/docs/auth/admin/manage-users#update_a_user | Update a user} + * for code samples and detailed documentation. + * + * @param uid The `uid` corresponding to the user to update. + * @param properties The properties to update on + * the provided user. + * + * @returns A promise fulfilled with the + * updated user data. + */ + public updateUser(uid: string, properties: UpdateRequest): Promise { + // Although we don't really advertise it, we want to also handle linking of + // non-federated idps with this call. So if we detect one of them, we'll + // adjust the properties parameter appropriately. This *does* imply that a + // conflict could arise, e.g. if the user provides a phoneNumber property, + // but also provides a providerToLink with a 'phone' provider id. In that + // case, we'll throw an error. + properties = deepCopy(properties); + + if (properties?.providerToLink) { + if (properties.providerToLink.providerId === 'email') { + if (typeof properties.email !== 'undefined') { + throw new FirebaseAuthError( + AuthClientErrorCode.INVALID_ARGUMENT, + "Both UpdateRequest.email and UpdateRequest.providerToLink.providerId='email' were set. To " + + 'link to the email/password provider, only specify the UpdateRequest.email field.'); + } + properties.email = properties.providerToLink.uid; + delete properties.providerToLink; + } else if (properties.providerToLink.providerId === 'phone') { + if (typeof properties.phoneNumber !== 'undefined') { + throw new FirebaseAuthError( + AuthClientErrorCode.INVALID_ARGUMENT, + "Both UpdateRequest.phoneNumber and UpdateRequest.providerToLink.providerId='phone' were set. To " + + 'link to a phone provider, only specify the UpdateRequest.phoneNumber field.'); + } + properties.phoneNumber = properties.providerToLink.uid; + delete properties.providerToLink; + } + } + if (properties?.providersToUnlink) { + if (properties.providersToUnlink.indexOf('phone') !== -1) { + // If we've been told to unlink the phone provider both via setting + // phoneNumber to null *and* by setting providersToUnlink to include + // 'phone', then we'll reject that. Though it might also be reasonable + // to relax this restriction and just unlink it. + if (properties.phoneNumber === null) { + throw new FirebaseAuthError( + AuthClientErrorCode.INVALID_ARGUMENT, + "Both UpdateRequest.phoneNumber=null and UpdateRequest.providersToUnlink=['phone'] were set. To " + + 'unlink from a phone provider, only specify the UpdateRequest.phoneNumber=null field.'); + } + } + } + + return this.authRequestHandler.updateExistingAccount(uid, properties) + .then((existingUid) => { + // Return the corresponding user record. + return this.getUser(existingUid); + }); + } + + /** + * Sets additional developer claims on an existing user identified by the + * provided `uid`, typically used to define user roles and levels of + * access. These claims should propagate to all devices where the user is + * already signed in (after token expiration or when token refresh is forced) + * and the next time the user signs in. If a reserved OIDC claim name + * is used (sub, iat, iss, etc), an error is thrown. They are set on the + * authenticated user's ID token JWT. + * + * See {@link https://firebase.google.com/docs/auth/admin/custom-claims | + * Defining user roles and access levels} + * for code samples and detailed documentation. + * + * @param uid The `uid` of the user to edit. + * @param customUserClaims The developer claims to set. If null is + * passed, existing custom claims are deleted. Passing a custom claims payload + * larger than 1000 bytes will throw an error. Custom claims are added to the + * user's ID token which is transmitted on every authenticated request. + * For profile non-access related user attributes, use database or other + * separate storage systems. + * @returns A promise that resolves when the operation completes + * successfully. + */ + public setCustomUserClaims(uid: string, customUserClaims: object | null): Promise { + return this.authRequestHandler.setCustomUserClaims(uid, customUserClaims) + .then(() => { + // Return nothing on success. + }); + } + + /** + * Revokes all refresh tokens for an existing user. + * + * This API will update the user's {@link UserRecord.tokensValidAfterTime} to + * the current UTC. It is important that the server on which this is called has + * its clock set correctly and synchronized. + * + * While this will revoke all sessions for a specified user and disable any + * new ID tokens for existing sessions from getting minted, existing ID tokens + * may remain active until their natural expiration (one hour). To verify that + * ID tokens are revoked, use {@link BaseAuth.verifyIdToken} + * where `checkRevoked` is set to true. + * + * @param uid The `uid` corresponding to the user whose refresh tokens + * are to be revoked. + * + * @returns An empty promise fulfilled once the user's refresh + * tokens have been revoked. + */ + public revokeRefreshTokens(uid: string): Promise { + return this.authRequestHandler.revokeRefreshTokens(uid) + .then(() => { + // Return nothing on success. + }); + } + + /** + * Imports the provided list of users into Firebase Auth. + * A maximum of 1000 users are allowed to be imported one at a time. + * When importing users with passwords, + * {@link UserImportOptions} are required to be + * specified. + * This operation is optimized for bulk imports and will ignore checks on `uid`, + * `email` and other identifier uniqueness which could result in duplications. + * + * @param users The list of user records to import to Firebase Auth. + * @param options The user import options, required when the users provided include + * password credentials. + * @returns A promise that resolves when + * the operation completes with the result of the import. This includes the + * number of successful imports, the number of failed imports and their + * corresponding errors. + */ + public importUsers( + users: UserImportRecord[], options?: UserImportOptions): Promise { + return this.authRequestHandler.uploadAccount(users, options); + } + + /** + * Creates a new Firebase session cookie with the specified options. The created + * JWT string can be set as a server-side session cookie with a custom cookie + * policy, and be used for session management. The session cookie JWT will have + * the same payload claims as the provided ID token. + * + * See {@link https://firebase.google.com/docs/auth/admin/manage-cookies | Manage Session Cookies} + * for code samples and detailed documentation. + * + * @param idToken The Firebase ID token to exchange for a session + * cookie. + * @param sessionCookieOptions The session + * cookie options which includes custom session duration. + * + * @returns A promise that resolves on success with the + * created session cookie. + */ + public createSessionCookie( + idToken: string, sessionCookieOptions: SessionCookieOptions): Promise { + // Return rejected promise if expiresIn is not available. + if (!validator.isNonNullObject(sessionCookieOptions) || + !validator.isNumber(sessionCookieOptions.expiresIn)) { + return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_SESSION_COOKIE_DURATION)); + } + return this.authRequestHandler.createSessionCookie( + idToken, sessionCookieOptions.expiresIn); + } + + /** + * Verifies a Firebase session cookie. Returns a Promise with the cookie claims. + * Rejects the promise if the cookie could not be verified. + * + * If `checkRevoked` is set to true, first verifies whether the corresponding + * user is disabled: If yes, an `auth/user-disabled` error is thrown. If no, + * verifies if the session corresponding to the session cookie was revoked. + * If the corresponding user's session was invalidated, an + * `auth/session-cookie-revoked` error is thrown. If not specified the check + * is not performed. + * + * See {@link https://firebase.google.com/docs/auth/admin/manage-cookies#verify_session_cookie_and_check_permissions | + * Verify Session Cookies} + * for code samples and detailed documentation + * + * @param sessionCookie The session cookie to verify. + * @param checkForRevocation Whether to check if the session cookie was + * revoked. This requires an extra request to the Firebase Auth backend to + * check the `tokensValidAfterTime` time for the corresponding user. + * When not specified, this additional check is not performed. + * + * @returns A promise fulfilled with the + * session cookie's decoded claims if the session cookie is valid; otherwise, + * a rejected promise. + */ + public verifySessionCookie( + sessionCookie: string, checkRevoked = false): Promise { + const isEmulator = useEmulator(); + return this.sessionCookieVerifier.verifyJWT(sessionCookie, isEmulator) + .then((decodedIdToken: DecodedIdToken) => { + // Whether to check if the token was revoked. + if (checkRevoked || isEmulator) { + return this.verifyDecodedJWTNotRevokedOrDisabled( + decodedIdToken, + AuthClientErrorCode.SESSION_COOKIE_REVOKED); + } + return decodedIdToken; + }); + } + + /** + * Generates the out of band email action link to reset a user's password. + * The link is generated for the user with the specified email address. The + * optional {@link ActionCodeSettings} object + * defines whether the link is to be handled by a mobile app or browser and the + * additional state information to be passed in the deep link, etc. + * + * @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, + * dynamicLinkDomain: 'custom.page.link' + * }; + * admin.auth() + * .generatePasswordResetLink('user@example.com', actionCodeSettings) + * .then(function(link) { + * // The link was successfully generated. + * }) + * .catch(function(error) { + * // Some error occurred, you can inspect the code: error.code + * }); + * ``` + * + * @param email The email address of the user whose password is to be + * reset. + * @param actionCodeSettings The action + * code settings. If specified, the state/continue URL is 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 is thrown. + * Mobile app redirects are only applicable if the developer configures + * and accepts the Firebase Dynamic Links terms of service. + * The Android package name and iOS bundle ID are respected only if they + * are configured in the same Firebase Auth project. + * @returns A promise that resolves with the generated link. + */ + public generatePasswordResetLink(email: string, actionCodeSettings?: ActionCodeSettings): Promise { + return this.authRequestHandler.getEmailActionLink('PASSWORD_RESET', email, actionCodeSettings); + } + + /** + * Generates the out of band email action link to verify the user's ownership + * of the specified email. The {@link ActionCodeSettings} object provided + * as an argument to this method defines whether the link is to be handled by a + * mobile app or browser along with additional state information to be passed in + * the deep link, etc. + * + * @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, + * dynamicLinkDomain: 'custom.page.link' + * }; + * admin.auth() + * .generateEmailVerificationLink('user@example.com', actionCodeSettings) + * .then(function(link) { + * // The link was successfully generated. + * }) + * .catch(function(error) { + * // Some error occurred, you can inspect the code: error.code + * }); + * ``` + * + * @param email The email account to verify. + * @param actionCodeSettings The action + * code settings. If specified, the state/continue URL is 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 is thrown. + * Mobile app redirects are only applicable if the developer configures + * and accepts the Firebase Dynamic Links terms of service. + * The Android package name and iOS bundle ID are respected only if they + * are configured in the same Firebase Auth project. + * @returns A promise that resolves with the generated link. + */ + public generateEmailVerificationLink(email: string, actionCodeSettings?: ActionCodeSettings): Promise { + return this.authRequestHandler.getEmailActionLink('VERIFY_EMAIL', email, actionCodeSettings); + } + + /** + * Generates the out of band email action link to verify the user's ownership + * of the specified email. The {@link ActionCodeSettings} object provided + * as an argument to this method defines whether the link is to be handled by a + * mobile app or browser along with additional state information to be passed in + * the deep link, etc. + * + * @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, + * dynamicLinkDomain: 'custom.page.link' + * }; + * admin.auth() + * .generateEmailVerificationLink('user@example.com', actionCodeSettings) + * .then(function(link) { + * // The link was successfully generated. + * }) + * .catch(function(error) { + * // Some error occurred, you can inspect the code: error.code + * }); + * ``` + * + * @param email The email account to verify. + * @param actionCodeSettings The action + * code settings. If specified, the state/continue URL is 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 is thrown. + * Mobile app redirects are only applicable if the developer configures + * and accepts the Firebase Dynamic Links terms of service. + * The Android package name and iOS bundle ID are respected only if they + * are configured in the same Firebase Auth project. + * @returns A promise that resolves with the generated link. + */ + public generateSignInWithEmailLink(email: string, actionCodeSettings: ActionCodeSettings): Promise { + return this.authRequestHandler.getEmailActionLink('EMAIL_SIGNIN', email, actionCodeSettings); + } + + /** + * Returns the list of existing provider configurations matching the filter + * provided. At most, 100 provider configs can be listed at a time. + * + * SAML and OIDC provider support requires Google Cloud's Identity Platform + * (GCIP). To learn more about GCIP, including pricing and features, + * see the {@link https://cloud.google.com/identity-platform | GCIP documentation}. + * + * @param options The provider config filter to apply. + * @returns A promise that resolves with the list of provider configs meeting the + * filter requirements. + */ + public listProviderConfigs(options: AuthProviderConfigFilter): Promise { + const processResponse = (response: any, providerConfigs: AuthProviderConfig[]): ListProviderConfigResults => { + // Return list of provider configuration and the next page token if available. + const result: ListProviderConfigResults = { + providerConfigs, + }; + // Delete result.pageToken if undefined. + if (Object.prototype.hasOwnProperty.call(response, 'nextPageToken')) { + result.pageToken = response.nextPageToken; + } + return result; + }; + if (options && options.type === 'oidc') { + return this.authRequestHandler.listOAuthIdpConfigs(options.maxResults, options.pageToken) + .then((response: any) => { + // List of provider configurations to return. + const providerConfigs: OIDCConfig[] = []; + // Convert each provider config response to a OIDCConfig. + response.oauthIdpConfigs.forEach((configResponse: any) => { + providerConfigs.push(new OIDCConfig(configResponse)); + }); + // Return list of provider configuration and the next page token if available. + return processResponse(response, providerConfigs); + }); + } else if (options && options.type === 'saml') { + return this.authRequestHandler.listInboundSamlConfigs(options.maxResults, options.pageToken) + .then((response: any) => { + // List of provider configurations to return. + const providerConfigs: SAMLConfig[] = []; + // Convert each provider config response to a SAMLConfig. + response.inboundSamlConfigs.forEach((configResponse: any) => { + providerConfigs.push(new SAMLConfig(configResponse)); + }); + // Return list of provider configuration and the next page token if available. + return processResponse(response, providerConfigs); + }); + } + return Promise.reject( + new FirebaseAuthError( + AuthClientErrorCode.INVALID_ARGUMENT, + '"AuthProviderConfigFilter.type" must be either "saml" or "oidc"')); + } + + /** + * Looks up an Auth provider configuration by the provided ID. + * Returns a promise that resolves with the provider configuration + * corresponding to the provider ID specified. If the specified ID does not + * exist, an `auth/configuration-not-found` error is thrown. + * + * SAML and OIDC provider support requires Google Cloud's Identity Platform + * (GCIP). To learn more about GCIP, including pricing and features, + * see the {@link https://cloud.google.com/identity-platform | GCIP documentation}. + * + * @param providerId The provider ID corresponding to the provider + * config to return. + * @returns A promise that resolves + * with the configuration corresponding to the provided ID. + */ + public getProviderConfig(providerId: string): Promise { + if (OIDCConfig.isProviderId(providerId)) { + return this.authRequestHandler.getOAuthIdpConfig(providerId) + .then((response: OIDCConfigServerResponse) => { + return new OIDCConfig(response); + }); + } else if (SAMLConfig.isProviderId(providerId)) { + return this.authRequestHandler.getInboundSamlConfig(providerId) + .then((response: SAMLConfigServerResponse) => { + return new SAMLConfig(response); + }); + } + return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID)); + } + + /** + * Deletes the provider configuration corresponding to the provider ID passed. + * If the specified ID does not exist, an `auth/configuration-not-found` error + * is thrown. + * + * SAML and OIDC provider support requires Google Cloud's Identity Platform + * (GCIP). To learn more about GCIP, including pricing and features, + * see the {@link https://cloud.google.com/identity-platform | GCIP documentation}. + * + * @param providerId The provider ID corresponding to the provider + * config to delete. + * @returns A promise that resolves on completion. + */ + public deleteProviderConfig(providerId: string): Promise { + if (OIDCConfig.isProviderId(providerId)) { + return this.authRequestHandler.deleteOAuthIdpConfig(providerId); + } else if (SAMLConfig.isProviderId(providerId)) { + return this.authRequestHandler.deleteInboundSamlConfig(providerId); + } + return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID)); + } + + /** + * Returns a promise that resolves with the updated `AuthProviderConfig` + * corresponding to the provider ID specified. + * If the specified ID does not exist, an `auth/configuration-not-found` error + * is thrown. + * + * SAML and OIDC provider support requires Google Cloud's Identity Platform + * (GCIP). To learn more about GCIP, including pricing and features, + * see the {@link https://cloud.google.com/identity-platform | GCIP documentation}. + * + * @param providerId The provider ID corresponding to the provider + * config to update. + * @param updatedConfig The updated configuration. + * @returns A promise that resolves with the updated provider configuration. + */ + public updateProviderConfig( + providerId: string, updatedConfig: UpdateAuthProviderRequest): Promise { + if (!validator.isNonNullObject(updatedConfig)) { + return Promise.reject(new FirebaseAuthError( + AuthClientErrorCode.INVALID_CONFIG, + 'Request is missing "UpdateAuthProviderRequest" configuration.', + )); + } + if (OIDCConfig.isProviderId(providerId)) { + return this.authRequestHandler.updateOAuthIdpConfig(providerId, updatedConfig) + .then((response) => { + return new OIDCConfig(response); + }); + } else if (SAMLConfig.isProviderId(providerId)) { + return this.authRequestHandler.updateInboundSamlConfig(providerId, updatedConfig) + .then((response) => { + return new SAMLConfig(response); + }); + } + return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID)); + } + + /** + * Returns a promise that resolves with the newly created `AuthProviderConfig` + * when the new provider configuration is created. + * + * SAML and OIDC provider support requires Google Cloud's Identity Platform + * (GCIP). To learn more about GCIP, including pricing and features, + * see the {@link https://cloud.google.com/identity-platform | GCIP documentation}. + * + * @param config The provider configuration to create. + * @returns A promise that resolves with the created provider configuration. + */ + public createProviderConfig(config: AuthProviderConfig): Promise { + if (!validator.isNonNullObject(config)) { + return Promise.reject(new FirebaseAuthError( + AuthClientErrorCode.INVALID_CONFIG, + 'Request is missing "AuthProviderConfig" configuration.', + )); + } + if (OIDCConfig.isProviderId(config.providerId)) { + return this.authRequestHandler.createOAuthIdpConfig(config as OIDCAuthProviderConfig) + .then((response) => { + return new OIDCConfig(response); + }); + } else if (SAMLConfig.isProviderId(config.providerId)) { + return this.authRequestHandler.createInboundSamlConfig(config as SAMLAuthProviderConfig) + .then((response) => { + return new SAMLConfig(response); + }); + } + return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID)); + } + + /** + * Verifies the decoded Firebase issued JWT is not revoked or disabled. Returns a promise that + * resolves with the decoded claims on success. Rejects the promise with revocation error if revoked + * or user disabled. + * + * @param decodedIdToken The JWT's decoded claims. + * @param revocationErrorInfo The revocation error info to throw on revocation + * detection. + * @returns A promise that will be fulfilled after a successful verification. + */ + private verifyDecodedJWTNotRevokedOrDisabled( + decodedIdToken: DecodedIdToken, revocationErrorInfo: ErrorInfo): Promise { + // Get tokens valid after time for the corresponding user. + return this.getUser(decodedIdToken.sub) + .then((user: UserRecord) => { + if (user.disabled) { + throw new FirebaseAuthError( + AuthClientErrorCode.USER_DISABLED, + 'The user record is disabled.'); + } + // If no tokens valid after time available, token is not revoked. + if (user.tokensValidAfterTime) { + // Get the ID token authentication time and convert to milliseconds UTC. + const authTimeUtc = decodedIdToken.auth_time * 1000; + // Get user tokens valid after time in milliseconds UTC. + const validSinceUtc = new Date(user.tokensValidAfterTime).getTime(); + // Check if authentication time is older than valid since time. + if (authTimeUtc < validSinceUtc) { + throw new FirebaseAuthError(revocationErrorInfo); + } + } + // All checks above passed. Return the decoded token. + return decodedIdToken; + }); + } +} \ No newline at end of file diff --git a/src/auth/identifier.ts b/src/auth/identifier.ts index b9e93b1fc0..8bae483d1d 100644 --- a/src/auth/identifier.ts +++ b/src/auth/identifier.ts @@ -14,13 +14,48 @@ * limitations under the License. */ -import { auth } from './index'; +/** + * Used for looking up an account by uid. + * + * See {@link BaseAuth.getUsers}. + */ +export interface UidIdentifier { + uid: string; +} + +/** + * Used for looking up an account by email. + * + * See {@link BaseAuth.getUsers}. + */ +export interface EmailIdentifier { + email: string; +} + +/** + * Used for looking up an account by phone number. + * + * See {@link BaseAuth.getUsers}. + */ +export interface PhoneIdentifier { + phoneNumber: string; +} + +/** + * Used for looking up an account by federated provider. + * + * See {@link BaseAuth.getUsers}. + */ +export interface ProviderIdentifier { + providerId: string; + providerUid: string; +} -import UserIdentifier = auth.UserIdentifier; -import UidIdentifier = auth.UidIdentifier; -import EmailIdentifier = auth.EmailIdentifier; -import PhoneIdentifier = auth.PhoneIdentifier; -import ProviderIdentifier = auth.ProviderIdentifier; +/** + * Identifies a user to be looked up. + */ +export type UserIdentifier = + UidIdentifier | EmailIdentifier | PhoneIdentifier | ProviderIdentifier; /* * User defined type guards. See diff --git a/src/auth/index.ts b/src/auth/index.ts index 875bdd35cd..b189e1ac56 100644 --- a/src/auth/index.ts +++ b/src/auth/index.ts @@ -14,2199 +14,124 @@ * limitations under the License. */ -import { app, FirebaseArrayIndexError } from '../firebase-namespace-api'; +/** + * Firebase Authentication. + * + * @packageDocumentation + */ + +import { App, getApp } from '../app/index'; +import { FirebaseApp } from '../app/firebase-app'; +import { Auth } from './auth'; /** * Gets the {@link auth.Auth `Auth`} service for the default app or a * given app. * - * `admin.auth()` can be called with no arguments to access the default app's - * {@link auth.Auth `Auth`} service or as `admin.auth(app)` to access the + * `getAuth()` can be called with no arguments to access the default app's + * {@link auth.Auth `Auth`} service or as `getAuth(app)` to access the * {@link auth.Auth `Auth`} service associated with a specific app. * * @example * ```javascript * // Get the Auth service for the default app - * var defaultAuth = admin.auth(); + * const defaultAuth = getAuth(); * ``` * * @example * ```javascript * // Get the Auth service for a given app - * var otherAuth = admin.auth(otherApp); + * const otherAuth = getAuth(otherApp); * ``` * */ -export declare function auth(app?: app.App): auth.Auth; - -/* eslint-disable @typescript-eslint/no-namespace */ -export namespace auth { - /** - * Interface representing a user's metadata. - */ - export interface UserMetadata { - - /** - * The date the user last signed in, formatted as a UTC string. - */ - lastSignInTime: string; - - /** - * The date the user was created, formatted as a UTC string. - */ - creationTime: string; - - /** - * The time at which the user was last active (ID token refreshed), - * formatted as a UTC Date string (eg 'Sat, 03 Feb 2001 04:05:06 GMT'). - * Returns null if the user was never active. - */ - lastRefreshTime?: string | null; - - /** - * @return A JSON-serializable representation of this object. - */ - toJSON(): object; - } - - /** - * Interface representing a user's info from a third-party identity provider - * such as Google or Facebook. - */ - export interface UserInfo { - - /** - * The user identifier for the linked provider. - */ - uid: string; - - /** - * The display name for the linked provider. - */ - displayName: string; - - /** - * The email for the linked provider. - */ - email: string; - - /** - * The phone number for the linked provider. - */ - phoneNumber: string; - - /** - * The photo URL for the linked provider. - */ - photoURL: string; - - /** - * The linked provider ID (for example, "google.com" for the Google provider). - */ - providerId: string; - - /** - * @return A JSON-serializable representation of this object. - */ - toJSON(): object; - } - - /** - * Interface representing the common properties of a user-enrolled second factor. - */ - export interface MultiFactorInfo { - - /** - * The ID of the enrolled second factor. This ID is unique to the user. - */ - uid: string; - - /** - * The optional display name of the enrolled second factor. - */ - displayName?: string; - - /** - * The optional date the second factor was enrolled, formatted as a UTC string. - */ - enrollmentTime?: string; - - /** - * The type identifier of the second factor. For SMS second factors, this is `phone`. - */ - factorId: string; - - /** - * @return A JSON-serializable representation of this object. - */ - toJSON(): object; - } - - /** - * Interface representing a phone specific user-enrolled second factor. - */ - export interface PhoneMultiFactorInfo extends MultiFactorInfo { - - /** - * The phone number associated with a phone second factor. - */ - phoneNumber: string; - } - - /** - * Represents a user identity provider that can be associated with a Firebase user. - */ - export interface UserProvider { - - /** - * The user identifier for the linked provider. - */ - uid?: string; - - /** - * The display name for the linked provider. - */ - displayName?: string; - - /** - * The email for the linked provider. - */ - email?: string; - - /** - * The phone number for the linked provider. - */ - phoneNumber?: string; - - /** - * The photo URL for the linked provider. - */ - photoURL?: string; - - /** - * The linked provider ID (for example, "google.com" for the Google provider). - */ - providerId?: string; - } - - /** - * Interface representing a user. - */ - export interface UserRecord { - - /** - * The user's `uid`. - */ - uid: string; - - /** - * The user's primary email, if set. - */ - email?: string; - - /** - * Whether or not the user's primary email is verified. - */ - emailVerified: boolean; - - /** - * The user's display name. - */ - displayName?: string; - - /** - * The user's primary phone number, if set. - */ - phoneNumber?: string; - - /** - * The user's photo URL. - */ - photoURL?: string; - - /** - * Whether or not the user is disabled: `true` for disabled; `false` for - * enabled. - */ - disabled: boolean; - - /** - * Additional metadata about the user. - */ - metadata: UserMetadata; - - /** - * An array of providers (for example, Google, Facebook) linked to the user. - */ - providerData: UserInfo[]; - - /** - * The user's hashed password (base64-encoded), only if Firebase Auth hashing - * algorithm (SCRYPT) is used. If a different hashing algorithm had been used - * when uploading this user, as is typical when migrating from another Auth - * system, this will be an empty string. If no password is set, this is - * null. This is only available when the user is obtained from - * {@link auth.Auth.listUsers `listUsers()`}. - * - */ - passwordHash?: string; - - /** - * The user's password salt (base64-encoded), only if Firebase Auth hashing - * algorithm (SCRYPT) is used. If a different hashing algorithm had been used to - * upload this user, typical when migrating from another Auth system, this will - * be an empty string. If no password is set, this is null. This is only - * available when the user is obtained from - * {@link auth.Auth.listUsers `listUsers()`}. - * - */ - passwordSalt?: string; - - /** - * The user's custom claims object if available, typically used to define - * user roles and propagated to an authenticated user's ID token. - * This is set via - * {@link auth.Auth.setCustomUserClaims `setCustomUserClaims()`} - */ - customClaims?: { [key: string]: any }; - - /** - * The date the user's tokens are valid after, formatted as a UTC string. - * This is updated every time the user's refresh token are revoked either - * from the {@link auth.Auth.revokeRefreshTokens `revokeRefreshTokens()`} - * API or from the Firebase Auth backend on big account changes (password - * resets, password or email updates, etc). - */ - tokensValidAfterTime?: string; - - /** - * The ID of the tenant the user belongs to, if available. - */ - tenantId?: string | null; - - /** - * The multi-factor related properties for the current user, if available. - */ - multiFactor?: MultiFactorSettings; - - /** - * @return A JSON-serializable representation of this object. - */ - toJSON(): object; - } - - /** - * The multi-factor related user settings. - */ - export interface MultiFactorSettings { - /** - * List of second factors enrolled with the current user. - * Currently only phone second factors are supported. - */ - enrolledFactors: MultiFactorInfo[]; - - /** - * @return A JSON-serializable representation of this multi-factor object. - */ - toJSON(): object; - } - - /** - * The multi-factor related user settings for create operations. - */ - export interface MultiFactorCreateSettings { - - /** - * The created user's list of enrolled second factors. - */ - enrolledFactors: CreateMultiFactorInfoRequest[]; - } - - /** - * The multi-factor related user settings for update operations. - */ - export interface MultiFactorUpdateSettings { - - /** - * The updated list of enrolled second factors. The provided list overwrites the user's - * existing list of second factors. - * When null is passed, all of the user's existing second factors are removed. - */ - enrolledFactors: UpdateMultiFactorInfoRequest[] | null; - } - - /** - * Interface representing common properties of a user-enrolled second factor - * for an `UpdateRequest`. - */ - export interface BaseUpdateMultiFactorInfoRequest { - - /** - * The ID of the enrolled second factor. This ID is unique to the user. When not provided, - * a new one is provisioned by the Auth server. - */ - uid?: string; - - /** - * The optional display name for an enrolled second factor. - */ - displayName?: string; - - /** - * The optional date the second factor was enrolled, formatted as a UTC string. - */ - enrollmentTime?: string; - - /** - * The type identifier of the second factor. For SMS second factors, this is `phone`. - */ - factorId: string; - } - - /** - * Interface representing a phone specific user-enrolled second factor - * for an `UpdateRequest`. - */ - export interface UpdatePhoneMultiFactorInfoRequest extends BaseUpdateMultiFactorInfoRequest { - - /** - * The phone number associated with a phone second factor. - */ - phoneNumber: string; - } - - /** - * Type representing the properties of a user-enrolled second factor - * for an `UpdateRequest`. - */ - export type UpdateMultiFactorInfoRequest = | UpdatePhoneMultiFactorInfoRequest; - - /** - * Interface representing the properties to update on the provided user. - */ - export interface UpdateRequest { - - /** - * Whether or not the user is disabled: `true` for disabled; - * `false` for enabled. - */ - disabled?: boolean; - - /** - * The user's display name. - */ - displayName?: string | null; - - /** - * The user's primary email. - */ - email?: string; - - /** - * Whether or not the user's primary email is verified. - */ - emailVerified?: boolean; - - /** - * The user's unhashed password. - */ - password?: string; - - /** - * The user's primary phone number. - */ - phoneNumber?: string | null; - - /** - * The user's photo URL. - */ - photoURL?: string | null; - - /** - * The user's updated multi-factor related properties. - */ - multiFactor?: MultiFactorUpdateSettings; - - /** - * Links this user to the specified provider. - * - * Linking a provider to an existing user account does not invalidate the - * refresh token of that account. In other words, the existing account - * would continue to be able to access resources, despite not having used - * the newly linked provider to log in. If you wish to force the user to - * authenticate with this new provider, you need to (a) revoke their - * refresh token (see - * https://firebase.google.com/docs/auth/admin/manage-sessions#revoke_refresh_tokens), - * and (b) ensure no other authentication methods are present on this - * account. - */ - providerToLink?: UserProvider; - - /** - * Unlinks this user from the specified providers. - */ - providersToUnlink?: string[]; - } - - /** - * Interface representing base properties of a user-enrolled second factor for a - * `CreateRequest`. - */ - export interface BaseCreateMultiFactorInfoRequest { - - /** - * The optional display name for an enrolled second factor. - */ - displayName?: string; - - /** - * The type identifier of the second factor. For SMS second factors, this is `phone`. - */ - factorId: string; - } - - /** - * Interface representing a phone specific user-enrolled second factor for a - * `CreateRequest`. - */ - export interface CreatePhoneMultiFactorInfoRequest extends BaseCreateMultiFactorInfoRequest { - - /** - * The phone number associated with a phone second factor. - */ - phoneNumber: string; - } - - /** - * Type representing the properties of a user-enrolled second factor - * for a `CreateRequest`. - */ - export type CreateMultiFactorInfoRequest = | CreatePhoneMultiFactorInfoRequest; - - /** - * Interface representing the properties to set on a new user record to be - * created. - */ - export interface CreateRequest extends UpdateRequest { - - /** - * The user's `uid`. - */ - uid?: string; - - /** - * The user's multi-factor related properties. - */ - multiFactor?: MultiFactorCreateSettings; - } - - /** - * Interface representing a decoded Firebase ID token, returned from the - * {@link auth.Auth.verifyIdToken `verifyIdToken()`} method. - * - * Firebase ID tokens are OpenID Connect spec-compliant JSON Web Tokens (JWTs). - * See the - * [ID Token section of the OpenID Connect spec](http://openid.net/specs/openid-connect-core-1_0.html#IDToken) - * for more information about the specific properties below. - */ - export interface DecodedIdToken { - - /** - * The audience for which this token is intended. - * - * This value is a string equal to your Firebase project ID, the unique - * identifier for your Firebase project, which can be found in [your project's - * settings](https://console.firebase.google.com/project/_/settings/general/android:com.random.android). - */ - aud: string; - - /** - * Time, in seconds since the Unix epoch, when the end-user authentication - * occurred. - * - * This value is not set when this particular ID token was created, but when the - * user initially logged in to this session. In a single session, the Firebase - * SDKs will refresh a user's ID tokens every hour. Each ID token will have a - * different [`iat`](#iat) value, but the same `auth_time` value. - */ - auth_time: number; - - /** - * The email of the user to whom the ID token belongs, if available. - */ - email?: string; - - /** - * Whether or not the email of the user to whom the ID token belongs is - * verified, provided the user has an email. - */ - email_verified?: boolean; - - /** - * The ID token's expiration time, in seconds since the Unix epoch. That is, the - * time at which this ID token expires and should no longer be considered valid. - * - * The Firebase SDKs transparently refresh ID tokens every hour, issuing a new - * ID token with up to a one hour expiration. - */ - exp: number; - - /** - * Information about the sign in event, including which sign in provider was - * used and provider-specific identity details. - * - * This data is provided by the Firebase Authentication service and is a - * reserved claim in the ID token. - */ - firebase: { - - /** - * Provider-specific identity details corresponding - * to the provider used to sign in the user. - */ - identities: { - [key: string]: any; - }; - - /** - * The ID of the provider used to sign in the user. - * One of `"anonymous"`, `"password"`, `"facebook.com"`, `"github.com"`, - * `"google.com"`, `"twitter.com"`, `"apple.com"`, `"microsoft.com"`, - * `"yahoo.com"`, `"phone"`, `"playgames.google.com"`, `"gc.apple.com"`, - * or `"custom"`. - * - * Additional Identity Platform provider IDs include `"linkedin.com"`, - * OIDC and SAML identity providers prefixed with `"saml."` and `"oidc."` - * respectively. - */ - sign_in_provider: string; - - /** - * The type identifier or `factorId` of the second factor, provided the - * ID token was obtained from a multi-factor authenticated user. - * For phone, this is `"phone"`. - */ - sign_in_second_factor?: string; - - /** - * The `uid` of the second factor used to sign in, provided the - * ID token was obtained from a multi-factor authenticated user. - */ - second_factor_identifier?: string; - - /** - * The ID of the tenant the user belongs to, if available. - */ - tenant?: string; - [key: string]: any; - }; - - /** - * The ID token's issued-at time, in seconds since the Unix epoch. That is, the - * time at which this ID token was issued and should start to be considered - * valid. - * - * The Firebase SDKs transparently refresh ID tokens every hour, issuing a new - * ID token with a new issued-at time. If you want to get the time at which the - * user session corresponding to the ID token initially occurred, see the - * [`auth_time`](#auth_time) property. - */ - iat: number; - - /** - * The issuer identifier for the issuer of the response. - * - * This value is a URL with the format - * `https://securetoken.google.com/`, where `` is the - * same project ID specified in the [`aud`](#aud) property. - */ - iss: string; - - /** - * The phone number of the user to whom the ID token belongs, if available. - */ - phone_number?: string; - - /** - * The photo URL for the user to whom the ID token belongs, if available. - */ - picture?: string; - - /** - * The `uid` corresponding to the user who the ID token belonged to. - * - * As a convenience, this value is copied over to the [`uid`](#uid) property. - */ - sub: string; - - /** - * The `uid` corresponding to the user who the ID token belonged to. - * - * This value is not actually in the JWT token claims itself. It is added as a - * convenience, and is set as the value of the [`sub`](#sub) property. - */ - uid: string; - [key: string]: any; - } - - /** Represents the result of the {@link auth.Auth.getUsers} API. */ - export interface GetUsersResult { - /** - * Set of user records, corresponding to the set of users that were - * requested. Only users that were found are listed here. The result set is - * unordered. - */ - users: UserRecord[]; - - /** Set of identifiers that were requested, but not found. */ - notFound: UserIdentifier[]; - } - - /** - * Interface representing the object returned from a - * {@link auth.Auth.listUsers `listUsers()`} operation. Contains the list - * of users for the current batch and the next page token if available. - */ - export interface ListUsersResult { - - /** - * The list of {@link auth.UserRecord `UserRecord`} objects for the - * current downloaded batch. - */ - users: UserRecord[]; - - /** - * The next page token if available. This is needed for the next batch download. - */ - pageToken?: string; - } - - export type HashAlgorithmType = 'SCRYPT' | 'STANDARD_SCRYPT' | 'HMAC_SHA512' | - 'HMAC_SHA256' | 'HMAC_SHA1' | 'HMAC_MD5' | 'MD5' | 'PBKDF_SHA1' | 'BCRYPT' | - 'PBKDF2_SHA256' | 'SHA512' | 'SHA256' | 'SHA1'; - - /** - * Interface representing the user import options needed for - * {@link auth.Auth.importUsers `importUsers()`} method. This is used to - * provide the password hashing algorithm information. - */ - export interface UserImportOptions { - - /** - * The password hashing information. - */ - hash: { - - /** - * The password hashing algorithm identifier. The following algorithm - * identifiers are supported: - * `SCRYPT`, `STANDARD_SCRYPT`, `HMAC_SHA512`, `HMAC_SHA256`, `HMAC_SHA1`, - * `HMAC_MD5`, `MD5`, `PBKDF_SHA1`, `BCRYPT`, `PBKDF2_SHA256`, `SHA512`, - * `SHA256` and `SHA1`. - */ - algorithm: HashAlgorithmType; - - /** - * The signing key used in the hash algorithm in buffer bytes. - * Required by hashing algorithms `SCRYPT`, `HMAC_SHA512`, `HMAC_SHA256`, - * `HAMC_SHA1` and `HMAC_MD5`. - */ - key?: Buffer; - - /** - * The salt separator in buffer bytes which is appended to salt when - * verifying a password. This is only used by the `SCRYPT` algorithm. - */ - saltSeparator?: Buffer; - - /** - * The number of rounds for hashing calculation. - * Required for `SCRYPT`, `MD5`, `SHA512`, `SHA256`, `SHA1`, `PBKDF_SHA1` and - * `PBKDF2_SHA256`. - */ - rounds?: number; - - /** - * The memory cost required for `SCRYPT` algorithm, or the CPU/memory cost. - * Required for `STANDARD_SCRYPT` algorithm. - */ - memoryCost?: number; - - /** - * The parallelization of the hashing algorithm. Required for the - * `STANDARD_SCRYPT` algorithm. - */ - parallelization?: number; - - /** - * The block size (normally 8) of the hashing algorithm. Required for the - * `STANDARD_SCRYPT` algorithm. - */ - blockSize?: number; - /** - * The derived key length of the hashing algorithm. Required for the - * `STANDARD_SCRYPT` algorithm. - */ - derivedKeyLength?: number; - }; - } - - /** - * Interface representing the response from the - * {@link auth.Auth.importUsers `importUsers()`} method for batch - * importing users to Firebase Auth. - */ - export interface UserImportResult { - - /** - * The number of user records that failed to import to Firebase Auth. - */ - failureCount: number; - - /** - * The number of user records that successfully imported to Firebase Auth. - */ - successCount: number; - - /** - * An array of errors corresponding to the provided users to import. The - * length of this array is equal to [`failureCount`](#failureCount). - */ - errors: FirebaseArrayIndexError[]; - } - - /** - * Represents the result of the - * {@link auth.Auth.deleteUsers `deleteUsers()`} - * API. - */ - export interface DeleteUsersResult { - /** - * The number of user records that failed to be deleted (possibly zero). - */ - failureCount: number; - - /** - * The number of users that were deleted successfully (possibly zero). - * Users that did not exist prior to calling `deleteUsers()` are - * considered to be successfully deleted. - */ - successCount: number; - - /** - * A list of `FirebaseArrayIndexError` instances describing the errors that - * were encountered during the deletion. Length of this list is equal to - * the return value of [`failureCount`](#failureCount). - */ - errors: FirebaseArrayIndexError[]; - } - - /** - * User metadata to include when importing a user. - */ - export interface UserMetadataRequest { - - /** - * The date the user last signed in, formatted as a UTC string. - */ - lastSignInTime?: string; - - /** - * The date the user was created, formatted as a UTC string. - */ - creationTime?: string; - } - - /** - * User provider data to include when importing a user. - */ - export interface UserProviderRequest { - - /** - * The user identifier for the linked provider. - */ - uid: string; - - /** - * The display name for the linked provider. - */ - displayName?: string; - - /** - * The email for the linked provider. - */ - email?: string; - - /** - * The phone number for the linked provider. - */ - phoneNumber?: string; - - /** - * The photo URL for the linked provider. - */ - photoURL?: string; - - /** - * The linked provider ID (for example, "google.com" for the Google provider). - */ - providerId: string; - } - - /** - * Interface representing a user to import to Firebase Auth via the - * {@link auth.Auth.importUsers `importUsers()`} method. - */ - export interface UserImportRecord { - - /** - * The user's `uid`. - */ - uid: string; - - /** - * The user's primary email, if set. - */ - email?: string; - - /** - * Whether or not the user's primary email is verified. - */ - emailVerified?: boolean; - - /** - * The user's display name. - */ - displayName?: string; - - /** - * The user's primary phone number, if set. - */ - phoneNumber?: string; - - /** - * The user's photo URL. - */ - photoURL?: string; - - /** - * Whether or not the user is disabled: `true` for disabled; `false` for - * enabled. - */ - disabled?: boolean; - - /** - * Additional metadata about the user. - */ - metadata?: UserMetadataRequest; - - /** - * An array of providers (for example, Google, Facebook) linked to the user. - */ - providerData?: UserProviderRequest[]; - - /** - * The user's custom claims object if available, typically used to define - * user roles and propagated to an authenticated user's ID token. - */ - customClaims?: { [key: string]: any }; - - /** - * The buffer of bytes representing the user's hashed password. - * When a user is to be imported with a password hash, - * {@link auth.UserImportOptions `UserImportOptions`} are required to be - * specified to identify the hashing algorithm used to generate this hash. - */ - passwordHash?: Buffer; - - /** - * The buffer of bytes representing the user's password salt. - */ - passwordSalt?: Buffer; - - /** - * The identifier of the tenant where user is to be imported to. - * When not provided in an `admin.auth.Auth` context, the user is uploaded to - * the default parent project. - * When not provided in an `admin.auth.TenantAwareAuth` context, the user is uploaded - * to the tenant corresponding to that `TenantAwareAuth` instance's tenant ID. - */ - tenantId?: string; - - /** - * The user's multi-factor related properties. - */ - multiFactor?: MultiFactorUpdateSettings; - } - - /** - * Interface representing the session cookie options needed for the - * {@link auth.Auth.createSessionCookie `createSessionCookie()`} method. - */ - export interface SessionCookieOptions { - - /** - * The session cookie custom expiration in milliseconds. The minimum allowed is - * 5 minutes and the maxium allowed is 2 weeks. - */ - expiresIn: number; - } - - /** - * This is the interface that defines the required continue/state URL with - * optional Android and iOS bundle identifiers. - */ - export interface ActionCodeSettings { - - /** - * Defines 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.
    • - *
    - */ - url: string; - - /** - * Whether to open the link via a mobile app or a browser. - * The default is false. When set to true, the action code link is sent - * as a Universal Link or Android App Link and is opened by the app if - * installed. In the false case, the code is sent to the web widget first - * and then redirects to the app if installed. - */ - handleCodeInApp?: boolean; - - /** - * Defines the iOS bundle ID. This will try to open the link in an iOS app if it - * is installed. - */ - iOS?: { - - /** - * Defines the required iOS bundle ID of the app where the link should be - * handled if the application is already installed on the device. - */ - bundleId: string; - }; - - /** - * Defines 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. - */ - android?: { - - /** - * Defines the required Android package name of the app where the link should be - * handled if the Android app is installed. - */ - packageName: string; - - /** - * Whether to install the Android app if the device supports it and the app is - * not already installed. - */ - installApp?: boolean; - - /** - * The Android minimum version if available. If the installed app is an older - * version, the user is taken to the GOogle Play Store to upgrade the app. - */ - minimumVersion?: string; - }; - - /** - * Defines the dynamic link domain to use for the current link if it is to be - * opened using Firebase Dynamic Links, as multiple dynamic link domains can be - * configured per project. This field provides the ability to explicitly choose - * configured per project. This fields provides the ability explicitly choose - * one. If none is provided, the oldest domain is used by default. - */ - dynamicLinkDomain?: string; - } - - /** - * Interface representing a tenant configuration. - * - * Multi-tenancy support requires Google Cloud's Identity Platform - * (GCIP). To learn more about GCIP, including pricing and features, - * see the [GCIP documentation](https://cloud.google.com/identity-platform) - * - * Before multi-tenancy can be used on a Google Cloud Identity Platform project, - * tenants must be allowed on that project via the Cloud Console UI. - * - * A tenant configuration provides information such as the display name, tenant - * identifier and email authentication configuration. - * For OIDC/SAML provider configuration management, `TenantAwareAuth` instances should - * be used instead of a `Tenant` to retrieve the list of configured IdPs on a tenant. - * When configuring these providers, note that tenants will inherit - * whitelisted domains and authenticated redirect URIs of their parent project. - * - * All other settings of a tenant will also be inherited. These will need to be managed - * from the Cloud Console UI. - */ - export interface Tenant { - - /** - * The tenant identifier. - */ - tenantId: string; - - /** - * The tenant display name. - */ - displayName?: string; - - /** - * The email sign in provider configuration. - */ - emailSignInConfig?: { - - /** - * Whether email provider is enabled. - */ - enabled: boolean; - - /** - * Whether password is required for email sign-in. When not required, - * email sign-in can be performed with password or via email link sign-in. - */ - passwordRequired?: boolean; - }; - - /** - * Whether the anonymous provider is enabled. - */ - anonymousSignInEnabled: boolean; - - /** - * The multi-factor auth configuration on the current tenant. - */ - multiFactorConfig?: MultiFactorConfig; - - /** - * The map containing the test phone number / code pairs for the tenant. - */ - testPhoneNumbers?: { [phoneNumber: string]: string }; - - /** - * @return A JSON-serializable representation of this object. - */ - toJSON(): object; - } - - /** - * Identifies a second factor type. - */ - export type AuthFactorType = 'phone'; - - /** - * Identifies a multi-factor configuration state. - */ - export type MultiFactorConfigState = 'ENABLED' | 'DISABLED'; - - /** - * Interface representing a multi-factor configuration. - * This can be used to define whether multi-factor authentication is enabled - * or disabled and the list of second factor challenges that are supported. - */ - export interface MultiFactorConfig { - /** - * The multi-factor config state. - */ - state: MultiFactorConfigState; - - /** - * The list of identifiers for enabled second factors. - * Currently only ‘phone’ is supported. - */ - factorIds?: AuthFactorType[]; - } - - /** - * The email sign in configuration. - */ - export interface EmailSignInProviderConfig { - /** - * Whether email provider is enabled. - */ - enabled: boolean; - - /** - * Whether password is required for email sign-in. When not required, - * email sign-in can be performed with password or via email link sign-in. - */ - passwordRequired?: boolean; // In the backend API, default is true if not provided - } - - /** - * Interface representing the properties to update on the provided tenant. - */ - export interface UpdateTenantRequest { - - /** - * The tenant display name. - */ - displayName?: string; - - /** - * The email sign in configuration. - */ - emailSignInConfig?: EmailSignInProviderConfig; - - /** - * Whether the anonymous provider is enabled. - */ - anonymousSignInEnabled?: boolean; - - /** - * The multi-factor auth configuration to update on the tenant. - */ - multiFactorConfig?: MultiFactorConfig; - - /** - * The updated map containing the test phone number / code pairs for the tenant. - * Passing null clears the previously save phone number / code pairs. - */ - testPhoneNumbers?: { [phoneNumber: string]: string } | null; - } - - /** - * Interface representing the properties to set on a new tenant. - */ - export type CreateTenantRequest = UpdateTenantRequest; - - /** - * Interface representing the object returned from a - * {@link auth.TenantManager.listTenants `listTenants()`} - * operation. - * Contains the list of tenants for the current batch and the next page token if available. - */ - export interface ListTenantsResult { - - /** - * The list of {@link auth.Tenant `Tenant`} objects for the downloaded batch. - */ - tenants: Tenant[]; - - /** - * The next page token if available. This is needed for the next batch download. - */ - pageToken?: string; - } - - /** - * The filter interface used for listing provider configurations. This is used - * when specifying how to list configured identity providers via - * {@link auth.Auth.listProviderConfigs `listProviderConfigs()`}. - */ - export interface AuthProviderConfigFilter { - - /** - * The Auth provider configuration filter. This can be either `saml` or `oidc`. - * The former is used to look up SAML providers only, while the latter is used - * for OIDC providers. - */ - type: 'saml' | 'oidc'; - - /** - * The maximum number of results to return per page. The default and maximum is - * 100. - */ - maxResults?: number; - - /** - * The next page token. When not specified, the lookup starts from the beginning - * of the list. - */ - pageToken?: string; - } - - /** - * The base Auth provider configuration interface. - */ - export interface BaseAuthProviderConfig { - - /** - * The provider ID defined by the developer. - * For a SAML provider, this is always prefixed by `saml.`. - * For an OIDC provider, this is always prefixed by `oidc.`. - */ - providerId: string; - - /** - * The user-friendly display name to the current configuration. This name is - * also used as the provider label in the Cloud Console. - */ - displayName?: string; - - /** - * Whether the provider configuration is enabled or disabled. A user - * cannot sign in using a disabled provider. - */ - enabled: boolean; - } - - /** - * The - * [SAML](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html) - * Auth provider configuration interface. A SAML provider can be created via - * {@link auth.Auth.createProviderConfig `createProviderConfig()`}. - */ - export interface SAMLAuthProviderConfig extends BaseAuthProviderConfig { - - /** - * The SAML IdP entity identifier. - */ - idpEntityId: string; - - /** - * The SAML IdP SSO URL. This must be a valid URL. - */ - ssoURL: string; - - /** - * The list of SAML IdP X.509 certificates issued by CA for this provider. - * Multiple certificates are accepted to prevent outages during - * IdP key rotation (for example ADFS rotates every 10 days). When the Auth - * server receives a SAML response, it will match the SAML response with the - * certificate on record. Otherwise the response is rejected. - * Developers are expected to manage the certificate updates as keys are - * rotated. - */ - x509Certificates: string[]; - - /** - * The SAML relying party (service provider) entity ID. - * This is defined by the developer but needs to be provided to the SAML IdP. - */ - rpEntityId: string; - - /** - * This is fixed and must always be the same as the OAuth redirect URL - * provisioned by Firebase Auth, - * `https://project-id.firebaseapp.com/__/auth/handler` unless a custom - * `authDomain` is used. - * The callback URL should also be provided to the SAML IdP during - * configuration. - */ - callbackURL?: string; - } - - /** - * The interface representing OIDC provider's response object for OAuth - * authorization flow. - * One of the following settings is required: - *
      - *
    • Set code to true for the code flow.
    • - *
    • Set idToken to true for the ID token flow.
    • - *
    - */ - export interface OAuthResponseType { - /** - * Whether ID token is returned from IdP's authorization endpoint. - */ - idToken?: boolean; - - /** - * Whether authorization code is returned from IdP's authorization endpoint. - */ - code?: boolean; +export function getAuth(app?: App): Auth { + if (typeof app === 'undefined') { + app = getApp(); } - /** - * The [OIDC](https://openid.net/specs/openid-connect-core-1_0-final.html) Auth - * provider configuration interface. An OIDC provider can be created via - * {@link auth.Auth.createProviderConfig `createProviderConfig()`}. - */ - export interface OIDCAuthProviderConfig extends BaseAuthProviderConfig { - - /** - * This is the required client ID used to confirm the audience of an OIDC - * provider's - * [ID token](https://openid.net/specs/openid-connect-core-1_0-final.html#IDToken). - */ - clientId: string; - - /** - * This is the required provider issuer used to match the provider issuer of - * the ID token and to determine the corresponding OIDC discovery document, eg. - * [`/.well-known/openid-configuration`](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig). - * This is needed for the following: - *
      - *
    • To verify the provided issuer.
    • - *
    • Determine the authentication/authorization endpoint during the OAuth - * `id_token` authentication flow.
    • - *
    • To retrieve the public signing keys via `jwks_uri` to verify the OIDC - * provider's ID token's signature.
    • - *
    • To determine the claims_supported to construct the user attributes to be - * returned in the additional user info response.
    • - *
    - * ID token validation will be performed as defined in the - * [spec](https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation). - */ - issuer: string; - - /** - * The OIDC provider's client secret to enable OIDC code flow. - */ - clientSecret?: string; - - /** - * The OIDC provider's response object for OAuth authorization flow. - */ - responseType?: OAuthResponseType; - } - - /** - * The Auth provider configuration type. - * {@link auth.Auth.createProviderConfig `createProviderConfig()`}. - */ - export type AuthProviderConfig = SAMLAuthProviderConfig | OIDCAuthProviderConfig; - - /** - * The request interface for updating a SAML Auth provider. This is used - * when updating a SAML provider's configuration via - * {@link auth.Auth.updateProviderConfig `updateProviderConfig()`}. - */ - export interface SAMLUpdateAuthProviderRequest { - - /** - * The SAML provider's updated display name. If not provided, the existing - * configuration's value is not modified. - */ - displayName?: string; - - /** - * Whether the SAML provider is enabled or not. If not provided, the existing - * configuration's setting is not modified. - */ - enabled?: boolean; - - /** - * The SAML provider's updated IdP entity ID. If not provided, the existing - * configuration's value is not modified. - */ - idpEntityId?: string; - - /** - * The SAML provider's updated SSO URL. If not provided, the existing - * configuration's value is not modified. - */ - ssoURL?: string; - - /** - * The SAML provider's updated list of X.509 certificated. If not provided, the - * existing configuration list is not modified. - */ - x509Certificates?: string[]; - - /** - * The SAML provider's updated RP entity ID. If not provided, the existing - * configuration's value is not modified. - */ - rpEntityId?: string; - - /** - * The SAML provider's callback URL. If not provided, the existing - * configuration's value is not modified. - */ - callbackURL?: string; - } - - /** - * The request interface for updating an OIDC Auth provider. This is used - * when updating an OIDC provider's configuration via - * {@link auth.Auth.updateProviderConfig `updateProviderConfig()`}. - */ - export interface OIDCUpdateAuthProviderRequest { - - /** - * The OIDC provider's updated display name. If not provided, the existing - * configuration's value is not modified. - */ - displayName?: string; - - /** - * Whether the OIDC provider is enabled or not. If not provided, the existing - * configuration's setting is not modified. - */ - enabled?: boolean; - - /** - * The OIDC provider's updated client ID. If not provided, the existing - * configuration's value is not modified. - */ - clientId?: string; - - /** - * The OIDC provider's updated issuer. If not provided, the existing - * configuration's value is not modified. - */ - issuer?: string; - - /** - * The OIDC provider's client secret to enable OIDC code flow. - * If not provided, the existing configuration's value is not modified. - */ - clientSecret?: string; - - /** - * The OIDC provider's response object for OAuth authorization flow. - */ - responseType?: OAuthResponseType; - } - - /** - * The response interface for listing provider configs. This is only available - * when listing all identity providers' configurations via - * {@link auth.Auth.listProviderConfigs `listProviderConfigs()`}. - */ - export interface ListProviderConfigResults { - - /** - * The list of providers for the specified type in the current page. - */ - providerConfigs: AuthProviderConfig[]; - - /** - * The next page token, if available. - */ - pageToken?: string; - } - - export type UpdateAuthProviderRequest = - SAMLUpdateAuthProviderRequest | OIDCUpdateAuthProviderRequest; - - /** - * Used for looking up an account by uid. - * - * See auth.getUsers() - */ - export interface UidIdentifier { - uid: string; - } - - /** - * Used for looking up an account by email. - * - * See auth.getUsers() - */ - export interface EmailIdentifier { - email: string; - } - - /** - * Used for looking up an account by phone number. - * - * See auth.getUsers() - */ - export interface PhoneIdentifier { - phoneNumber: string; - } - - /** - * Used for looking up an account by federated provider. - * - * See auth.getUsers() - */ - export interface ProviderIdentifier { - providerId: string; - providerUid: string; - } - - /** - * Identifies a user to be looked up. - */ - export type UserIdentifier = - UidIdentifier | EmailIdentifier | PhoneIdentifier | ProviderIdentifier; - - export interface BaseAuth { - - /** - * Creates a new Firebase custom token (JWT) that can be sent back to a client - * device to use to sign in with the client SDKs' `signInWithCustomToken()` - * methods. (Tenant-aware instances will also embed the tenant ID in the - * token.) - * - * See [Create Custom Tokens](/docs/auth/admin/create-custom-tokens) for code - * samples and detailed documentation. - * - * @param uid The `uid` to use as the custom token's subject. - * @param developerClaims Optional additional claims to include - * in the custom token's payload. - * - * @return A promise fulfilled with a custom token for the - * provided `uid` and payload. - */ - createCustomToken(uid: string, developerClaims?: object): Promise; - - /** - * Creates a new user. - * - * See [Create a user](/docs/auth/admin/manage-users#create_a_user) for code - * samples and detailed documentation. - * - * @param properties The properties to set on the - * new user record to be created. - * - * @return A promise fulfilled with the user - * data corresponding to the newly created user. - */ - createUser(properties: CreateRequest): Promise; - - /** - * Deletes an existing user. - * - * See [Delete a user](/docs/auth/admin/manage-users#delete_a_user) for code - * samples and detailed documentation. - * - * @param uid The `uid` corresponding to the user to delete. - * - * @return An empty promise fulfilled once the user has been - * deleted. - */ - deleteUser(uid: string): Promise; - - /** - * Deletes the users specified by the given uids. - * - * Deleting a non-existing user won't generate an error (i.e. this method - * is idempotent.) Non-existing users are considered to be successfully - * deleted, and are therefore counted in the - * `DeleteUsersResult.successCount` value. - * - * Only a maximum of 1000 identifiers may be supplied. If more than 1000 - * identifiers are supplied, this method throws a FirebaseAuthError. - * - * This API is currently rate limited at the server to 1 QPS. If you exceed - * this, you may get a quota exceeded error. Therefore, if you want to - * delete more than 1000 users, you may need to add a delay to ensure you - * don't go over this limit. - * - * @param uids The `uids` corresponding to the users to delete. - * - * @return A Promise that resolves to the total number of successful/failed - * deletions, as well as the array of errors that corresponds to the - * failed deletions. - */ - deleteUsers(uids: string[]): Promise; - - /** - * Gets the user data for the user corresponding to a given `uid`. - * - * See [Retrieve user data](/docs/auth/admin/manage-users#retrieve_user_data) - * for code samples and detailed documentation. - * - * @param uid The `uid` corresponding to the user whose data to fetch. - * - * @return A promise fulfilled with the user - * data corresponding to the provided `uid`. - */ - getUser(uid: string): Promise; - - /** - * Gets the user data for the user corresponding to a given email. - * - * See [Retrieve user data](/docs/auth/admin/manage-users#retrieve_user_data) - * for code samples and detailed documentation. - * - * @param email The email corresponding to the user whose data to - * fetch. - * - * @return A promise fulfilled with the user - * data corresponding to the provided email. - */ - getUserByEmail(email: string): Promise; - - /** - * Gets the user data for the user corresponding to a given phone number. The - * phone number has to conform to the E.164 specification. - * - * See [Retrieve user data](/docs/auth/admin/manage-users#retrieve_user_data) - * for code samples and detailed documentation. - * - * @param phoneNumber The phone number corresponding to the user whose - * data to fetch. - * - * @return A promise fulfilled with the user - * data corresponding to the provided phone number. - */ - getUserByPhoneNumber(phoneNumber: string): Promise; - - /** - * Gets the user data for the user corresponding to a given provider ID. - * - * See [Retrieve user data](/docs/auth/admin/manage-users#retrieve_user_data) - * for code samples and detailed documentation. - * - * @param providerId The provider ID, for example, "google.com" for the - * Google provider. - * @param uid The user identifier for the given provider. - * - * @return A promise fulfilled with the user data corresponding to the - * given provider id. - */ - getUserByProviderUid(providerId: string, uid: string): Promise; - - /** - * Gets the user data corresponding to the specified identifiers. - * - * There are no ordering guarantees; in particular, the nth entry in the result list is not - * guaranteed to correspond to the nth entry in the input parameters list. - * - * Only a maximum of 100 identifiers may be supplied. If more than 100 identifiers are supplied, - * this method throws a FirebaseAuthError. - * - * @param identifiers The identifiers used to indicate which user records should be returned. - * Must have <= 100 entries. - * @return {Promise} A promise that resolves to the corresponding user records. - * @throws FirebaseAuthError If any of the identifiers are invalid or if more than 100 - * identifiers are specified. - */ - getUsers(identifiers: UserIdentifier[]): Promise; - - /** - * Retrieves a list of users (single batch only) with a size of `maxResults` - * starting from the offset as specified by `pageToken`. This is used to - * retrieve all the users of a specified project in batches. - * - * See [List all users](/docs/auth/admin/manage-users#list_all_users) - * for code samples and detailed documentation. - * - * @param maxResults The page size, 1000 if undefined. This is also - * the maximum allowed limit. - * @param pageToken The next page token. If not specified, returns - * users starting without any offset. - * @return A promise that resolves with - * the current batch of downloaded users and the next page token. - */ - listUsers(maxResults?: number, pageToken?: string): Promise; - - /** - * Updates an existing user. - * - * See [Update a user](/docs/auth/admin/manage-users#update_a_user) for code - * samples and detailed documentation. - * - * @param uid The `uid` corresponding to the user to update. - * @param properties The properties to update on - * the provided user. - * - * @return A promise fulfilled with the - * updated user data. - */ - updateUser(uid: string, properties: UpdateRequest): Promise; - - /** - * Verifies a JWT auth token. Returns a promise with the token‘s claims. - * Rejects the promise if the token cannot be verified. - * If `checkRevoked` is set to true, first verifies whether the corresponding - * user is disabled. - * If yes, an `auth/user-disabled` error is thrown. - * If no, verifies if the session corresponding to the ID token was revoked. - * If the corresponding user's session was invalidated, an - * `auth/id-token-revoked` error is thrown. - * If not specified the check is not applied. - * - * See [Verify ID Tokens](/docs/auth/admin/verify-id-tokens) for code samples - * and detailed documentation. - * - * @param idToken The ID token to verify. - * @param checkRevoked Whether to check if the ID token was revoked. - * This requires an extra request to the Firebase Auth backend to check - * the `tokensValidAfterTime` time for the corresponding user. - * When not specified, this additional check is not applied. - * - * @return A promise fulfilled with the - * token's decoded claims if the ID token is valid; otherwise, a rejected - * promise. - */ - verifyIdToken(idToken: string, checkRevoked?: boolean): Promise; - - /** - * Sets additional developer claims on an existing user identified by the - * provided `uid`, typically used to define user roles and levels of - * access. These claims should propagate to all devices where the user is - * already signed in (after token expiration or when token refresh is forced) - * and the next time the user signs in. If a reserved OIDC claim name - * is used (sub, iat, iss, etc), an error is thrown. They are set on the - * authenticated user's ID token JWT. - * - * See - * [Defining user roles and access levels](/docs/auth/admin/custom-claims) - * for code samples and detailed documentation. - * - * @param uid The `uid` of the user to edit. - * @param customUserClaims The developer claims to set. If null is - * passed, existing custom claims are deleted. Passing a custom claims payload - * larger than 1000 bytes will throw an error. Custom claims are added to the - * user's ID token which is transmitted on every authenticated request. - * For profile non-access related user attributes, use database or other - * separate storage systems. - * @return A promise that resolves when the operation completes - * successfully. - */ - setCustomUserClaims(uid: string, customUserClaims: object | null): Promise; - - /** - * Revokes all refresh tokens for an existing user. - * - * This API will update the user's - * {@link auth.UserRecord.tokensValidAfterTime `tokensValidAfterTime`} to - * the current UTC. It is important that the server on which this is called has - * its clock set correctly and synchronized. - * - * While this will revoke all sessions for a specified user and disable any - * new ID tokens for existing sessions from getting minted, existing ID tokens - * may remain active until their natural expiration (one hour). To verify that - * ID tokens are revoked, use - * {@link auth.Auth.verifyIdToken `verifyIdToken(idToken, true)`} - * where `checkRevoked` is set to true. - * - * @param uid The `uid` corresponding to the user whose refresh tokens - * are to be revoked. - * - * @return An empty promise fulfilled once the user's refresh - * tokens have been revoked. - */ - revokeRefreshTokens(uid: string): Promise; - - /** - * Imports the provided list of users into Firebase Auth. - * A maximum of 1000 users are allowed to be imported one at a time. - * When importing users with passwords, - * {@link auth.UserImportOptions `UserImportOptions`} are required to be - * specified. - * This operation is optimized for bulk imports and will ignore checks on `uid`, - * `email` and other identifier uniqueness which could result in duplications. - * - * @param users The list of user records to import to Firebase Auth. - * @param options The user import options, required when the users provided include - * password credentials. - * @return A promise that resolves when - * the operation completes with the result of the import. This includes the - * number of successful imports, the number of failed imports and their - * corresponding errors. - */ - importUsers( - users: UserImportRecord[], - options?: UserImportOptions, - ): Promise; - - /** - * Creates a new Firebase session cookie with the specified options. The created - * JWT string can be set as a server-side session cookie with a custom cookie - * policy, and be used for session management. The session cookie JWT will have - * the same payload claims as the provided ID token. - * - * See [Manage Session Cookies](/docs/auth/admin/manage-cookies) for code - * samples and detailed documentation. - * - * @param idToken The Firebase ID token to exchange for a session - * cookie. - * @param sessionCookieOptions The session - * cookie options which includes custom session duration. - * - * @return A promise that resolves on success with the - * created session cookie. - */ - createSessionCookie( - idToken: string, - sessionCookieOptions: SessionCookieOptions, - ): Promise; - - /** - * Verifies a Firebase session cookie. Returns a promise with the token’s claims. - * Rejects the promise if the cookie could not be verified. - * If `checkRevoked` is set to true, first verifies whether the corresponding - * user is disabled: - * If yes, an `auth/user-disabled` error is thrown. - * If no, verifies if the session corresponding to the session cookie was - * revoked. - * If the corresponding user's session was invalidated, an - * `auth/session-cookie-revoked` error is thrown. - * If not specified the check is not performed. - * - * See [Verify Session Cookies](/docs/auth/admin/manage-cookies#verify_session_cookie_and_check_permissions) - * for code samples and detailed documentation - * - * @param sessionCookie The session cookie to verify. - * @param checkRevoked Whether to check if the session cookie was - * revoked. This requires an extra request to the Firebase Auth backend to - * check the `tokensValidAfterTime` time for the corresponding user. - * When not specified, this additional check is not performed. - * - * @return A promise fulfilled with the - * session cookie's decoded claims if the session cookie is valid; otherwise, - * a rejected promise. - */ - verifySessionCookie( - sessionCookie: string, - checkRevoked?: boolean, - ): Promise; - - /** - * Generates the out of band email action link to reset a user's password. - * The link is generated for the user with the specified email address. The - * optional {@link auth.ActionCodeSettings `ActionCodeSettings`} object - * defines whether the link is to be handled by a mobile app or browser and the - * additional state information to be passed in the deep link, etc. - * - * @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, - * dynamicLinkDomain: 'custom.page.link' - * }; - * admin.auth() - * .generatePasswordResetLink('user@example.com', actionCodeSettings) - * .then(function(link) { - * // The link was successfully generated. - * }) - * .catch(function(error) { - * // Some error occurred, you can inspect the code: error.code - * }); - * ``` - * - * @param email The email address of the user whose password is to be - * reset. - * @param actionCodeSettings The action - * code settings. If specified, the state/continue URL is 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 is thrown. - * Mobile app redirects are only applicable if the developer configures - * and accepts the Firebase Dynamic Links terms of service. - * The Android package name and iOS bundle ID are respected only if they - * are configured in the same Firebase Auth project. - * @return A promise that resolves with the generated link. - */ - generatePasswordResetLink( - email: string, - actionCodeSettings?: ActionCodeSettings, - ): Promise; - - /** - * Generates the out of band email action link to verify the user's ownership - * of the specified email. The - * {@link auth.ActionCodeSettings `ActionCodeSettings`} object provided - * as an argument to this method defines whether the link is to be handled by a - * mobile app or browser along with additional state information to be passed in - * the deep link, etc. - * - * @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, - * dynamicLinkDomain: 'custom.page.link' - * }; - * admin.auth() - * .generateEmailVerificationLink('user@example.com', actionCodeSettings) - * .then(function(link) { - * // The link was successfully generated. - * }) - * .catch(function(error) { - * // Some error occurred, you can inspect the code: error.code - * }); - * ``` - * - * @param email The email account to verify. - * @param actionCodeSettings The action - * code settings. If specified, the state/continue URL is 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 is thrown. - * Mobile app redirects are only applicable if the developer configures - * and accepts the Firebase Dynamic Links terms of service. - * The Android package name and iOS bundle ID are respected only if they - * are configured in the same Firebase Auth project. - * @return A promise that resolves with the generated link. - */ - generateEmailVerificationLink( - email: string, - actionCodeSettings?: ActionCodeSettings, - ): Promise; - - /** - * Generates the out of band email action link to sign in or sign up the owner - * of the specified email. The - * {@link auth.ActionCodeSettings `ActionCodeSettings`} object provided - * as an argument to this method defines whether the link is to be handled by a - * mobile app or browser along with additional state information to be passed in - * the deep link, etc. - * - * @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, - * dynamicLinkDomain: 'custom.page.link' - * }; - * admin.auth() - * .generateSignInWithEmailLink('user@example.com', actionCodeSettings) - * .then(function(link) { - * // The link was successfully generated. - * }) - * .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. These settings provide Firebase with instructions on how - * to construct the email link. This includes the sign in completion URL or - * the deep link for redirects and the mobile apps to use when the - * sign-in link is opened on an Android or iOS device. - * Mobile app redirects are only applicable if the developer configures - * and accepts the Firebase Dynamic Links terms of service. - * The Android package name and iOS bundle ID are respected only if they - * are configured in the same Firebase Auth project. - * @return A promise that resolves with the generated link. - */ - generateSignInWithEmailLink( - email: string, - actionCodeSettings: ActionCodeSettings, - ): Promise; - - /** - * Returns the list of existing provider configurations matching the filter - * provided. At most, 100 provider configs can be listed at a time. - * - * SAML and OIDC provider support requires Google Cloud's Identity Platform - * (GCIP). To learn more about GCIP, including pricing and features, - * see the [GCIP documentation](https://cloud.google.com/identity-platform). - * - * @param options The provider config filter to apply. - * @return A promise that resolves with the list of provider configs meeting the - * filter requirements. - */ - listProviderConfigs( - options: AuthProviderConfigFilter - ): Promise; - - /** - * Looks up an Auth provider configuration by the provided ID. - * Returns a promise that resolves with the provider configuration - * corresponding to the provider ID specified. If the specified ID does not - * exist, an `auth/configuration-not-found` error is thrown. - * - * SAML and OIDC provider support requires Google Cloud's Identity Platform - * (GCIP). To learn more about GCIP, including pricing and features, - * see the [GCIP documentation](https://cloud.google.com/identity-platform). - * - * @param providerId The provider ID corresponding to the provider - * config to return. - * @return A promise that resolves - * with the configuration corresponding to the provided ID. - */ - getProviderConfig(providerId: string): Promise; - - /** - * Deletes the provider configuration corresponding to the provider ID passed. - * If the specified ID does not exist, an `auth/configuration-not-found` error - * is thrown. - * - * SAML and OIDC provider support requires Google Cloud's Identity Platform - * (GCIP). To learn more about GCIP, including pricing and features, - * see the [GCIP documentation](https://cloud.google.com/identity-platform). - * - * @param providerId The provider ID corresponding to the provider - * config to delete. - * @return A promise that resolves on completion. - */ - deleteProviderConfig(providerId: string): Promise; - - /** - * Returns a promise that resolves with the updated `AuthProviderConfig` - * corresponding to the provider ID specified. - * If the specified ID does not exist, an `auth/configuration-not-found` error - * is thrown. - * - * SAML and OIDC provider support requires Google Cloud's Identity Platform - * (GCIP). To learn more about GCIP, including pricing and features, - * see the [GCIP documentation](https://cloud.google.com/identity-platform). - * - * @param providerId The provider ID corresponding to the provider - * config to update. - * @param updatedConfig The updated configuration. - * @return A promise that resolves with the updated provider configuration. - */ - updateProviderConfig( - providerId: string, updatedConfig: UpdateAuthProviderRequest - ): Promise; - - /** - * Returns a promise that resolves with the newly created `AuthProviderConfig` - * when the new provider configuration is created. - * - * SAML and OIDC provider support requires Google Cloud's Identity Platform - * (GCIP). To learn more about GCIP, including pricing and features, - * see the [GCIP documentation](https://cloud.google.com/identity-platform). - * - * @param config The provider configuration to create. - * @return A promise that resolves with the created provider configuration. - */ - createProviderConfig( - config: AuthProviderConfig - ): Promise; - } - - /** - * Tenant-aware `Auth` interface used for managing users, configuring SAML/OIDC providers, - * generating email links for password reset, email verification, etc for specific tenants. - * - * Multi-tenancy support requires Google Cloud's Identity Platform - * (GCIP). To learn more about GCIP, including pricing and features, - * see the [GCIP documentation](https://cloud.google.com/identity-platform) - * - * Each tenant contains its own identity providers, settings and sets of users. - * Using `TenantAwareAuth`, users for a specific tenant and corresponding OIDC/SAML - * configurations can also be managed, ID tokens for users signed in to a specific tenant - * can be verified, and email action links can also be generated for users belonging to the - * tenant. - * - * `TenantAwareAuth` instances for a specific `tenantId` can be instantiated by calling - * `auth.tenantManager().authForTenant(tenantId)`. - */ - export interface TenantAwareAuth extends BaseAuth { - - /** - * The tenant identifier corresponding to this `TenantAwareAuth` instance. - * All calls to the user management APIs, OIDC/SAML provider management APIs, email link - * generation APIs, etc will only be applied within the scope of this tenant. - */ - tenantId: string; - } - - export interface Auth extends BaseAuth { - app: app.App; - - /** - * @return The tenant manager instance associated with the current project. - */ - tenantManager(): TenantManager; - } - - /** - * Defines the tenant manager used to help manage tenant related operations. - * This includes: - *
      - *
    • The ability to create, update, list, get and delete tenants for the underlying - * project.
    • - *
    • Getting a `TenantAwareAuth` instance for running Auth related operations - * (user management, provider configuration management, token verification, - * email link generation, etc) in the context of a specified tenant.
    • - *
    - */ - export interface TenantManager { - /** - * @param tenantId The tenant ID whose `TenantAwareAuth` instance is to be returned. - * - * @return The `TenantAwareAuth` instance corresponding to this tenant identifier. - */ - authForTenant(tenantId: string): TenantAwareAuth; - - /** - * Gets the tenant configuration for the tenant corresponding to a given `tenantId`. - * - * @param tenantId The tenant identifier corresponding to the tenant whose data to fetch. - * - * @return A promise fulfilled with the tenant configuration to the provided `tenantId`. - */ - getTenant(tenantId: string): Promise; - - /** - * Retrieves a list of tenants (single batch only) with a size of `maxResults` - * starting from the offset as specified by `pageToken`. This is used to - * retrieve all the tenants of a specified project in batches. - * - * @param maxResults The page size, 1000 if undefined. This is also - * the maximum allowed limit. - * @param pageToken The next page token. If not specified, returns - * tenants starting without any offset. - * - * @return A promise that resolves with - * a batch of downloaded tenants and the next page token. - */ - listTenants(maxResults?: number, pageToken?: string): Promise; - - /** - * Deletes an existing tenant. - * - * @param tenantId The `tenantId` corresponding to the tenant to delete. - * - * @return An empty promise fulfilled once the tenant has been deleted. - */ - deleteTenant(tenantId: string): Promise; - - /** - * Creates a new tenant. - * When creating new tenants, tenants that use separate billing and quota will require their - * own project and must be defined as `full_service`. - * - * @param tenantOptions The properties to set on the new tenant configuration to be created. - * - * @return A promise fulfilled with the tenant configuration corresponding to the newly - * created tenant. - */ - createTenant(tenantOptions: CreateTenantRequest): Promise; - - /** - * Updates an existing tenant configuration. - * - * @param tenantId The `tenantId` corresponding to the tenant to delete. - * @param tenantOptions The properties to update on the provided tenant. - * - * @return A promise fulfilled with the update tenant data. - */ - updateTenant(tenantId: string, tenantOptions: UpdateTenantRequest): Promise; - } + const firebaseApp: FirebaseApp = app as FirebaseApp; + return firebaseApp.getOrInitService('auth', (app) => new Auth(app)); } + +export { ActionCodeSettings } from './action-code-settings-builder'; + +export { + Auth, +} from './auth'; + +export { + AuthFactorType, + AuthProviderConfig, + AuthProviderConfigFilter, + BaseAuthProviderConfig, + BaseCreateMultiFactorInfoRequest, + BaseUpdateMultiFactorInfoRequest, + CreateMultiFactorInfoRequest, + CreatePhoneMultiFactorInfoRequest, + CreateRequest, + EmailSignInProviderConfig, + ListProviderConfigResults, + MultiFactorConfig, + MultiFactorConfigState, + MultiFactorCreateSettings, + MultiFactorUpdateSettings, + OAuthResponseType, + OIDCAuthProviderConfig, + OIDCUpdateAuthProviderRequest, + SAMLAuthProviderConfig, + SAMLUpdateAuthProviderRequest, + UserProvider, + UpdateAuthProviderRequest, + UpdateMultiFactorInfoRequest, + UpdatePhoneMultiFactorInfoRequest, + UpdateRequest, +} from './auth-config'; + +export { + BaseAuth, + DeleteUsersResult, + GetUsersResult, + ListUsersResult, + SessionCookieOptions, +} from './base-auth'; + +export { + EmailIdentifier, + PhoneIdentifier, + ProviderIdentifier, + UidIdentifier, + UserIdentifier, +} from './identifier'; + +export { + CreateTenantRequest, + Tenant, + UpdateTenantRequest, +} from './tenant'; + +export { + ListTenantsResult, + TenantAwareAuth, + TenantManager, +} from './tenant-manager'; + +export { DecodedIdToken } from './token-verifier'; + +export { + HashAlgorithmType, + UserImportOptions, + UserImportRecord, + UserImportResult, + UserMetadataRequest, + UserProviderRequest, +} from './user-import-builder'; + +export { + MultiFactorInfo, + MultiFactorSettings, + PhoneMultiFactorInfo, + UserInfo, + UserMetadata, + UserRecord, +} from './user-record'; diff --git a/src/auth/tenant-manager.ts b/src/auth/tenant-manager.ts index f97f0aaefc..571f237d9b 100644 --- a/src/auth/tenant-manager.ts +++ b/src/auth/tenant-manager.ts @@ -14,44 +14,160 @@ * limitations under the License. */ -import { AuthRequestHandler } from './auth-api-request'; -import { FirebaseApp } from '../firebase-app'; -import { TenantAwareAuth } from './auth'; -import { Tenant, TenantServerResponse } from './tenant'; -import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; import * as validator from '../utils/validator'; -import { auth } from './index'; +import { App } from '../app'; +import * as utils from '../utils/index'; +import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; + +import { BaseAuth, createFirebaseTokenGenerator, SessionCookieOptions } from './base-auth'; +import { Tenant, TenantServerResponse, CreateTenantRequest, UpdateTenantRequest } from './tenant'; +import { + AuthRequestHandler, TenantAwareAuthRequestHandler, +} from './auth-api-request'; +import { DecodedIdToken } from './token-verifier'; + +/** + * Interface representing the object returned from a + * {@link TenantManager.listTenants} + * operation. + * Contains the list of tenants for the current batch and the next page token if available. + */ +export interface ListTenantsResult { + + /** + * The list of {@link Tenant} objects for the downloaded batch. + */ + tenants: Tenant[]; + + /** + * The next page token if available. This is needed for the next batch download. + */ + pageToken?: string; +} + +/** + * Tenant-aware `Auth` interface used for managing users, configuring SAML/OIDC providers, + * generating email links for password reset, email verification, etc for specific tenants. + * + * Multi-tenancy support requires Google Cloud's Identity Platform + * (GCIP). To learn more about GCIP, including pricing and features, + * see the {@link https://cloud.google.com/identity-platform | GCIP documentation}. + * + * Each tenant contains its own identity providers, settings and sets of users. + * Using `TenantAwareAuth`, users for a specific tenant and corresponding OIDC/SAML + * configurations can also be managed, ID tokens for users signed in to a specific tenant + * can be verified, and email action links can also be generated for users belonging to the + * tenant. + * + * `TenantAwareAuth` instances for a specific `tenantId` can be instantiated by calling + * {@link TenantManager.authForTenant}. + */ +export class TenantAwareAuth extends BaseAuth { + + /** + * The tenant identifier corresponding to this `TenantAwareAuth` instance. + * All calls to the user management APIs, OIDC/SAML provider management APIs, email link + * generation APIs, etc will only be applied within the scope of this tenant. + */ + public readonly tenantId: string; + + /** + * The TenantAwareAuth class constructor. + * + * @param app The app that created this tenant. + * @param tenantId The corresponding tenant ID. + * @constructor + * @internal + */ + constructor(app: App, tenantId: string) { + super(app, new TenantAwareAuthRequestHandler( + app, tenantId), createFirebaseTokenGenerator(app, tenantId)); + utils.addReadonlyGetter(this, 'tenantId', tenantId); + } -import ListTenantsResult = auth.ListTenantsResult; -import TenantManagerInterface = auth.TenantManager; -import CreateTenantRequest = auth.CreateTenantRequest; -import UpdateTenantRequest = auth.UpdateTenantRequest; + /** + * {@inheritdoc BaseAuth.verifyIdToken} + */ + public verifyIdToken(idToken: string, checkRevoked = false): Promise { + return super.verifyIdToken(idToken, checkRevoked) + .then((decodedClaims) => { + // Validate tenant ID. + if (decodedClaims.firebase.tenant !== this.tenantId) { + throw new FirebaseAuthError(AuthClientErrorCode.MISMATCHING_TENANT_ID); + } + return decodedClaims; + }); + } + + /** + * {@inheritdoc BaseAuth.createSessionCookie} + */ + public createSessionCookie( + idToken: string, sessionCookieOptions: SessionCookieOptions): Promise { + // Validate arguments before processing. + if (!validator.isNonEmptyString(idToken)) { + return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_ID_TOKEN)); + } + if (!validator.isNonNullObject(sessionCookieOptions) || + !validator.isNumber(sessionCookieOptions.expiresIn)) { + return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_SESSION_COOKIE_DURATION)); + } + // This will verify the ID token and then match the tenant ID before creating the session cookie. + return this.verifyIdToken(idToken) + .then(() => { + return super.createSessionCookie(idToken, sessionCookieOptions); + }); + } + + /** + * {@inheritdoc BaseAuth.verifySessionCookie} + */ + public verifySessionCookie( + sessionCookie: string, checkRevoked = false): Promise { + return super.verifySessionCookie(sessionCookie, checkRevoked) + .then((decodedClaims) => { + if (decodedClaims.firebase.tenant !== this.tenantId) { + throw new FirebaseAuthError(AuthClientErrorCode.MISMATCHING_TENANT_ID); + } + return decodedClaims; + }); + } +} /** - * Data structure used to help manage tenant related operations. + * Defines the tenant manager used to help manage tenant related operations. * This includes: - * - The ability to create, update, list, get and delete tenants for the underlying project. - * - Getting a TenantAwareAuth instance for running Auth related operations (user mgmt, provider config mgmt, etc) - * in the context of a specified tenant. + *
      + *
    • The ability to create, update, list, get and delete tenants for the underlying + * project.
    • + *
    • Getting a `TenantAwareAuth` instance for running Auth related operations + * (user management, provider configuration management, token verification, + * email link generation, etc) in the context of a specified tenant.
    • + *
    */ -export class TenantManager implements TenantManagerInterface { +export class TenantManager { private readonly authRequestHandler: AuthRequestHandler; private readonly tenantsMap: {[key: string]: TenantAwareAuth}; /** * Initializes a TenantManager instance for a specified FirebaseApp. + * * @param app The app for this TenantManager instance. + * + * @constructor + * @internal */ - constructor(private readonly app: FirebaseApp) { + constructor(private readonly app: App) { this.authRequestHandler = new AuthRequestHandler(app); this.tenantsMap = {}; } /** - * Returns a TenantAwareAuth instance for the corresponding tenant ID. + * Returns a `TenantAwareAuth` instance bound to the given tenant ID. * - * @param tenantId The tenant ID whose TenantAwareAuth is to be returned. - * @return The corresponding TenantAwareAuth instance. + * @param tenantId The tenant ID whose `TenantAwareAuth` instance is to be returned. + * + * @returns The `TenantAwareAuth` instance corresponding to this tenant identifier. */ public authForTenant(tenantId: string): TenantAwareAuth { if (!validator.isNonEmptyString(tenantId)) { @@ -64,11 +180,11 @@ export class TenantManager implements TenantManagerInterface { } /** - * Looks up the tenant identified by the provided tenant ID and returns a promise that is - * fulfilled with the corresponding tenant if it is found. + * Gets the tenant configuration for the tenant corresponding to a given `tenantId`. + * + * @param tenantId The tenant identifier corresponding to the tenant whose data to fetch. * - * @param tenantId The tenant ID of the tenant to look up. - * @return A promise that resolves with the corresponding tenant. + * @returns A promise fulfilled with the tenant configuration to the provided `tenantId`. */ public getTenant(tenantId: string): Promise { return this.authRequestHandler.getTenant(tenantId) @@ -78,16 +194,17 @@ export class TenantManager implements TenantManagerInterface { } /** - * Exports a batch of tenant accounts. Batch size is determined by the maxResults argument. - * Starting point of the batch is determined by the pageToken argument. + * Retrieves a list of tenants (single batch only) with a size of `maxResults` + * starting from the offset as specified by `pageToken`. This is used to + * retrieve all the tenants of a specified project in batches. + * + * @param maxResults The page size, 1000 if undefined. This is also + * the maximum allowed limit. + * @param pageToken The next page token. If not specified, returns + * tenants starting without any offset. * - * @param maxResults The page size, 1000 if undefined. This is also the maximum - * allowed limit. - * @param pageToken The next page token. If not specified, returns users starting - * without any offset. - * @return A promise that resolves with - * the current batch of downloaded tenants and the next page token. For the last page, an - * empty list of tenants and no page token are returned. + * @returns A promise that resolves with + * a batch of downloaded tenants and the next page token. */ public listTenants( maxResults?: number, @@ -114,21 +231,25 @@ export class TenantManager implements TenantManagerInterface { } /** - * Deletes the tenant identified by the provided tenant ID and returns a promise that is - * fulfilled when the tenant is found and successfully deleted. + * Deletes an existing tenant. * - * @param tenantId The tenant ID of the tenant to delete. - * @return A promise that resolves when the tenant is successfully deleted. + * @param tenantId The `tenantId` corresponding to the tenant to delete. + * + * @returns An empty promise fulfilled once the tenant has been deleted. */ public deleteTenant(tenantId: string): Promise { return this.authRequestHandler.deleteTenant(tenantId); } /** - * Creates a new tenant with the properties provided. + * Creates a new tenant. + * When creating new tenants, tenants that use separate billing and quota will require their + * own project and must be defined as `full_service`. + * + * @param tenantOptions The properties to set on the new tenant configuration to be created. * - * @param tenantOptions The properties to set on the new tenant to be created. - * @return A promise that resolves with the newly created tenant. + * @returns A promise fulfilled with the tenant configuration corresponding to the newly + * created tenant. */ public createTenant(tenantOptions: CreateTenantRequest): Promise { return this.authRequestHandler.createTenant(tenantOptions) @@ -138,11 +259,12 @@ export class TenantManager implements TenantManagerInterface { } /** - * Updates an existing tenant identified by the tenant ID with the properties provided. + * Updates an existing tenant configuration. + * + * @param tenantId The `tenantId` corresponding to the tenant to delete. + * @param tenantOptions The properties to update on the provided tenant. * - * @param tenantId The tenant identifier of the tenant to update. - * @param tenantOptions The properties to update on the existing tenant. - * @return A promise that resolves with the modified tenant. + * @returns A promise fulfilled with the update tenant data. */ public updateTenant(tenantId: string, tenantOptions: UpdateTenantRequest): Promise { return this.authRequestHandler.updateTenant(tenantId, tenantOptions) diff --git a/src/auth/tenant.ts b/src/auth/tenant.ts index 392494739b..6c5577bda2 100644 --- a/src/auth/tenant.ts +++ b/src/auth/tenant.ts @@ -17,14 +17,50 @@ import * as validator from '../utils/validator'; import { deepCopy } from '../utils/deep-copy'; import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; + import { EmailSignInConfig, EmailSignInConfigServerRequest, MultiFactorAuthServerConfig, - MultiFactorAuthConfig, validateTestPhoneNumbers, + MultiFactorConfig, validateTestPhoneNumbers, EmailSignInProviderConfig, + MultiFactorAuthConfig, } from './auth-config'; -import { auth } from './index'; -import TenantInterface = auth.Tenant; -import UpdateTenantRequest = auth.UpdateTenantRequest; +/** + * Interface representing the properties to update on the provided tenant. + */ +export interface UpdateTenantRequest { + + /** + * The tenant display name. + */ + displayName?: string; + + /** + * The email sign in configuration. + */ + emailSignInConfig?: EmailSignInProviderConfig; + + /** + * Whether the anonymous provider is enabled. + */ + anonymousSignInEnabled?: boolean; + + /** + * The multi-factor auth configuration to update on the tenant. + */ + multiFactorConfig?: MultiFactorConfig; + + /** + * The updated map containing the test phone number / code pairs for the tenant. + * Passing null clears the previously save phone number / code pairs. + */ + testPhoneNumbers?: { [phoneNumber: string]: string } | null; +} + +/** + * Interface representing the properties to set on a new tenant. + */ +export type CreateTenantRequest = UpdateTenantRequest; + /** The corresponding server side representation of a TenantOptions object. */ export interface TenantOptionsServerRequest extends EmailSignInConfigServerRequest { @@ -46,22 +82,55 @@ export interface TenantServerResponse { } /** - * Tenant class that defines a Firebase Auth tenant. + * Represents a tenant configuration. + * + * Multi-tenancy support requires Google Cloud's Identity Platform + * (GCIP). To learn more about GCIP, including pricing and features, + * see the {@link https://cloud.google.com/identity-platform | GCIP documentation}. + * + * Before multi-tenancy can be used on a Google Cloud Identity Platform project, + * tenants must be allowed on that project via the Cloud Console UI. + * + * A tenant configuration provides information such as the display name, tenant + * identifier and email authentication configuration. + * For OIDC/SAML provider configuration management, `TenantAwareAuth` instances should + * be used instead of a `Tenant` to retrieve the list of configured IdPs on a tenant. + * When configuring these providers, note that tenants will inherit + * whitelisted domains and authenticated redirect URIs of their parent project. + * + * All other settings of a tenant will also be inherited. These will need to be managed + * from the Cloud Console UI. */ -export class Tenant implements TenantInterface { +export class Tenant { + + /** + * The tenant identifier. + */ public readonly tenantId: string; + + /** + * The tenant display name. + */ public readonly displayName?: string; - public readonly emailSignInConfig?: EmailSignInConfig; + public readonly anonymousSignInEnabled: boolean; - public readonly multiFactorConfig?: MultiFactorAuthConfig; + + /** + * The map containing the test phone number / code pairs for the tenant. + */ public readonly testPhoneNumbers?: {[phoneNumber: string]: string}; + private readonly emailSignInConfig_?: EmailSignInConfig; + private readonly multiFactorConfig_?: MultiFactorAuthConfig; + /** * Builds the corresponding server request for a TenantOptions object. * - * @param {TenantOptions} tenantOptions The properties to convert to a server request. - * @param {boolean} createRequest Whether this is a create request. - * @return {object} The equivalent server request. + * @param tenantOptions The properties to convert to a server request. + * @param createRequest Whether this is a create request. + * @returns The equivalent server request. + * + * @internal */ public static buildServerRequest( tenantOptions: UpdateTenantRequest, createRequest: boolean): TenantOptionsServerRequest { @@ -89,8 +158,10 @@ export class Tenant implements TenantInterface { /** * Returns the tenant ID corresponding to the resource name if available. * - * @param {string} resourceName The server side resource name - * @return {?string} The tenant ID corresponding to the resource, null otherwise. + * @param resourceName The server side resource name + * @returns The tenant ID corresponding to the resource, null otherwise. + * + * @internal */ public static getTenantIdFromResourceName(resourceName: string): string | null { // name is of form projects/project1/tenants/tenant1 @@ -167,6 +238,7 @@ export class Tenant implements TenantInterface { * * @param response The server side response used to initialize the Tenant object. * @constructor + * @internal */ constructor(response: TenantServerResponse) { const tenantId = Tenant.getTenantIdFromResourceName(response.name); @@ -179,30 +251,48 @@ export class Tenant implements TenantInterface { this.tenantId = tenantId; this.displayName = response.displayName; try { - this.emailSignInConfig = new EmailSignInConfig(response); + this.emailSignInConfig_ = new EmailSignInConfig(response); } catch (e) { // If allowPasswordSignup is undefined, it is disabled by default. - this.emailSignInConfig = new EmailSignInConfig({ + this.emailSignInConfig_ = new EmailSignInConfig({ allowPasswordSignup: false, }); } this.anonymousSignInEnabled = !!response.enableAnonymousUser; if (typeof response.mfaConfig !== 'undefined') { - this.multiFactorConfig = new MultiFactorAuthConfig(response.mfaConfig); + this.multiFactorConfig_ = new MultiFactorAuthConfig(response.mfaConfig); } if (typeof response.testPhoneNumbers !== 'undefined') { this.testPhoneNumbers = deepCopy(response.testPhoneNumbers || {}); } } - /** @return {object} The plain object representation of the tenant. */ + /** + * The email sign in provider configuration. + */ + get emailSignInConfig(): EmailSignInProviderConfig | undefined { + return this.emailSignInConfig_; + } + + /** + * The multi-factor auth configuration on the current tenant. + */ + get multiFactorConfig(): MultiFactorConfig | undefined { + return this.multiFactorConfig_; + } + + /** + * Returns a JSON-serializable representation of this object. + * + * @returns A JSON-serializable representation of this object. + */ public toJSON(): object { const json = { tenantId: this.tenantId, displayName: this.displayName, - emailSignInConfig: this.emailSignInConfig?.toJSON(), + emailSignInConfig: this.emailSignInConfig_?.toJSON(), + multiFactorConfig: this.multiFactorConfig_?.toJSON(), anonymousSignInEnabled: this.anonymousSignInEnabled, - multiFactorConfig: this.multiFactorConfig?.toJSON(), testPhoneNumbers: this.testPhoneNumbers, }; if (typeof json.multiFactorConfig === 'undefined') { diff --git a/src/auth/token-generator.ts b/src/auth/token-generator.ts index 6c464ec5f2..7e930916ab 100644 --- a/src/auth/token-generator.ts +++ b/src/auth/token-generator.ts @@ -15,15 +15,13 @@ * limitations under the License. */ -import { - AuthClientErrorCode, ErrorInfo, FirebaseAuthError -} from '../utils/error'; +import { AuthClientErrorCode, ErrorInfo, FirebaseAuthError } from '../utils/error'; +import { HttpError } from '../utils/api-request'; import { CryptoSigner, CryptoSignerError, CryptoSignerErrorCode } from '../utils/crypto-signer'; import * as validator from '../utils/validator'; import { toWebSafeBase64 } from '../utils'; import { Algorithm } from 'jsonwebtoken'; -import { HttpError } from '../utils/api-request'; const ALGORITHM_NONE: Algorithm = 'none' as const; @@ -86,6 +84,8 @@ export class EmulatedSigner implements CryptoSigner { /** * Class for generating different types of Firebase Auth tokens (JWTs). + * + * @internal */ export class FirebaseTokenGenerator { @@ -117,7 +117,7 @@ export class FirebaseTokenGenerator { * @param uid The user ID to use for the generated Firebase Auth Custom token. * @param developerClaims Optional developer claims to include in the generated Firebase * Auth Custom token. - * @return A Promise fulfilled with a Firebase Auth Custom token signed with a + * @returns A Promise fulfilled with a Firebase Auth Custom token signed with a * service account key and containing the provided payload. */ public createCustomToken(uid: string, developerClaims?: {[key: string]: any}): Promise { @@ -189,8 +189,8 @@ export class FirebaseTokenGenerator { /** * Returns whether or not the provided developer claims are valid. * - * @param {object} [developerClaims] Optional developer claims to validate. - * @return {boolean} True if the provided claims are valid; otherwise, false. + * @param developerClaims Optional developer claims to validate. + * @returns True if the provided claims are valid; otherwise, false. */ private isDeveloperClaimsValid_(developerClaims?: object): boolean { if (typeof developerClaims === 'undefined') { @@ -204,8 +204,8 @@ export class FirebaseTokenGenerator { * Creates a new FirebaseAuthError by extracting the error code, message and other relevant * details from a CryptoSignerError. * - * @param {Error} err The Error to convert into a FirebaseAuthError error - * @return {FirebaseAuthError} A Firebase Auth error that can be returned to the user. + * @param err The Error to convert into a FirebaseAuthError error + * @returns A Firebase Auth error that can be returned to the user. */ export function handleCryptoSignerError(err: Error): Error { if (!(err instanceof CryptoSignerError)) { diff --git a/src/auth/token-verifier.ts b/src/auth/token-verifier.ts index e100cc25f6..12c7e72a29 100644 --- a/src/auth/token-verifier.ts +++ b/src/auth/token-verifier.ts @@ -17,14 +17,164 @@ import { AuthClientErrorCode, FirebaseAuthError, ErrorInfo } from '../utils/error'; import * as util from '../utils/index'; import * as validator from '../utils/validator'; -import { - DecodedToken, decodeJwt, JwtError, JwtErrorCode, - EmulatorSignatureVerifier, PublicKeySignatureVerifier, ALGORITHM_RS256, SignatureVerifier, +import { + DecodedToken, decodeJwt, JwtError, JwtErrorCode, EmulatorSignatureVerifier, + PublicKeySignatureVerifier, ALGORITHM_RS256, SignatureVerifier, } from '../utils/jwt'; -import { FirebaseApp } from '../firebase-app'; -import { auth } from './index'; +import { App } from '../app/index'; -import DecodedIdToken = auth.DecodedIdToken; +/** + * Interface representing a decoded Firebase ID token, returned from the + * {@link auth.Auth.verifyIdToken `verifyIdToken()`} method. + * + * Firebase ID tokens are OpenID Connect spec-compliant JSON Web Tokens (JWTs). + * See the + * [ID Token section of the OpenID Connect spec](http://openid.net/specs/openid-connect-core-1_0.html#IDToken) + * for more information about the specific properties below. + */ +export interface DecodedIdToken { + + /** + * The audience for which this token is intended. + * + * This value is a string equal to your Firebase project ID, the unique + * identifier for your Firebase project, which can be found in [your project's + * settings](https://console.firebase.google.com/project/_/settings/general/android:com.random.android). + */ + aud: string; + + /** + * Time, in seconds since the Unix epoch, when the end-user authentication + * occurred. + * + * This value is not set when this particular ID token was created, but when the + * user initially logged in to this session. In a single session, the Firebase + * SDKs will refresh a user's ID tokens every hour. Each ID token will have a + * different [`iat`](#iat) value, but the same `auth_time` value. + */ + auth_time: number; + + /** + * The email of the user to whom the ID token belongs, if available. + */ + email?: string; + + /** + * Whether or not the email of the user to whom the ID token belongs is + * verified, provided the user has an email. + */ + email_verified?: boolean; + + /** + * The ID token's expiration time, in seconds since the Unix epoch. That is, the + * time at which this ID token expires and should no longer be considered valid. + * + * The Firebase SDKs transparently refresh ID tokens every hour, issuing a new + * ID token with up to a one hour expiration. + */ + exp: number; + + /** + * Information about the sign in event, including which sign in provider was + * used and provider-specific identity details. + * + * This data is provided by the Firebase Authentication service and is a + * reserved claim in the ID token. + */ + firebase: { + + /** + * Provider-specific identity details corresponding + * to the provider used to sign in the user. + */ + identities: { + [key: string]: any; + }; + + /** + * The ID of the provider used to sign in the user. + * One of `"anonymous"`, `"password"`, `"facebook.com"`, `"github.com"`, + * `"google.com"`, `"twitter.com"`, `"apple.com"`, `"microsoft.com"`, + * "yahoo.com"`, `"phone"`, `"playgames.google.com"`, `"gc.apple.com"`, + * or `"custom"`. + * + * Additional Identity Platform provider IDs include `"linkedin.com"`, + * OIDC and SAML identity providers prefixed with `"saml."` and `"oidc."` + * respectively. + */ + sign_in_provider: string; + + /** + * The type identifier or `factorId` of the second factor, provided the + * ID token was obtained from a multi-factor authenticated user. + * For phone, this is `"phone"`. + */ + sign_in_second_factor?: string; + + /** + * The `uid` of the second factor used to sign in, provided the + * ID token was obtained from a multi-factor authenticated user. + */ + second_factor_identifier?: string; + + /** + * The ID of the tenant the user belongs to, if available. + */ + tenant?: string; + [key: string]: any; + }; + + /** + * The ID token's issued-at time, in seconds since the Unix epoch. That is, the + * time at which this ID token was issued and should start to be considered + * valid. + * + * The Firebase SDKs transparently refresh ID tokens every hour, issuing a new + * ID token with a new issued-at time. If you want to get the time at which the + * user session corresponding to the ID token initially occurred, see the + * [`auth_time`](#auth_time) property. + */ + iat: number; + + /** + * The issuer identifier for the issuer of the response. + * + * This value is a URL with the format + * `https://securetoken.google.com/`, where `` is the + * same project ID specified in the [`aud`](#aud) property. + */ + iss: string; + + /** + * The phone number of the user to whom the ID token belongs, if available. + */ + phone_number?: string; + + /** + * The photo URL for the user to whom the ID token belongs, if available. + */ + picture?: string; + + /** + * The `uid` corresponding to the user who the ID token belonged to. + * + * As a convenience, this value is copied over to the [`uid`](#uid) property. + */ + sub: string; + + /** + * The `uid` corresponding to the user who the ID token belonged to. + * + * This value is not actually in the JWT token claims itself. It is added as a + * convenience, and is set as the value of the [`sub`](#sub) property. + */ + uid: string; + + /** + * Other arbitrary claims included in the ID token. + */ + [key: string]: any; +} // Audience to use for Firebase Auth Custom tokens const FIREBASE_AUDIENCE = 'https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit'; @@ -38,7 +188,11 @@ const SESSION_COOKIE_CERT_URL = 'https://www.googleapis.com/identitytoolkit/v3/r const EMULATOR_VERIFIER = new EmulatorSignatureVerifier(); -/** User facing token information related to the Firebase ID token. */ +/** + * User facing token information related to the Firebase ID token. + * + * @internal + */ export const ID_TOKEN_INFO: FirebaseTokenInfo = { url: 'https://firebase.google.com/docs/auth/admin/verify-id-tokens', verifyApiName: 'verifyIdToken()', @@ -47,7 +201,11 @@ export const ID_TOKEN_INFO: FirebaseTokenInfo = { expiredErrorCode: AuthClientErrorCode.ID_TOKEN_EXPIRED, }; -/** User facing token information related to the Firebase session cookie. */ +/** + * User facing token information related to the Firebase session cookie. + * + * @internal + */ export const SESSION_COOKIE_INFO: FirebaseTokenInfo = { url: 'https://firebase.google.com/docs/auth/admin/manage-cookies', verifyApiName: 'verifySessionCookie()', @@ -56,7 +214,11 @@ export const SESSION_COOKIE_INFO: FirebaseTokenInfo = { expiredErrorCode: AuthClientErrorCode.SESSION_COOKIE_EXPIRED, }; -/** Interface that defines token related user facing information. */ +/** + * Interface that defines token related user facing information. + * + * @internal + */ export interface FirebaseTokenInfo { /** Documentation URL. */ url: string; @@ -71,14 +233,17 @@ export interface FirebaseTokenInfo { } /** - * Class for verifying ID tokens and session cookies. + * Class for verifying general purpose Firebase JWTs. This verifies ID tokens and session cookies. + * + * @internal */ export class FirebaseTokenVerifier { + private readonly shortNameArticle: string; private readonly signatureVerifier: SignatureVerifier; constructor(clientCertUrl: string, private issuer: string, private tokenInfo: FirebaseTokenInfo, - private readonly app: FirebaseApp) { + private readonly app: App) { if (!validator.isURL(clientCertUrl)) { throw new FirebaseAuthError( @@ -134,7 +299,7 @@ export class FirebaseTokenVerifier { * * @param jwtToken The Firebase Auth JWT token to verify. * @param isEmulator Whether to accept Auth Emulator tokens. - * @return A promise fulfilled with the decoded claims of the Firebase Auth ID token. + * @returns A promise fulfilled with the decoded claims of the Firebase Auth ID token. */ public verifyJWT(jwtToken: string, isEmulator = false): Promise { if (!validator.isString(jwtToken)) { @@ -205,7 +370,6 @@ export class FirebaseTokenVerifier { fullDecodedToken: DecodedToken, projectId: string | null, isEmulator: boolean): void { - const header = fullDecodedToken && fullDecodedToken.header; const payload = fullDecodedToken && fullDecodedToken.payload; @@ -265,7 +429,7 @@ export class FirebaseTokenVerifier { /** * Maps JwtError to FirebaseAuthError - * + * * @param error JwtError to be mapped. * @returns FirebaseAuthError or Error instance. */ @@ -293,10 +457,11 @@ export class FirebaseTokenVerifier { /** * Creates a new FirebaseTokenVerifier to verify Firebase ID tokens. * + * @internal * @param app Firebase app instance. - * @return FirebaseTokenVerifier + * @returns FirebaseTokenVerifier */ -export function createIdTokenVerifier(app: FirebaseApp): FirebaseTokenVerifier { +export function createIdTokenVerifier(app: App): FirebaseTokenVerifier { return new FirebaseTokenVerifier( CLIENT_CERT_URL, 'https://securetoken.google.com/', @@ -308,10 +473,11 @@ export function createIdTokenVerifier(app: FirebaseApp): FirebaseTokenVerifier { /** * Creates a new FirebaseTokenVerifier to verify Firebase session cookies. * + * @internal * @param app Firebase app instance. - * @return FirebaseTokenVerifier + * @returns FirebaseTokenVerifier */ -export function createSessionCookieVerifier(app: FirebaseApp): FirebaseTokenVerifier { +export function createSessionCookieVerifier(app: App): FirebaseTokenVerifier { return new FirebaseTokenVerifier( SESSION_COOKIE_CERT_URL, 'https://session.firebase.google.com/', diff --git a/src/auth/user-import-builder.ts b/src/auth/user-import-builder.ts index 92e84cc08c..811e7c9963 100644 --- a/src/auth/user-import-builder.ts +++ b/src/auth/user-import-builder.ts @@ -14,18 +14,246 @@ * limitations under the License. */ +import { FirebaseArrayIndexError } from '../app/index'; import { deepCopy, deepExtend } from '../utils/deep-copy'; import * as utils from '../utils'; import * as validator from '../utils/validator'; import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; -import { FirebaseArrayIndexError } from '../firebase-namespace-api'; -import { auth } from './index'; +import { + UpdateMultiFactorInfoRequest, UpdatePhoneMultiFactorInfoRequest, MultiFactorUpdateSettings +} from './auth-config'; -import UpdateMultiFactorInfoRequest = auth.UpdateMultiFactorInfoRequest; -import UpdatePhoneMultiFactorInfoRequest = auth.UpdatePhoneMultiFactorInfoRequest; -import UserImportRecord = auth.UserImportRecord; -import UserImportOptions = auth.UserImportOptions; -import UserImportResult = auth.UserImportResult; +export type HashAlgorithmType = 'SCRYPT' | 'STANDARD_SCRYPT' | 'HMAC_SHA512' | + 'HMAC_SHA256' | 'HMAC_SHA1' | 'HMAC_MD5' | 'MD5' | 'PBKDF_SHA1' | 'BCRYPT' | + 'PBKDF2_SHA256' | 'SHA512' | 'SHA256' | 'SHA1'; + +/** + * Interface representing the user import options needed for + * {@link auth.Auth.importUsers `importUsers()`} method. This is used to + * provide the password hashing algorithm information. + */ +export interface UserImportOptions { + + /** + * The password hashing information. + */ + hash: { + + /** + * The password hashing algorithm identifier. The following algorithm + * identifiers are supported: + * `SCRYPT`, `STANDARD_SCRYPT`, `HMAC_SHA512`, `HMAC_SHA256`, `HMAC_SHA1`, + * `HMAC_MD5`, `MD5`, `PBKDF_SHA1`, `BCRYPT`, `PBKDF2_SHA256`, `SHA512`, + * `SHA256` and `SHA1`. + */ + algorithm: HashAlgorithmType; + + /** + * The signing key used in the hash algorithm in buffer bytes. + * Required by hashing algorithms `SCRYPT`, `HMAC_SHA512`, `HMAC_SHA256`, + * `HAMC_SHA1` and `HMAC_MD5`. + */ + key?: Buffer; + + /** + * The salt separator in buffer bytes which is appended to salt when + * verifying a password. This is only used by the `SCRYPT` algorithm. + */ + saltSeparator?: Buffer; + + /** + * The number of rounds for hashing calculation. + * Required for `SCRYPT`, `MD5`, `SHA512`, `SHA256`, `SHA1`, `PBKDF_SHA1` and + * `PBKDF2_SHA256`. + */ + rounds?: number; + + /** + * The memory cost required for `SCRYPT` algorithm, or the CPU/memory cost. + * Required for `STANDARD_SCRYPT` algorithm. + */ + memoryCost?: number; + + /** + * The parallelization of the hashing algorithm. Required for the + * `STANDARD_SCRYPT` algorithm. + */ + parallelization?: number; + + /** + * The block size (normally 8) of the hashing algorithm. Required for the + * `STANDARD_SCRYPT` algorithm. + */ + blockSize?: number; + /** + * The derived key length of the hashing algorithm. Required for the + * `STANDARD_SCRYPT` algorithm. + */ + derivedKeyLength?: number; + }; +} + +/** + * Interface representing a user to import to Firebase Auth via the + * {@link auth.Auth.importUsers `importUsers()`} method. + */ +export interface UserImportRecord { + + /** + * The user's `uid`. + */ + uid: string; + + /** + * The user's primary email, if set. + */ + email?: string; + + /** + * Whether or not the user's primary email is verified. + */ + emailVerified?: boolean; + + /** + * The user's display name. + */ + displayName?: string; + + /** + * The user's primary phone number, if set. + */ + phoneNumber?: string; + + /** + * The user's photo URL. + */ + photoURL?: string; + + /** + * Whether or not the user is disabled: `true` for disabled; `false` for + * enabled. + */ + disabled?: boolean; + + /** + * Additional metadata about the user. + */ + metadata?: UserMetadataRequest; + + /** + * An array of providers (for example, Google, Facebook) linked to the user. + */ + providerData?: UserProviderRequest[]; + + /** + * The user's custom claims object if available, typically used to define + * user roles and propagated to an authenticated user's ID token. + */ + customClaims?: { [key: string]: any }; + + /** + * The buffer of bytes representing the user's hashed password. + * When a user is to be imported with a password hash, + * {@link auth.UserImportOptions `UserImportOptions`} are required to be + * specified to identify the hashing algorithm used to generate this hash. + */ + passwordHash?: Buffer; + + /** + * The buffer of bytes representing the user's password salt. + */ + passwordSalt?: Buffer; + + /** + * The identifier of the tenant where user is to be imported to. + * When not provided in an `admin.auth.Auth` context, the user is uploaded to + * the default parent project. + * When not provided in an `admin.auth.TenantAwareAuth` context, the user is uploaded + * to the tenant corresponding to that `TenantAwareAuth` instance's tenant ID. + */ + tenantId?: string; + + /** + * The user's multi-factor related properties. + */ + multiFactor?: MultiFactorUpdateSettings; +} + +/** + * User metadata to include when importing a user. + */ +export interface UserMetadataRequest { + + /** + * The date the user last signed in, formatted as a UTC string. + */ + lastSignInTime?: string; + + /** + * The date the user was created, formatted as a UTC string. + */ + creationTime?: string; +} + +/** + * User provider data to include when importing a user. + */ +export interface UserProviderRequest { + + /** + * The user identifier for the linked provider. + */ + uid: string; + + /** + * The display name for the linked provider. + */ + displayName?: string; + + /** + * The email for the linked provider. + */ + email?: string; + + /** + * The phone number for the linked provider. + */ + phoneNumber?: string; + + /** + * The photo URL for the linked provider. + */ + photoURL?: string; + + /** + * The linked provider ID (for example, "google.com" for the Google provider). + */ + providerId: string; +} + +/** + * Interface representing the response from the + * {@link auth.Auth.importUsers `importUsers()`} method for batch + * importing users to Firebase Auth. + */ +export interface UserImportResult { + + /** + * The number of user records that failed to import to Firebase Auth. + */ + failureCount: number; + + /** + * The number of user records that successfully imported to Firebase Auth. + */ + successCount: number; + + /** + * An array of errors corresponding to the provided users to import. The + * length of this array is equal to [`failureCount`](#failureCount). + */ + errors: FirebaseArrayIndexError[]; +} /** Interface representing an Auth second factor in Auth server format. */ export interface AuthFactorInfo { @@ -91,7 +319,7 @@ export type ValidatorFunction = (data: UploadAccountUser) => void; /** * Converts a client format second factor object to server format. * @param multiFactorInfo The client format second factor. - * @return The corresponding AuthFactorInfo server request format. + * @returns The corresponding AuthFactorInfo server request format. */ export function convertMultiFactorInfoToServerFormat(multiFactorInfo: UpdateMultiFactorInfoRequest): AuthFactorInfo { let enrolledAt; @@ -138,7 +366,7 @@ function isPhoneFactor(multiFactorInfo: UpdateMultiFactorInfoRequest): /** * @param {any} obj The object to check for number field within. * @param {string} key The entry key. - * @return {number} The corresponding number if available. Otherwise, NaN. + * @returns {number} The corresponding number if available. Otherwise, NaN. */ function getNumberField(obj: any, key: string): number { if (typeof obj[key] !== 'undefined' && obj[key] !== null) { @@ -153,7 +381,7 @@ function getNumberField(obj: any, key: string): number { * fields are provided. * @param {UserImportRecord} user The UserImportRecord to conver to UploadAccountUser. * @param {ValidatorFunction=} userValidator The user validator function. - * @return {UploadAccountUser} The corresponding UploadAccountUser to return. + * @returns {UploadAccountUser} The corresponding UploadAccountUser to return. */ function populateUploadAccountUser( user: UserImportRecord, userValidator?: ValidatorFunction): UploadAccountUser { @@ -269,7 +497,7 @@ export class UserImportBuilder { /** * Returns the corresponding constructed uploadAccount request. - * @return {UploadAccountRequest} The constructed uploadAccount request. + * @returns {UploadAccountRequest} The constructed uploadAccount request. */ public buildRequest(): UploadAccountRequest { const users = this.validatedUsers.map((user) => { @@ -281,7 +509,7 @@ export class UserImportBuilder { /** * Populates the UserImportResult using the client side detected errors and the server * side returned errors. - * @return {UserImportResult} The user import result based on the returned failed + * @returns {UserImportResult} The user import result based on the returned failed * uploadAccount response. */ public buildResponse( @@ -317,7 +545,7 @@ export class UserImportBuilder { * Throws an error whenever an invalid or missing options is detected. * @param {UserImportOptions} options The UserImportOptions. * @param {boolean} requiresHashOptions Whether to require hash options. - * @return {UploadAccountOptions} The populated UploadAccount options. + * @returns {UploadAccountOptions} The populated UploadAccount options. */ private populateOptions( options: UserImportOptions | undefined, requiresHashOptions: boolean): UploadAccountOptions { @@ -505,7 +733,7 @@ export class UserImportBuilder { * @param {UserImportRecord[]} users The UserImportRecords to convert to UnploadAccountUser * objects. * @param {ValidatorFunction=} userValidator The user validator function. - * @return {UploadAccountUser[]} The populated uploadAccount users. + * @returns {UploadAccountUser[]} The populated uploadAccount users. */ private populateUsers( users: UserImportRecord[], userValidator?: ValidatorFunction): UploadAccountUser[] { diff --git a/src/auth/user-record.ts b/src/auth/user-record.ts index 01092a25da..7b49618be5 100644 --- a/src/auth/user-record.ts +++ b/src/auth/user-record.ts @@ -19,14 +19,6 @@ import { deepCopy } from '../utils/deep-copy'; import { isNonNullObject } from '../utils/validator'; import * as utils from '../utils'; import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; -import { auth } from './index'; - -import MultiFactorInfoInterface = auth.MultiFactorInfo; -import PhoneMultiFactorInfoInterface = auth.PhoneMultiFactorInfo; -import MultiFactorSettings = auth.MultiFactorSettings; -import UserMetadataInterface = auth.UserMetadata; -import UserInfoInterface = auth.UserInfo; -import UserRecordInterface = auth.UserRecord; /** * 'REDACTED', encoded as a base64 string. @@ -36,8 +28,8 @@ const B64_REDACTED = Buffer.from('REDACTED').toString('base64'); /** * Parses a time stamp string or number and returns the corresponding date if valid. * - * @param {any} time The unix timestamp string or number in milliseconds. - * @return {string} The corresponding date as a UTC string, if valid. Otherwise, null. + * @param time The unix timestamp string or number in milliseconds. + * @returns The corresponding date as a UTC string, if valid. Otherwise, null. */ function parseDate(time: any): string | null { try { @@ -86,6 +78,7 @@ export interface GetAccountInfoUserResponse { mfaInfo?: MultiFactorInfoResponse[]; createdAt?: string; lastLoginAt?: string; + lastRefreshAt?: string; [key: string]: any; } @@ -94,12 +87,28 @@ enum MultiFactorId { } /** - * Abstract class representing a multi-factor info interface. + * Interface representing the common properties of a user-enrolled second factor. */ -export abstract class MultiFactorInfo implements MultiFactorInfoInterface { +export abstract class MultiFactorInfo { + + /** + * The ID of the enrolled second factor. This ID is unique to the user. + */ public readonly uid: string; + + /** + * The optional display name of the enrolled second factor. + */ public readonly displayName?: string; + + /** + * The type identifier of the second factor. For SMS second factors, this is `phone`. + */ public readonly factorId: string; + + /** + * The optional date the second factor was enrolled, formatted as a UTC string. + */ public readonly enrollmentTime?: string; /** @@ -107,7 +116,7 @@ export abstract class MultiFactorInfo implements MultiFactorInfoInterface { * If no MultiFactorInfo is associated with the response, null is returned. * * @param response The server side response. - * @constructor + * @internal */ public static initMultiFactorInfo(response: MultiFactorInfoResponse): MultiFactorInfo | null { let multiFactorInfo: MultiFactorInfo | null = null; @@ -125,13 +134,18 @@ export abstract class MultiFactorInfo implements MultiFactorInfoInterface { * * @param response The server side response. * @constructor + * @internal */ constructor(response: MultiFactorInfoResponse) { this.initFromServerResponse(response); } - /** @return The plain object representation. */ - public toJSON(): any { + /** + * Returns a JSON-serializable representation of this object. + * + * @returns A JSON-serializable representation of this object. + */ + public toJSON(): object { return { uid: this.uid, displayName: this.displayName, @@ -144,8 +158,10 @@ export abstract class MultiFactorInfo implements MultiFactorInfoInterface { * Returns the factor ID based on the response provided. * * @param response The server side response. - * @return The multi-factor ID associated with the provided response. If the response is + * @returns The multi-factor ID associated with the provided response. If the response is * not associated with any known multi-factor ID, null is returned. + * + * @internal */ protected abstract getFactorId(response: MultiFactorInfoResponse): string | null; @@ -177,8 +193,14 @@ export abstract class MultiFactorInfo implements MultiFactorInfoInterface { } } -/** Class representing a phone MultiFactorInfo object. */ -export class PhoneMultiFactorInfo extends MultiFactorInfo implements PhoneMultiFactorInfoInterface { +/** + * Interface representing a phone specific user-enrolled second factor. + */ +export class PhoneMultiFactorInfo extends MultiFactorInfo { + + /** + * The phone number associated with a phone second factor. + */ public readonly phoneNumber: string; /** @@ -186,14 +208,17 @@ export class PhoneMultiFactorInfo extends MultiFactorInfo implements PhoneMultiF * * @param response The server side response. * @constructor + * @internal */ constructor(response: MultiFactorInfoResponse) { super(response); utils.addReadonlyGetter(this, 'phoneNumber', response.phoneInfo); } - /** @return The plain object representation. */ - public toJSON(): any { + /** + * {@inheritdoc MultiFactorInfo.toJSON} + */ + public toJSON(): object { return Object.assign( super.toJSON(), { @@ -205,16 +230,25 @@ export class PhoneMultiFactorInfo extends MultiFactorInfo implements PhoneMultiF * Returns the factor ID based on the response provided. * * @param response The server side response. - * @return The multi-factor ID associated with the provided response. If the response is + * @returns The multi-factor ID associated with the provided response. If the response is * not associated with any known multi-factor ID, null is returned. + * + * @internal */ protected getFactorId(response: MultiFactorInfoResponse): string | null { return (response && response.phoneInfo) ? MultiFactorId.Phone : null; } } -/** Class representing multi-factor related properties of a user. */ -export class MultiFactor implements MultiFactorSettings { +/** + * The multi-factor related user settings. + */ +export class MultiFactorSettings { + + /** + * List of second factors enrolled with the current user. + * Currently only phone second factors are supported. + */ public enrolledFactors: MultiFactorInfo[]; /** @@ -222,6 +256,7 @@ export class MultiFactor implements MultiFactorSettings { * * @param response The server side response. * @constructor + * @internal */ constructor(response: GetAccountInfoUserResponse) { const parsedEnrolledFactors: MultiFactorInfo[] = []; @@ -242,8 +277,12 @@ export class MultiFactor implements MultiFactorSettings { this, 'enrolledFactors', Object.freeze(parsedEnrolledFactors)); } - /** @return The plain object representation. */ - public toJSON(): any { + /** + * Returns a JSON-serializable representation of this multi-factor object. + * + * @returns A JSON-serializable representation of this multi-factor object. + */ + public toJSON(): object { return { enrolledFactors: this.enrolledFactors.map((info) => info.toJSON()), }; @@ -251,24 +290,33 @@ export class MultiFactor implements MultiFactorSettings { } /** - * User metadata class that provides metadata information like user account creation - * and last sign in time. - * - * @param response The server side response returned from the getAccountInfo - * endpoint. - * @constructor + * Represents a user's metadata. */ -export class UserMetadata implements UserMetadataInterface { +export class UserMetadata { + + /** + * The date the user was created, formatted as a UTC string. + */ public readonly creationTime: string; + + /** + * The date the user last signed in, formatted as a UTC string. + */ public readonly lastSignInTime: string; /** - * The time at which the user was last active (ID token refreshed), or null - * if the user was never active. Formatted as a UTC Date string (eg - * 'Sat, 03 Feb 2001 04:05:06 GMT') + * The time at which the user was last active (ID token refreshed), + * formatted as a UTC Date string (eg 'Sat, 03 Feb 2001 04:05:06 GMT'). + * Returns null if the user was never active. */ - public readonly lastRefreshTime: string | null; + public readonly lastRefreshTime?: string | null; + /** + * @param response The server side response returned from the getAccountInfo + * endpoint. + * @constructor + * @internal + */ constructor(response: GetAccountInfoUserResponse) { // Creation date should always be available but due to some backend bugs there // were cases in the past where users did not have creation date properly set. @@ -280,7 +328,11 @@ export class UserMetadata implements UserMetadataInterface { utils.addReadonlyGetter(this, 'lastRefreshTime', lastRefreshAt); } - /** @return The plain object representation of the user's metadata. */ + /** + * Returns a JSON-serializable representation of this object. + * + * @returns A JSON-serializable representation of this object. + */ public toJSON(): object { return { lastSignInTime: this.lastSignInTime, @@ -290,21 +342,48 @@ export class UserMetadata implements UserMetadataInterface { } /** - * User info class that provides provider user information for different - * Firebase providers like google.com, facebook.com, password, etc. - * - * @param response The server side response returned from the getAccountInfo - * endpoint. - * @constructor + * Represents a user's info from a third-party identity provider + * such as Google or Facebook. */ -export class UserInfo implements UserInfoInterface { +export class UserInfo { + + /** + * The user identifier for the linked provider. + */ public readonly uid: string; + + /** + * The display name for the linked provider. + */ public readonly displayName: string; + + /** + * The email for the linked provider. + */ public readonly email: string; + + /** + * The photo URL for the linked provider. + */ public readonly photoURL: string; + + /** + * The linked provider ID (for example, "google.com" for the Google provider). + */ public readonly providerId: string; + + /** + * The phone number for the linked provider. + */ public readonly phoneNumber: string; + + /** + * @param response The server side response returned from the getAccountInfo + * endpoint. + * @constructor + * @internal + */ constructor(response: ProviderUserInfoResponse) { // Provider user id and provider id are required. if (!response.rawId || !response.providerId) { @@ -321,7 +400,11 @@ export class UserInfo implements UserInfoInterface { utils.addReadonlyGetter(this, 'phoneNumber', response.phoneNumber); } - /** @return The plain object representation of the current provider data. */ + /** + * Returns a JSON-serializable representation of this object. + * + * @returns A JSON-serializable representation of this object. + */ public toJSON(): object { return { uid: this.uid, @@ -335,30 +418,107 @@ export class UserInfo implements UserInfoInterface { } /** - * User record class that defines the Firebase user object populated from - * the Firebase Auth getAccountInfo response. - * - * @param response The server side response returned from the getAccountInfo - * endpoint. - * @constructor + * Represents a user. */ -export class UserRecord implements UserRecordInterface { +export class UserRecord { + + /** + * The user's `uid`. + */ public readonly uid: string; - public readonly email: string; + + /** + * The user's primary email, if set. + */ + public readonly email?: string; + + /** + * Whether or not the user's primary email is verified. + */ public readonly emailVerified: boolean; - public readonly displayName: string; - public readonly photoURL: string; - public readonly phoneNumber: string; + + /** + * The user's display name. + */ + public readonly displayName?: string; + + /** + * The user's photo URL. + */ + public readonly photoURL?: string; + + /** + * The user's primary phone number, if set. + */ + public readonly phoneNumber?: string; + + /** + * Whether or not the user is disabled: `true` for disabled; `false` for + * enabled. + */ public readonly disabled: boolean; + + /** + * Additional metadata about the user. + */ public readonly metadata: UserMetadata; + + /** + * An array of providers (for example, Google, Facebook) linked to the user. + */ public readonly providerData: UserInfo[]; + + /** + * The user's hashed password (base64-encoded), only if Firebase Auth hashing + * algorithm (SCRYPT) is used. If a different hashing algorithm had been used + * when uploading this user, as is typical when migrating from another Auth + * system, this will be an empty string. If no password is set, this is + * null. This is only available when the user is obtained from + * {@link BaseAuth.listUsers}. + */ public readonly passwordHash?: string; + + /** + * The user's password salt (base64-encoded), only if Firebase Auth hashing + * algorithm (SCRYPT) is used. If a different hashing algorithm had been used to + * upload this user, typical when migrating from another Auth system, this will + * be an empty string. If no password is set, this is null. This is only + * available when the user is obtained from {@link BaseAuth.listUsers}. + */ public readonly passwordSalt?: string; - public readonly customClaims: {[key: string]: any}; + + /** + * The user's custom claims object if available, typically used to define + * user roles and propagated to an authenticated user's ID token. + * This is set via {@link BaseAuth.setCustomUserClaims} + */ + public readonly customClaims?: {[key: string]: any}; + + /** + * The ID of the tenant the user belongs to, if available. + */ public readonly tenantId?: string | null; + + /** + * The date the user's tokens are valid after, formatted as a UTC string. + * This is updated every time the user's refresh token are revoked either + * from the {@link BaseAuth.revokeRefreshTokens} + * API or from the Firebase Auth backend on big account changes (password + * resets, password or email updates, etc). + */ public readonly tokensValidAfterTime?: string; - public readonly multiFactor?: MultiFactor; + /** + * The multi-factor related properties for the current user, if available. + */ + public readonly multiFactor?: MultiFactorSettings; + + /** + * @param response The server side response returned from the getAccountInfo + * endpoint. + * @constructor + * @internal + */ constructor(response: GetAccountInfoUserResponse) { // The Firebase user id is required. if (!response.localId) { @@ -404,13 +564,17 @@ export class UserRecord implements UserRecordInterface { } utils.addReadonlyGetter(this, 'tokensValidAfterTime', validAfterTime || undefined); utils.addReadonlyGetter(this, 'tenantId', response.tenantId); - const multiFactor = new MultiFactor(response); + const multiFactor = new MultiFactorSettings(response); if (multiFactor.enrolledFactors.length > 0) { utils.addReadonlyGetter(this, 'multiFactor', multiFactor); } } - /** @return The plain object representation of the user record. */ + /** + * Returns a JSON-serializable representation of this object. + * + * @returns A JSON-serializable representation of this object. + */ public toJSON(): object { const json: any = { uid: this.uid, diff --git a/src/credential/credential.ts b/src/credential/credential.ts deleted file mode 100644 index a2b4413b73..0000000000 --- a/src/credential/credential.ts +++ /dev/null @@ -1,49 +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. - */ - -import { - ServiceAccountCredential, RefreshTokenCredential, getApplicationDefault -} from './credential-internal'; -import { credential } from './index'; - -let globalAppDefaultCred: credential.Credential; -const globalCertCreds: { [key: string]: ServiceAccountCredential } = {}; -const globalRefreshTokenCreds: { [key: string]: RefreshTokenCredential } = {}; - -export const applicationDefault: typeof credential.applicationDefault = (httpAgent?) => { - if (typeof globalAppDefaultCred === 'undefined') { - globalAppDefaultCred = getApplicationDefault(httpAgent); - } - return globalAppDefaultCred; -} - -export const cert: typeof credential.cert = (serviceAccountPathOrObject, httpAgent?) => { - const stringifiedServiceAccount = JSON.stringify(serviceAccountPathOrObject); - if (!(stringifiedServiceAccount in globalCertCreds)) { - globalCertCreds[stringifiedServiceAccount] = new ServiceAccountCredential(serviceAccountPathOrObject, httpAgent); - } - return globalCertCreds[stringifiedServiceAccount]; -} - -export const refreshToken: typeof credential.refreshToken = (refreshTokenPathOrObject, httpAgent) => { - const stringifiedRefreshToken = JSON.stringify(refreshTokenPathOrObject); - if (!(stringifiedRefreshToken in globalRefreshTokenCreds)) { - globalRefreshTokenCreds[stringifiedRefreshToken] = new RefreshTokenCredential( - refreshTokenPathOrObject, httpAgent); - } - return globalRefreshTokenCreds[stringifiedRefreshToken]; -} diff --git a/src/credential/index.ts b/src/credential/index.ts index dfd27819ed..7cc49758e8 100644 --- a/src/credential/index.ts +++ b/src/credential/index.ts @@ -14,21 +14,14 @@ * limitations under the License. */ -import { Agent } from 'http'; +import { + Credential as TCredential, + applicationDefault as applicationDefaultFn, + cert as certFn, + refreshToken as refreshTokenFn, +} from '../app/index'; -export interface ServiceAccount { - projectId?: string; - clientEmail?: string; - privateKey?: string; -} - -/** - * Interface for Google OAuth 2.0 access tokens. - */ -export interface GoogleOAuthAccessToken { - access_token: string; - expires_in: number; -} +export { ServiceAccount, GoogleOAuthAccessToken } from '../app/index'; /* eslint-disable @typescript-eslint/no-namespace */ export namespace credential { @@ -40,39 +33,20 @@ export namespace credential { * use the default implementations provided by * {@link credential `admin.credential`}. */ - export interface Credential { - /** - * Returns a Google OAuth2 access token object used to authenticate with - * Firebase services. - * - * This object contains the following properties: - * * `access_token` (`string`): The actual Google OAuth2 access token. - * * `expires_in` (`number`): The number of seconds from when the token was - * issued that it expires. - * - * @return A Google OAuth2 access token object. - */ - getAccessToken(): Promise; - } + export type Credential = TCredential; /** * Returns a credential created from the - * {@link - * https://developers.google.com/identity/protocols/application-default-credentials - * Google Application Default Credentials} + * {@link https://developers.google.com/identity/protocols/application-default-credentials | + * Google Application Default Credentials} * that grants admin access to Firebase services. This credential can be used - * in the call to - * {@link - * https://firebase.google.com/docs/reference/admin/node/admin#.initializeApp - * `admin.initializeApp()`}. + * in the call to {@link firebase-admin.app#initializeApp}. * * Google Application Default Credentials are available on any Google * infrastructure, such as Google App Engine and Google Compute Engine. * * See - * {@link - * https://firebase.google.com/docs/admin/setup#initialize_the_sdk - * Initialize the SDK} + * {@link https://firebase.google.com/docs/admin/setup#initialize_the_sdk | Initialize the SDK} * for more details. * * @example @@ -83,26 +57,21 @@ export namespace credential { * }); * ``` * - * @param {!Object=} httpAgent Optional [HTTP Agent](https://nodejs.org/api/http.html#http_class_http_agent) + * @param httpAgent Optional {@link https://nodejs.org/api/http.html#http_class_http_agent | HTTP Agent} * to be used when retrieving access tokens from Google token servers. * - * @return {!admin.credential.Credential} A credential authenticated via Google + * @returns A credential authenticated via Google * Application Default Credentials that can be used to initialize an app. */ - export declare function applicationDefault(httpAgent?: Agent): Credential; + export const applicationDefault = applicationDefaultFn; /** * Returns a credential created from the provided service account that grants * admin access to Firebase services. This credential can be used in the call - * to - * {@link - * https://firebase.google.com/docs/reference/admin/node/admin#.initializeApp - * `admin.initializeApp()`}. + * to {@link firebase-admin.app#initializeApp}. * * See - * {@link - * https://firebase.google.com/docs/admin/setup#initialize_the_sdk - * Initialize the SDK} + * {@link https://firebase.google.com/docs/admin/setup#initialize_the_sdk | Initialize the SDK} * for more details. * * @example @@ -130,27 +99,21 @@ export namespace credential { * * @param serviceAccountPathOrObject The path to a service * account key JSON file or an object representing a service account key. - * @param httpAgent Optional [HTTP Agent](https://nodejs.org/api/http.html#http_class_http_agent) + * @param httpAgent Optional {@link https://nodejs.org/api/http.html#http_class_http_agent | HTTP Agent} * to be used when retrieving access tokens from Google token servers. * - * @return A credential authenticated via the + * @returns A credential authenticated via the * provided service account that can be used to initialize an app. */ - export declare function cert( - serviceAccountPathOrObject: string | ServiceAccount, httpAgent?: Agent): Credential; + export const cert = certFn; /** * Returns a credential created from the provided refresh token that grants * admin access to Firebase services. This credential can be used in the call - * to - * {@link - * https://firebase.google.com/docs/reference/admin/node/admin#.initializeApp - * `admin.initializeApp()`}. + * to {@link firebase-admin.app#initializeApp}. * * See - * {@link - * https://firebase.google.com/docs/admin/setup#initialize_the_sdk - * Initialize the SDK} + * {@link https://firebase.google.com/docs/admin/setup#initialize_the_sdk | Initialize the SDK} * for more details. * * @example @@ -166,12 +129,11 @@ export namespace credential { * @param refreshTokenPathOrObject The path to a Google * OAuth2 refresh token JSON file or an object representing a Google OAuth2 * refresh token. - * @param httpAgent Optional [HTTP Agent](https://nodejs.org/api/http.html#http_class_http_agent) + * @param httpAgent Optional {@link https://nodejs.org/api/http.html#http_class_http_agent | HTTP Agent} * to be used when retrieving access tokens from Google token servers. * - * @return A credential authenticated via the + * @returns A credential authenticated via the * provided service account that can be used to initialize an app. */ - export declare function refreshToken( - refreshTokenPathOrObject: string | object, httpAgent?: Agent): Credential; + export const refreshToken = refreshTokenFn; } diff --git a/src/database/database-namespace.ts b/src/database/database-namespace.ts new file mode 100644 index 0000000000..cc2a1111fa --- /dev/null +++ b/src/database/database-namespace.ts @@ -0,0 +1,106 @@ +/*! + * Copyright 2021 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 * as rtdb from '@firebase/database-types'; +import { App } from '../app'; +import { Database as TDatabase } from './database'; + +/** + * Gets the {@link firebase-admin.database#Database} service for the default + * app or a given app. + * + * `admin.database()` can be called with no arguments to access the default + * app's `Database` service or as `admin.database(app)` to access the + * `Database` service associated with a specific app. + * + * `admin.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 = admin.database(); + * ``` + * + * @example + * ```javascript + * // Get the Database service for a specific app + * var otherDatabase = admin.database(app); + * ``` + * + * @param App whose `Database` service to + * return. If not provided, the default `Database` service will be returned. + * + * @returns The default `Database` service if no app + * is provided or the `Database` service associated with the provided app. + */ +export declare function database(app?: App): database.Database; + +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace database { + /** + * Type alias to {@link firebase-admin.database#Database}. + */ + export type Database = TDatabase; + + /** + * Type alias to {@link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot | DataSnapshot} + * type from the `@firebase/database` package. + */ + export type DataSnapshot = rtdb.DataSnapshot; + + /** + * Type alias to the {@link https://firebase.google.com/docs/reference/js/firebase.database#eventtype | EventType} + * type from the `@firebase/database` package. + */ + export type EventType = rtdb.EventType; + + /** + * Type alias to {@link https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect | OnDisconnect} + * type from the `@firebase/database` package. + */ + export type OnDisconnect = rtdb.OnDisconnect; + + /** + * Type alias to {@link https://firebase.google.com/docs/reference/js/firebase.database.Query | Query} + * type from the `@firebase/database` package. + */ + export type Query = rtdb.Query; + + /** + * Type alias to {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference | Reference} + * type from the `@firebase/database` package. + */ + export type Reference = rtdb.Reference; + + /** + * Type alias to {@link https://firebase.google.com/docs/reference/js/firebase.database.ThenableReference | + * ThenableReference} type from the `@firebase/database` package. + */ + export type ThenableReference = rtdb.ThenableReference; + + /** + * {@link https://firebase.google.com/docs/reference/js/firebase.database#enablelogging | enableLogging} + * function from the `@firebase/database` package. + */ + export declare const enableLogging: typeof rtdb.enableLogging; + + /** + * {@link https://firebase.google.com/docs/reference/js/firebase.database.ServerValue | ServerValue} + * constant from the `@firebase/database` package. + */ + export declare const ServerValue: rtdb.ServerValue; +} diff --git a/src/database/database-internal.ts b/src/database/database.ts similarity index 79% rename from src/database/database-internal.ts rename to src/database/database.ts index 4cec7175a7..dc17419d5e 100644 --- a/src/database/database-internal.ts +++ b/src/database/database.ts @@ -17,22 +17,53 @@ import { URL } from 'url'; import * as path from 'path'; -import { FirebaseApp } from '../firebase-app'; +import { FirebaseDatabase } from '@firebase/database-types'; import { FirebaseDatabaseError, AppErrorCodes, FirebaseAppError } from '../utils/error'; import { Database as DatabaseImpl } from '@firebase/database-compat/standalone'; -import { database } from './index'; +import { App } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; import * as validator from '../utils/validator'; import { AuthorizedHttpClient, HttpRequestConfig, HttpError } from '../utils/api-request'; import { getSdkVersion } from '../utils/index'; -import Database = database.Database; +/** + * The Firebase Database service interface. Extends the + * {@link https://firebase.google.com/docs/reference/js/firebase.database.Database | Database} + * interface provided by the `@firebase/database` package. + */ +export interface Database extends FirebaseDatabase { + /** + * Gets the currently applied security rules as a string. The return value consists of + * the rules source including comments. + * + * @returns A promise fulfilled with the rules as a raw string. + */ + getRules(): Promise; + + /** + * Gets the currently applied security rules as a parsed JSON object. Any comments in + * the original source are stripped away. + * + * @returns A promise fulfilled with the parsed rules object. + */ + getRulesJSON(): Promise; + + /** + * Sets the specified rules on the Firebase Realtime Database instance. If the rules source is + * specified as a string or a Buffer, it may include comments. + * + * @param source Source of the rules to apply. Must not be `null` or empty. + * @returns Resolves when the rules are set on the Realtime Database. + */ + setRules(source: string | Buffer | object): Promise; +} const TOKEN_REFRESH_THRESHOLD_MILLIS = 5 * 60 * 1000; export class DatabaseService { - private readonly appInternal: FirebaseApp; + private readonly appInternal: App; private tokenListener: (token: string) => void; private tokenRefreshTimeout: NodeJS.Timeout; @@ -40,7 +71,7 @@ export class DatabaseService { [dbUrl: string]: Database; } = {}; - constructor(app: FirebaseApp) { + constructor(app: App) { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseDatabaseError({ code: 'invalid-argument', @@ -50,12 +81,16 @@ export class DatabaseService { this.appInternal = app; } + private get firebaseApp(): FirebaseApp { + return this.app as FirebaseApp; + } + /** * @internal */ public delete(): Promise { if (this.tokenListener) { - this.appInternal.INTERNAL.removeAuthTokenListener(this.tokenListener); + this.firebaseApp.INTERNAL.removeAuthTokenListener(this.tokenListener); clearTimeout(this.tokenRefreshTimeout); } @@ -72,9 +107,9 @@ export class DatabaseService { /** * Returns the app associated with this DatabaseService instance. * - * @return The app associated with this DatabaseService instance. + * @returns The app associated with this DatabaseService instance. */ - get app(): FirebaseApp { + get app(): App { return this.appInternal; } @@ -109,7 +144,7 @@ export class DatabaseService { if (!this.tokenListener) { this.tokenListener = this.onTokenChange.bind(this); - this.appInternal.INTERNAL.addAuthTokenListener(this.tokenListener); + this.firebaseApp.INTERNAL.addAuthTokenListener(this.tokenListener); } return db; @@ -117,7 +152,7 @@ export class DatabaseService { // eslint-disable-next-line @typescript-eslint/no-unused-vars private onTokenChange(_: string): void { - const token = this.appInternal.INTERNAL.getCachedToken(); + const token = this.firebaseApp.INTERNAL.getCachedToken(); if (token) { const delayMillis = token.expirationTime - TOKEN_REFRESH_THRESHOLD_MILLIS - Date.now(); // If the new token is set to expire soon (unlikely), do nothing. Somebody will eventually @@ -131,7 +166,7 @@ export class DatabaseService { private scheduleTokenRefresh(delayMillis: number): void { clearTimeout(this.tokenRefreshTimeout); this.tokenRefreshTimeout = setTimeout(() => { - this.appInternal.INTERNAL.getToken(/*forceRefresh=*/ true) + this.firebaseApp.INTERNAL.getToken(/*forceRefresh=*/ true) .catch(() => { // Ignore the error since this might just be an intermittent failure. If we really cannot // refresh the token, an error will be logged once the existing token expires and we try @@ -163,7 +198,7 @@ class DatabaseRulesClient { private readonly dbUrl: string; private readonly httpClient: AuthorizedHttpClient; - constructor(app: FirebaseApp, dbUrl: string) { + constructor(app: App, dbUrl: string) { let parsedUrl = new URL(dbUrl); const emulatorHost = process.env.FIREBASE_DATABASE_EMULATOR_HOST; if (emulatorHost) { @@ -173,14 +208,14 @@ class DatabaseRulesClient { parsedUrl.pathname = path.join(parsedUrl.pathname, RULES_URL_PATH); this.dbUrl = parsedUrl.toString(); - this.httpClient = new AuthorizedHttpClient(app); + this.httpClient = new AuthorizedHttpClient(app as FirebaseApp); } /** * Gets the currently applied security rules as a string. The return value consists of * the rules source including comments. * - * @return A promise fulfilled with the rules as a raw string. + * @returns A promise fulfilled with the rules as a raw string. */ public getRules(): Promise { const req: HttpRequestConfig = { @@ -203,7 +238,7 @@ class DatabaseRulesClient { * Gets the currently applied security rules as a parsed JSON object. Any comments in * the original source are stripped away. * - * @return {Promise} A promise fulfilled with the parsed rules source. + * @returns {Promise} A promise fulfilled with the parsed rules source. */ public getRulesJSON(): Promise { const req: HttpRequestConfig = { @@ -226,7 +261,7 @@ class DatabaseRulesClient { * * @param {string|Buffer|object} source Source of the rules to apply. Must not be `null` * or empty. - * @return {Promise} Resolves when the rules are set on the Database. + * @returns {Promise} Resolves when the rules are set on the Database. */ public setRules(source: string | Buffer | object): Promise { if (!validator.isNonEmptyString(source) && diff --git a/src/database/index.ts b/src/database/index.ts index c2f30e0fe7..53a72f5fe4 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -14,84 +14,113 @@ * limitations under the License. */ -import { app } from '../firebase-namespace-api'; -import { ServerValue as sv } from '@firebase/database-compat/standalone'; +/** + * Firebase Realtime Database. + * + * @packageDocumentation + */ + import * as rtdb from '@firebase/database-types'; +import { + enableLogging as enableLoggingFunc, + ServerValue as serverValueConst, +} from '@firebase/database-compat/standalone'; + +import { App, getApp } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; +import { Database, DatabaseService } from './database'; + +export { Database }; +export { + DataSnapshot, + EventType, + OnDisconnect, + Query, + Reference, + ThenableReference, +} from '@firebase/database-types'; + +// TODO: Remove the following any-cast once the typins in @firebase/database-types are fixed. + +/** + * {@link https://firebase.google.com/docs/reference/js/firebase.database#enablelogging | enableLogging} + * function from the `@firebase/database` package. + */ +export const enableLogging: typeof rtdb.enableLogging = enableLoggingFunc as any; /** - * Gets the {@link database.Database `Database`} service for the default + * {@link https://firebase.google.com/docs/reference/js/firebase.database.ServerValue | ServerValue} + * constant from the `@firebase/database` package. + */ +export const ServerValue: rtdb.ServerValue = serverValueConst; + +/** + * Gets the {@link Database} service for the default * app or a given app. * - * `admin.database()` can be called with no arguments to access the default - * app's {@link database.Database `Database`} service or as - * `admin.database(app)` to access the - * {@link database.Database `Database`} service associated with a specific - * app. - * - * `admin.database` is also a namespace that can be used to access global - * constants and methods associated with the `Database` service. + * `getDatabase()` can be called with no arguments to access the default + * app's `Database` service or as `getDatabase(app)` to access the + * `Database` service associated with a specific app. * * @example * ```javascript * // Get the Database service for the default app - * var defaultDatabase = admin.database(); + * const defaultDatabase = getDatabase(); * ``` * * @example * ```javascript * // Get the Database service for a specific app - * var otherDatabase = admin.database(app); + * const otherDatabase = getDatabase(app); * ``` * * @param App whose `Database` service to * return. If not provided, the default `Database` service will be returned. * - * @return The default `Database` service if no app + * @returns The default `Database` service if no app * is provided or the `Database` service associated with the provided app. */ -export declare function database(app?: app.App): database.Database; - -/* eslint-disable @typescript-eslint/no-namespace */ -export namespace database { - export interface Database extends rtdb.FirebaseDatabase { - /** - * Gets the currently applied security rules as a string. The return value consists of - * the rules source including comments. - * - * @return A promise fulfilled with the rules as a raw string. - */ - getRules(): Promise; +export function getDatabase(app?: App): Database { + return getDatabaseInstance({ app }); +} - /** - * Gets the currently applied security rules as a parsed JSON object. Any comments in - * the original source are stripped away. - * - * @return A promise fulfilled with the parsed rules object. - */ - getRulesJSON(): Promise; +/** + * Gets the {@link Database} service for the default + * app or a given app. + * + * `getDatabaseWithUrl()` can be called with no arguments to access the default + * app's {@link Database} service or as `getDatabaseWithUrl(app)` to access the + * {@link Database} service associated with a specific app. + * + * @example + * ```javascript + * // Get the Database service for the default app + * const defaultDatabase = getDatabaseWithUrl('https://example.firebaseio.com'); + * ``` + * + * @example + * ```javascript + * // Get the Database service for a specific app + * const otherDatabase = getDatabaseWithUrl('https://example.firebaseio.com', app); + * ``` + * + * @param App whose `Database` service to + * return. If not provided, the default `Database` service will be returned. + * + * @returns The default `Database` service if no app + * is provided or the `Database` service associated with the provided app. + */ +export function getDatabaseWithUrl(url: string, app?: App): Database { + return getDatabaseInstance({ url, app }); +} - /** - * Sets the specified rules on the Firebase Realtime Database instance. If the rules source is - * specified as a string or a Buffer, it may include comments. - * - * @param source Source of the rules to apply. Must not be `null` or empty. - * @return Resolves when the rules are set on the Realtime Database. - */ - setRules(source: string | Buffer | object): Promise; +function getDatabaseInstance(options: { url?: string; app?: App }): Database { + let { app } = options; + if (typeof app === 'undefined') { + app = getApp(); } - /* eslint-disable @typescript-eslint/no-unused-vars */ - export import DataSnapshot = rtdb.DataSnapshot; - export import EventType = rtdb.EventType; - export import OnDisconnect = rtdb.OnDisconnect; - export import Query = rtdb.Query; - export import Reference = rtdb.Reference; - export import ThenableReference = rtdb.ThenableReference; - export import enableLogging = rtdb.enableLogging; - - /** - * [`ServerValue`](https://firebase.google.com/docs/reference/js/firebase.database.ServerValue) - * module from the `@firebase/database` package. - */ - export const ServerValue: rtdb.ServerValue = sv; + const firebaseApp: FirebaseApp = app as FirebaseApp; + const dbService = firebaseApp.getOrInitService('database', (app) => new DatabaseService(app)); + return dbService.getDatabase(options.url); } diff --git a/docgen/typedoc.js b/src/default-namespace.d.ts similarity index 70% rename from docgen/typedoc.js rename to src/default-namespace.d.ts index 788c9cc071..5405cc67c6 100644 --- a/docgen/typedoc.js +++ b/src/default-namespace.d.ts @@ -1,6 +1,5 @@ -/** - * @license - * Copyright 2019 Google Inc. +/*! + * Copyright 2020 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,13 +14,10 @@ * limitations under the License. */ -const options = { - includeDeclarations: true, - excludeExternals: true, - ignoreCompilerErrors: true, - name: 'Admin Node.js SDK', - mode: 'file', - hideGenerator: true -}; +/** + * Firebase namespaced API (legacy). + * + * @packageDocumentation + */ -module.exports = options; +export * from './firebase-namespace-api'; diff --git a/src/default-namespace.ts b/src/default-namespace.ts index d15f3cae02..12e855e2d8 100644 --- a/src/default-namespace.ts +++ b/src/default-namespace.ts @@ -15,9 +15,7 @@ * limitations under the License. */ -import { FirebaseNamespace } from './firebase-namespace'; - -const firebaseAdmin = new FirebaseNamespace(); +import { defaultNamespace as firebaseAdmin } from './app/firebase-namespace'; // Inject a circular default export to allow users to use both: // diff --git a/src/firebase-namespace-api.ts b/src/firebase-namespace-api.ts index 509a2d1d18..185fbfd53f 100644 --- a/src/firebase-namespace-api.ts +++ b/src/firebase-namespace-api.ts @@ -1,5 +1,5 @@ /*! - * Copyright 2020 Google Inc. + * Copyright 2021 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,164 +14,22 @@ * limitations under the License. */ -import { Agent } from 'http'; -import { appCheck } from './app-check/index'; -import { auth } from './auth/index'; -import { credential } from './credential/index'; -import { database } from './database/index'; -import { firestore } from './firestore/index'; -import { installations } from './installations/index'; -import { instanceId } from './instance-id/index'; -import { machineLearning } from './machine-learning/index'; -import { messaging } from './messaging/index'; -import { projectManagement } from './project-management/index'; -import { remoteConfig } from './remote-config/index'; -import { securityRules } from './security-rules/index'; -import { storage } from './storage/index'; +import { appCheck } from './app-check/app-check-namespace'; +import { auth } from './auth/auth-namespace'; +import { database } from './database/database-namespace'; +import { firestore } from './firestore/firestore-namespace'; +import { instanceId } from './instance-id/instance-id-namespace'; +import { installations } from './installations/installations-namespace'; +import { machineLearning } from './machine-learning/machine-learning-namespace'; +import { messaging } from './messaging/messaging-namespace'; +import { projectManagement } from './project-management/project-management-namespace'; +import { remoteConfig } from './remote-config/remote-config-namespace'; +import { securityRules } from './security-rules/security-rules-namespace'; +import { storage } from './storage/storage-namespace'; -/** - * `FirebaseError` is a subclass of the standard JavaScript `Error` object. In - * addition to a message string and stack trace, it contains a string code. - */ -export interface FirebaseError { - - /** - * Error codes are strings using the following format: `"service/string-code"`. - * Some examples include `"auth/invalid-uid"` and - * `"messaging/invalid-recipient"`. - * - * 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; - - /** - * A string value containing the execution backtrace when the error originally - * occurred. - * - * 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. - */ - stack?: string; - - /** - * @return A JSON-serializable representation of this object. - */ - toJSON(): object; -} - -/** - * Composite type which includes both a `FirebaseError` object and an index - * which can be used to get the errored item. - * - * @example - * ```javascript - * var registrationTokens = [token1, token2, token3]; - * admin.messaging().subscribeToTopic(registrationTokens, 'topic-name') - * .then(function(response) { - * if (response.failureCount > 0) { - * console.log("Following devices unsucessfully subscribed to topic:"); - * response.errors.forEach(function(error) { - * var invalidToken = registrationTokens[error.index]; - * console.log(invalidToken, error.error); - * }); - * } else { - * console.log("All devices successfully subscribed to topic:", response); - * } - * }) - * .catch(function(error) { - * console.log("Error subscribing to topic:", error); - * }); - *``` - */ -export interface FirebaseArrayIndexError { - - /** - * The index of the errored item within the original array passed as part of the - * called API method. - */ - index: number; - - /** - * The error object. - */ - error: FirebaseError; -} +import { App as AppCore, AppOptions } from './app/index'; -/** - * Available options to pass to [`initializeApp()`](admin#.initializeApp). - */ -export interface AppOptions { - - /** - * A {@link credential.Credential `Credential`} object used to - * authenticate the Admin SDK. - * - * See [Initialize the SDK](/docs/admin/setup#initialize_the_sdk) for detailed - * documentation and code samples. - */ - credential?: credential.Credential; - - /** - * The object to use as the [`auth`](/docs/reference/security/database/#auth) - * variable in your Realtime Database Rules when the Admin SDK reads from or - * writes to the Realtime Database. This allows you to downscope the Admin SDK - * from its default full read and write privileges. - * - * You can pass `null` to act as an unauthenticated client. - * - * See - * [Authenticate with limited privileges](/docs/database/admin/start#authenticate-with-limited-privileges) - * for detailed documentation and code samples. - */ - databaseAuthVariableOverride?: object | null; - - /** - * The URL of the Realtime Database from which to read and write data. - */ - databaseURL?: string; - - /** - * The ID of the service account to be used for signing custom tokens. This - * can be found in the `client_email` field of a service account JSON file. - */ - serviceAccountId?: string; - - /** - * The name of the Google Cloud Storage bucket used for storing application data. - * Use only the bucket name without any prefixes or additions (do *not* prefix - * the name with "gs://"). - */ - storageBucket?: string; - - /** - * The ID of the Google Cloud project associated with the App. - */ - projectId?: string; - - /** - * An [HTTP Agent](https://nodejs.org/api/http.html#http_class_http_agent) - * to be used when making outgoing HTTP calls. This Agent instance is used - * by all services that make REST calls (e.g. `auth`, `messaging`, - * `projectManagement`). - * - * Realtime Database and Firestore use other means of communicating with - * the backend servers, so they do not use this HTTP Agent. `Credential` - * instances also do not use this HTTP Agent, but instead support - * specifying an HTTP Agent in the corresponding factory methods. - */ - httpAgent?: Agent; -} +export { AppOptions, FirebaseError, FirebaseArrayIndexError } from './app/index'; // eslint-disable-next-line @typescript-eslint/no-namespace export namespace app { @@ -185,45 +43,7 @@ export namespace app { * `admin.initializeApp()`} * to create an app. */ - export interface App { - - /** - * The (read-only) name for this app. - * - * The default app's name is `"[DEFAULT]"`. - * - * @example - * ```javascript - * // The default app's name is "[DEFAULT]" - * admin.initializeApp(defaultAppConfig); - * console.log(admin.app().name); // "[DEFAULT]" - * ``` - * - * @example - * ```javascript - * // A named app's name is what you provide to initializeApp() - * var otherApp = admin.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 - * https://firebase.google.com/docs/reference/admin/node/admin#.initializeApp - * `admin.initializeApp()`}. - * - * @example - * ```javascript - * var app = admin.initializeApp(config); - * console.log(app.options.credential === config.credential); // true - * console.log(app.options.databaseURL === config.databaseURL); // true - * ``` - */ - options: AppOptions; - + export interface App extends AppCore { appCheck(): appCheck.AppCheck; auth(): auth.Auth; database(url?: string): database.Database; @@ -259,6 +79,20 @@ export namespace app { } } +export * from './credential/index'; +export { appCheck } from './app-check/app-check-namespace'; +export { auth } from './auth/auth-namespace'; +export { database } from './database/database-namespace'; +export { firestore } from './firestore/firestore-namespace'; +export { instanceId } from './instance-id/instance-id-namespace'; +export { installations } from './installations/installations-namespace'; +export { machineLearning } from './machine-learning/machine-learning-namespace'; +export { messaging } from './messaging/messaging-namespace'; +export { projectManagement } from './project-management/project-management-namespace'; +export { remoteConfig } from './remote-config/remote-config-namespace'; +export { securityRules } from './security-rules/security-rules-namespace'; +export { storage } from './storage/storage-namespace'; + // Declare other top-level members of the admin namespace below. Unfortunately, there's no // compile-time mechanism to ensure that the FirebaseNamespace class actually provides these // signatures. But this part of the API is quite small and stable. It should be easy enough to diff --git a/src/firestore/firestore-internal.ts b/src/firestore/firestore-internal.ts index 2188ec223a..b883251d41 100644 --- a/src/firestore/firestore-internal.ts +++ b/src/firestore/firestore-internal.ts @@ -15,20 +15,20 @@ * limitations under the License. */ -import { FirebaseApp } from '../firebase-app'; import { FirebaseFirestoreError } from '../utils/error'; -import { ServiceAccountCredential, isApplicationDefault } from '../credential/credential-internal'; +import { ServiceAccountCredential, isApplicationDefault } from '../app/credential-internal'; import { Firestore, Settings } from '@google-cloud/firestore'; import * as validator from '../utils/validator'; import * as utils from '../utils/index'; +import { App } from '../app'; export class FirestoreService { - private appInternal: FirebaseApp; + private appInternal: App; private firestoreClient: Firestore; - constructor(app: FirebaseApp) { + constructor(app: App) { this.firestoreClient = initFirestore(app); this.appInternal = app; } @@ -36,9 +36,9 @@ export class FirestoreService { /** * Returns the app associated with this Storage instance. * - * @return {FirebaseApp} The app associated with this Storage instance. + * @returns The app associated with this Storage instance. */ - get app(): FirebaseApp { + get app(): App { return this.appInternal; } @@ -47,7 +47,7 @@ export class FirestoreService { } } -export function getFirestoreOptions(app: FirebaseApp): Settings { +export function getFirestoreOptions(app: App): Settings { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseFirestoreError({ code: 'invalid-argument', @@ -85,7 +85,7 @@ export function getFirestoreOptions(app: FirebaseApp): Settings { }); } -function initFirestore(app: FirebaseApp): Firestore { +function initFirestore(app: App): Firestore { const options = getFirestoreOptions(app); let firestoreDatabase: typeof Firestore; try { diff --git a/src/firestore/firestore-namespace.ts b/src/firestore/firestore-namespace.ts new file mode 100644 index 0000000000..5f1cef8cfa --- /dev/null +++ b/src/firestore/firestore-namespace.ts @@ -0,0 +1,57 @@ +/*! + * Copyright 2021 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 * as _firestore from '@google-cloud/firestore'; +import { App } from '../app'; + +export declare function firestore(app?: App): _firestore.Firestore; + +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace firestore { + /* eslint-disable @typescript-eslint/no-unused-vars */ + // See https://github.com/typescript-eslint/typescript-eslint/issues/363 + export import v1beta1 = _firestore.v1beta1; + export import v1 = _firestore.v1; + + export import BulkWriter = _firestore.BulkWriter; + export import BulkWriterOptions = _firestore.BulkWriterOptions; + export import CollectionGroup = _firestore.CollectionGroup; + export import CollectionReference = _firestore.CollectionReference; + export import DocumentChangeType = _firestore.DocumentChangeType; + export import DocumentData = _firestore.DocumentData; + export import DocumentReference = _firestore.DocumentReference; + export import DocumentSnapshot = _firestore.DocumentSnapshot; + export import FieldPath = _firestore.FieldPath; + export import FieldValue = _firestore.FieldValue; + export import Firestore = _firestore.Firestore; + export import FirestoreDataConverter = _firestore.FirestoreDataConverter; + export import GeoPoint = _firestore.GeoPoint; + export import GrpcStatus = _firestore.GrpcStatus; + export import Precondition = _firestore.Precondition; + export import Query = _firestore.Query; + export import QueryDocumentSnapshot = _firestore.QueryDocumentSnapshot; + export import QueryPartition = _firestore.QueryPartition; + export import QuerySnapshot = _firestore.QuerySnapshot; + export import ReadOptions = _firestore.ReadOptions; + export import Settings = _firestore.Settings; + export import Timestamp = _firestore.Timestamp; + export import Transaction = _firestore.Transaction; + export import UpdateData = _firestore.UpdateData; + export import WriteBatch = _firestore.WriteBatch; + export import WriteResult = _firestore.WriteResult; + + export import setLogFunction = _firestore.setLogFunction; +} diff --git a/src/firestore/index.ts b/src/firestore/index.ts index 4efe99e053..bc52480a44 100644 --- a/src/firestore/index.ts +++ b/src/firestore/index.ts @@ -14,44 +14,82 @@ * limitations under the License. */ -import { app } from '../firebase-namespace-api'; -import * as _firestore from '@google-cloud/firestore'; +/** + * Cloud Firestore. + * + * @packageDocumentation + */ -export declare function firestore(app?: app.App): _firestore.Firestore; +import { Firestore } from '@google-cloud/firestore'; +import { App, getApp } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; +import { FirestoreService } from './firestore-internal'; -/* eslint-disable @typescript-eslint/no-namespace */ -export namespace firestore { - /* eslint-disable @typescript-eslint/no-unused-vars */ - // See https://github.com/typescript-eslint/typescript-eslint/issues/363 - export import v1beta1 = _firestore.v1beta1; - export import v1 = _firestore.v1; +export { + BulkWriter, + BulkWriterOptions, + CollectionGroup, + CollectionReference, + DocumentChangeType, + DocumentData, + DocumentReference, + DocumentSnapshot, + FieldPath, + FieldValue, + Firestore, + FirestoreDataConverter, + GeoPoint, + GrpcStatus, + Precondition, + Query, + QueryDocumentSnapshot, + QueryPartition, + QuerySnapshot, + ReadOptions, + Settings, + Timestamp, + Transaction, + UpdateData, + WriteBatch, + WriteResult, + v1, + setLogFunction, +} from '@google-cloud/firestore'; - export import BulkWriter = _firestore.BulkWriter; - export import BulkWriterOptions = _firestore.BulkWriterOptions; - export import CollectionGroup = _firestore.CollectionGroup; - export import CollectionReference = _firestore.CollectionReference; - export import DocumentChangeType = _firestore.DocumentChangeType; - export import DocumentData = _firestore.DocumentData; - export import DocumentReference = _firestore.DocumentReference; - export import DocumentSnapshot = _firestore.DocumentSnapshot; - export import FieldPath = _firestore.FieldPath; - export import FieldValue = _firestore.FieldValue; - export import Firestore = _firestore.Firestore; - export import FirestoreDataConverter = _firestore.FirestoreDataConverter; - export import GeoPoint = _firestore.GeoPoint; - export import GrpcStatus = _firestore.GrpcStatus; - export import Precondition = _firestore.Precondition; - export import Query = _firestore.Query; - export import QueryDocumentSnapshot = _firestore.QueryDocumentSnapshot; - export import QueryPartition = _firestore.QueryPartition; - export import QuerySnapshot = _firestore.QuerySnapshot; - export import ReadOptions = _firestore.ReadOptions; - export import Settings = _firestore.Settings; - export import Timestamp = _firestore.Timestamp; - export import Transaction = _firestore.Transaction; - export import UpdateData = _firestore.UpdateData; - export import WriteBatch = _firestore.WriteBatch; - export import WriteResult = _firestore.WriteResult; +/** + * Gets the {@link https://googleapis.dev/nodejs/firestore/latest/Firestore.html | Firestore} + * service for the default app or a given app. + * + * `getFirestore()` can be called with no arguments to access the default + * app's `Firestore` service or as `getFirestore(app)` to access the + * `Firestore` service associated with a specific app. + * + * @example + * ```javascript + * // Get the Firestore service for the default app + * const defaultFirestore = getFirestore(); + * ``` + * + * @example + * ```javascript + * // Get the Firestore service for a specific app + * const otherFirestore = getFirestore(app); + * ``` + * + * @param App whose `Firestore` service to + * return. If not provided, the default `Firestore` service will be returned. + * + * @returns The default {@link https://googleapis.dev/nodejs/firestore/latest/Firestore.html | Firestore} + * service if no app is provided or the `Firestore` service associated with the + * provided app. + */ +export function getFirestore(app?: App): Firestore { + if (typeof app === 'undefined') { + app = getApp(); + } - export import setLogFunction = _firestore.setLogFunction; + const firebaseApp: FirebaseApp = app as FirebaseApp; + const firestoreService = firebaseApp.getOrInitService( + 'firestore', (app) => new FirestoreService(app)); + return firestoreService.client; } diff --git a/src/index.d.ts b/src/index.d.ts index 1807b1e079..b893c88183 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import * as admin from './firebase-namespace'; +import * as admin from './default-namespace'; declare module 'firebase-admin' { } diff --git a/src/installations/index.ts b/src/installations/index.ts index 373b904c9c..83efccae9c 100644 --- a/src/installations/index.ts +++ b/src/installations/index.ts @@ -14,72 +14,50 @@ * limitations under the License. */ -import { app } from '../firebase-namespace-api'; - /** - * Gets the {@link installations.Installations `Installations`} service for the - * default app or a given app. - * - * `admin.installations()` can be called with no arguments to access the default - * app's {@link installations.Installations `Installations`} service or as - * `admin.installations(app)` to access the - * {@link installations.Installations `Installations`} service associated with a - * specific app. - * - * @example - * ```javascript - * // Get the Installations service for the default app - * var defaultInstallations = admin.installations(); - * ``` - * - * @example - * ```javascript - * // Get the Installations service for a given app - * var otherInstallations = admin.installations(otherApp); - *``` + * Firebase Instance ID service. * - * @param app Optional app whose `Installations` service to - * return. If not provided, the default `Installations` service is - * returned. - * - * @return The default `Installations` service if - * no app is provided or the `Installations` service associated with the - * provided app. + * @packageDocumentation */ -export declare function installations(app?: app.App): installations.Installations; -/* eslint-disable @typescript-eslint/no-namespace */ -export namespace installations { - /** - * Gets the {@link Installations `Installations`} service for the - * current app. - * - * @example - * ```javascript - * var installations = app.installations(); - * // The above is shorthand for: - * // var installations = admin.installations(app); - * ``` - * - * @return The `Installations` service for the - * current app. - */ - export interface Installations { - app: app.App; +import { App, getApp } from '../app/index'; +import { Installations } from './installations'; +import { FirebaseApp } from '../app/firebase-app'; + +export { Installations }; - /** - * Deletes the specified installation ID and the associated data from Firebase. - * - * Note that Google Analytics for Firebase uses its own form of Instance ID to - * keep track of analytics data. Therefore deleting a Firebase installation ID does - * not delete Analytics data. See - * [Delete a Firebase installation](/docs/projects/manage-installations#delete-installation) - * for more information. - * - * @param fid The Firebase installation ID to be deleted. - * - * @return A promise fulfilled when the installation ID is deleted. - */ - deleteInstallation(fid: string): Promise; +/** + * Gets the {@link Installations} service for the default app or a given app. + * + * `getInstallations()` can be called with no arguments to access the default + * app's `Installations` service or as `getInstallations(app)` to access the + * `Installations` service associated with a specific app. + * + * @example + * ```javascript + * // Get the Installations service for the default app + * const defaultInstallations = getInstallations(); + * ``` + * + * @example + * ```javascript + * // Get the Installations service for a given app + * const otherInstallations = getInstallations(otherApp); + *``` + * + * @param app Optional app whose `Installations` service to + * return. If not provided, the default `Installations` service will be + * returned. + * + * @returns The default `Installations` service if + * no app is provided or the `Installations` service associated with the + * provided app. + */ +export function getInstallations(app?: App): Installations { + if (typeof app === 'undefined') { + app = getApp(); } + + const firebaseApp: FirebaseApp = app as FirebaseApp; + return firebaseApp.getOrInitService('installations', (app) => new Installations(app)); } diff --git a/src/installations/installations-namespace.ts b/src/installations/installations-namespace.ts new file mode 100644 index 0000000000..8a2495c788 --- /dev/null +++ b/src/installations/installations-namespace.ts @@ -0,0 +1,58 @@ +/*! + * Copyright 2021 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 { App } from '../app/index'; +import { Installations as TInstallations } from './installations'; + +/** + * Gets the {@link installations.Installations `Installations`} service for the + * default app or a given app. + * + * `admin.installations()` can be called with no arguments to access the default + * app's {@link installations.Installations `Installations`} service or as + * `admin.installations(app)` to access the + * {@link installations.Installations `Installations`} service associated with a + * specific app. + * + * @example + * ```javascript + * // Get the Installations service for the default app + * var defaultInstallations = admin.installations(); + * ``` + * + * @example + * ```javascript + * // Get the Installations service for a given app + * var otherInstallations = admin.installations(otherApp); + *``` + * + * @param app Optional app whose `Installations` service to + * return. If not provided, the default `Installations` service is + * returned. + * + * @returns The default `Installations` service if + * no app is provided or the `Installations` service associated with the + * provided app. + */ +export declare function installations(app?: App): installations.Installations; + +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace installations { + /** + * Type alias to {@link firebase-admin.installations#Installations}. + */ + export type Installations = TInstallations; +} diff --git a/src/installations/installations-request-handler.ts b/src/installations/installations-request-handler.ts index 299fd3136e..44d17e41d4 100644 --- a/src/installations/installations-request-handler.ts +++ b/src/installations/installations-request-handler.ts @@ -15,7 +15,8 @@ * limitations under the License. */ -import { FirebaseApp } from '../firebase-app'; +import { App } from '../app/index'; +import { FirebaseApp } from '../app/firebase-app'; import { FirebaseInstallationsError, InstallationsClientErrorCode } from '../utils/error'; import { ApiSettings, AuthorizedHttpClient, HttpRequestConfig, HttpError, @@ -58,8 +59,8 @@ export class FirebaseInstallationsRequestHandler { * * @constructor */ - constructor(private readonly app: FirebaseApp) { - this.httpClient = new AuthorizedHttpClient(app); + constructor(private readonly app: App) { + this.httpClient = new AuthorizedHttpClient(app as FirebaseApp); } public deleteInstallation(fid: string): Promise { @@ -76,7 +77,7 @@ export class FirebaseInstallationsRequestHandler { * Invokes the request handler based on the API settings object passed. * * @param apiSettings The API endpoint settings to apply to request and response. - * @return A promise that resolves when the request is complete. + * @returns A promise that resolves when the request is complete. */ private invokeRequestHandler(apiSettings: ApiSettings): Promise { return this.getPathPrefix() diff --git a/src/installations/installations.ts b/src/installations/installations.ts index a55e5af295..98ab0f0ceb 100644 --- a/src/installations/installations.ts +++ b/src/installations/installations.ts @@ -14,27 +14,25 @@ * limitations under the License. */ -import { FirebaseApp } from '../firebase-app'; +import { App } from '../app/index'; import { FirebaseInstallationsError, InstallationsClientErrorCode } from '../utils/error'; import { FirebaseInstallationsRequestHandler } from './installations-request-handler'; -import { installations } from './index'; import * as validator from '../utils/validator'; -import InstallationsInterface = installations.Installations; - /** * The `Installations` service for the current app. */ -export class Installations implements InstallationsInterface { +export class Installations { - private app_: FirebaseApp; + private app_: App; private requestHandler: FirebaseInstallationsRequestHandler; /** * @param app The app for this Installations service. * @constructor + * @internal */ - constructor(app: FirebaseApp) { + constructor(app: App) { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseInstallationsError( InstallationsClientErrorCode.INVALID_ARGUMENT, @@ -51,7 +49,7 @@ export class Installations implements InstallationsInterface { * * @param fid The Firebase installation ID to be deleted. * - * @return A promise fulfilled when the installation ID is deleted. + * @returns A promise fulfilled when the installation ID is deleted. */ public deleteInstallation(fid: string): Promise { return this.requestHandler.deleteInstallation(fid); @@ -60,9 +58,9 @@ export class Installations implements InstallationsInterface { /** * Returns the app associated with this Installations instance. * - * @return The app associated with this Installations instance. + * @returns The app associated with this Installations instance. */ - get app(): FirebaseApp { + get app(): App { return this.app_; } } diff --git a/src/instance-id/index.ts b/src/instance-id/index.ts index a5ea77eb8b..22fff0a89a 100644 --- a/src/instance-id/index.ts +++ b/src/instance-id/index.ts @@ -14,28 +14,39 @@ * limitations under the License. */ -import { app } from '../firebase-namespace-api'; +/** + * Firebase Instance ID service. + * + * @packageDocumentation + */ + +import { App, getApp } from '../app/index'; +import { InstanceId } from './instance-id'; +import { FirebaseApp } from '../app/firebase-app'; + +export { InstanceId }; /** - * Gets the {@link instanceId.InstanceId `InstanceId`} service for the - * default app or a given app. + * Gets the {@link InstanceId} service for the default app or a given app. * - * `admin.instanceId()` can be called with no arguments to access the default - * app's {@link instanceId.InstanceId `InstanceId`} service or as - * `admin.instanceId(app)` to access the - * {@link instanceId.InstanceId `InstanceId`} service associated with a - * specific app. + * This API is deprecated. Developers are advised to use the + * {@link firebase-admin.installations#getInstallations} + * API to delete their instance IDs and Firebase installation IDs. + * + * `getInstanceId()` can be called with no arguments to access the default + * app's `InstanceId` service or as `getInstanceId(app)` to access the + * `InstanceId` service associated with a specific app. * * @example * ```javascript * // Get the Instance ID service for the default app - * var defaultInstanceId = admin.instanceId(); + * const defaultInstanceId = getInstanceId(); * ``` * * @example * ```javascript * // Get the Instance ID service for a given app - * var otherInstanceId = admin.instanceId(otherApp); + * const otherInstanceId = getInstanceId(otherApp); *``` * * This API is deprecated. Developers are advised to use the `admin.installations()` @@ -45,43 +56,17 @@ import { app } from '../firebase-namespace-api'; * return. If not provided, the default `InstanceId` service will be * returned. * - * @return The default `InstanceId` service if + * @returns The default `InstanceId` service if * no app is provided or the `InstanceId` service associated with the * provided app. * * @deprecated */ -export declare function instanceId(app?: app.App): instanceId.InstanceId; - -/* eslint-disable @typescript-eslint/no-namespace */ -export namespace instanceId { - /** - * The {@link InstanceId `InstanceId`} service for the - * current app. - * - * @deprecated - */ - export interface InstanceId { - app: app.App; - - /** - * Deletes the specified instance ID and the associated data from Firebase. - * - * Note that Google Analytics for Firebase uses its own form of Instance ID to - * keep track of analytics data. Therefore deleting a Firebase Instance ID does - * not delete Analytics data. See - * [Delete an Instance ID](/support/privacy/manage-iids#delete_an_instance_id) - * for more information. - * - * This API is deprecated. Developers are advised to use the `Installations.deleteInstallation()` - * API instead. - * - * @param instanceId The instance ID to be deleted. - * - * @return A promise fulfilled when the instance ID is deleted. - * - * @deprecated - */ - deleteInstanceId(instanceId: string): Promise; +export function getInstanceId(app?: App): InstanceId { + if (typeof app === 'undefined') { + app = getApp(); } + + const firebaseApp: FirebaseApp = app as FirebaseApp; + return firebaseApp.getOrInitService('instanceId', (app) => new InstanceId(app)); } diff --git a/src/instance-id/instance-id-namespace.ts b/src/instance-id/instance-id-namespace.ts new file mode 100644 index 0000000000..9d05874a7b --- /dev/null +++ b/src/instance-id/instance-id-namespace.ts @@ -0,0 +1,40 @@ +import { App } from '../app/index'; +import { InstanceId as TInstanceId } from './instance-id'; + +/** + * Gets the {@link firebase-admin.instance-id#InstanceId} service for the + * default app or a given app. + * + * `admin.instanceId()` can be called with no arguments to access the default + * app's `InstanceId` service or as `admin.instanceId(app)` to access the + * `InstanceId` service associated with a specific app. + * + * @example + * ```javascript + * // Get the Instance ID service for the default app + * var defaultInstanceId = admin.instanceId(); + * ``` + * + * @example + * ```javascript + * // Get the Instance ID service for a given app + * var otherInstanceId = admin.instanceId(otherApp); + *``` + * + * @param app Optional app whose `InstanceId` service to + * return. If not provided, the default `InstanceId` service will be + * returned. + * + * @returns The default `InstanceId` service if + * no app is provided or the `InstanceId` service associated with the + * provided app. + */ +export declare function instanceId(app?: App): instanceId.InstanceId; + +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace instanceId { + /** + * Type alias to {@link firebase-admin.instance-id#InstanceId}. + */ + export type InstanceId = TInstanceId; +} diff --git a/src/instance-id/instance-id.ts b/src/instance-id/instance-id.ts index 80937ffc0c..6bd92d991d 100644 --- a/src/instance-id/instance-id.ts +++ b/src/instance-id/instance-id.ts @@ -14,42 +14,34 @@ * limitations under the License. */ -import { FirebaseApp } from '../firebase-app'; +import { getInstallations } from '../installations'; +import { App } from '../app/index'; import { - FirebaseInstallationsError, FirebaseInstanceIdError, InstallationsClientErrorCode, InstanceIdClientErrorCode, + FirebaseInstallationsError, FirebaseInstanceIdError, + InstallationsClientErrorCode, InstanceIdClientErrorCode, } from '../utils/error'; -import { instanceId } from './index'; import * as validator from '../utils/validator'; -import InstanceIdInterface = instanceId.InstanceId; - /** - * Gets the {@link InstanceId `InstanceId`} service for the - * current app. - * - * @example - * ```javascript - * var instanceId = app.instanceId(); - * // The above is shorthand for: - * // var instanceId = admin.instanceId(app); - * ``` + * The `InstanceId` service enables deleting the Firebase instance IDs + * associated with Firebase client app instances. * - * @return The `InstanceId` service for the - * current app. + * @deprecated */ -export class InstanceId implements InstanceIdInterface { +export class InstanceId { - private app_: FirebaseApp; + private app_: App; /** * @param app The app for this InstanceId service. * @constructor + * @internal */ - constructor(app: FirebaseApp) { + constructor(app: App) { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseInstanceIdError( InstanceIdClientErrorCode.INVALID_ARGUMENT, - 'First argument passed to admin.instanceId() must be a valid Firebase app instance.', + 'First argument passed to instanceId() must be a valid Firebase app instance.', ); } @@ -62,15 +54,16 @@ export class InstanceId implements InstanceIdInterface { * Note that Google Analytics for Firebase uses its own form of Instance ID to * keep track of analytics data. Therefore deleting a Firebase Instance ID does * not delete Analytics data. See - * [Delete an Instance ID](/support/privacy/manage-iids#delete_an_instance_id) + * {@link https://firebase.google.com/support/privacy/manage-iids#delete_an_instance_id | + * Delete an Instance ID} * for more information. * * @param instanceId The instance ID to be deleted. * - * @return A promise fulfilled when the instance ID is deleted. + * @returns A promise fulfilled when the instance ID is deleted. */ public deleteInstanceId(instanceId: string): Promise { - return this.app.installations().deleteInstallation(instanceId) + return getInstallations(this.app).deleteInstallation(instanceId) .catch((err) => { if (err instanceof FirebaseInstallationsError) { let code = err.code.replace('installations/', ''); @@ -88,9 +81,9 @@ export class InstanceId implements InstanceIdInterface { /** * Returns the app associated with this InstanceId instance. * - * @return The app associated with this InstanceId instance. + * @returns The app associated with this InstanceId instance. */ - get app(): FirebaseApp { + get app(): App { return this.app_; } } diff --git a/src/machine-learning/index.ts b/src/machine-learning/index.ts index 7e5a8078cd..a3bf1d8f8a 100644 --- a/src/machine-learning/index.ts +++ b/src/machine-learning/index.ts @@ -14,269 +14,61 @@ * limitations under the License. */ -import { app } from '../firebase-namespace-api'; +/** + * Firebase Machine Learning. + * + * @packageDocumentation + */ + +import { App, getApp } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; +import { MachineLearning } from './machine-learning'; + +export { + MachineLearning, + ListModelsResult, + Model, + TFLiteModel, +} from './machine-learning'; +export { + AutoMLTfliteModelOptions, + GcsTfliteModelOptions, + ListModelsOptions, + ModelOptions, + ModelOptionsBase, +} from './machine-learning-api-client'; /** - * Gets the {@link machineLearning.MachineLearning `MachineLearning`} service for the - * default app or a given app. + * Gets the {@link MachineLearning} service for the default app or a given app. * - * `admin.machineLearning()` can be called with no arguments to access the - * default app's {@link machineLearning.MachineLearning - * `MachineLearning`} service or as `admin.machineLearning(app)` to access - * the {@link machineLearning.MachineLearning `MachineLearning`} - * service associated with a specific app. + * `getMachineLearning()` can be called with no arguments to access the + * default app's {`MachineLearning` service or as `getMachineLearning(app)` to access + * the `MachineLearning` service associated with a specific app. * * @example * ```javascript * // Get the MachineLearning service for the default app - * var defaultMachineLearning = admin.machineLearning(); + * const defaultMachineLearning = getMachineLearning(); * ``` * * @example * ```javascript * // Get the MachineLearning service for a given app - * var otherMachineLearning = admin.machineLearning(otherApp); + * const otherMachineLearning = getMachineLearning(otherApp); * ``` * * @param app Optional app whose `MachineLearning` service to * return. If not provided, the default `MachineLearning` service * will be returned. * - * @return The default `MachineLearning` service if no app is provided or the + * @returns The default `MachineLearning` service if no app is provided or the * `MachineLearning` service associated with the provided app. */ -export declare function machineLearning(app?: app.App): machineLearning.MachineLearning; - -/* eslint-disable @typescript-eslint/no-namespace */ -export namespace machineLearning { - /** - * Firebase ML Model input objects - */ - export interface ModelOptionsBase { - displayName?: string; - tags?: string[]; - } - - export interface GcsTfliteModelOptions extends ModelOptionsBase { - tfliteModel: { - gcsTfliteUri: string; - }; - } - - export interface AutoMLTfliteModelOptions extends ModelOptionsBase { - tfliteModel: { - automlModel: string; - }; - } - - export type ModelOptions = ModelOptionsBase | GcsTfliteModelOptions | AutoMLTfliteModelOptions; - - /** - * A TensorFlow Lite Model output object - * - * One of either the `gcsTfliteUri` or `automlModel` properties will be - * defined. - */ - export interface TFLiteModel { - /** The size of the model. */ - readonly sizeBytes: number; - - /** The URI from which the model was originally provided to Firebase. */ - readonly gcsTfliteUri?: string; - /** - * The AutoML model reference from which the model was originally provided - * to Firebase. - */ - readonly automlModel?: string; +export function getMachineLearning(app?: App): MachineLearning { + if (typeof app === 'undefined') { + app = getApp(); } - /** - * A Firebase ML Model output object - */ - export interface Model { - /** The ID of the model. */ - readonly modelId: string; - - /** - * The model's name. This is the name you use from your app to load the - * model. - */ - readonly displayName: string; - - /** - * The model's tags, which can be used to group or filter models in list - * operations. - */ - readonly tags?: string[]; - - /** The timestamp of the model's creation. */ - readonly createTime: string; - - /** The timestamp of the model's most recent update. */ - readonly updateTime: string; - - /** Error message when model validation fails. */ - readonly validationError?: string; - - /** True if the model is published. */ - readonly published: boolean; - - /** - * The ETag identifier of the current version of the model. This value - * changes whenever you update any of the model's properties. - */ - readonly etag: string; - - /** - * The hash of the model's `tflite` file. This value changes only when - * you upload a new TensorFlow Lite model. - */ - readonly modelHash?: string; - - /** - * True if the model is locked by a server-side operation. You can't make - * changes to a locked model. See {@link waitForUnlocked `waitForUnlocked()`}. - */ - readonly locked: boolean; - - /** - * Wait for the model to be unlocked. - * - * @param {number} maxTimeMillis The maximum time in milliseconds to wait. - * If not specified, a default maximum of 2 minutes is used. - * - * @return {Promise} A promise that resolves when the model is unlocked - * or the maximum wait time has passed. - */ - waitForUnlocked(maxTimeMillis?: number): Promise; - - /** - * Return the model as a JSON object. - */ - toJSON(): {[key: string]: any}; - - /** Metadata about the model's TensorFlow Lite model file. */ - readonly tfliteModel?: TFLiteModel; - } - - /** - * Interface representing options for listing Models. - */ - export interface ListModelsOptions { - /** - * An expression that specifies how to filter the results. - * - * Examples: - * - * ``` - * display_name = your_model - * display_name : experimental_* - * tags: face_detector AND tags: experimental - * state.published = true - * ``` - * - * See https://firebase.google.com/docs/ml/manage-hosted-models#list_your_projects_models - */ - filter?: string; - - /** The number of results to return in each page. */ - pageSize?: number; - - /** A token that specifies the result page to return. */ - pageToken?: string; - } - - /** Response object for a listModels operation. */ - export interface ListModelsResult { - /** A list of models in your project. */ - readonly models: Model[]; - - /** - * A token you can use to retrieve the next page of results. If null, the - * current page is the final page. - */ - readonly pageToken?: string; - } - - - /** - * The Firebase `MachineLearning` service interface. - * - * Do not call this constructor directly. Instead, use - * [`admin.machineLearning()`](admin.machineLearning#machineLearning). - */ - export interface MachineLearning { - /** - * The {@link app.App} associated with the current `MachineLearning` - * service instance. - */ - app: app.App; - - /** - * Creates a model in the current Firebase project. - * - * @param {ModelOptions} model The model to create. - * - * @return {Promise} A Promise fulfilled with the created model. - */ - createModel(model: ModelOptions): Promise; - - /** - * Updates a model's metadata or model file. - * - * @param {string} modelId The ID of the model to update. - * @param {ModelOptions} model The model fields to update. - * - * @return {Promise} A Promise fulfilled with the updated model. - */ - updateModel(modelId: string, model: ModelOptions): Promise; - - /** - * Publishes a Firebase ML model. - * - * A published model can be downloaded to client apps. - * - * @param {string} modelId The ID of the model to publish. - * - * @return {Promise} A Promise fulfilled with the published model. - */ - publishModel(modelId: string): Promise; - - /** - * Unpublishes a Firebase ML model. - * - * @param {string} modelId The ID of the model to unpublish. - * - * @return {Promise} A Promise fulfilled with the unpublished model. - */ - unpublishModel(modelId: string): Promise; - - /** - * Gets the model specified by the given ID. - * - * @param {string} modelId The ID of the model to get. - * - * @return {Promise} A Promise fulfilled with the model object. - */ - getModel(modelId: string): Promise; - - /** - * Lists the current project's models. - * - * @param {ListModelsOptions} options The listing options. - * - * @return {Promise} A promise that - * resolves with the current (filtered) list of models and the next page - * token. For the last page, an empty list of models and no page token - * are returned. - */ - listModels(options?: ListModelsOptions): Promise; - - /** - * Deletes a model from the current project. - * - * @param {string} modelId The ID of the model to delete. - */ - deleteModel(modelId: string): Promise; - } + const firebaseApp: FirebaseApp = app as FirebaseApp; + return firebaseApp.getOrInitService('machineLearning', (app) => new MachineLearning(app)); } diff --git a/src/machine-learning/machine-learning-api-client.ts b/src/machine-learning/machine-learning-api-client.ts index 2281b84d53..d2923a3143 100644 --- a/src/machine-learning/machine-learning-api-client.ts +++ b/src/machine-learning/machine-learning-api-client.ts @@ -14,19 +14,64 @@ * limitations under the License. */ +import { App } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; import { HttpRequestConfig, HttpClient, HttpError, AuthorizedHttpClient, ExponentialBackoffPoller } from '../utils/api-request'; import { PrefixedFirebaseError } from '../utils/error'; -import { FirebaseMachineLearningError, MachineLearningErrorCode } from './machine-learning-utils'; import * as utils from '../utils/index'; import * as validator from '../utils/validator'; -import { FirebaseApp } from '../firebase-app'; -import { machineLearning } from './index'; +import { FirebaseMachineLearningError, MachineLearningErrorCode } from './machine-learning-utils'; + +/** + * Firebase ML Model input objects + */ +export interface ModelOptionsBase { + displayName?: string; + tags?: string[]; +} + +export interface GcsTfliteModelOptions extends ModelOptionsBase { + tfliteModel: { + gcsTfliteUri: string; + }; +} + +export interface AutoMLTfliteModelOptions extends ModelOptionsBase { + tfliteModel: { + automlModel: string; + }; +} -import GcsTfliteModelOptions = machineLearning.GcsTfliteModelOptions; -import ListModelsOptions = machineLearning.ListModelsOptions; -import ModelOptions = machineLearning.ModelOptions; +export type ModelOptions = ModelOptionsBase | GcsTfliteModelOptions | AutoMLTfliteModelOptions; + +/** + * Interface representing options for listing Models. + */ +export interface ListModelsOptions { + /** + * An expression that specifies how to filter the results. + * + * Examples: + * + * ``` + * display_name = your_model + * display_name : experimental_* + * tags: face_detector AND tags: experimental + * state.published = true + * ``` + * + * See https://firebase.google.com/docs/ml/manage-hosted-models#list_your_projects_models + */ + filter?: string; + + /** The number of results to return in each page. */ + pageSize?: number; + + /** A token that specifies the result page to return. */ + pageToken?: string; +} const ML_V1BETA2_API = 'https://firebaseml.googleapis.com/v1beta2'; const FIREBASE_VERSION_HEADER = { @@ -92,13 +137,14 @@ export interface OperationResponse { /** * Class that facilitates sending requests to the Firebase ML backend API. * - * @private + * @internal */ export class MachineLearningApiClient { + private readonly httpClient: HttpClient; private projectIdPrefix?: string; - constructor(private readonly app: FirebaseApp) { + constructor(private readonly app: App) { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseMachineLearningError( 'invalid-argument', @@ -106,7 +152,7 @@ export class MachineLearningApiClient { + 'Firebase app instance.'); } - this.httpClient = new AuthorizedHttpClient(app); + this.httpClient = new AuthorizedHttpClient(app as FirebaseApp); } public createModel(model: ModelOptions): Promise { diff --git a/src/machine-learning/machine-learning-namespace.ts b/src/machine-learning/machine-learning-namespace.ts new file mode 100644 index 0000000000..96a867fed2 --- /dev/null +++ b/src/machine-learning/machine-learning-namespace.ts @@ -0,0 +1,107 @@ +/*! + * Copyright 2021 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 { App } from '../app'; +import { + ListModelsResult as TListModelsResult, + MachineLearning as TMachineLearning, + Model as TModel, + TFLiteModel as TTFLiteModel, +} from './machine-learning'; +import { + AutoMLTfliteModelOptions as TAutoMLTfliteModelOptions, + GcsTfliteModelOptions as TGcsTfliteModelOptions, + ListModelsOptions as TListModelsOptions, + ModelOptions as TModelOptions, + ModelOptionsBase as TModelOptionsBase, +} from './machine-learning-api-client'; + +/** + * Gets the {@link firebase-admin.machine-learning#MachineLearning} service for the + * default app or a given app. + * + * `admin.machineLearning()` can be called with no arguments to access the + * default app's `MachineLearning` service or as `admin.machineLearning(app)` to access + * the `MachineLearning` service associated with a specific app. + * + * @example + * ```javascript + * // Get the MachineLearning service for the default app + * var defaultMachineLearning = admin.machineLearning(); + * ``` + * + * @example + * ```javascript + * // Get the MachineLearning service for a given app + * var otherMachineLearning = admin.machineLearning(otherApp); + * ``` + * + * @param app Optional app whose `MachineLearning` service to + * return. If not provided, the default `MachineLearning` service + * will be returned. + * + * @returns The default `MachineLearning` service if no app is provided or the + * `MachineLearning` service associated with the provided app. + */ +export declare function machineLearning(app?: App): machineLearning.MachineLearning; + +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace machineLearning { + /** + * Type alias to {@link firebase-admin.machine-learning#ListModelsResult}. + */ + export type ListModelsResult = TListModelsResult; + + /** + * Type alias to {@link firebase-admin.machine-learning#MachineLearning}. + */ + export type MachineLearning = TMachineLearning; + + /** + * Type alias to {@link firebase-admin.machine-learning#Model}. + */ + export type Model = TModel; + + /** + * Type alias to {@link firebase-admin.machine-learning#TFLiteModel}. + */ + export type TFLiteModel = TTFLiteModel; + + /** + * Type alias to {@link firebase-admin.machine-learning#AutoMLTfliteModelOptions}. + */ + export type AutoMLTfliteModelOptions = TAutoMLTfliteModelOptions; + + /** + * Type alias to {@link firebase-admin.machine-learning#GcsTfliteModelOptions}. + */ + export type GcsTfliteModelOptions = TGcsTfliteModelOptions; + + /** + * Type alias to {@link firebase-admin.machine-learning#ListModelsOptions}. + */ + export type ListModelsOptions = TListModelsOptions; + + /** + * Type alias to {@link firebase-admin.machine-learning#ModelOptions}. + */ + export type ModelOptions = TModelOptions; + + /** + * Type alias to {@link firebase-admin.machine-learning#ModelOptionsBase}. + */ + export type ModelOptionsBase = TModelOptionsBase; +} diff --git a/src/machine-learning/machine-learning.ts b/src/machine-learning/machine-learning.ts index dcbb72caec..849aad7546 100644 --- a/src/machine-learning/machine-learning.ts +++ b/src/machine-learning/machine-learning.ts @@ -14,37 +14,63 @@ * limitations under the License. */ -import { FirebaseApp } from '../firebase-app'; -import { - MachineLearningApiClient, ModelResponse, ModelUpdateOptions, isGcsTfliteModelOptions -} from './machine-learning-api-client'; +import { App } from '../app'; +import { getStorage } from '../storage/index'; import { FirebaseError } from '../utils/error'; import * as validator from '../utils/validator'; -import { FirebaseMachineLearningError } from './machine-learning-utils'; import { deepCopy } from '../utils/deep-copy'; import * as utils from '../utils'; -import { machineLearning } from './index'; +import { + MachineLearningApiClient, ModelResponse, ModelUpdateOptions, isGcsTfliteModelOptions, + ListModelsOptions, ModelOptions, +} from './machine-learning-api-client'; +import { FirebaseMachineLearningError } from './machine-learning-utils'; + +/** Response object for a listModels operation. */ +export interface ListModelsResult { + /** A list of models in your project. */ + readonly models: Model[]; + + /** + * A token you can use to retrieve the next page of results. If null, the + * current page is the final page. + */ + readonly pageToken?: string; +} -import ListModelsOptions = machineLearning.ListModelsOptions; -import ListModelsResult = machineLearning.ListModelsResult; -import MachineLearningInterface = machineLearning.MachineLearning; -import ModelInterface = machineLearning.Model; -import ModelOptions = machineLearning.ModelOptions; -import TFLiteModel = machineLearning.TFLiteModel; +/** + * A TensorFlow Lite Model output object + * + * One of either the `gcsTfliteUri` or `automlModel` properties will be + * defined. + */ +export interface TFLiteModel { + /** The size of the model. */ + readonly sizeBytes: number; + + /** The URI from which the model was originally provided to Firebase. */ + readonly gcsTfliteUri?: string; + /** + * The AutoML model reference from which the model was originally provided + * to Firebase. + */ + readonly automlModel?: string; +} /** - * The Firebase Machine Learning class + * The Firebase `MachineLearning` service interface. */ -export class MachineLearning implements MachineLearningInterface { +export class MachineLearning { private readonly client: MachineLearningApiClient; - private readonly appInternal: FirebaseApp; + private readonly appInternal: App; /** - * @param {FirebaseApp} app The app for this ML service. + * @param app The app for this ML service. * @constructor + * @internal */ - constructor(app: FirebaseApp) { + constructor(app: App) { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseError({ code: 'machine-learning/invalid-argument', @@ -58,20 +84,19 @@ export class MachineLearning implements MachineLearningInterface { } /** - * Returns the app associated with this ML instance. - * - * @return {FirebaseApp} The app associated with this ML instance. + * The {@link firebase-admin.app#App} associated with the current `MachineLearning` + * service instance. */ - public get app(): FirebaseApp { + public get app(): App { return this.appInternal; } /** - * Creates a model in Firebase ML. + * Creates a model in the current Firebase project. * - * @param {ModelOptions} model The model to create. + * @param model The model to create. * - * @return {Promise} A Promise fulfilled with the created model. + * @returns A Promise fulfilled with the created model. */ public createModel(model: ModelOptions): Promise { return this.signUrlIfPresent(model) @@ -81,12 +106,12 @@ export class MachineLearning implements MachineLearningInterface { } /** - * Updates a model in Firebase ML. + * Updates a model's metadata or model file. * - * @param {string} modelId The id of the model to update. - * @param {ModelOptions} model The model fields to update. + * @param modelId The ID of the model to update. + * @param model The model fields to update. * - * @return {Promise} A Promise fulfilled with the updated model. + * @returns A Promise fulfilled with the updated model. */ public updateModel(modelId: string, model: ModelOptions): Promise { const updateMask = utils.generateUpdateMask(model); @@ -97,33 +122,35 @@ export class MachineLearning implements MachineLearningInterface { } /** - * Publishes a model in Firebase ML. + * Publishes a Firebase ML model. + * + * A published model can be downloaded to client apps. * - * @param {string} modelId The id of the model to publish. + * @param modelId The ID of the model to publish. * - * @return {Promise} A Promise fulfilled with the published model. + * @returns A Promise fulfilled with the published model. */ public publishModel(modelId: string): Promise { return this.setPublishStatus(modelId, true); } /** - * Unpublishes a model in Firebase ML. + * Unpublishes a Firebase ML model. * - * @param {string} modelId The id of the model to unpublish. + * @param modelId The ID of the model to unpublish. * - * @return {Promise} A Promise fulfilled with the unpublished model. + * @returns A Promise fulfilled with the unpublished model. */ public unpublishModel(modelId: string): Promise { return this.setPublishStatus(modelId, false); } /** - * Gets a model from Firebase ML. + * Gets the model specified by the given ID. * - * @param {string} modelId The id of the model to get. + * @param modelId The ID of the model to get. * - * @return {Promise} A Promise fulfilled with the unpublished model. + * @returns A Promise fulfilled with the model object. */ public getModel(modelId: string): Promise { return this.client.getModel(modelId) @@ -131,14 +158,14 @@ export class MachineLearning implements MachineLearningInterface { } /** - * Lists models from Firebase ML. + * Lists the current project's models. * - * @param {ListModelsOptions} options The listing options. + * @param options The listing options. * - * @return {Promise<{models: Model[], pageToken?: string}>} A promise that + * @returns A promise that * resolves with the current (filtered) list of models and the next page - * token. For the last page, an empty list of models and no page token are - * returned. + * token. For the last page, an empty list of models and no page token + * are returned. */ public listModels(options: ListModelsOptions = {}): Promise { return this.client.listModels(options) @@ -161,9 +188,9 @@ export class MachineLearning implements MachineLearningInterface { } /** - * Deletes a model from Firebase ML. + * Deletes a model from the current project. * - * @param {string} modelId The id of the model to delete. + * @param modelId The ID of the model to delete. */ public deleteModel(modelId: string): Promise { return this.client.deleteModel(modelId); @@ -207,7 +234,7 @@ export class MachineLearning implements MachineLearningInterface { } const bucketName = matches[1]; const blobName = matches[2]; - const bucket = this.appInternal.storage().bucket(bucketName); + const bucket = getStorage(this.app).bucket(bucketName); const blob = bucket.file(blobName); return blob.getSignedUrl({ action: 'read', @@ -219,64 +246,92 @@ export class MachineLearning implements MachineLearningInterface { /** * A Firebase ML Model output object. */ -export class Model implements ModelInterface { +export class Model { private model: ModelResponse; private readonly client?: MachineLearningApiClient; + /** + * @internal + */ constructor(model: ModelResponse, client: MachineLearningApiClient) { this.model = Model.validateAndClone(model); this.client = client; } + /** The ID of the model. */ get modelId(): string { return extractModelId(this.model.name); } + /** + * The model's name. This is the name you use from your app to load the + * model. + */ get displayName(): string { return this.model.displayName!; } + /** + * The model's tags, which can be used to group or filter models in list + * operations. + */ get tags(): string[] { return this.model.tags || []; } + /** The timestamp of the model's creation. */ get createTime(): string { return new Date(this.model.createTime).toUTCString(); } + /** The timestamp of the model's most recent update. */ get updateTime(): string { return new Date(this.model.updateTime).toUTCString(); } + /** Error message when model validation fails. */ get validationError(): string | undefined { return this.model.state?.validationError?.message; } + /** True if the model is published. */ get published(): boolean { return this.model.state?.published || false; } + /** + * The ETag identifier of the current version of the model. This value + * changes whenever you update any of the model's properties. + */ get etag(): string { return this.model.etag; } + /** + * The hash of the model's `tflite` file. This value changes only when + * you upload a new TensorFlow Lite model. + */ get modelHash(): string | undefined { return this.model.modelHash; } + /** Metadata about the model's TensorFlow Lite model file. */ get tfliteModel(): TFLiteModel | undefined { // Make a copy so people can't directly modify the private this.model object. return deepCopy(this.model.tfliteModel); } /** - * Locked indicates if there are active long running operations on the model. - * Models may not be modified when they are locked. + * True if the model is locked by a server-side operation. You can't make + * changes to a locked model. See {@link waitForUnlocked `waitForUnlocked()`}. */ public get locked(): boolean { return (this.model.activeOperations?.length ?? 0) > 0; } + /** + * Return the model as a JSON object. + */ public toJSON(): {[key: string]: any} { // We can't just return this.model because it has extra fields and // different formats etc. So we build the expected model object. @@ -308,11 +363,14 @@ export class Model implements ModelInterface { return jsonModel; } - /** - * Wait for the active operations on the model to complete. - * @param maxTimeMillis The number of milliseconds to wait for the model to be unlocked. If unspecified, - * a default will be used. + * Wait for the model to be unlocked. + * + * @param maxTimeMillis The maximum time in milliseconds to wait. + * If not specified, a default maximum of 2 minutes is used. + * + * @returns A promise that resolves when the model is unlocked + * or the maximum wait time has passed. */ public waitForUnlocked(maxTimeMillis?: number): Promise { if ((this.model.activeOperations?.length ?? 0) > 0) { diff --git a/src/messaging/batch-request-internal.ts b/src/messaging/batch-request-internal.ts index 0c6995a874..0e79af4c79 100644 --- a/src/messaging/batch-request-internal.ts +++ b/src/messaging/batch-request-internal.ts @@ -56,8 +56,8 @@ export class BatchRequestClient { * Sends the given array of sub requests as a single batch, and parses the results into an array * of HttpResponse objects. * - * @param {SubRequest[]} requests An array of sub requests to send. - * @return {Promise} A promise that resolves when the send operation is complete. + * @param requests An array of sub requests to send. + * @returns A promise that resolves when the send operation is complete. */ public send(requests: SubRequest[]): Promise { requests = requests.map((req) => { @@ -100,10 +100,10 @@ export class BatchRequestClient { * API, sets the content-type header to application/http, and the content-transfer-encoding to * binary. * - * @param {SubRequest} request A sub request that will be used to populate the part. - * @param {string} boundary Multipart boundary string. - * @param {number} idx An index number that is used to set the content-id header. - * @return {string} The part as a string that can be included in the HTTP body. + * @param request A sub request that will be used to populate the part. + * @param boundary Multipart boundary string. + * @param idx An index number that is used to set the content-id header. + * @returns The part as a string that can be included in the HTTP body. */ function createPart(request: SubRequest, boundary: string, idx: number): string { const serializedRequest: string = serializeSubRequest(request); @@ -122,8 +122,8 @@ function createPart(request: SubRequest, boundary: string, idx: number): string * format of the string is the wire format of a typical HTTP request, consisting of a header and a * body. * - * @param request {SubRequest} The sub request to be serialized. - * @return {string} String representation of the SubRequest. + * @param request The sub request to be serialized. + * @returns String representation of the SubRequest. */ function serializeSubRequest(request: SubRequest): string { const requestBody: string = JSON.stringify(request.body); diff --git a/src/messaging/index.ts b/src/messaging/index.ts index 0ed93e01e9..233bf98988 100644 --- a/src/messaging/index.ts +++ b/src/messaging/index.ts @@ -14,1330 +14,89 @@ * limitations under the License. */ -import { app, FirebaseError, FirebaseArrayIndexError } from '../firebase-namespace-api'; +/** + * Firebase Cloud Messaging (FCM). + * + * @packageDocumentation + */ + +import { App, getApp } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; +import { Messaging } from './messaging'; + +export { + Messaging, +} from './messaging'; + +export { + AndroidConfig, + AndroidFcmOptions, + AndroidNotification, + ApnsConfig, + ApnsFcmOptions, + ApnsPayload, + Aps, + ApsAlert, + BaseMessage, + BatchResponse, + CriticalSound, + ConditionMessage, + FcmOptions, + LightSettings, + Message, + MessagingTopicManagementResponse, + MulticastMessage, + Notification, + SendResponse, + TokenMessage, + TopicMessage, + WebpushConfig, + WebpushFcmOptions, + WebpushNotification, + + // Legacy APIs + DataMessagePayload, + MessagingConditionResponse, + MessagingDeviceGroupResponse, + MessagingDeviceResult, + MessagingDevicesResponse, + MessagingOptions, + MessagingPayload, + MessagingTopicResponse, + NotificationMessagePayload, +} from './messaging-api'; /** - * Gets the {@link messaging.Messaging `Messaging`} service for the - * default app or a given app. + * Gets the {@link Messaging} service for the default app or a given app. * * `admin.messaging()` can be called with no arguments to access the default - * app's {@link messaging.Messaging `Messaging`} service or as - * `admin.messaging(app)` to access the - * {@link messaging.Messaging `Messaging`} service associated with a - * specific app. + * app's `Messaging` service or as `admin.messaging(app)` to access the + * `Messaging` service associated with aspecific app. * * @example * ```javascript * // Get the Messaging service for the default app - * var defaultMessaging = admin.messaging(); + * const defaultMessaging = getMessaging(); * ``` * * @example * ```javascript * // Get the Messaging service for a given app - * var otherMessaging = admin.messaging(otherApp); + * const otherMessaging = getMessaging(otherApp); * ``` * * @param app Optional app whose `Messaging` service to * return. If not provided, the default `Messaging` service will be returned. * - * @return The default `Messaging` service if no + * @returns The default `Messaging` service if no * app is provided or the `Messaging` service associated with the provided * app. */ -export declare function messaging(app?: app.App): messaging.Messaging; - -/* eslint-disable @typescript-eslint/no-namespace */ -export namespace messaging { - interface BaseMessage { - data?: { [key: string]: string }; - notification?: Notification; - android?: AndroidConfig; - webpush?: WebpushConfig; - apns?: ApnsConfig; - fcmOptions?: FcmOptions; - } - - export interface TokenMessage extends BaseMessage { - token: string; - } - - export interface TopicMessage extends BaseMessage { - topic: string; - } - - export interface ConditionMessage extends BaseMessage { - condition: string; - } - - /** - * Payload for the `admin.messaging.send()` operation. The payload contains all the fields - * in the `BaseMessage` type, and exactly one of token, topic or condition. - */ - export type Message = TokenMessage | TopicMessage | ConditionMessage; - - /** - * Payload for the `admin.messaging.sendMulticast()` method. The payload contains all the fields - * in the `BaseMessage` type, and a list of tokens. - */ - export interface MulticastMessage extends BaseMessage { - tokens: string[]; - } - - /** - * A notification that can be included in {@link messaging.Message}. - */ - export interface Notification { - /** - * The title of the notification. - */ - title?: string; - /** - * The notification body - */ - body?: string; - /** - * URL of an image to be displayed in the notification. - */ - imageUrl?: string; - } - - /** - * Represents platform-independent options for features provided by the FCM SDKs. - */ - export interface FcmOptions { - /** - * The label associated with the message's analytics data. - */ - analyticsLabel?: string; - } - - /** - * Represents the WebPush protocol options that can be included in an - * {@link messaging.Message}. - */ - export interface WebpushConfig { - - /** - * A collection of WebPush headers. Header values must be strings. - * - * See [WebPush specification](https://tools.ietf.org/html/rfc8030#section-5) - * for supported headers. - */ - headers?: { [key: string]: string }; - - /** - * A collection of data fields. - */ - data?: { [key: string]: string }; - - /** - * A WebPush notification payload to be included in the message. - */ - notification?: WebpushNotification; - - /** - * Options for features provided by the FCM SDK for Web. - */ - fcmOptions?: WebpushFcmOptions; - } - - /** Represents options for features provided by the FCM SDK for Web - * (which are not part of the Webpush standard). - */ - export interface WebpushFcmOptions { - - /** - * The link to open when the user clicks on the notification. - * For all URL values, HTTPS is required. - */ - link?: string; - } - - /** - * Represents the WebPush-specific notification options that can be included in - * {@link messaging.WebpushConfig}. This supports most of the standard - * options as defined in the Web Notification - * [specification](https://developer.mozilla.org/en-US/docs/Web/API/notification/Notification). - */ - export interface WebpushNotification { - - /** - * Title text of the notification. - */ - title?: string; - - /** - * An array of notification actions representing the actions - * available to the user when the notification is presented. - */ - actions?: Array<{ - - /** - * An action available to the user when the notification is presented - */ - action: string; - - /** - * Optional icon for a notification action. - */ - icon?: string; - - /** - * Title of the notification action. - */ - title: string; - }>; - - /** - * URL of the image used to represent the notification when there is - * not enough space to display the notification itself. - */ - badge?: string; - - /** - * Body text of the notification. - */ - body?: string; - - /** - * Arbitrary data that you want associated with the notification. - * This can be of any data type. - */ - data?: any; - - /** - * The direction in which to display the notification. Must be one - * of `auto`, `ltr` or `rtl`. - */ - dir?: 'auto' | 'ltr' | 'rtl'; - - /** - * URL to the notification icon. - */ - icon?: string; - - /** - * URL of an image to be displayed in the notification. - */ - image?: string; - - /** - * The notification's language as a BCP 47 language tag. - */ - lang?: string; - - /** - * A boolean specifying whether the user should be notified after a - * new notification replaces an old one. Defaults to false. - */ - renotify?: boolean; - - /** - * Indicates that a notification should remain active until the user - * clicks or dismisses it, rather than closing automatically. - * Defaults to false. - */ - requireInteraction?: boolean; - - /** - * A boolean specifying whether the notification should be silent. - * Defaults to false. - */ - silent?: boolean; - - /** - * An identifying tag for the notification. - */ - tag?: string; - - /** - * Timestamp of the notification. Refer to - * https://developer.mozilla.org/en-US/docs/Web/API/notification/timestamp - * for details. - */ - timestamp?: number; - - /** - * A vibration pattern for the device's vibration hardware to emit - * when the notification fires. - */ - vibrate?: number | number[]; - [key: string]: any; - } - - /** - * Represents the APNs-specific options that can be included in an - * {@link messaging.Message}. Refer to - * [Apple documentation](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html) - * for various headers and payload fields supported by APNs. - */ - export interface ApnsConfig { - /** - * A collection of APNs headers. Header values must be strings. - */ - headers?: { [key: string]: string }; - - /** - * An APNs payload to be included in the message. - */ - payload?: ApnsPayload; - - /** - * Options for features provided by the FCM SDK for iOS. - */ - fcmOptions?: ApnsFcmOptions; - } - - /** - * Represents the payload of an APNs message. Mainly consists of the `aps` - * dictionary. But may also contain other arbitrary custom keys. - */ - export interface ApnsPayload { - - /** - * The `aps` dictionary to be included in the message. - */ - aps: Aps; - [customData: string]: any; - } - - /** - * Represents the [aps dictionary](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html) - * that is part of APNs messages. - */ - export interface Aps { - - /** - * Alert to be included in the message. This may be a string or an object of - * type `admin.messaging.ApsAlert`. - */ - alert?: string | ApsAlert; - - /** - * Badge to be displayed with the message. Set to 0 to remove the badge. When - * not specified, the badge will remain unchanged. - */ - badge?: number; - - /** - * Sound to be played with the message. - */ - sound?: string | CriticalSound; - - /** - * Specifies whether to configure a background update notification. - */ - contentAvailable?: boolean; - - /** - * Specifies whether to set the `mutable-content` property on the message - * so the clients can modify the notification via app extensions. - */ - mutableContent?: boolean; - - /** - * Type of the notification. - */ - category?: string; - - /** - * An app-specific identifier for grouping notifications. - */ - threadId?: string; - [customData: string]: any; - } - - export interface ApsAlert { - title?: string; - subtitle?: string; - body?: string; - locKey?: string; - locArgs?: string[]; - titleLocKey?: string; - titleLocArgs?: string[]; - subtitleLocKey?: string; - subtitleLocArgs?: string[]; - actionLocKey?: string; - launchImage?: string; - } - - /** - * Represents a critical sound configuration that can be included in the - * `aps` dictionary of an APNs payload. - */ - export interface CriticalSound { - - /** - * The critical alert flag. Set to `true` to enable the critical alert. - */ - critical?: boolean; - - /** - * The name of a sound file in the app's main bundle or in the `Library/Sounds` - * folder of the app's container directory. Specify the string "default" to play - * the system sound. - */ - name: string; - - /** - * The volume for the critical alert's sound. Must be a value between 0.0 - * (silent) and 1.0 (full volume). - */ - volume?: number; - } - - /** - * Represents options for features provided by the FCM SDK for iOS. - */ - export interface ApnsFcmOptions { - - /** - * The label associated with the message's analytics data. - */ - analyticsLabel?: string; - - /** - * URL of an image to be displayed in the notification. - */ - imageUrl?: string; - } - - - /** - * Represents the Android-specific options that can be included in an - * {@link messaging.Message}. - */ - export interface AndroidConfig { - - /** - * Collapse key for the message. Collapse key serves as an identifier for a - * group of messages that can be collapsed, so that only the last message gets - * sent when delivery can be resumed. A maximum of four different collapse keys - * may be active at any given time. - */ - collapseKey?: string; - - /** - * Priority of the message. Must be either `normal` or `high`. - */ - priority?: ('high' | 'normal'); - - /** - * Time-to-live duration of the message in milliseconds. - */ - ttl?: number; - - /** - * Package name of the application where the registration tokens must match - * in order to receive the message. - */ - restrictedPackageName?: string; - - /** - * A collection of data fields to be included in the message. All values must - * be strings. When provided, overrides any data fields set on the top-level - * `admin.messaging.Message`.} - */ - data?: { [key: string]: string }; - - /** - * Android notification to be included in the message. - */ - notification?: AndroidNotification; - - /** - * Options for features provided by the FCM SDK for Android. - */ - fcmOptions?: AndroidFcmOptions; - } - - /** - * Represents the Android-specific notification options that can be included in - * {@link messaging.AndroidConfig}. - */ - export interface AndroidNotification { - /** - * Title of the Android notification. When provided, overrides the title set via - * `admin.messaging.Notification`. - */ - title?: string; - - /** - * Body of the Android notification. When provided, overrides the body set via - * `admin.messaging.Notification`. - */ - body?: string; - - /** - * Icon resource for the Android notification. - */ - icon?: string; - - /** - * Notification icon color in `#rrggbb` format. - */ - color?: string; - - /** - * File name of the sound to be played when the device receives the - * notification. - */ - sound?: string; - - /** - * Notification tag. This is an identifier used to replace existing - * notifications in the notification drawer. If not specified, each request - * creates a new notification. - */ - tag?: string; - - /** - * URL of an image to be displayed in the notification. - */ - imageUrl?: string; - - /** - * Action associated with a user click on the notification. If specified, an - * activity with a matching Intent Filter is launched when a user clicks on the - * notification. - */ - clickAction?: string; - - /** - * Key of the body string in the app's string resource to use to localize the - * body text. - * - */ - bodyLocKey?: string; - - /** - * An array of resource keys that will be used in place of the format - * specifiers in `bodyLocKey`. - */ - bodyLocArgs?: string[]; - - /** - * Key of the title string in the app's string resource to use to localize the - * title text. - */ - titleLocKey?: string; - - /** - * An array of resource keys that will be used in place of the format - * specifiers in `titleLocKey`. - */ - titleLocArgs?: string[]; - - /** - * The Android notification channel ID (new in Android O). The app must create - * a channel with this channel ID before any notification with this channel ID - * can be received. If you don't send this channel ID in the request, or if the - * channel ID provided has not yet been created by the app, FCM uses the channel - * ID specified in the app manifest. - */ - channelId?: string; - - /** - * Sets the "ticker" text, which is sent to accessibility services. Prior to - * API level 21 (Lollipop), sets the text that is displayed in the status bar - * when the notification first arrives. - */ - ticker?: string; - - /** - * When set to `false` or unset, the notification is automatically dismissed when - * the user clicks it in the panel. When set to `true`, the notification persists - * even when the user clicks it. - */ - sticky?: boolean; - - /** - * For notifications that inform users about events with an absolute time reference, sets - * the time that the event in the notification occurred. Notifications - * in the panel are sorted by this time. - */ - eventTimestamp?: Date; - - /** - * Sets whether or not this notification is relevant only to the current device. - * Some notifications can be bridged to other devices for remote display, such as - * a Wear OS watch. This hint can be set to recommend this notification not be bridged. - * See [Wear OS guides](https://developer.android.com/training/wearables/notifications/bridger#existing-method-of-preventing-bridging) - */ - localOnly?: boolean; - - /** - * Sets the relative priority for this notification. Low-priority notifications - * may be hidden from the user in certain situations. Note this priority differs - * from `AndroidMessagePriority`. This priority is processed by the client after - * the message has been delivered. Whereas `AndroidMessagePriority` is an FCM concept - * that controls when the message is delivered. - */ - priority?: ('min' | 'low' | 'default' | 'high' | 'max'); - - /** - * Sets the vibration pattern to use. Pass in an array of milliseconds to - * turn the vibrator on or off. The first value indicates the duration to wait before - * turning the vibrator on. The next value indicates the duration to keep the - * vibrator on. Subsequent values alternate between duration to turn the vibrator - * off and to turn the vibrator on. If `vibrate_timings` is set and `default_vibrate_timings` - * is set to `true`, the default value is used instead of the user-specified `vibrate_timings`. - */ - vibrateTimingsMillis?: number[]; - - /** - * If set to `true`, use the Android framework's default vibrate pattern for the - * notification. Default values are specified in [`config.xml`](https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/values/config.xml). - * If `default_vibrate_timings` is set to `true` and `vibrate_timings` is also set, - * the default value is used instead of the user-specified `vibrate_timings`. - */ - defaultVibrateTimings?: boolean; - - /** - * If set to `true`, use the Android framework's default sound for the notification. - * Default values are specified in [`config.xml`](https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/values/config.xml). - */ - defaultSound?: boolean; - - /** - * Settings to control the notification's LED blinking rate and color if LED is - * available on the device. The total blinking time is controlled by the OS. - */ - lightSettings?: LightSettings; - - /** - * If set to `true`, use the Android framework's default LED light settings - * for the notification. Default values are specified in [`config.xml`](https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/values/config.xml). - * If `default_light_settings` is set to `true` and `light_settings` is also set, - * the user-specified `light_settings` is used instead of the default value. - */ - defaultLightSettings?: boolean; - - /** - * Sets the visibility of the notification. Must be either `private`, `public`, - * or `secret`. If unspecified, defaults to `private`. - */ - visibility?: ('private' | 'public' | 'secret'); - - /** - * Sets the number of items this notification represents. May be displayed as a - * badge count for Launchers that support badging. See [`NotificationBadge`(https://developer.android.com/training/notify-user/badges). - * For example, this might be useful if you're using just one notification to - * represent multiple new messages but you want the count here to represent - * the number of total new messages. If zero or unspecified, systems - * that support badging use the default, which is to increment a number - * displayed on the long-press menu each time a new notification arrives. - */ - notificationCount?: number; - } - - /** - * Represents settings to control notification LED that can be included in - * {@link messaging.AndroidNotification}. - */ - export interface LightSettings { - /** - * Required. Sets color of the LED in `#rrggbb` or `#rrggbbaa` format. - */ - color: string; - - /** - * Required. Along with `light_off_duration`, defines the blink rate of LED flashes. - */ - lightOnDurationMillis: number; - - /** - * Required. Along with `light_on_duration`, defines the blink rate of LED flashes. - */ - lightOffDurationMillis: number; - } - - /** - * Represents options for features provided by the FCM SDK for Android. - */ - export interface AndroidFcmOptions { - - /** - * The label associated with the message's analytics data. - */ - analyticsLabel?: string; - } - - /** - * Interface representing an FCM legacy API data message payload. Data - * messages let developers send up to 4KB of custom key-value pairs. The - * keys and values must both be strings. Keys can be any custom string, - * except for the following reserved strings: - * - * * `"from"` - * * Anything starting with `"google."`. - * - * See [Build send requests](/docs/cloud-messaging/send-message) - * for code samples and detailed documentation. - */ - export interface DataMessagePayload { - [key: string]: string; - } - - /** - * Interface representing an FCM legacy API notification message payload. - * Notification messages let developers send up to 4KB of predefined - * key-value pairs. Accepted keys are outlined below. - * - * See [Build send requests](/docs/cloud-messaging/send-message) - * for code samples and detailed documentation. - */ - export interface NotificationMessagePayload { - - /** - * Identifier used to replace existing notifications in the notification drawer. - * - * If not specified, each request creates a new notification. - * - * If specified and a notification with the same tag is already being shown, - * the new notification replaces the existing one in the notification drawer. - * - * **Platforms:** Android - */ - tag?: string; - - /** - * The notification's body text. - * - * **Platforms:** iOS, Android, Web - */ - body?: string; - - /** - * The notification's icon. - * - * **Android:** Sets the notification icon to `myicon` for drawable resource - * `myicon`. If you don't send this key in the request, FCM displays the - * launcher icon specified in your app manifest. - * - * **Web:** The URL to use for the notification's icon. - * - * **Platforms:** Android, Web - */ - icon?: string; - - /** - * The value of the badge on the home screen app icon. - * - * If not specified, the badge is not changed. - * - * If set to `0`, the badge is removed. - * - * **Platforms:** iOS - */ - badge?: string; - - /** - * The notification icon's color, expressed in `#rrggbb` format. - * - * **Platforms:** Android - */ - color?: string; - - /** - * The sound to be played when the device receives a notification. Supports - * "default" for the default notification sound of the device or the filename of a - * sound resource bundled in the app. - * Sound files must reside in `/res/raw/`. - * - * **Platforms:** Android - */ - sound?: string; - - /** - * The notification's title. - * - * **Platforms:** iOS, Android, Web - */ - title?: string; - - /** - * The key to the body string in the app's string resources to use to localize - * the body text to the user's current localization. - * - * **iOS:** Corresponds to `loc-key` in the APNs payload. See - * [Payload Key Reference](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html) - * and - * [Localizing the Content of Your Remote Notifications](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH10-SW9) - * for more information. - * - * **Android:** See - * [String Resources](http://developer.android.com/guide/topics/resources/string-resource.html) * for more information. - * - * **Platforms:** iOS, Android - */ - bodyLocKey?: string; - - /** - * Variable string values to be used in place of the format specifiers in - * `body_loc_key` to use to localize the body text to the user's current - * localization. - * - * The value should be a stringified JSON array. - * - * **iOS:** Corresponds to `loc-args` in the APNs payload. See - * [Payload Key Reference](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html) - * and - * [Localizing the Content of Your Remote Notifications](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH10-SW9) - * for more information. - * - * **Android:** See - * [Formatting and Styling](http://developer.android.com/guide/topics/resources/string-resource.html#FormattingAndStyling) - * for more information. - * - * **Platforms:** iOS, Android - */ - bodyLocArgs?: string; - - /** - * Action associated with a user click on the notification. If specified, an - * activity with a matching Intent Filter is launched when a user clicks on the - * notification. - * - * * **Platforms:** Android - */ - clickAction?: string; - - /** - * The key to the title string in the app's string resources to use to localize - * the title text to the user's current localization. - * - * **iOS:** Corresponds to `title-loc-key` in the APNs payload. See - * [Payload Key Reference](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html) - * and - * [Localizing the Content of Your Remote Notifications](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH10-SW9) - * for more information. - * - * **Android:** See - * [String Resources](http://developer.android.com/guide/topics/resources/string-resource.html) - * for more information. - * - * **Platforms:** iOS, Android - */ - titleLocKey?: string; - - /** - * Variable string values to be used in place of the format specifiers in - * `title_loc_key` to use to localize the title text to the user's current - * localization. - * - * The value should be a stringified JSON array. - * - * **iOS:** Corresponds to `title-loc-args` in the APNs payload. See - * [Payload Key Reference](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html) - * and - * [Localizing the Content of Your Remote Notifications](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH10-SW9) - * for more information. - * - * **Android:** See - * [Formatting and Styling](http://developer.android.com/guide/topics/resources/string-resource.html#FormattingAndStyling) - * for more information. - * - * **Platforms:** iOS, Android - */ - titleLocArgs?: string; - [key: string]: string | undefined; - } - - /** - * Interface representing a Firebase Cloud Messaging message payload. One or - * both of the `data` and `notification` keys are required. - * - * See - * [Build send requests](/docs/cloud-messaging/send-message) - * for code samples and detailed documentation. - */ - export interface MessagingPayload { - - /** - * The data message payload. - */ - data?: DataMessagePayload; - - /** - * The notification message payload. - */ - notification?: NotificationMessagePayload; - } - - /** - * Interface representing the options that can be provided when sending a - * message via the FCM legacy APIs. - * - * See [Build send requests](/docs/cloud-messaging/send-message) - * for code samples and detailed documentation. - */ - export interface MessagingOptions { - - /** - * Whether or not the message should actually be sent. When set to `true`, - * allows developers to test a request without actually sending a message. When - * set to `false`, the message will be sent. - * - * **Default value:** `false` - */ - dryRun?: boolean; - - /** - * The priority of the message. Valid values are `"normal"` and `"high".` On - * iOS, these correspond to APNs priorities `5` and `10`. - * - * By default, notification messages are sent with high priority, and data - * messages are sent with normal priority. Normal priority optimizes the client - * app's battery consumption and should be used unless immediate delivery is - * required. For messages with normal priority, the app may receive the message - * with unspecified delay. - * - * When a message is sent with high priority, it is sent immediately, and the - * app can wake a sleeping device and open a network connection to your server. - * - * For more information, see - * [Setting the priority of a message](/docs/cloud-messaging/concept-options#setting-the-priority-of-a-message). - * - * **Default value:** `"high"` for notification messages, `"normal"` for data - * messages - */ - priority?: string; - - /** - * How long (in seconds) the message should be kept in FCM storage if the device - * is offline. The maximum time to live supported is four weeks, and the default - * value is also four weeks. For more information, see - * [Setting the lifespan of a message](/docs/cloud-messaging/concept-options#ttl). - * - * **Default value:** `2419200` (representing four weeks, in seconds) - */ - timeToLive?: number; - - /** - * String identifying a group of messages (for example, "Updates Available") - * that can be collapsed, so that only the last message gets sent when delivery - * can be resumed. This is used to avoid sending too many of the same messages - * when the device comes back online or becomes active. - * - * There is no guarantee of the order in which messages get sent. - * - * A maximum of four different collapse keys is allowed at any given time. This - * means FCM server can simultaneously store four different - * send-to-sync messages per client app. If you exceed this number, there is no - * guarantee which four collapse keys the FCM server will keep. - * - * **Default value:** None - */ - collapseKey?: string; - - /** - * On iOS, use this field to represent `mutable-content` in the APNs payload. - * When a notification is sent and this is set to `true`, the content of the - * notification can be modified before it is displayed, using a - * [Notification Service app extension](https://developer.apple.com/reference/usernotifications/unnotificationserviceextension) - * - * On Android and Web, this parameter will be ignored. - * - * **Default value:** `false` - */ - mutableContent?: boolean; - - /** - * On iOS, use this field to represent `content-available` in the APNs payload. - * When a notification or data message is sent and this is set to `true`, an - * inactive client app is awoken. On Android, data messages wake the app by - * default. On Chrome, this flag is currently not supported. - * - * **Default value:** `false` - */ - contentAvailable?: boolean; - - /** - * The package name of the application which the registration tokens must match - * in order to receive the message. - * - * **Default value:** None - */ - restrictedPackageName?: string; - [key: string]: any | undefined; - } - - /* Individual status response payload from single devices */ - export interface MessagingDeviceResult { - /** - * The error that occurred when processing the message for the recipient. - */ - error?: FirebaseError; - - /** - * A unique ID for the successfully processed message. - */ - messageId?: string; - - /** - * The canonical registration token for the client app that the message was - * processed and sent to. You should use this value as the registration token - * for future requests. Otherwise, future messages might be rejected. - */ - canonicalRegistrationToken?: string; - } - - /** - * Interface representing the status of a message sent to an individual device - * via the FCM legacy APIs. - * - * See - * [Send to individual devices](/docs/cloud-messaging/admin/send-messages#send_to_individual_devices) - * for code samples and detailed documentation. - */ - export interface MessagingDevicesResponse { - canonicalRegistrationTokenCount: number; - failureCount: number; - multicastId: number; - results: MessagingDeviceResult[]; - successCount: number; - } - - /** - * Interface representing the server response from the - * {@link messaging.Messaging.sendToDeviceGroup `sendToDeviceGroup()`} - * method. - * - * See - * [Send messages to device groups](/docs/cloud-messaging/send-message?authuser=0#send_messages_to_device_groups) - * for code samples and detailed documentation. - */ - export interface MessagingDeviceGroupResponse { - - /** - * The number of messages that could not be processed and resulted in an error. - */ - successCount: number; - - /** - * The number of messages that could not be processed and resulted in an error. - */ - failureCount: number; - - /** - * An array of registration tokens that failed to receive the message. - */ - failedRegistrationTokens: string[]; - } - - /** - * Interface representing the server response from the legacy - * {@link messaging.Messaging.sendToTopic `sendToTopic()`} method. - * - * See - * [Send to a topic](/docs/cloud-messaging/admin/send-messages#send_to_a_topic) - * for code samples and detailed documentation. - */ - export interface MessagingTopicResponse { - /** - * The message ID for a successfully received request which FCM will attempt to - * deliver to all subscribed devices. - */ - messageId: number; - } - - /** - * Interface representing the server response from the legacy - * {@link messaging.Messaging.sendToCondition `sendToCondition()`} method. - * - * See - * [Send to a condition](/docs/cloud-messaging/admin/send-messages#send_to_a_condition) - * for code samples and detailed documentation. - */ - export interface MessagingConditionResponse { - /** - * The message ID for a successfully received request which FCM will attempt to - * deliver to all subscribed devices. - */ - messageId: number; +export function getMessaging(app?: App): Messaging { + if (typeof app === 'undefined') { + app = getApp(); } - /** - * Interface representing the server response from the - * {@link messaging.Messaging.subscribeToTopic `subscribeToTopic()`} and - * {@link messaging.Messaging.unsubscribeFromTopic `unsubscribeFromTopic()`} - * methods. - * - * See - * [Manage topics from the server](/docs/cloud-messaging/manage-topics) - * for code samples and detailed documentation. - */ - export interface MessagingTopicManagementResponse { - /** - * The number of registration tokens that could not be subscribed to the topic - * and resulted in an error. - */ - failureCount: number; - - /** - * The number of registration tokens that were successfully subscribed to the - * topic. - */ - successCount: number; - - /** - * An array of errors corresponding to the provided registration token(s). The - * length of this array will be equal to [`failureCount`](#failureCount). - */ - errors: FirebaseArrayIndexError[]; - } - - /** - * Interface representing the server response from the - * {@link messaging.Messaging.sendAll `sendAll()`} and - * {@link messaging.Messaging.sendMulticast `sendMulticast()`} methods. - */ - export interface BatchResponse { - - /** - * An array of responses, each corresponding to a message. - */ - responses: SendResponse[]; - - /** - * The number of messages that were successfully handed off for sending. - */ - successCount: number; - - /** - * The number of messages that resulted in errors when sending. - */ - failureCount: number; - } - - /** - * Interface representing the status of an individual message that was sent as - * part of a batch request. - */ - export interface SendResponse { - /** - * A boolean indicating if the message was successfully handed off to FCM or - * not. When true, the `messageId` attribute is guaranteed to be set. When - * false, the `error` attribute is guaranteed to be set. - */ - success: boolean; - - /** - * A unique message ID string, if the message was handed off to FCM for - * delivery. - * - */ - messageId?: string; - - /** - * An error, if the message was not handed off to FCM successfully. - */ - error?: FirebaseError; - } - - export interface Messaging { - /** - * The {@link app.App app} associated with the current `Messaging` service - * instance. - * - * @example - * ```javascript - * var app = messaging.app; - * ``` - */ - app: app.App; - - /** - * Sends the given message via FCM. - * - * @param message The message payload. - * @param dryRun Whether to send the message in the dry-run - * (validation only) mode. - * @return A promise fulfilled with a unique message ID - * string after the message has been successfully handed off to the FCM - * service for delivery. - */ - send(message: Message, dryRun?: boolean): Promise; - - /** - * Sends all the messages in the given array via Firebase Cloud Messaging. - * Employs batching to send the entire list as a single RPC call. Compared - * to the `send()` method, this method is a significantly more efficient way - * to send multiple messages. - * - * The responses list obtained from the return value - * corresponds to the order of tokens in the `MulticastMessage`. An error - * from this method indicates a total failure -- i.e. none of the messages in - * the list could be sent. Partial failures are indicated by a `BatchResponse` - * return value. - * - * @param messages A non-empty array - * containing up to 500 messages. - * @param dryRun Whether to send the messages in the dry-run - * (validation only) mode. - * @return A Promise fulfilled with an object representing the result of the - * send operation. - */ - sendAll( - messages: Array, - dryRun?: boolean - ): Promise; - - /** - * Sends the given multicast message to all the FCM registration tokens - * specified in it. - * - * This method uses the `sendAll()` API under the hood to send the given - * message to all the target recipients. The responses list obtained from the - * return value corresponds to the order of tokens in the `MulticastMessage`. - * An error from this method indicates a total failure -- i.e. the message was - * not sent to any of the tokens in the list. Partial failures are indicated by - * a `BatchResponse` return value. - * - * @param message A multicast message - * containing up to 500 tokens. - * @param dryRun Whether to send the message in the dry-run - * (validation only) mode. - * @return A Promise fulfilled with an object representing the result of the - * send operation. - */ - sendMulticast( - message: MulticastMessage, - dryRun?: boolean - ): Promise; - - /** - * Sends an FCM message to a single device corresponding to the provided - * registration token. - * - * See - * [Send to individual devices](/docs/cloud-messaging/admin/legacy-fcm#send_to_individual_devices) - * for code samples and detailed documentation. Takes either a - * `registrationToken` to send to a single device or a - * `registrationTokens` parameter containing an array of tokens to send - * to multiple devices. - * - * @param registrationToken A device registration token or an array of - * device registration tokens to which the message should be sent. - * @param payload The message payload. - * @param options Optional options to - * alter the message. - * - * @return A promise fulfilled with the server's response after the message - * has been sent. - */ - sendToDevice( - registrationToken: string | string[], - payload: MessagingPayload, - options?: MessagingOptions - ): Promise; - - /** - * Sends an FCM message to a device group corresponding to the provided - * notification key. - * - * See - * [Send to a device group](/docs/cloud-messaging/admin/legacy-fcm#send_to_a_device_group) - * for code samples and detailed documentation. - * - * @param notificationKey The notification key for the device group to - * which to send the message. - * @param payload The message payload. - * @param options Optional options to - * alter the message. - * - * @return A promise fulfilled with the server's response after the message - * has been sent. - */ - sendToDeviceGroup( - notificationKey: string, - payload: MessagingPayload, - options?: MessagingOptions - ): Promise; - - /** - * Sends an FCM message to a topic. - * - * See - * [Send to a topic](/docs/cloud-messaging/admin/legacy-fcm#send_to_a_topic) - * for code samples and detailed documentation. - * - * @param topic The topic to which to send the message. - * @param payload The message payload. - * @param options Optional options to - * alter the message. - * - * @return A promise fulfilled with the server's response after the message - * has been sent. - */ - sendToTopic( - topic: string, - payload: MessagingPayload, - options?: MessagingOptions - ): Promise; - - /** - * Sends an FCM message to a condition. - * - * See - * [Send to a condition](/docs/cloud-messaging/admin/legacy-fcm#send_to_a_condition) - * for code samples and detailed documentation. - * - * @param condition The condition determining to which topics to send - * the message. - * @param payload The message payload. - * @param options Optional options to - * alter the message. - * - * @return A promise fulfilled with the server's response after the message - * has been sent. - */ - sendToCondition( - condition: string, - payload: MessagingPayload, - options?: MessagingOptions - ): Promise; - - /** - * Subscribes a device to an FCM topic. - * - * See [Subscribe to a - * topic](/docs/cloud-messaging/manage-topics#suscribe_and_unsubscribe_using_the) - * for code samples and detailed documentation. Optionally, you can provide an - * array of tokens to subscribe multiple devices. - * - * @param registrationTokens A token or array of registration tokens - * for the devices to subscribe to the topic. - * @param topic The topic to which to subscribe. - * - * @return A promise fulfilled with the server's response after the device has been - * subscribed to the topic. - */ - subscribeToTopic( - registrationTokens: string | string[], - topic: string - ): Promise; - - /** - * Unsubscribes a device from an FCM topic. - * - * See [Unsubscribe from a - * topic](/docs/cloud-messaging/admin/manage-topic-subscriptions#unsubscribe_from_a_topic) - * for code samples and detailed documentation. Optionally, you can provide an - * array of tokens to unsubscribe multiple devices. - * - * @param registrationTokens A device registration token or an array of - * device registration tokens to unsubscribe from the topic. - * @param topic The topic from which to unsubscribe. - * - * @return A promise fulfilled with the server's response after the device has been - * unsubscribed from the topic. - */ - unsubscribeFromTopic( - registrationTokens: string | string[], - topic: string - ): Promise; - } + const firebaseApp: FirebaseApp = app as FirebaseApp; + return firebaseApp.getOrInitService('messaging', (app) => new Messaging(app)); } diff --git a/src/messaging/messaging-api-request-internal.ts b/src/messaging/messaging-api-request-internal.ts index d74f621e0d..59f67ba3c1 100644 --- a/src/messaging/messaging-api-request-internal.ts +++ b/src/messaging/messaging-api-request-internal.ts @@ -15,17 +15,16 @@ * limitations under the License. */ -import { FirebaseApp } from '../firebase-app'; +import { App } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; import { HttpMethod, AuthorizedHttpClient, HttpRequestConfig, HttpError, HttpResponse, } from '../utils/api-request'; import { createFirebaseError, getErrorCode } from './messaging-errors-internal'; import { SubRequest, BatchRequestClient } from './batch-request-internal'; -import { messaging } from './index'; import { getSdkVersion } from '../utils/index'; +import { SendResponse, BatchResponse } from './messaging-api'; -import SendResponse = messaging.SendResponse; -import BatchResponse = messaging.BatchResponse; // FCM backend constants const FIREBASE_MESSAGING_TIMEOUT = 10000; @@ -48,11 +47,11 @@ export class FirebaseMessagingRequestHandler { private readonly batchClient: BatchRequestClient; /** - * @param {FirebaseApp} app The app used to fetch access tokens to sign API requests. + * @param app The app used to fetch access tokens to sign API requests. * @constructor */ - constructor(app: FirebaseApp) { - this.httpClient = new AuthorizedHttpClient(app); + constructor(app: App) { + this.httpClient = new AuthorizedHttpClient(app as FirebaseApp); this.batchClient = new BatchRequestClient( this.httpClient, FIREBASE_MESSAGING_BATCH_URL, FIREBASE_MESSAGING_HEADERS); } @@ -60,10 +59,10 @@ export class FirebaseMessagingRequestHandler { /** * Invokes the request handler with the provided request data. * - * @param {string} host The host to which to send the request. - * @param {string} path The path to which to send the request. - * @param {object} requestData The request data. - * @return {Promise} A promise that resolves with the response. + * @param host The host to which to send the request. + * @param path The path to which to send the request. + * @param requestData The request data. + * @returns A promise that resolves with the response. */ public invokeRequestHandler(host: string, path: string, requestData: object): Promise { const request: HttpRequestConfig = { @@ -101,8 +100,8 @@ export class FirebaseMessagingRequestHandler { * Sends the given array of sub requests as a single batch to FCM, and parses the result into * a BatchResponse object. * - * @param {SubRequest[]} requests An array of sub requests to send. - * @return {Promise} A promise that resolves when the send operation is complete. + * @param requests An array of sub requests to send. + * @returns A promise that resolves when the send operation is complete. */ public sendBatchRequest(requests: SubRequest[]): Promise { return this.batchClient.send(requests) diff --git a/src/messaging/messaging-api.ts b/src/messaging/messaging-api.ts new file mode 100644 index 0000000000..cdde578c2b --- /dev/null +++ b/src/messaging/messaging-api.ts @@ -0,0 +1,1110 @@ +/*! + * @license + * Copyright 2021 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 { FirebaseArrayIndexError, FirebaseError } from '../app/index'; + +export interface BaseMessage { + data?: { [key: string]: string }; + notification?: Notification; + android?: AndroidConfig; + webpush?: WebpushConfig; + apns?: ApnsConfig; + fcmOptions?: FcmOptions; +} + +export interface TokenMessage extends BaseMessage { + token: string; +} + +export interface TopicMessage extends BaseMessage { + topic: string; +} + +export interface ConditionMessage extends BaseMessage { + condition: string; +} + +/** + * Payload for the {@link Messaging.send} operation. The payload contains all the fields + * in the BaseMessage type, and exactly one of token, topic or condition. + */ +export type Message = TokenMessage | TopicMessage | ConditionMessage; + +/** + * Payload for the {@link Messaging.sendMulticast} method. The payload contains all the fields + * in the BaseMessage type, and a list of tokens. + */ +export interface MulticastMessage extends BaseMessage { + tokens: string[]; +} + +/** + * A notification that can be included in {@link Message}. + */ +export interface Notification { + /** + * The title of the notification. + */ + title?: string; + /** + * The notification body + */ + body?: string; + /** + * URL of an image to be displayed in the notification. + */ + imageUrl?: string; +} + +/** + * Represents platform-independent options for features provided by the FCM SDKs. + */ +export interface FcmOptions { + /** + * The label associated with the message's analytics data. + */ + analyticsLabel?: string; +} + +/** + * Represents the WebPush protocol options that can be included in an + * {@link Message}. + */ +export interface WebpushConfig { + + /** + * A collection of WebPush headers. Header values must be strings. + * + * See {@link https://tools.ietf.org/html/rfc8030#section-5 | WebPush specification} + * for supported headers. + */ + headers?: { [key: string]: string }; + + /** + * A collection of data fields. + */ + data?: { [key: string]: string }; + + /** + * A WebPush notification payload to be included in the message. + */ + notification?: WebpushNotification; + + /** + * Options for features provided by the FCM SDK for Web. + */ + fcmOptions?: WebpushFcmOptions; +} + +/** Represents options for features provided by the FCM SDK for Web + * (which are not part of the Webpush standard). + */ +export interface WebpushFcmOptions { + + /** + * The link to open when the user clicks on the notification. + * For all URL values, HTTPS is required. + */ + link?: string; +} + +/** + * Represents the WebPush-specific notification options that can be included in + * {@link WebpushConfig}. This supports most of the standard + * options as defined in the Web Notification + * {@link https://developer.mozilla.org/en-US/docs/Web/API/notification/Notification | specification}. + */ +export interface WebpushNotification { + + /** + * Title text of the notification. + */ + title?: string; + + /** + * An array of notification actions representing the actions + * available to the user when the notification is presented. + */ + actions?: Array<{ + + /** + * An action available to the user when the notification is presented + */ + action: string; + + /** + * Optional icon for a notification action. + */ + icon?: string; + + /** + * Title of the notification action. + */ + title: string; + }>; + + /** + * URL of the image used to represent the notification when there is + * not enough space to display the notification itself. + */ + badge?: string; + + /** + * Body text of the notification. + */ + body?: string; + + /** + * Arbitrary data that you want associated with the notification. + * This can be of any data type. + */ + data?: any; + + /** + * The direction in which to display the notification. Must be one + * of `auto`, `ltr` or `rtl`. + */ + dir?: 'auto' | 'ltr' | 'rtl'; + + /** + * URL to the notification icon. + */ + icon?: string; + + /** + * URL of an image to be displayed in the notification. + */ + image?: string; + + /** + * The notification's language as a BCP 47 language tag. + */ + lang?: string; + + /** + * A boolean specifying whether the user should be notified after a + * new notification replaces an old one. Defaults to false. + */ + renotify?: boolean; + + /** + * Indicates that a notification should remain active until the user + * clicks or dismisses it, rather than closing automatically. + * Defaults to false. + */ + requireInteraction?: boolean; + + /** + * A boolean specifying whether the notification should be silent. + * Defaults to false. + */ + silent?: boolean; + + /** + * An identifying tag for the notification. + */ + tag?: string; + + /** + * Timestamp of the notification. Refer to + * https://developer.mozilla.org/en-US/docs/Web/API/notification/timestamp + * for details. + */ + timestamp?: number; + + /** + * A vibration pattern for the device's vibration hardware to emit + * when the notification fires. + */ + vibrate?: number | number[]; + [key: string]: any; +} + +/** + * Represents the APNs-specific options that can be included in an + * {@link Message}. Refer to + * {@link https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html | + * Apple documentation} for various headers and payload fields supported by APNs. + */ +export interface ApnsConfig { + /** + * A collection of APNs headers. Header values must be strings. + */ + headers?: { [key: string]: string }; + + /** + * An APNs payload to be included in the message. + */ + payload?: ApnsPayload; + + /** + * Options for features provided by the FCM SDK for iOS. + */ + fcmOptions?: ApnsFcmOptions; +} + +/** + * Represents the payload of an APNs message. Mainly consists of the `aps` + * dictionary. But may also contain other arbitrary custom keys. + */ +export interface ApnsPayload { + + /** + * The `aps` dictionary to be included in the message. + */ + aps: Aps; + [customData: string]: any; +} + +/** + * Represents the {@link https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html | + * aps dictionary} that is part of APNs messages. + */ +export interface Aps { + + /** + * Alert to be included in the message. This may be a string or an object of + * type `admin.messaging.ApsAlert`. + */ + alert?: string | ApsAlert; + + /** + * Badge to be displayed with the message. Set to 0 to remove the badge. When + * not specified, the badge will remain unchanged. + */ + badge?: number; + + /** + * Sound to be played with the message. + */ + sound?: string | CriticalSound; + + /** + * Specifies whether to configure a background update notification. + */ + contentAvailable?: boolean; + + /** + * Specifies whether to set the `mutable-content` property on the message + * so the clients can modify the notification via app extensions. + */ + mutableContent?: boolean; + + /** + * Type of the notification. + */ + category?: string; + + /** + * An app-specific identifier for grouping notifications. + */ + threadId?: string; + [customData: string]: any; +} + +export interface ApsAlert { + title?: string; + subtitle?: string; + body?: string; + locKey?: string; + locArgs?: string[]; + titleLocKey?: string; + titleLocArgs?: string[]; + subtitleLocKey?: string; + subtitleLocArgs?: string[]; + actionLocKey?: string; + launchImage?: string; +} + +/** + * Represents a critical sound configuration that can be included in the + * `aps` dictionary of an APNs payload. + */ +export interface CriticalSound { + + /** + * The critical alert flag. Set to `true` to enable the critical alert. + */ + critical?: boolean; + + /** + * The name of a sound file in the app's main bundle or in the `Library/Sounds` + * folder of the app's container directory. Specify the string "default" to play + * the system sound. + */ + name: string; + + /** + * The volume for the critical alert's sound. Must be a value between 0.0 + * (silent) and 1.0 (full volume). + */ + volume?: number; +} + +/** + * Represents options for features provided by the FCM SDK for iOS. + */ +export interface ApnsFcmOptions { + + /** + * The label associated with the message's analytics data. + */ + analyticsLabel?: string; + + /** + * URL of an image to be displayed in the notification. + */ + imageUrl?: string; +} + + +/** + * Represents the Android-specific options that can be included in an + * {@link Message}. + */ +export interface AndroidConfig { + + /** + * Collapse key for the message. Collapse key serves as an identifier for a + * group of messages that can be collapsed, so that only the last message gets + * sent when delivery can be resumed. A maximum of four different collapse keys + * may be active at any given time. + */ + collapseKey?: string; + + /** + * Priority of the message. Must be either `normal` or `high`. + */ + priority?: ('high' | 'normal'); + + /** + * Time-to-live duration of the message in milliseconds. + */ + ttl?: number; + + /** + * Package name of the application where the registration tokens must match + * in order to receive the message. + */ + restrictedPackageName?: string; + + /** + * A collection of data fields to be included in the message. All values must + * be strings. When provided, overrides any data fields set on the top-level + * `admin.messaging.Message`.} + */ + data?: { [key: string]: string }; + + /** + * Android notification to be included in the message. + */ + notification?: AndroidNotification; + + /** + * Options for features provided by the FCM SDK for Android. + */ + fcmOptions?: AndroidFcmOptions; +} + +/** + * Represents the Android-specific notification options that can be included in + * {@link AndroidConfig}. + */ +export interface AndroidNotification { + /** + * Title of the Android notification. When provided, overrides the title set via + * `admin.messaging.Notification`. + */ + title?: string; + + /** + * Body of the Android notification. When provided, overrides the body set via + * `admin.messaging.Notification`. + */ + body?: string; + + /** + * Icon resource for the Android notification. + */ + icon?: string; + + /** + * Notification icon color in `#rrggbb` format. + */ + color?: string; + + /** + * File name of the sound to be played when the device receives the + * notification. + */ + sound?: string; + + /** + * Notification tag. This is an identifier used to replace existing + * notifications in the notification drawer. If not specified, each request + * creates a new notification. + */ + tag?: string; + + /** + * URL of an image to be displayed in the notification. + */ + imageUrl?: string; + + /** + * Action associated with a user click on the notification. If specified, an + * activity with a matching Intent Filter is launched when a user clicks on the + * notification. + */ + clickAction?: string; + + /** + * Key of the body string in the app's string resource to use to localize the + * body text. + * + */ + bodyLocKey?: string; + + /** + * An array of resource keys that will be used in place of the format + * specifiers in `bodyLocKey`. + */ + bodyLocArgs?: string[]; + + /** + * Key of the title string in the app's string resource to use to localize the + * title text. + */ + titleLocKey?: string; + + /** + * An array of resource keys that will be used in place of the format + * specifiers in `titleLocKey`. + */ + titleLocArgs?: string[]; + + /** + * The Android notification channel ID (new in Android O). The app must create + * a channel with this channel ID before any notification with this channel ID + * can be received. If you don't send this channel ID in the request, or if the + * channel ID provided has not yet been created by the app, FCM uses the channel + * ID specified in the app manifest. + */ + channelId?: string; + + /** + * Sets the "ticker" text, which is sent to accessibility services. Prior to + * API level 21 (Lollipop), sets the text that is displayed in the status bar + * when the notification first arrives. + */ + ticker?: string; + + /** + * When set to `false` or unset, the notification is automatically dismissed when + * the user clicks it in the panel. When set to `true`, the notification persists + * even when the user clicks it. + */ + sticky?: boolean; + + /** + * For notifications that inform users about events with an absolute time reference, sets + * the time that the event in the notification occurred. Notifications + * in the panel are sorted by this time. + */ + eventTimestamp?: Date; + + /** + * Sets whether or not this notification is relevant only to the current device. + * Some notifications can be bridged to other devices for remote display, such as + * a Wear OS watch. This hint can be set to recommend this notification not be bridged. + * See {@link https://developer.android.com/training/wearables/notifications/bridger#existing-method-of-preventing-bridging | + * Wear OS guides}. + */ + localOnly?: boolean; + + /** + * Sets the relative priority for this notification. Low-priority notifications + * may be hidden from the user in certain situations. Note this priority differs + * from `AndroidMessagePriority`. This priority is processed by the client after + * the message has been delivered. Whereas `AndroidMessagePriority` is an FCM concept + * that controls when the message is delivered. + */ + priority?: ('min' | 'low' | 'default' | 'high' | 'max'); + + /** + * Sets the vibration pattern to use. Pass in an array of milliseconds to + * turn the vibrator on or off. The first value indicates the duration to wait before + * turning the vibrator on. The next value indicates the duration to keep the + * vibrator on. Subsequent values alternate between duration to turn the vibrator + * off and to turn the vibrator on. If `vibrate_timings` is set and `default_vibrate_timings` + * is set to `true`, the default value is used instead of the user-specified `vibrate_timings`. + */ + vibrateTimingsMillis?: number[]; + + /** + * If set to `true`, use the Android framework's default vibrate pattern for the + * notification. Default values are specified in {@link https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/values/config.xml | + * config.xml}. If `default_vibrate_timings` is set to `true` and `vibrate_timings` is also set, + * the default value is used instead of the user-specified `vibrate_timings`. + */ + defaultVibrateTimings?: boolean; + + /** + * If set to `true`, use the Android framework's default sound for the notification. + * Default values are specified in {@link https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/values/config.xml | + * config.xml}. + */ + defaultSound?: boolean; + + /** + * Settings to control the notification's LED blinking rate and color if LED is + * available on the device. The total blinking time is controlled by the OS. + */ + lightSettings?: LightSettings; + + /** + * If set to `true`, use the Android framework's default LED light settings + * for the notification. Default values are specified in {@link https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/values/config.xml | + * config.xml}. + * If `default_light_settings` is set to `true` and `light_settings` is also set, + * the user-specified `light_settings` is used instead of the default value. + */ + defaultLightSettings?: boolean; + + /** + * Sets the visibility of the notification. Must be either `private`, `public`, + * or `secret`. If unspecified, defaults to `private`. + */ + visibility?: ('private' | 'public' | 'secret'); + + /** + * Sets the number of items this notification represents. May be displayed as a + * badge count for Launchers that support badging. See {@link https://developer.android.com/training/notify-user/badges | + * NotificationBadge}. + * For example, this might be useful if you're using just one notification to + * represent multiple new messages but you want the count here to represent + * the number of total new messages. If zero or unspecified, systems + * that support badging use the default, which is to increment a number + * displayed on the long-press menu each time a new notification arrives. + */ + notificationCount?: number; +} + +/** + * Represents settings to control notification LED that can be included in + * {@link AndroidNotification}. + */ +export interface LightSettings { + /** + * Required. Sets color of the LED in `#rrggbb` or `#rrggbbaa` format. + */ + color: string; + + /** + * Required. Along with `light_off_duration`, defines the blink rate of LED flashes. + */ + lightOnDurationMillis: number; + + /** + * Required. Along with `light_on_duration`, defines the blink rate of LED flashes. + */ + lightOffDurationMillis: number; +} + +/** + * Represents options for features provided by the FCM SDK for Android. + */ +export interface AndroidFcmOptions { + + /** + * The label associated with the message's analytics data. + */ + analyticsLabel?: string; +} + +/** + * Interface representing an FCM legacy API data message payload. Data + * messages let developers send up to 4KB of custom key-value pairs. The + * keys and values must both be strings. Keys can be any custom string, + * except for the following reserved strings: + * + *
      + *
    • from
    • + *
    • Anything starting with google.
    • + *
    + * + * See {@link https://firebase.google.com/docs/cloud-messaging/send-message | Build send requests} + * for code samples and detailed documentation. + */ +export interface DataMessagePayload { + [key: string]: string; +} + +/** + * Interface representing an FCM legacy API notification message payload. + * Notification messages let developers send up to 4KB of predefined + * key-value pairs. Accepted keys are outlined below. + * + * See {@link https://firebase.google.com/docs/cloud-messaging/send-message | Build send requests} + * for code samples and detailed documentation. + */ +export interface NotificationMessagePayload { + + /** + * Identifier used to replace existing notifications in the notification drawer. + * + * If not specified, each request creates a new notification. + * + * If specified and a notification with the same tag is already being shown, + * the new notification replaces the existing one in the notification drawer. + * + * **Platforms:** Android + */ + tag?: string; + + /** + * The notification's body text. + * + * **Platforms:** iOS, Android, Web + */ + body?: string; + + /** + * The notification's icon. + * + * **Android:** Sets the notification icon to `myicon` for drawable resource + * `myicon`. If you don't send this key in the request, FCM displays the + * launcher icon specified in your app manifest. + * + * **Web:** The URL to use for the notification's icon. + * + * **Platforms:** Android, Web + */ + icon?: string; + + /** + * The value of the badge on the home screen app icon. + * + * If not specified, the badge is not changed. + * + * If set to `0`, the badge is removed. + * + * **Platforms:** iOS + */ + badge?: string; + + /** + * The notification icon's color, expressed in `#rrggbb` format. + * + * **Platforms:** Android + */ + color?: string; + + /** + * The sound to be played when the device receives a notification. Supports + * "default" for the default notification sound of the device or the filename of a + * sound resource bundled in the app. + * Sound files must reside in `/res/raw/`. + * + * **Platforms:** Android + */ + sound?: string; + + /** + * The notification's title. + * + * **Platforms:** iOS, Android, Web + */ + title?: string; + + /** + * The key to the body string in the app's string resources to use to localize + * the body text to the user's current localization. + * + * **iOS:** Corresponds to `loc-key` in the APNs payload. See + * {@link https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html | + * Payload Key Reference} and + * {@link https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH10-SW9 | + * Localizing the Content of Your Remote Notifications} for more information. + * + * **Android:** See + * {@link http://developer.android.com/guide/topics/resources/string-resource.html | String Resources} + * for more information. + * + * **Platforms:** iOS, Android + */ + bodyLocKey?: string; + + /** + * Variable string values to be used in place of the format specifiers in + * `body_loc_key` to use to localize the body text to the user's current + * localization. + * + * The value should be a stringified JSON array. + * + * **iOS:** Corresponds to `loc-args` in the APNs payload. See + * {@link https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html | + * Payload Key Reference} and + * {@link https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH10-SW9 | + * Localizing the Content of Your Remote Notifications} for more information. + * + * **Android:** See + * {@link http://developer.android.com/guide/topics/resources/string-resource.html#FormattingAndStyling | + * Formatting and Styling} for more information. + * + * **Platforms:** iOS, Android + */ + bodyLocArgs?: string; + + /** + * Action associated with a user click on the notification. If specified, an + * activity with a matching Intent Filter is launched when a user clicks on the + * notification. + * + * * **Platforms:** Android + */ + clickAction?: string; + + /** + * The key to the title string in the app's string resources to use to localize + * the title text to the user's current localization. + * + * **iOS:** Corresponds to `title-loc-key` in the APNs payload. See + * {@link https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html | + * Payload Key Reference} and + * {@link https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH10-SW9 | + * Localizing the Content of Your Remote Notifications} for more information. + * + * **Android:** See + * {@link http://developer.android.com/guide/topics/resources/string-resource.html | String Resources} + * for more information. + * + * **Platforms:** iOS, Android + */ + titleLocKey?: string; + + /** + * Variable string values to be used in place of the format specifiers in + * `title_loc_key` to use to localize the title text to the user's current + * localization. + * + * The value should be a stringified JSON array. + * + * **iOS:** Corresponds to `title-loc-args` in the APNs payload. See + * {@link https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html | + * Payload Key Reference} and + * {@link https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH10-SW9 | + * Localizing the Content of Your Remote Notifications} for more information. + * + * **Android:** See + * {@link http://developer.android.com/guide/topics/resources/string-resource.html#FormattingAndStyling | + * Formatting and Styling} for more information. + * + * **Platforms:** iOS, Android + */ + titleLocArgs?: string; + [key: string]: string | undefined; +} + +/** + * Interface representing a Firebase Cloud Messaging message payload. One or + * both of the `data` and `notification` keys are required. + * + * See {@link https://firebase.google.com/docs/cloud-messaging/send-message | Build send requests} + * for code samples and detailed documentation. + */ +export interface MessagingPayload { + + /** + * The data message payload. + */ + data?: DataMessagePayload; + + /** + * The notification message payload. + */ + notification?: NotificationMessagePayload; +} + +/** + * Interface representing the options that can be provided when sending a + * message via the FCM legacy APIs. + * + * See {@link https://firebase.google.com/docs/cloud-messaging/send-message | Build send requests} + * for code samples and detailed documentation. + */ +export interface MessagingOptions { + + /** + * Whether or not the message should actually be sent. When set to `true`, + * allows developers to test a request without actually sending a message. When + * set to `false`, the message will be sent. + * + * **Default value:** `false` + */ + dryRun?: boolean; + + /** + * The priority of the message. Valid values are `"normal"` and `"high".` On + * iOS, these correspond to APNs priorities `5` and `10`. + * + * By default, notification messages are sent with high priority, and data + * messages are sent with normal priority. Normal priority optimizes the client + * app's battery consumption and should be used unless immediate delivery is + * required. For messages with normal priority, the app may receive the message + * with unspecified delay. + * + * When a message is sent with high priority, it is sent immediately, and the + * app can wake a sleeping device and open a network connection to your server. + * + * For more information, see + * {@link https://firebase.google.com/docs/cloud-messaging/concept-options#setting-the-priority-of-a-message | + * Setting the priority of a message}. + * + * **Default value:** `"high"` for notification messages, `"normal"` for data + * messages + */ + priority?: string; + + /** + * How long (in seconds) the message should be kept in FCM storage if the device + * is offline. The maximum time to live supported is four weeks, and the default + * value is also four weeks. For more information, see + * {@link https://firebase.google.com/docs/cloud-messaging/concept-options#ttl | Setting the lifespan of a message}. + * + * **Default value:** `2419200` (representing four weeks, in seconds) + */ + timeToLive?: number; + + /** + * String identifying a group of messages (for example, "Updates Available") + * that can be collapsed, so that only the last message gets sent when delivery + * can be resumed. This is used to avoid sending too many of the same messages + * when the device comes back online or becomes active. + * + * There is no guarantee of the order in which messages get sent. + * + * A maximum of four different collapse keys is allowed at any given time. This + * means FCM server can simultaneously store four different + * send-to-sync messages per client app. If you exceed this number, there is no + * guarantee which four collapse keys the FCM server will keep. + * + * **Default value:** None + */ + collapseKey?: string; + + /** + * On iOS, use this field to represent `mutable-content` in the APNs payload. + * When a notification is sent and this is set to `true`, the content of the + * notification can be modified before it is displayed, using a + * {@link https://developer.apple.com/reference/usernotifications/unnotificationserviceextension | + * Notification Service app extension}. + * + * On Android and Web, this parameter will be ignored. + * + * **Default value:** `false` + */ + mutableContent?: boolean; + + /** + * On iOS, use this field to represent `content-available` in the APNs payload. + * When a notification or data message is sent and this is set to `true`, an + * inactive client app is awoken. On Android, data messages wake the app by + * default. On Chrome, this flag is currently not supported. + * + * **Default value:** `false` + */ + contentAvailable?: boolean; + + /** + * The package name of the application which the registration tokens must match + * in order to receive the message. + * + * **Default value:** None + */ + restrictedPackageName?: string; + [key: string]: any | undefined; +} + +/* Individual status response payload from single devices */ +export interface MessagingDeviceResult { + /** + * The error that occurred when processing the message for the recipient. + */ + error?: FirebaseError; + + /** + * A unique ID for the successfully processed message. + */ + messageId?: string; + + /** + * The canonical registration token for the client app that the message was + * processed and sent to. You should use this value as the registration token + * for future requests. Otherwise, future messages might be rejected. + */ + canonicalRegistrationToken?: string; +} + +/** + * Interface representing the status of a message sent to an individual device + * via the FCM legacy APIs. + * + * See + * {@link https://firebase.google.com/docs/cloud-messaging/admin/send-messages#send_to_individual_devices | + * Send to individual devices} for code samples and detailed documentation. + */ +export interface MessagingDevicesResponse { + canonicalRegistrationTokenCount: number; + failureCount: number; + multicastId: number; + results: MessagingDeviceResult[]; + successCount: number; +} + +/** + * Interface representing the server response from the {@link Messaging.sendToDeviceGroup} + * method. + * + * See + * {@link https://firebase.google.com/docs/cloud-messaging/send-message?authuser=0#send_messages_to_device_groups | + * Send messages to device groups} for code samples and detailed documentation. + */ +export interface MessagingDeviceGroupResponse { + + /** + * The number of messages that could not be processed and resulted in an error. + */ + successCount: number; + + /** + * The number of messages that could not be processed and resulted in an error. + */ + failureCount: number; + + /** + * An array of registration tokens that failed to receive the message. + */ + failedRegistrationTokens: string[]; +} + +/** + * Interface representing the server response from the legacy {@link Messaging.sendToTopic} method. + * + * See + * {@link https://firebase.google.com/docs/cloud-messaging/admin/send-messages#send_to_a_topic | + * Send to a topic} for code samples and detailed documentation. + */ +export interface MessagingTopicResponse { + /** + * The message ID for a successfully received request which FCM will attempt to + * deliver to all subscribed devices. + */ + messageId: number; +} + +/** + * Interface representing the server response from the legacy + * {@link Messaging.sendToCondition} method. + * + * See + * {@link https://firebase.google.com/docs/cloud-messaging/admin/send-messages#send_to_a_condition | + * Send to a condition} for code samples and detailed documentation. + */ +export interface MessagingConditionResponse { + /** + * The message ID for a successfully received request which FCM will attempt to + * deliver to all subscribed devices. + */ + messageId: number; +} + +/** + * Interface representing the server response from the + * {@link Messaging.subscribeToTopic} and {@link Messaging.unsubscribeFromTopic} + * methods. + * + * See + * {@link https://firebase.google.com/docs/cloud-messaging/manage-topics | + * Manage topics from the server} for code samples and detailed documentation. + */ +export interface MessagingTopicManagementResponse { + /** + * The number of registration tokens that could not be subscribed to the topic + * and resulted in an error. + */ + failureCount: number; + + /** + * The number of registration tokens that were successfully subscribed to the + * topic. + */ + successCount: number; + + /** + * An array of errors corresponding to the provided registration token(s). The + * length of this array will be equal to {@link MessagingTopicManagementResponse.failureCount}. + */ + errors: FirebaseArrayIndexError[]; +} + +/** + * Interface representing the server response from the + * {@link Messaging.sendAll} and {@link Messaging.sendMulticast} methods. + */ +export interface BatchResponse { + + /** + * An array of responses, each corresponding to a message. + */ + responses: SendResponse[]; + + /** + * The number of messages that were successfully handed off for sending. + */ + successCount: number; + + /** + * The number of messages that resulted in errors when sending. + */ + failureCount: number; +} + +/** + * Interface representing the status of an individual message that was sent as + * part of a batch request. + */ +export interface SendResponse { + /** + * A boolean indicating if the message was successfully handed off to FCM or + * not. When true, the `messageId` attribute is guaranteed to be set. When + * false, the `error` attribute is guaranteed to be set. + */ + success: boolean; + + /** + * A unique message ID string, if the message was handed off to FCM for + * delivery. + * + */ + messageId?: string; + + /** + * An error, if the message was not handed off to FCM successfully. + */ + error?: FirebaseError; +} diff --git a/src/messaging/messaging-errors-internal.ts b/src/messaging/messaging-errors-internal.ts index ecd3155bae..e87f635d55 100644 --- a/src/messaging/messaging-errors-internal.ts +++ b/src/messaging/messaging-errors-internal.ts @@ -22,8 +22,8 @@ import * as validator from '../utils/validator'; * Creates a new FirebaseMessagingError by extracting the error code, message and other relevant * details from an HTTP error response. * - * @param {HttpError} err The HttpError to convert into a Firebase error - * @return {FirebaseMessagingError} A Firebase error that can be returned to the user. + * @param err The HttpError to convert into a Firebase error + * @returns A Firebase error that can be returned to the user. */ export function createFirebaseError(err: HttpError): FirebaseMessagingError { if (err.response.isJson()) { @@ -62,8 +62,8 @@ export function createFirebaseError(err: HttpError): FirebaseMessagingError { } /** - * @param {object} response The response to check for errors. - * @return {string|null} The error code if present; null otherwise. + * @param response The response to check for errors. + * @returns The error code if present; null otherwise. */ export function getErrorCode(response: any): string | null { if (validator.isNonNullObject(response) && 'error' in response) { @@ -92,8 +92,8 @@ export function getErrorCode(response: any): string | null { /** * Extracts error message from the given response object. * - * @param {object} response The response to check for errors. - * @return {string|null} The error message if present; null otherwise. + * @param response The response to check for errors. + * @returns The error message if present; null otherwise. */ function getErrorMessage(response: any): string | null { if (validator.isNonNullObject(response) && diff --git a/src/messaging/messaging-internal.ts b/src/messaging/messaging-internal.ts index d84328e722..178ca0a0d7 100644 --- a/src/messaging/messaging-internal.ts +++ b/src/messaging/messaging-internal.ts @@ -16,23 +16,13 @@ import { renameProperties, transformMillisecondsToSecondsString } from '../utils/index'; import { MessagingClientErrorCode, FirebaseMessagingError, } from '../utils/error'; -import { messaging } from './index'; import * as validator from '../utils/validator'; -import AndroidConfig = messaging.AndroidConfig; -import AndroidFcmOptions = messaging.AndroidFcmOptions; -import AndroidNotification = messaging.AndroidNotification; -import ApsAlert = messaging.ApsAlert; -import ApnsConfig = messaging.ApnsConfig; -import ApnsFcmOptions = messaging.ApnsFcmOptions; -import ApnsPayload = messaging.ApnsPayload; -import Aps = messaging.Aps; -import CriticalSound = messaging.CriticalSound; -import FcmOptions = messaging.FcmOptions; -import LightSettings = messaging.LightSettings; -import Message = messaging.Message; -import Notification = messaging.Notification; -import WebpushConfig = messaging.WebpushConfig; +import { + AndroidConfig, AndroidFcmOptions, AndroidNotification, ApsAlert, ApnsConfig, + ApnsFcmOptions, ApnsPayload, Aps, CriticalSound, FcmOptions, LightSettings, Message, + Notification, WebpushConfig, +} from './messaging-api'; // Keys which are not allowed in the messaging data payload object. export const BLACKLISTED_DATA_PAYLOAD_KEYS = ['from']; @@ -382,7 +372,7 @@ function validateApsAlert(alert: string | ApsAlert | undefined): void { * and notification fields. If successful, transforms the input object by renaming keys to valid * Android keys. Also transforms the ttl value to the format expected by FCM service. * - * @param {AndroidConfig} config An object to be validated. + * @param config An object to be validated. */ function validateAndroidConfig(config: AndroidConfig | undefined): void { if (typeof config === 'undefined') { diff --git a/src/messaging/messaging-namespace.ts b/src/messaging/messaging-namespace.ts new file mode 100644 index 0000000000..abfd432bfd --- /dev/null +++ b/src/messaging/messaging-namespace.ts @@ -0,0 +1,253 @@ +/*! + * Copyright 2021 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 { App } from '../app'; +import { Messaging as TMessaging } from './messaging'; +import { + AndroidConfig as TAndroidConfig, + AndroidFcmOptions as TAndroidFcmOptions, + AndroidNotification as TAndroidNotification, + ApnsConfig as TApnsConfig, + ApnsFcmOptions as TApnsFcmOptions, + ApnsPayload as TApnsPayload, + Aps as TAps, + ApsAlert as TApsAlert, + BatchResponse as TBatchResponse, + CriticalSound as TCriticalSound, + ConditionMessage as TConditionMessage, + FcmOptions as TFcmOptions, + LightSettings as TLightSettings, + Message as TMessage, + MessagingTopicManagementResponse as TMessagingTopicManagementResponse, + MulticastMessage as TMulticastMessage, + Notification as TNotification, + SendResponse as TSendResponse, + TokenMessage as TTokenMessage, + TopicMessage as TTopicMessage, + WebpushConfig as TWebpushConfig, + WebpushFcmOptions as TWebpushFcmOptions, + WebpushNotification as TWebpushNotification, + + // Legacy APIs + DataMessagePayload as TDataMessagePayload, + MessagingConditionResponse as TMessagingConditionResponse, + MessagingDeviceGroupResponse as TMessagingDeviceGroupResponse, + MessagingDeviceResult as TMessagingDeviceResult, + MessagingDevicesResponse as TMessagingDevicesResponse, + MessagingOptions as TMessagingOptions, + MessagingPayload as TMessagingPayload, + MessagingTopicResponse as TMessagingTopicResponse, + NotificationMessagePayload as TNotificationMessagePayload, +} from './messaging-api'; + +/** + * Gets the {@link firebase-admin.messaging#Messaging} service for the + * default app or a given app. + * + * `admin.messaging()` can be called with no arguments to access the default + * app's `Messaging` service or as `admin.messaging(app)` to access the + * `Messaging` service associated with a specific app. + * + * @example + * ```javascript + * // Get the Messaging service for the default app + * var defaultMessaging = admin.messaging(); + * ``` + * + * @example + * ```javascript + * // Get the Messaging service for a given app + * var otherMessaging = admin.messaging(otherApp); + * ``` + * + * @param app Optional app whose `Messaging` service to + * return. If not provided, the default `Messaging` service will be returned. + * + * @returns The default `Messaging` service if no + * app is provided or the `Messaging` service associated with the provided + * app. + */ +export declare function messaging(app?: App): messaging.Messaging; + +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace messaging { + /** + * Type alias to {@link firebase-admin.messaging#Messaging}. + */ + export type Messaging = TMessaging; + + /** + * Type alias to {@link firebase-admin.messaging#AndroidConfig}. + */ + export type AndroidConfig = TAndroidConfig; + + /** + * Type alias to {@link firebase-admin.messaging#AndroidFcmOptions}. + */ + export type AndroidFcmOptions = TAndroidFcmOptions; + + /** + * Type alias to {@link firebase-admin.messaging#AndroidNotification}. + */ + export type AndroidNotification = TAndroidNotification; + + /** + * Type alias to {@link firebase-admin.messaging#ApnsConfig}. + */ + export type ApnsConfig = TApnsConfig; + + /** + * Type alias to {@link firebase-admin.messaging#ApnsFcmOptions}. + */ + export type ApnsFcmOptions = TApnsFcmOptions; + + /** + * Type alias to {@link firebase-admin.messaging#ApnsPayload}. + */ + export type ApnsPayload = TApnsPayload; + + /** + * Type alias to {@link firebase-admin.messaging#Aps}. + */ + export type Aps = TAps; + + /** + * Type alias to {@link firebase-admin.messaging#ApsAlert}. + */ + export type ApsAlert = TApsAlert; + + /** + * Type alias to {@link firebase-admin.messaging#BatchResponse}. + */ + export type BatchResponse = TBatchResponse; + + /** + * Type alias to {@link firebase-admin.messaging#CriticalSound}. + */ + export type CriticalSound = TCriticalSound; + + /** + * Type alias to {@link firebase-admin.messaging#ConditionMessage}. + */ + export type ConditionMessage = TConditionMessage; + + /** + * Type alias to {@link firebase-admin.messaging#FcmOptions}. + */ + export type FcmOptions = TFcmOptions; + + /** + * Type alias to {@link firebase-admin.messaging#LightSettings}. + */ + export type LightSettings = TLightSettings; + + /** + * Type alias to {@link firebase-admin.messaging#Message}. + */ + export type Message = TMessage; + + /** + * Type alias to {@link firebase-admin.messaging#MessagingTopicManagementResponse}. + */ + export type MessagingTopicManagementResponse = TMessagingTopicManagementResponse; + + /** + * Type alias to {@link firebase-admin.messaging#MulticastMessage}. + */ + export type MulticastMessage = TMulticastMessage; + + /** + * Type alias to {@link firebase-admin.messaging#Notification}. + */ + export type Notification = TNotification; + + /** + * Type alias to {@link firebase-admin.messaging#SendResponse}. + */ + export type SendResponse = TSendResponse; + + /** + * Type alias to {@link firebase-admin.messaging#TokenMessage}. + */ + export type TokenMessage = TTokenMessage; + + /** + * Type alias to {@link firebase-admin.messaging#TopicMessage}. + */ + export type TopicMessage = TTopicMessage; + + /** + * Type alias to {@link firebase-admin.messaging#WebpushConfig}. + */ + export type WebpushConfig = TWebpushConfig; + + /** + * Type alias to {@link firebase-admin.messaging#WebpushFcmOptions}. + */ + export type WebpushFcmOptions = TWebpushFcmOptions; + + /** + * Type alias to {@link firebase-admin.messaging#WebpushNotification}. + */ + export type WebpushNotification = TWebpushNotification; + + // Legacy APIs + + /** + * Type alias to {@link firebase-admin.messaging#DataMessagePayload}. + */ + export type DataMessagePayload = TDataMessagePayload; + + /** + * Type alias to {@link firebase-admin.messaging#MessagingConditionResponse}. + */ + export type MessagingConditionResponse = TMessagingConditionResponse; + + /** + * Type alias to {@link firebase-admin.messaging#MessagingDeviceGroupResponse}. + */ + export type MessagingDeviceGroupResponse = TMessagingDeviceGroupResponse; + + /** + * Type alias to {@link firebase-admin.messaging#MessagingDeviceResult}. + */ + export type MessagingDeviceResult = TMessagingDeviceResult; + + /** + * Type alias to {@link firebase-admin.messaging#MessagingDevicesResponse}. + */ + export type MessagingDevicesResponse = TMessagingDevicesResponse; + + /** + * Type alias to {@link firebase-admin.messaging#MessagingOptions}. + */ + export type MessagingOptions = TMessagingOptions; + + /** + * Type alias to {@link firebase-admin.messaging#MessagingPayload}. + */ + export type MessagingPayload = TMessagingPayload; + + /** + * Type alias to {@link firebase-admin.messaging#MessagingTopicResponse}. + */ + export type MessagingTopicResponse = TMessagingTopicResponse; + + /** + * Type alias to {@link firebase-admin.messaging#NotificationMessagePayload}. + */ + export type NotificationMessagePayload = TNotificationMessagePayload; +} diff --git a/src/messaging/messaging.ts b/src/messaging/messaging.ts index 88e66cf565..688ec08fdb 100644 --- a/src/messaging/messaging.ts +++ b/src/messaging/messaging.ts @@ -15,31 +15,32 @@ * limitations under the License. */ -import { FirebaseApp } from '../firebase-app'; +import { App } from '../app'; import { deepCopy, deepExtend } from '../utils/deep-copy'; import { SubRequest } from './batch-request-internal'; -import { validateMessage, BLACKLISTED_DATA_PAYLOAD_KEYS, BLACKLISTED_OPTIONS_KEYS } from './messaging-internal'; -import { messaging } from './index'; -import { FirebaseMessagingRequestHandler } from './messaging-api-request-internal'; import { ErrorInfo, MessagingClientErrorCode, FirebaseMessagingError } from '../utils/error'; import * as utils from '../utils'; import * as validator from '../utils/validator'; +import { validateMessage, BLACKLISTED_DATA_PAYLOAD_KEYS, BLACKLISTED_OPTIONS_KEYS } from './messaging-internal'; +import { FirebaseMessagingRequestHandler } from './messaging-api-request-internal'; + +import { + BatchResponse, + Message, + MessagingTopicManagementResponse, + MulticastMessage, + + // Legacy API types + MessagingDevicesResponse, + MessagingDeviceGroupResponse, + MessagingPayload, + MessagingOptions, + MessagingTopicResponse, + MessagingConditionResponse, + DataMessagePayload, + NotificationMessagePayload, +} from './messaging-api'; -import MessagingInterface = messaging.Messaging; -import Message = messaging.Message; -import BatchResponse = messaging.BatchResponse; -import MulticastMessage = messaging.MulticastMessage; -import MessagingTopicManagementResponse = messaging.MessagingTopicManagementResponse; - -// Legacy API types -import MessagingDevicesResponse = messaging.MessagingDevicesResponse; -import MessagingDeviceGroupResponse = messaging.MessagingDeviceGroupResponse; -import MessagingPayload = messaging.MessagingPayload; -import MessagingOptions = messaging.MessagingOptions; -import MessagingTopicResponse = messaging.MessagingTopicResponse; -import MessagingConditionResponse = messaging.MessagingConditionResponse; -import DataMessagePayload = messaging.DataMessagePayload; -import NotificationMessagePayload = messaging.NotificationMessagePayload; /* eslint-disable @typescript-eslint/camelcase */ @@ -106,9 +107,9 @@ const MESSAGING_CONDITION_RESPONSE_KEYS_MAP = { /** * Maps a raw FCM server response to a MessagingDevicesResponse object. * - * @param {object} response The raw FCM server response to map. + * @param response The raw FCM server response to map. * - * @return {MessagingDeviceGroupResponse} The mapped MessagingDevicesResponse object. + * @returns The mapped MessagingDevicesResponse object. */ function mapRawResponseToDevicesResponse(response: object): MessagingDevicesResponse { // Rename properties on the server response @@ -133,9 +134,9 @@ function mapRawResponseToDevicesResponse(response: object): MessagingDevicesResp /** * Maps a raw FCM server response to a MessagingDeviceGroupResponse object. * - * @param {object} response The raw FCM server response to map. + * @param response The raw FCM server response to map. * - * @return {MessagingDeviceGroupResponse} The mapped MessagingDeviceGroupResponse object. + * @returns The mapped MessagingDeviceGroupResponse object. */ function mapRawResponseToDeviceGroupResponse(response: object): MessagingDeviceGroupResponse { // Rename properties on the server response @@ -153,7 +154,7 @@ function mapRawResponseToDeviceGroupResponse(response: object): MessagingDeviceG * * @param {object} response The raw FCM server response to map. * - * @return {MessagingTopicManagementResponse} The mapped MessagingTopicManagementResponse object. + * @returns {MessagingTopicManagementResponse} The mapped MessagingTopicManagementResponse object. */ function mapRawResponseToTopicManagementResponse(response: object): MessagingTopicManagementResponse { // Add the success and failure counts. @@ -188,26 +189,16 @@ function mapRawResponseToTopicManagementResponse(response: object): MessagingTop /** * Messaging service bound to the provided app. */ -export class Messaging implements MessagingInterface { +export class Messaging { private urlPath: string; - private readonly appInternal: FirebaseApp; + private readonly appInternal: App; private readonly messagingRequestHandler: FirebaseMessagingRequestHandler; /** - * Gets the {@link messaging.Messaging `Messaging`} service for the - * current app. - * - * @example - * ```javascript - * var messaging = app.messaging(); - * // The above is shorthand for: - * // var messaging = admin.messaging(app); - * ``` - * - * @return The `Messaging` service for the current app. + * @internal */ - constructor(app: FirebaseApp) { + constructor(app: App) { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseMessagingError( MessagingClientErrorCode.INVALID_ARGUMENT, @@ -220,11 +211,15 @@ export class Messaging implements MessagingInterface { } /** - * Returns the app associated with this Messaging instance. + * The {@link firebase-admin.app#App} associated with the current `Messaging` service + * instance. * - * @return {FirebaseApp} The app associated with this Messaging instance. + * @example + * ```javascript + * var app = messaging.app; + * ``` */ - get app(): FirebaseApp { + get app(): App { return this.appInternal; } @@ -234,7 +229,7 @@ export class Messaging implements MessagingInterface { * @param message The message payload. * @param dryRun Whether to send the message in the dry-run * (validation only) mode. - * @return A promise fulfilled with a unique message ID + * @returns A promise fulfilled with a unique message ID * string after the message has been successfully handed off to the FCM * service for delivery. */ @@ -274,7 +269,7 @@ export class Messaging implements MessagingInterface { * containing up to 500 messages. * @param dryRun Whether to send the messages in the dry-run * (validation only) mode. - * @return A Promise fulfilled with an object representing the result of the + * @returns A Promise fulfilled with an object representing the result of the * send operation. */ public sendAll(messages: Message[], dryRun?: boolean): Promise { @@ -332,7 +327,7 @@ export class Messaging implements MessagingInterface { * containing up to 500 tokens. * @param dryRun Whether to send the message in the dry-run * (validation only) mode. - * @return A Promise fulfilled with an object representing the result of the + * @returns A Promise fulfilled with an object representing the result of the * send operation. */ public sendMulticast(message: MulticastMessage, dryRun?: boolean): Promise { @@ -369,8 +364,8 @@ export class Messaging implements MessagingInterface { * Sends an FCM message to a single device corresponding to the provided * registration token. * - * See - * [Send to individual devices](/docs/cloud-messaging/admin/legacy-fcm#send_to_individual_devices) + * See {@link https://firebase.google.com/docs/cloud-messaging/admin/legacy-fcm#send_to_individual_devices | + * Send to individual devices} * for code samples and detailed documentation. Takes either a * `registrationToken` to send to a single device or a * `registrationTokens` parameter containing an array of tokens to send @@ -382,7 +377,7 @@ export class Messaging implements MessagingInterface { * @param options Optional options to * alter the message. * - * @return A promise fulfilled with the server's response after the message + * @returns A promise fulfilled with the server's response after the message * has been sent. */ public sendToDevice( @@ -442,9 +437,8 @@ export class Messaging implements MessagingInterface { * Sends an FCM message to a device group corresponding to the provided * notification key. * - * See - * [Send to a device group](/docs/cloud-messaging/admin/legacy-fcm#send_to_a_device_group) - * for code samples and detailed documentation. + * See {@link https://firebase.google.com/docs/cloud-messaging/admin/legacy-fcm#send_to_a_device_group | + * Send to a device group} for code samples and detailed documentation. * * @param notificationKey The notification key for the device group to * which to send the message. @@ -452,7 +446,7 @@ export class Messaging implements MessagingInterface { * @param options Optional options to * alter the message. * - * @return A promise fulfilled with the server's response after the message + * @returns A promise fulfilled with the server's response after the message * has been sent. */ public sendToDeviceGroup( @@ -526,16 +520,15 @@ export class Messaging implements MessagingInterface { /** * Sends an FCM message to a topic. * - * See - * [Send to a topic](/docs/cloud-messaging/admin/legacy-fcm#send_to_a_topic) - * for code samples and detailed documentation. + * See {@link https://firebase.google.com/docs/cloud-messaging/admin/legacy-fcm#send_to_a_topic | + * Send to a topic} for code samples and detailed documentation. * * @param topic The topic to which to send the message. * @param payload The message payload. * @param options Optional options to * alter the message. * - * @return A promise fulfilled with the server's response after the message + * @returns A promise fulfilled with the server's response after the message * has been sent. */ public sendToTopic( @@ -576,8 +569,8 @@ export class Messaging implements MessagingInterface { /** * Sends an FCM message to a condition. * - * See - * [Send to a condition](/docs/cloud-messaging/admin/legacy-fcm#send_to_a_condition) + * See {@link https://firebase.google.com/docs/cloud-messaging/admin/legacy-fcm#send_to_a_condition | + * Send to a condition} * for code samples and detailed documentation. * * @param condition The condition determining to which topics to send @@ -586,7 +579,7 @@ export class Messaging implements MessagingInterface { * @param options Optional options to * alter the message. * - * @return A promise fulfilled with the server's response after the message + * @returns A promise fulfilled with the server's response after the message * has been sent. */ public sendToCondition( @@ -634,8 +627,8 @@ export class Messaging implements MessagingInterface { /** * Subscribes a device to an FCM topic. * - * See [Subscribe to a - * topic](/docs/cloud-messaging/manage-topics#suscribe_and_unsubscribe_using_the) + * See {@link https://firebase.google.com/docs/cloud-messaging/manage-topics#suscribe_and_unsubscribe_using_the | + * Subscribe to a topic} * for code samples and detailed documentation. Optionally, you can provide an * array of tokens to subscribe multiple devices. * @@ -643,7 +636,7 @@ export class Messaging implements MessagingInterface { * for the devices to subscribe to the topic. * @param topic The topic to which to subscribe. * - * @return A promise fulfilled with the server's response after the device has been + * @returns A promise fulfilled with the server's response after the device has been * subscribed to the topic. */ public subscribeToTopic( @@ -661,8 +654,8 @@ export class Messaging implements MessagingInterface { /** * Unsubscribes a device from an FCM topic. * - * See [Unsubscribe from a - * topic](/docs/cloud-messaging/admin/manage-topic-subscriptions#unsubscribe_from_a_topic) + * See {@link https://firebase.google.com/docs/cloud-messaging/admin/manage-topic-subscriptions#unsubscribe_from_a_topic | + * Unsubscribe from a topic} * for code samples and detailed documentation. Optionally, you can provide an * array of tokens to unsubscribe multiple devices. * @@ -670,7 +663,7 @@ export class Messaging implements MessagingInterface { * device registration tokens to unsubscribe from the topic. * @param topic The topic from which to unsubscribe. * - * @return A promise fulfilled with the server's response after the device has been + * @returns A promise fulfilled with the server's response after the device has been * unsubscribed from the topic. */ public unsubscribeFromTopic( @@ -710,13 +703,13 @@ export class Messaging implements MessagingInterface { /** * Helper method which sends and handles topic subscription management requests. * - * @param {string|string[]} registrationTokenOrTokens The registration token or an array of + * @param registrationTokenOrTokens The registration token or an array of * registration tokens to unsubscribe from the topic. - * @param {string} topic The topic to which to subscribe. - * @param {string} methodName The name of the original method called. - * @param {string} path The endpoint path to use for the request. + * @param topic The topic to which to subscribe. + * @param methodName The name of the original method called. + * @param path The endpoint path to use for the request. * - * @return {Promise} A Promise fulfilled with the parsed server + * @returns A Promise fulfilled with the parsed server * response. */ private sendTopicManagementRequest( @@ -761,8 +754,8 @@ export class Messaging implements MessagingInterface { /** * Validates the types of the messaging payload and options. If invalid, an error will be thrown. * - * @param {MessagingPayload} payload The messaging payload to validate. - * @param {MessagingOptions} options The messaging options to validate. + * @param payload The messaging payload to validate. + * @param options The messaging options to validate. */ private validateMessagingPayloadAndOptionsTypes( payload: MessagingPayload, @@ -788,9 +781,9 @@ export class Messaging implements MessagingInterface { /** * Validates the messaging payload. If invalid, an error will be thrown. * - * @param {MessagingPayload} payload The messaging payload to validate. + * @param payload The messaging payload to validate. * - * @return {MessagingPayload} A copy of the provided payload with whitelisted properties switched + * @returns A copy of the provided payload with whitelisted properties switched * from camelCase to underscore_case. */ private validateMessagingPayload(payload: MessagingPayload): MessagingPayload { @@ -879,9 +872,9 @@ export class Messaging implements MessagingInterface { /** * Validates the messaging options. If invalid, an error will be thrown. * - * @param {MessagingOptions} options The messaging options to validate. + * @param options The messaging options to validate. * - * @return {MessagingOptions} A copy of the provided options with whitelisted properties switched + * @returns A copy of the provided options with whitelisted properties switched * from camelCase to underscore_case. */ private validateMessagingOptions(options: MessagingOptions): MessagingOptions { @@ -958,9 +951,9 @@ export class Messaging implements MessagingInterface { /** * Validates the type of the provided registration token(s). If invalid, an error will be thrown. * - * @param {string|string[]} registrationTokenOrTokens The registration token(s) to validate. - * @param {string} method The method name to use in error messages. - * @param {ErrorInfo?} [errorInfo] The error info to use if the registration tokens are invalid. + * @param registrationTokenOrTokens The registration token(s) to validate. + * @param method The method name to use in error messages. + * @param errorInfo The error info to use if the registration tokens are invalid. */ private validateRegistrationTokensType( registrationTokenOrTokens: string | string[], @@ -980,10 +973,10 @@ export class Messaging implements MessagingInterface { /** * Validates the provided registration tokens. If invalid, an error will be thrown. * - * @param {string|string[]} registrationTokenOrTokens The registration token or an array of + * @param registrationTokenOrTokens The registration token or an array of * registration tokens to validate. - * @param {string} method The method name to use in error messages. - * @param {errorInfo?} [ErrorInfo] The error info to use if the registration tokens are invalid. + * @param method The method name to use in error messages. + * @param errorInfo The error info to use if the registration tokens are invalid. */ private validateRegistrationTokens( registrationTokenOrTokens: string | string[], @@ -1016,9 +1009,9 @@ export class Messaging implements MessagingInterface { /** * Validates the type of the provided topic. If invalid, an error will be thrown. * - * @param {string} topic The topic to validate. - * @param {string} method The method name to use in error messages. - * @param {ErrorInfo?} [errorInfo] The error info to use if the topic is invalid. + * @param topic The topic to validate. + * @param method The method name to use in error messages. + * @param errorInfo The error info to use if the topic is invalid. */ private validateTopicType( topic: string | string[], @@ -1037,9 +1030,9 @@ export class Messaging implements MessagingInterface { /** * Validates the provided topic. If invalid, an error will be thrown. * - * @param {string} topic The topic to validate. - * @param {string} method The method name to use in error messages. - * @param {ErrorInfo?} [errorInfo] The error info to use if the topic is invalid. + * @param topic The topic to validate. + * @param method The method name to use in error messages. + * @param errorInfo The error info to use if the topic is invalid. */ private validateTopic( topic: string, @@ -1058,9 +1051,9 @@ export class Messaging implements MessagingInterface { /** * Normalizes the provided topic name by prepending it with '/topics/', if necessary. * - * @param {string} topic The topic name to normalize. + * @param topic The topic name to normalize. * - * @return {string} The normalized topic name. + * @returns The normalized topic name. */ private normalizeTopic(topic: string): string { if (!/^\/topics\//.test(topic)) { diff --git a/src/project-management/android-app.ts b/src/project-management/android-app.ts index 8afba57094..dc63b47da9 100644 --- a/src/project-management/android-app.ts +++ b/src/project-management/android-app.ts @@ -17,16 +17,40 @@ import { FirebaseProjectManagementError } from '../utils/error'; import * as validator from '../utils/validator'; import { ProjectManagementRequestHandler, assertServerResponse } from './project-management-api-request-internal'; -import { projectManagement } from './index'; +import { AppMetadata, AppPlatform } from './app-metadata'; -import AndroidAppInterface = projectManagement.AndroidApp; -import AndroidAppMetadata = projectManagement.AndroidAppMetadata; -import AppPlatform = projectManagement.AppPlatform; -import ShaCertificateInterface = projectManagement.ShaCertificate; -export class AndroidApp implements AndroidAppInterface { +/** + * Metadata about a Firebase Android App. + */ +export interface AndroidAppMetadata extends AppMetadata { + + platform: AppPlatform.ANDROID; + + /** + * The canonical package name of the Android App, as would appear in the Google Play Developer + * Console. + * + * @example + * ```javascript + * var packageName = androidAppMetadata.packageName; + * ``` + */ + packageName: string; +} + +/** + * A reference to a Firebase Android app. + * + * Do not call this constructor directly. Instead, use {@link ProjectManagement.androidApp}. + */ +export class AndroidApp { + private readonly resourceName: string; + /** + * @internal + */ constructor( public readonly appId: string, private readonly requestHandler: ProjectManagementRequestHandler) { @@ -41,7 +65,7 @@ export class AndroidApp implements AndroidAppInterface { /** * Retrieves metadata about this Android app. * - * @return A promise that resolves to the retrieved metadata about this Android app. + * @returns A promise that resolves to the retrieved metadata about this Android app. */ public getMetadata(): Promise { return this.requestHandler.getResource(this.resourceName) @@ -76,7 +100,7 @@ export class AndroidApp implements AndroidAppInterface { * * @param newDisplayName The new display name to set. * - * @return A promise that resolves when the display name has been set. + * @returns A promise that resolves when the display name has been set. */ public setDisplayName(newDisplayName: string): Promise { return this.requestHandler.setDisplayName(this.resourceName, newDisplayName); @@ -85,7 +109,7 @@ export class AndroidApp implements AndroidAppInterface { /** * Gets the list of SHA certificates associated with this Android app in Firebase. * - * @return The list of SHA-1 and SHA-256 certificates associated with this Android app in + * @returns The list of SHA-1 and SHA-256 certificates associated with this Android app in * Firebase. */ public getShaCertificates(): Promise { @@ -126,7 +150,7 @@ export class AndroidApp implements AndroidAppInterface { * * @param certificateToAdd The SHA certificate to add. * - * @return A promise that resolves when the given certificate + * @returns A promise that resolves when the given certificate * has been added to the Android app. */ public addShaCertificate(certificateToAdd: ShaCertificate): Promise { @@ -138,7 +162,7 @@ export class AndroidApp implements AndroidAppInterface { * * @param certificateToDelete The SHA certificate to delete. * - * @return A promise that resolves when the specified + * @returns A promise that resolves when the specified * certificate has been removed from the Android app. */ public deleteShaCertificate(certificateToDelete: ShaCertificate): Promise { @@ -154,7 +178,7 @@ export class AndroidApp implements AndroidAppInterface { /** * Gets the configuration artifact associated with this app. * - * @return A promise that resolves to the Android app's + * @returns A promise that resolves to the Android app's * Firebase config file, in UTF-8 string format. This string is typically * intended to be written to a JSON file that gets shipped with your Android * app. @@ -184,7 +208,7 @@ export class AndroidApp implements AndroidAppInterface { * Do not call this constructor directly. Instead, use * [`projectManagement.shaCertificate()`](projectManagement.ProjectManagement#shaCertificate). */ -export class ShaCertificate implements ShaCertificateInterface { +export class ShaCertificate { /** * The SHA certificate type. * @@ -210,6 +234,8 @@ export class ShaCertificate implements ShaCertificateInterface { * ```javascript * var resourceName = shaCertificate.resourceName; * ``` + * + * @internal */ constructor(public readonly shaHash: string, public readonly resourceName?: string) { if (/^[a-fA-F0-9]{40}$/.test(shaHash)) { diff --git a/src/project-management/app-metadata.ts b/src/project-management/app-metadata.ts new file mode 100644 index 0000000000..8fd69255d2 --- /dev/null +++ b/src/project-management/app-metadata.ts @@ -0,0 +1,92 @@ +/*! + * Copyright 2021 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. + */ + +/** + * Platforms with which a Firebase App can be associated. + */ +export enum AppPlatform { + /** + * Unknown state. This is only used for distinguishing unset values. + */ + PLATFORM_UNKNOWN = 'PLATFORM_UNKNOWN', + + /** + * The Firebase App is associated with iOS. + */ + IOS = 'IOS', + + /** + * The Firebase App is associated with Android. + */ + ANDROID = 'ANDROID', +} + +/** + * Metadata about a Firebase app. + */ +export interface AppMetadata { + /** + * The globally unique, Firebase-assigned identifier of the app. + * + * @example + * ```javascript + * var appId = appMetadata.appId; + * ``` + */ + appId: string; + + /** + * The optional user-assigned display name of the app. + * + * @example + * ```javascript + * var displayName = appMetadata.displayName; + * ``` + */ + displayName?: string; + + /** + * The development platform of the app. Supporting Android and iOS app platforms. + * + * @example + * ```javascript + * var platform = AppPlatform.ANDROID; + * ``` + */ + platform: AppPlatform; + + /** + * The globally unique, user-assigned ID of the parent project for the app. + * + * @example + * ```javascript + * var projectId = appMetadata.projectId; + * ``` + */ + projectId: string; + + /** + * The fully-qualified resource name that identifies this app. + * + * This is useful when manually constructing requests for Firebase's public API. + * + * @example + * ```javascript + * var resourceName = androidAppMetadata.resourceName; + * ``` + */ + resourceName: string; +} diff --git a/src/project-management/index.ts b/src/project-management/index.ts index 2c905d6c22..1bdff385f9 100644 --- a/src/project-management/index.ts +++ b/src/project-management/index.ts @@ -14,391 +14,51 @@ * limitations under the License. */ -import { app } from '../firebase-namespace-api'; +/** + * Firebase project management. + * + * @packageDocumentation + */ + +import { App, getApp } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; +import { ProjectManagement } from './project-management'; + +export { AppMetadata, AppPlatform } from './app-metadata'; +export { ProjectManagement } from './project-management'; +export { AndroidApp, AndroidAppMetadata, ShaCertificate } from './android-app'; +export { IosApp, IosAppMetadata } from './ios-app'; /** - * Gets the {@link projectManagement.ProjectManagement - * `ProjectManagement`} service for the default app or a given app. + * Gets the {@link ProjectManagement} service for the default app or a given app. * - * `admin.projectManagement()` can be called with no arguments to access the - * default app's {@link projectManagement.ProjectManagement - * `ProjectManagement`} service, or as `admin.projectManagement(app)` to access - * the {@link projectManagement.ProjectManagement `ProjectManagement`} - * service associated with a specific app. + * `getProjectManagement()` can be called with no arguments to access the + * default app's `ProjectManagement` service, or as `getProjectManagement(app)` to access + * the `ProjectManagement` service associated with a specific app. * * @example * ```javascript * // Get the ProjectManagement service for the default app - * var defaultProjectManagement = admin.projectManagement(); + * const defaultProjectManagement = getProjectManagement(); * ``` * * @example * ```javascript * // Get the ProjectManagement service for a given app - * var otherProjectManagement = admin.projectManagement(otherApp); + * const otherProjectManagement = getProjectManagement(otherApp); * ``` * * @param app Optional app whose `ProjectManagement` service * to return. If not provided, the default `ProjectManagement` service will * be returned. * - * @return The default `ProjectManagement` service if no app is provided or the + * @returns The default `ProjectManagement` service if no app is provided or the * `ProjectManagement` service associated with the provided app. */ -export declare function projectManagement(app?: app.App): projectManagement.ProjectManagement; - -/* eslint-disable @typescript-eslint/no-namespace */ -export namespace projectManagement { - /** - * Metadata about a Firebase Android App. - */ - export interface AndroidAppMetadata extends AppMetadata { - - platform: AppPlatform.ANDROID; - - /** - * The canonical package name of the Android App, as would appear in the Google Play Developer - * Console. - * - * @example - * ```javascript - * var packageName = androidAppMetadata.packageName; - * ``` - */ - packageName: string; +export function getProjectManagement(app?: App): ProjectManagement { + if (typeof app === 'undefined') { + app = getApp(); } - /** - * Metadata about a Firebase app. - */ - export interface AppMetadata { - /** - * The globally unique, Firebase-assigned identifier of the app. - * - * @example - * ```javascript - * var appId = appMetadata.appId; - * ``` - */ - appId: string; - - /** - * The optional user-assigned display name of the app. - * - * @example - * ```javascript - * var displayName = appMetadata.displayName; - * ``` - */ - displayName?: string; - - /** - * The development platform of the app. Supporting Android and iOS app platforms. - * - * @example - * ```javascript - * var platform = AppPlatform.ANDROID; - * ``` - */ - platform: AppPlatform; - - /** - * The globally unique, user-assigned ID of the parent project for the app. - * - * @example - * ```javascript - * var projectId = appMetadata.projectId; - * ``` - */ - projectId: string; - - /** - * The fully-qualified resource name that identifies this app. - * - * This is useful when manually constructing requests for Firebase's public API. - * - * @example - * ```javascript - * var resourceName = androidAppMetadata.resourceName; - * ``` - */ - resourceName: string; - } - - /** - * Platforms with which a Firebase App can be associated. - */ - export enum AppPlatform { - /** - * Unknown state. This is only used for distinguishing unset values. - */ - PLATFORM_UNKNOWN = 'PLATFORM_UNKNOWN', - - /** - * The Firebase App is associated with iOS. - */ - IOS = 'IOS', - - /** - * The Firebase App is associated with Android. - */ - ANDROID = 'ANDROID', - } - - /** - * Metadata about a Firebase iOS App. - */ - export interface IosAppMetadata extends AppMetadata { - platform: AppPlatform.IOS; - - /** - * The canonical bundle ID of the iOS App as it would appear in the iOS App Store. - * - * @example - * ```javascript - * var bundleId = iosAppMetadata.bundleId; - *``` - */ - bundleId: string; - } - - /** - * A reference to a Firebase Android app. - * - * Do not call this constructor directly. Instead, use - * [`projectManagement.androidApp()`](projectManagement.ProjectManagement#androidApp). - */ - export interface AndroidApp { - appId: string; - - /** - * Retrieves metadata about this Android app. - * - * @return A promise that resolves to the retrieved metadata about this Android app. - */ - getMetadata(): Promise; - - /** - * Sets the optional user-assigned display name of the app. - * - * @param newDisplayName The new display name to set. - * - * @return A promise that resolves when the display name has been set. - */ - setDisplayName(newDisplayName: string): Promise; - - /** - * Gets the list of SHA certificates associated with this Android app in Firebase. - * - * @return The list of SHA-1 and SHA-256 certificates associated with this Android app in - * Firebase. - */ - getShaCertificates(): Promise; - - /** - * Adds the given SHA certificate to this Android app. - * - * @param certificateToAdd The SHA certificate to add. - * - * @return A promise that resolves when the given certificate - * has been added to the Android app. - */ - addShaCertificate(certificateToAdd: ShaCertificate): Promise; - - /** - * Deletes the specified SHA certificate from this Android app. - * - * @param certificateToDelete The SHA certificate to delete. - * - * @return A promise that resolves when the specified - * certificate has been removed from the Android app. - */ - deleteShaCertificate(certificateToRemove: ShaCertificate): Promise; - - /** - * Gets the configuration artifact associated with this app. - * - * @return A promise that resolves to the Android app's - * Firebase config file, in UTF-8 string format. This string is typically - * intended to be written to a JSON file that gets shipped with your Android - * app. - */ - getConfig(): Promise; - } - - /** - * A reference to a Firebase iOS app. - * - * Do not call this constructor directly. Instead, use - * [`projectManagement.iosApp()`](projectManagement.ProjectManagement#iosApp). - */ - export interface IosApp { - appId: string; - - /** - * Retrieves metadata about this iOS app. - * - * @return {!Promise} A promise that - * resolves to the retrieved metadata about this iOS app. - */ - getMetadata(): Promise; - - /** - * Sets the optional user-assigned display name of the app. - * - * @param newDisplayName The new display name to set. - * - * @return A promise that resolves when the display name has - * been set. - */ - setDisplayName(newDisplayName: string): Promise; - - /** - * Gets the configuration artifact associated with this app. - * - * @return A promise that resolves to the iOS app's Firebase - * config file, in UTF-8 string format. This string is typically intended to - * be written to a plist file that gets shipped with your iOS app. - */ - getConfig(): Promise; - } - - /** - * A SHA-1 or SHA-256 certificate. - * - * Do not call this constructor directly. Instead, use - * [`projectManagement.shaCertificate()`](projectManagement.ProjectManagement#shaCertificate). - */ - export interface ShaCertificate { - - /** - * The SHA certificate type. - * - * @example - * ```javascript - * var certType = shaCertificate.certType; - * ``` - */ - certType: ('sha1' | 'sha256'); - - /** - * The SHA-1 or SHA-256 hash for this certificate. - * - * @example - * ```javascript - * var shaHash = shaCertificate.shaHash; - * ``` - */ - shaHash: string; - - /** - * The fully-qualified resource name that identifies this sha-key. - * - * This is useful when manually constructing requests for Firebase's public API. - * - * @example - * ```javascript - * var resourceName = shaCertificate.resourceName; - * ``` - */ - resourceName?: string; - } - - /** - * The Firebase ProjectManagement service interface. - * - * Do not call this constructor directly. Instead, use - * [`admin.projectManagement()`](projectManagement#projectManagement). - */ - export interface ProjectManagement { - app: app.App; - - /** - * Lists up to 100 Firebase apps associated with this Firebase project. - * - * @return A promise that resolves to the metadata list of the apps. - */ - listAppMetadata(): Promise; - - /** - * Lists up to 100 Firebase Android apps associated with this Firebase project. - * - * @return The list of Android apps. - */ - listAndroidApps(): Promise; - - /** - * Lists up to 100 Firebase iOS apps associated with this Firebase project. - * - * @return The list of iOS apps. - */ - listIosApps(): Promise; - - /** - * Creates an `AndroidApp` object, referencing the specified Android app within - * this Firebase project. - * - * This method does not perform an RPC. - * - * @param appId The `appId` of the Android app to reference. - * - * @return An `AndroidApp` object that references the specified Firebase Android app. - */ - androidApp(appId: string): AndroidApp; - - /** - * Update the display name of this Firebase project. - * - * @param newDisplayName The new display name to be updated. - * - * @return A promise that resolves when the project display name has been updated. - */ - setDisplayName(newDisplayName: string): Promise; - - /** - * Creates an `iOSApp` object, referencing the specified iOS app within - * this Firebase project. - * - * This method does not perform an RPC. - * - * @param appId The `appId` of the iOS app to reference. - * - * @return An `iOSApp` object that references the specified Firebase iOS app. - */ - iosApp(appId: string): IosApp; - - /** - * Creates a `ShaCertificate` object. - * - * This method does not perform an RPC. - * - * @param shaHash The SHA-1 or SHA-256 hash for this certificate. - * - * @return A `ShaCertificate` object contains the specified SHA hash. - */ - shaCertificate(shaHash: string): ShaCertificate; - - /** - * Creates a new Firebase Android app associated with this Firebase project. - * - * @param packageName The canonical package name of the Android App, - * as would appear in the Google Play Developer Console. - * @param displayName An optional user-assigned display name for this - * new app. - * - * @return A promise that resolves to the newly created Android app. - */ - createAndroidApp( - packageName: string, displayName?: string): Promise; - - /** - * Creates a new Firebase iOS app associated with this Firebase project. - * - * @param bundleId The iOS app bundle ID to use for this new app. - * @param displayName An optional user-assigned display name for this - * new app. - * - * @return A promise that resolves to the newly created iOS app. - */ - createIosApp(bundleId: string, displayName?: string): Promise; - } + const firebaseApp: FirebaseApp = app as FirebaseApp; + return firebaseApp.getOrInitService('projectManagement', (app) => new ProjectManagement(app)); } diff --git a/src/project-management/ios-app.ts b/src/project-management/ios-app.ts index b29af6326f..dc1ff3a23c 100644 --- a/src/project-management/ios-app.ts +++ b/src/project-management/ios-app.ts @@ -17,15 +17,37 @@ import { FirebaseProjectManagementError } from '../utils/error'; import * as validator from '../utils/validator'; import { ProjectManagementRequestHandler, assertServerResponse } from './project-management-api-request-internal'; -import { projectManagement } from './index'; +import { AppMetadata, AppPlatform } from './app-metadata'; -import IosAppInterface = projectManagement.IosApp; -import IosAppMetadata = projectManagement.IosAppMetadata; -import AppPlatform = projectManagement.AppPlatform; +/** + * Metadata about a Firebase iOS App. + */ +export interface IosAppMetadata extends AppMetadata { + platform: AppPlatform.IOS; + + /** + * The canonical bundle ID of the iOS App as it would appear in the iOS App Store. + * + * @example + * ```javascript + * var bundleId = iosAppMetadata.bundleId; + *``` + */ + bundleId: string; +} + +/** + * A reference to a Firebase iOS app. + * + * Do not call this constructor directly. Instead, use {@link ProjectManagement.iosApp}. + */ +export class IosApp { -export class IosApp implements IosAppInterface { private readonly resourceName: string; + /** + * @internal + */ constructor( public readonly appId: string, private readonly requestHandler: ProjectManagementRequestHandler) { @@ -40,7 +62,7 @@ export class IosApp implements IosAppInterface { /** * Retrieves metadata about this iOS app. * - * @return {!Promise} A promise that + * @returns A promise that * resolves to the retrieved metadata about this iOS app. */ public getMetadata(): Promise { @@ -76,7 +98,7 @@ export class IosApp implements IosAppInterface { * * @param newDisplayName The new display name to set. * - * @return A promise that resolves when the display name has + * @returns A promise that resolves when the display name has * been set. */ public setDisplayName(newDisplayName: string): Promise { @@ -86,7 +108,7 @@ export class IosApp implements IosAppInterface { /** * Gets the configuration artifact associated with this app. * - * @return A promise that resolves to the iOS app's Firebase + * @returns A promise that resolves to the iOS app's Firebase * config file, in UTF-8 string format. This string is typically intended to * be written to a plist file that gets shipped with your iOS app. */ diff --git a/src/project-management/project-management-api-request-internal.ts b/src/project-management/project-management-api-request-internal.ts index 445faf83dc..7c9651e50a 100644 --- a/src/project-management/project-management-api-request-internal.ts +++ b/src/project-management/project-management-api-request-internal.ts @@ -14,14 +14,16 @@ * limitations under the License. */ -import { FirebaseApp } from '../firebase-app'; +import { App } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; import { AuthorizedHttpClient, HttpError, HttpMethod, HttpRequestConfig, ExponentialBackoffPoller, } from '../utils/api-request'; import { FirebaseProjectManagementError, ProjectManagementErrorCode } from '../utils/error'; +import { getSdkVersion } from '../utils/index'; import * as validator from '../utils/validator'; import { ShaCertificate } from './android-app'; -import { getSdkVersion } from '../utils/index'; + /** Project management backend host and port. */ const PROJECT_MANAGEMENT_HOST_AND_PORT = 'firebase.googleapis.com:443'; @@ -56,7 +58,7 @@ export function assertServerResponse( * Class that provides mechanism to send requests to the Firebase project management backend * endpoints. * - * @private + * @internal */ export class ProjectManagementRequestHandler { private readonly baseUrl: string = @@ -112,15 +114,15 @@ export class ProjectManagementRequestHandler { } /** - * @param {FirebaseApp} app The app used to fetch access tokens to sign API requests. + * @param app The app used to fetch access tokens to sign API requests. * @constructor */ - constructor(app: FirebaseApp) { - this.httpClient = new AuthorizedHttpClient(app); + constructor(app: App) { + this.httpClient = new AuthorizedHttpClient(app as FirebaseApp); } /** - * @param {string} parentResourceName Fully-qualified resource name of the project whose Android + * @param parentResourceName Fully-qualified resource name of the project whose Android * apps you want to list. */ public listAndroidApps(parentResourceName: string): Promise { @@ -132,7 +134,7 @@ export class ProjectManagementRequestHandler { } /** - * @param {string} parentResourceName Fully-qualified resource name of the project whose iOS apps + * @param parentResourceName Fully-qualified resource name of the project whose iOS apps * you want to list. */ public listIosApps(parentResourceName: string): Promise { @@ -144,7 +146,7 @@ export class ProjectManagementRequestHandler { } /** - * @param {string} parentResourceName Fully-qualified resource name of the project whose iOS apps + * @param parentResourceName Fully-qualified resource name of the project whose iOS apps * you want to list. */ public listAppMetadata(parentResourceName: string): Promise { @@ -156,7 +158,7 @@ export class ProjectManagementRequestHandler { } /** - * @param {string} parentResourceName Fully-qualified resource name of the project that you want + * @param parentResourceName Fully-qualified resource name of the project that you want * to create the Android app within. */ public createAndroidApp( @@ -183,7 +185,7 @@ export class ProjectManagementRequestHandler { } /** - * @param {string} parentResourceName Fully-qualified resource name of the project that you want + * @param parentResourceName Fully-qualified resource name of the project that you want * to create the iOS app within. */ public createIosApp( @@ -210,7 +212,7 @@ export class ProjectManagementRequestHandler { } /** - * @param {string} resourceName Fully-qualified resource name of the entity whose display name you + * @param resourceName Fully-qualified resource name of the entity whose display name you * want to set. */ public setDisplayName(resourceName: string, newDisplayName: string): Promise { @@ -224,7 +226,7 @@ export class ProjectManagementRequestHandler { } /** - * @param {string} parentResourceName Fully-qualified resource name of the Android app whose SHA + * @param parentResourceName Fully-qualified resource name of the Android app whose SHA * certificates you want to get. */ public getAndroidShaCertificates(parentResourceName: string): Promise { @@ -233,7 +235,7 @@ export class ProjectManagementRequestHandler { } /** - * @param {string} parentResourceName Fully-qualified resource name of the Android app that you + * @param parentResourceName Fully-qualified resource name of the Android app that you * want to add the given SHA certificate to. */ public addAndroidShaCertificate( @@ -248,7 +250,7 @@ export class ProjectManagementRequestHandler { } /** - * @param {string} parentResourceName Fully-qualified resource name of the app whose config you + * @param parentResourceName Fully-qualified resource name of the app whose config you * want to get. */ public getConfig(parentResourceName: string): Promise { @@ -257,7 +259,7 @@ export class ProjectManagementRequestHandler { } /** - * @param {string} parentResourceName Fully-qualified resource name of the entity that you want to + * @param parentResourceName Fully-qualified resource name of the entity that you want to * get. */ public getResource(parentResourceName: string): Promise { @@ -265,7 +267,7 @@ export class ProjectManagementRequestHandler { } /** - * @param {string} resourceName Fully-qualified resource name of the entity that you want to + * @param resourceName Fully-qualified resource name of the entity that you want to * delete. */ public deleteResource(resourceName: string): Promise { diff --git a/src/project-management/project-management-namespace.ts b/src/project-management/project-management-namespace.ts new file mode 100644 index 0000000000..60bfd34f8f --- /dev/null +++ b/src/project-management/project-management-namespace.ts @@ -0,0 +1,102 @@ +/*! + * Copyright 2021 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 { App } from '../app'; +import { + AppMetadata as TAppMetadata, + AppPlatform as TAppPlatform, +} from './app-metadata'; +import { ProjectManagement as TProjectManagement } from './project-management'; +import { + AndroidApp as TAndroidApp, + AndroidAppMetadata as TAndroidAppMetadata, + ShaCertificate as TShaCertificate, +} from './android-app'; +import { + IosApp as TIosApp, + IosAppMetadata as TIosAppMetadata, +} from './ios-app'; + +/** + * Gets the {@link firebase-admin.project-management#ProjectManagement} service for the + * default app or a given app. + * + * `admin.projectManagement()` can be called with no arguments to access the + * default app's `ProjectManagement` service, or as `admin.projectManagement(app)` to access + * the `ProjectManagement` service associated with a specific app. + * + * @example + * ```javascript + * // Get the ProjectManagement service for the default app + * var defaultProjectManagement = admin.projectManagement(); + * ``` + * + * @example + * ```javascript + * // Get the ProjectManagement service for a given app + * var otherProjectManagement = admin.projectManagement(otherApp); + * ``` + * + * @param app Optional app whose `ProjectManagement` service + * to return. If not provided, the default `ProjectManagement` service will + * be returned. * + * @returns The default `ProjectManagement` service if no app is provided or the + * `ProjectManagement` service associated with the provided app. + */ +export declare function projectManagement(app?: App): projectManagement.ProjectManagement; + +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace projectManagement { + /** + * Type alias to {@link firebase-admin.project-management#AppMetadata}. + */ + export type AppMetadata = TAppMetadata; + + /** + * Type alias to {@link firebase-admin.project-management#AppPlatform}. + */ + export type AppPlatform = TAppPlatform; + + /** + * Type alias to {@link firebase-admin.project-management#ProjectManagement}. + */ + export type ProjectManagement = TProjectManagement; + + /** + * Type alias to {@link firebase-admin.project-management#IosApp}. + */ + export type IosApp = TIosApp; + + /** + * Type alias to {@link firebase-admin.project-management#IosAppMetadata}. + */ + export type IosAppMetadata = TIosAppMetadata; + + /** + * Type alias to {@link firebase-admin.project-management#AndroidApp}. + */ + export type AndroidApp = TAndroidApp; + + /** + * Type alias to {@link firebase-admin.project-management#AndroidAppMetadata}. + */ + export type AndroidAppMetadata = TAndroidAppMetadata; + + /** + * Type alias to {@link firebase-admin.project-management#ShaCertificate}. + */ + export type ShaCertificate = TShaCertificate; +} diff --git a/src/project-management/project-management.ts b/src/project-management/project-management.ts index 9dc8b29902..ff7a5089bb 100644 --- a/src/project-management/project-management.ts +++ b/src/project-management/project-management.ts @@ -14,35 +14,29 @@ * limitations under the License. */ -import { FirebaseApp } from '../firebase-app'; +import { App } from '../app'; import { FirebaseProjectManagementError } from '../utils/error'; import * as utils from '../utils/index'; import * as validator from '../utils/validator'; import { AndroidApp, ShaCertificate } from './android-app'; import { IosApp } from './ios-app'; import { ProjectManagementRequestHandler, assertServerResponse } from './project-management-api-request-internal'; -import { projectManagement } from './index'; - -import AppMetadata = projectManagement.AppMetadata; -import AppPlatform = projectManagement.AppPlatform; -import ProjectManagementInterface = projectManagement.ProjectManagement; +import { AppMetadata, AppPlatform } from './app-metadata'; /** * The Firebase ProjectManagement service interface. - * - * Do not call this constructor directly. Instead, use - * [`admin.projectManagement()`](projectManagement#projectManagement). */ -export class ProjectManagement implements ProjectManagementInterface { +export class ProjectManagement { private readonly requestHandler: ProjectManagementRequestHandler; private projectId: string; /** - * @param {object} app The app for this ProjectManagement service. + * @param app The app for this ProjectManagement service. * @constructor + * @internal */ - constructor(readonly app: FirebaseApp) { + constructor(readonly app: App) { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseProjectManagementError( 'invalid-argument', @@ -56,7 +50,7 @@ export class ProjectManagement implements ProjectManagementInterface { /** * Lists up to 100 Firebase Android apps associated with this Firebase project. * - * @return The list of Android apps. + * @returns The list of Android apps. */ public listAndroidApps(): Promise { return this.listPlatformApps('android', 'listAndroidApps()'); @@ -65,7 +59,7 @@ export class ProjectManagement implements ProjectManagementInterface { /** * Lists up to 100 Firebase iOS apps associated with this Firebase project. * - * @return The list of iOS apps. + * @returns The list of iOS apps. */ public listIosApps(): Promise { return this.listPlatformApps('ios', 'listIosApps()'); @@ -79,7 +73,7 @@ export class ProjectManagement implements ProjectManagementInterface { * * @param appId The `appId` of the Android app to reference. * - * @return An `AndroidApp` object that references the specified Firebase Android app. + * @returns An `AndroidApp` object that references the specified Firebase Android app. */ public androidApp(appId: string): AndroidApp { return new AndroidApp(appId, this.requestHandler); @@ -93,7 +87,7 @@ export class ProjectManagement implements ProjectManagementInterface { * * @param appId The `appId` of the iOS app to reference. * - * @return An `iOSApp` object that references the specified Firebase iOS app. + * @returns An `iOSApp` object that references the specified Firebase iOS app. */ public iosApp(appId: string): IosApp { return new IosApp(appId, this.requestHandler); @@ -106,7 +100,7 @@ export class ProjectManagement implements ProjectManagementInterface { * * @param shaHash The SHA-1 or SHA-256 hash for this certificate. * - * @return A `ShaCertificate` object contains the specified SHA hash. + * @returns A `ShaCertificate` object contains the specified SHA hash. */ public shaCertificate(shaHash: string): ShaCertificate { return new ShaCertificate(shaHash); @@ -120,7 +114,7 @@ export class ProjectManagement implements ProjectManagementInterface { * @param displayName An optional user-assigned display name for this * new app. * - * @return A promise that resolves to the newly created Android app. + * @returns A promise that resolves to the newly created Android app. */ public createAndroidApp(packageName: string, displayName?: string): Promise { return this.getResourceName() @@ -148,7 +142,7 @@ export class ProjectManagement implements ProjectManagementInterface { * @param displayName An optional user-assigned display name for this * new app. * - * @return A promise that resolves to the newly created iOS app. + * @returns A promise that resolves to the newly created iOS app. */ public createIosApp(bundleId: string, displayName?: string): Promise { return this.getResourceName() @@ -172,7 +166,7 @@ export class ProjectManagement implements ProjectManagementInterface { /** * Lists up to 100 Firebase apps associated with this Firebase project. * - * @return A promise that resolves to the metadata list of the apps. + * @returns A promise that resolves to the metadata list of the apps. */ public listAppMetadata(): Promise { return this.getResourceName() @@ -192,7 +186,7 @@ export class ProjectManagement implements ProjectManagementInterface { * * @param newDisplayName The new display name to be updated. * - * @return A promise that resolves when the project display name has been updated. + * @returns A promise that resolves when the project display name has been updated. */ public setDisplayName(newDisplayName: string): Promise { return this.getResourceName() diff --git a/src/remote-config/index.ts b/src/remote-config/index.ts index aa870f6940..1797298da5 100644 --- a/src/remote-config/index.ts +++ b/src/remote-config/index.ts @@ -14,398 +14,64 @@ * limitations under the License. */ -import { app } from '../firebase-namespace-api'; +/** + * Firebase Remote Config. + * + * @packageDocumentation + */ + +import { App, getApp } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; +import { RemoteConfig } from './remote-config'; + +export { + ExplicitParameterValue, + InAppDefaultValue, + ListVersionsOptions, + ListVersionsResult, + ParameterValueType, + RemoteConfigCondition, + RemoteConfigParameter, + RemoteConfigParameterGroup, + RemoteConfigParameterValue, + RemoteConfigTemplate, + RemoteConfigUser, + TagColor, + Version, +} from './remote-config-api'; +export { RemoteConfig } from './remote-config'; /** - * Gets the {@link remoteConfig.RemoteConfig `RemoteConfig`} service for the - * default app or a given app. + * Gets the {@link RemoteConfig} service for the default app or a given app. * - * `admin.remoteConfig()` can be called with no arguments to access the default - * app's {@link remoteConfig.RemoteConfig `RemoteConfig`} service or as - * `admin.remoteConfig(app)` to access the - * {@link remoteConfig.RemoteConfig `RemoteConfig`} service associated with a - * specific app. + * `getRemoteConfig()` can be called with no arguments to access the default + * app's `RemoteConfig` service or as `getRemoteConfig(app)` to access the + * `RemoteConfig` service associated with a specific app. * * @example * ```javascript * // Get the `RemoteConfig` service for the default app - * var defaultRemoteConfig = admin.remoteConfig(); + * const defaultRemoteConfig = getRemoteConfig(); * ``` * * @example * ```javascript * // Get the `RemoteConfig` service for a given app - * var otherRemoteConfig = admin.remoteConfig(otherApp); + * const otherRemoteConfig = getRemoteConfig(otherApp); * ``` * * @param app Optional app for which to return the `RemoteConfig` service. * If not provided, the default `RemoteConfig` service is returned. * - * @return The default `RemoteConfig` service if no + * @returns The default `RemoteConfig` service if no * app is provided, or the `RemoteConfig` service associated with the provided * app. */ -export declare function remoteConfig(app?: app.App): remoteConfig.RemoteConfig; - -/* eslint-disable @typescript-eslint/no-namespace */ -export namespace remoteConfig { - /** - * Interface representing options for Remote Config list versions operation. - */ - export interface ListVersionsOptions { - /** - * The maximum number of items to return per page. - */ - pageSize?: number; - - /** - * The `nextPageToken` value returned from a previous list versions request, if any. - */ - pageToken?: string; - - /** - * Specifies the newest version number to include in the results. - * If specified, must be greater than zero. Defaults to the newest version. - */ - endVersionNumber?: string | number; - - /** - * Specifies the earliest update time to include in the results. Any entries updated before this - * time are omitted. - */ - startTime?: Date | string; - - /** - * Specifies the latest update time to include in the results. Any entries updated on or after - * this time are omitted. - */ - endTime?: Date | string; - } - - /** - * Interface representing a list of Remote Config template versions. - */ - export interface ListVersionsResult { - /** - * A list of version metadata objects, sorted in reverse chronological order. - */ - versions: Version[]; - - /** - * Token to retrieve the next page of results, or empty if there are no more results - * in the list. - */ - nextPageToken?: string; - } - - /** - * Interface representing a Remote Config condition. - * A condition targets a specific group of users. A list of these conditions make up - * part of a Remote Config template. - */ - export interface RemoteConfigCondition { - - /** - * A non-empty and unique name of this condition. - */ - name: string; - - /** - * The logic of this condition. - * See the documentation on - * {@link https://firebase.google.com/docs/remote-config/condition-reference condition expressions} - * for the expected syntax of this field. - */ - expression: string; - - /** - * The color associated with this condition for display purposes in the Firebase Console. - * Not specifying this value results in the console picking an arbitrary color to associate - * with the condition. - */ - tagColor?: TagColor; - } - - /** - * Interface representing a Remote Config parameter. - * At minimum, a `defaultValue` or a `conditionalValues` entry must be present for the - * parameter to have any effect. - */ - export interface RemoteConfigParameter { - - /** - * The value to set the parameter to, when none of the named conditions evaluate to `true`. - */ - defaultValue?: RemoteConfigParameterValue; - - /** - * A `(condition name, value)` map. The condition name of the highest priority - * (the one listed first in the Remote Config template's conditions list) determines the value of - * this parameter. - */ - conditionalValues?: { [key: string]: RemoteConfigParameterValue }; - - /** - * A description for this parameter. Should not be over 100 characters and may contain any - * Unicode characters. - */ - description?: string; - - /** - * The data type for all values of this parameter in the current version of the template. - * Defaults to `ParameterValueType.STRING` if unspecified. - */ - valueType?: ParameterValueType; - } - - /** - * Interface representing a Remote Config parameter group. - * Grouping parameters is only for management purposes and does not affect client-side - * fetching of parameter values. - */ - export interface RemoteConfigParameterGroup { - /** - * A description for the group. Its length must be less than or equal to 256 characters. - * A description may contain any Unicode characters. - */ - description?: string; - - /** - * Map of parameter keys to their optional default values and optional conditional values for - * parameters that belong to this group. A parameter only appears once per - * Remote Config template. An ungrouped parameter appears at the top level, whereas a - * parameter organized within a group appears within its group's map of parameters. - */ - parameters: { [key: string]: RemoteConfigParameter }; - } - - /** - * Interface representing an explicit parameter value. - */ - export interface ExplicitParameterValue { - /** - * The `string` value that the parameter is set to. - */ - value: string; - } - - /** - * Interface representing an in-app-default value. - */ - export interface InAppDefaultValue { - /** - * If `true`, the parameter is omitted from the parameter values returned to a client. - */ - useInAppDefault: boolean; +export function getRemoteConfig(app?: App): RemoteConfig { + if (typeof app === 'undefined') { + app = getApp(); } - /** - * Type representing a Remote Config parameter value. - * A `RemoteConfigParameterValue` could be either an `ExplicitParameterValue` or - * an `InAppDefaultValue`. - */ - export type RemoteConfigParameterValue = ExplicitParameterValue | InAppDefaultValue; - - /** - * Interface representing a Remote Config template. - */ - export interface RemoteConfigTemplate { - /** - * A list of conditions in descending order by priority. - */ - conditions: RemoteConfigCondition[]; - - /** - * Map of parameter keys to their optional default values and optional conditional values. - */ - parameters: { [key: string]: RemoteConfigParameter }; - - /** - * Map of parameter group names to their parameter group objects. - * A group's name is mutable but must be unique among groups in the Remote Config template. - * The name is limited to 256 characters and intended to be human-readable. Any Unicode - * characters are allowed. - */ - parameterGroups: { [key: string]: RemoteConfigParameterGroup }; - - /** - * ETag of the current Remote Config template (readonly). - */ - readonly etag: string; - - /** - * Version information for the current Remote Config template. - */ - version?: Version; - } - - /** - * Interface representing a Remote Config user. - */ - export interface RemoteConfigUser { - /** - * Email address. Output only. - */ - email: string; - - /** - * Display name. Output only. - */ - name?: string; - - /** - * Image URL. Output only. - */ - imageUrl?: string; - } - - /** - * Colors that are associated with conditions for display purposes. - */ - export type TagColor = 'BLUE' | 'BROWN' | 'CYAN' | 'DEEP_ORANGE' | 'GREEN' | - 'INDIGO' | 'LIME' | 'ORANGE' | 'PINK' | 'PURPLE' | 'TEAL'; - - /** - * Type representing a Remote Config parameter value data type. - * Defaults to `STRING` if unspecified. - */ - export type ParameterValueType = 'STRING' | 'BOOLEAN' | 'NUMBER' | 'JSON' - - /** - * Interface representing a Remote Config template version. - * Output only, except for the version description. Contains metadata about a particular - * version of the Remote Config template. All fields are set at the time the specified Remote - * Config template is published. A version's description field may be specified in - * `publishTemplate` calls. - */ - export interface Version { - /** - * The version number of a Remote Config template. - */ - versionNumber?: string; - - /** - * The timestamp of when this version of the Remote Config template was written to the - * Remote Config backend. - */ - updateTime?: string; - - /** - * The origin of the template update action. - */ - updateOrigin?: ('REMOTE_CONFIG_UPDATE_ORIGIN_UNSPECIFIED' | 'CONSOLE' | - 'REST_API' | 'ADMIN_SDK_NODE'); - - /** - * The type of the template update action. - */ - updateType?: ('REMOTE_CONFIG_UPDATE_TYPE_UNSPECIFIED' | - 'INCREMENTAL_UPDATE' | 'FORCED_UPDATE' | 'ROLLBACK'); - - /** - * Aggregation of all metadata fields about the account that performed the update. - */ - updateUser?: RemoteConfigUser; - - /** - * The user-provided description of the corresponding Remote Config template. - */ - description?: string; - - /** - * The version number of the Remote Config template that has become the current version - * due to a rollback. Only present if this version is the result of a rollback. - */ - rollbackSource?: string; - - /** - * Indicates whether this Remote Config template was published before version history was - * supported. - */ - isLegacy?: boolean; - } - - /** - * The Firebase `RemoteConfig` service interface. - */ - export interface RemoteConfig { - app: app.App; - - /** - * Gets the current active version of the {@link remoteConfig.RemoteConfigTemplate - * `RemoteConfigTemplate`} of the project. - * - * @return A promise that fulfills with a `RemoteConfigTemplate`. - */ - getTemplate(): Promise; - - /** - * Gets the requested version of the {@link remoteConfig.RemoteConfigTemplate - * `RemoteConfigTemplate`} of the project. - * - * @param versionNumber Version number of the Remote Config template to look up. - * - * @return A promise that fulfills with a `RemoteConfigTemplate`. - */ - getTemplateAtVersion(versionNumber: number | string): Promise; - - /** - * Validates a {@link remoteConfig.RemoteConfigTemplate `RemoteConfigTemplate`}. - * - * @param template The Remote Config template to be validated. - * @returns A promise that fulfills with the validated `RemoteConfigTemplate`. - */ - validateTemplate(template: RemoteConfigTemplate): Promise; - - /** - * Publishes a Remote Config template. - * - * @param template The Remote Config template to be published. - * @param options Optional options object when publishing a Remote Config template: - * - {boolean} `force` Setting this to `true` forces the Remote Config template to - * be updated and circumvent the ETag. This approach is not recommended - * because it risks causing the loss of updates to your Remote Config - * template if multiple clients are updating the Remote Config template. - * See {@link https://firebase.google.com/docs/remote-config/use-config-rest#etag_usage_and_forced_updates - * ETag usage and forced updates}. - * - * @return A Promise that fulfills with the published `RemoteConfigTemplate`. - */ - publishTemplate(template: RemoteConfigTemplate, options?: { force: boolean }): Promise; - - /** - * Rolls back a project's published Remote Config template to the specified version. - * A rollback is equivalent to getting a previously published Remote Config - * template and re-publishing it using a force update. - * - * @param versionNumber The version number of the Remote Config template to roll back to. - * The specified version number must be lower than the current version number, and not have - * been deleted due to staleness. Only the last 300 versions are stored. - * All versions that correspond to non-active Remote Config templates (that is, all except the - * template that is being fetched by clients) are also deleted if they are more than 90 days old. - * @return A promise that fulfills with the published `RemoteConfigTemplate`. - */ - rollback(versionNumber: string | number): Promise; - - /** - * Gets a list of Remote Config template versions that have been published, sorted in reverse - * chronological order. Only the last 300 versions are stored. - * All versions that correspond to non-active Remote Config templates (that is, all except the - * template that is being fetched by clients) are also deleted if they are more than 90 days old. - * - * @param options Optional {@link remoteConfig.ListVersionsOptions `ListVersionsOptions`} - * object for getting a list of template versions. - * @return A promise that fulfills with a `ListVersionsResult`. - */ - listVersions(options?: ListVersionsOptions): Promise; - - /** - * Creates and returns a new Remote Config template from a JSON string. - * - * @param json The JSON string to populate a Remote Config template. - * - * @return A new template instance. - */ - createTemplateFromJSON(json: string): RemoteConfigTemplate; - } + const firebaseApp: FirebaseApp = app as FirebaseApp; + return firebaseApp.getOrInitService('remoteConfig', (app) => new RemoteConfig(app)); } diff --git a/src/remote-config/remote-config-api-client-internal.ts b/src/remote-config/remote-config-api-client-internal.ts index ac2d071453..9ea77bb532 100644 --- a/src/remote-config/remote-config-api-client-internal.ts +++ b/src/remote-config/remote-config-api-client-internal.ts @@ -14,17 +14,14 @@ * limitations under the License. */ -import { remoteConfig } from './index'; +import { App } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; import { HttpRequestConfig, HttpClient, HttpError, AuthorizedHttpClient, HttpResponse } from '../utils/api-request'; import { PrefixedFirebaseError } from '../utils/error'; -import { FirebaseApp } from '../firebase-app'; import * as utils from '../utils/index'; import * as validator from '../utils/validator'; import { deepCopy } from '../utils/deep-copy'; - -import RemoteConfigTemplate = remoteConfig.RemoteConfigTemplate; -import ListVersionsOptions = remoteConfig.ListVersionsOptions; -import ListVersionsResult = remoteConfig.ListVersionsResult; +import { ListVersionsOptions, ListVersionsResult, RemoteConfigTemplate } from './remote-config-api'; // Remote Config backend constants const FIREBASE_REMOTE_CONFIG_V1_API = 'https://firebaseremoteconfig.googleapis.com/v1'; @@ -41,20 +38,20 @@ const FIREBASE_REMOTE_CONFIG_HEADERS = { /** * Class that facilitates sending requests to the Firebase Remote Config backend API. * - * @private + * @internal */ export class RemoteConfigApiClient { private readonly httpClient: HttpClient; private projectIdPrefix?: string; - constructor(private readonly app: FirebaseApp) { + constructor(private readonly app: App) { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseRemoteConfigError( 'invalid-argument', 'First argument passed to admin.remoteConfig() must be a valid Firebase app instance.'); } - this.httpClient = new AuthorizedHttpClient(app); + this.httpClient = new AuthorizedHttpClient(app as FirebaseApp); } public getTemplate(): Promise { @@ -339,7 +336,7 @@ export class RemoteConfigApiClient { * * @param {ListVersionsOptions} options An options object to be validated. * - * @return {ListVersionsOptions} A copy of the provided options object with timestamps converted + * @returns {ListVersionsOptions} A copy of the provided options object with timestamps converted * to UTC Zulu format. */ private validateListVersionsOptions(options: ListVersionsOptions): ListVersionsOptions { diff --git a/src/remote-config/remote-config-api.ts b/src/remote-config/remote-config-api.ts new file mode 100644 index 0000000000..90f3bd4970 --- /dev/null +++ b/src/remote-config/remote-config-api.ts @@ -0,0 +1,291 @@ +/*! + * Copyright 2021 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. + */ + +/** + * Colors that are associated with conditions for display purposes. + */ +export type TagColor = 'BLUE' | 'BROWN' | 'CYAN' | 'DEEP_ORANGE' | 'GREEN' | + 'INDIGO' | 'LIME' | 'ORANGE' | 'PINK' | 'PURPLE' | 'TEAL'; + +/** + * Type representing a Remote Config parameter value data type. + * Defaults to `STRING` if unspecified. + */ +export type ParameterValueType = 'STRING' | 'BOOLEAN' | 'NUMBER' | 'JSON' + +/** + * Interface representing a Remote Config condition. + * A condition targets a specific group of users. A list of these conditions make up + * part of a Remote Config template. + */ +export interface RemoteConfigCondition { + + /** + * A non-empty and unique name of this condition. + */ + name: string; + + /** + * The logic of this condition. + * See the documentation on + * {@link https://firebase.google.com/docs/remote-config/condition-reference | condition expressions} + * for the expected syntax of this field. + */ + expression: string; + + /** + * The color associated with this condition for display purposes in the Firebase Console. + * Not specifying this value results in the console picking an arbitrary color to associate + * with the condition. + */ + tagColor?: TagColor; +} + +/** + * Interface representing an explicit parameter value. + */ +export interface ExplicitParameterValue { + /** + * The `string` value that the parameter is set to. + */ + value: string; +} + +/** + * Interface representing an in-app-default value. + */ +export interface InAppDefaultValue { + /** + * If `true`, the parameter is omitted from the parameter values returned to a client. + */ + useInAppDefault: boolean; +} + +/** + * Type representing a Remote Config parameter value. + * A `RemoteConfigParameterValue` could be either an `ExplicitParameterValue` or + * an `InAppDefaultValue`. + */ +export type RemoteConfigParameterValue = ExplicitParameterValue | InAppDefaultValue; + +/** + * Interface representing a Remote Config parameter. + * At minimum, a `defaultValue` or a `conditionalValues` entry must be present for the + * parameter to have any effect. + */ +export interface RemoteConfigParameter { + + /** + * The value to set the parameter to, when none of the named conditions evaluate to `true`. + */ + defaultValue?: RemoteConfigParameterValue; + + /** + * A `(condition name, value)` map. The condition name of the highest priority + * (the one listed first in the Remote Config template's conditions list) determines the value of + * this parameter. + */ + conditionalValues?: { [key: string]: RemoteConfigParameterValue }; + + /** + * A description for this parameter. Should not be over 100 characters and may contain any + * Unicode characters. + */ + description?: string; + + /** + * The data type for all values of this parameter in the current version of the template. + * Defaults to `ParameterValueType.STRING` if unspecified. + */ + valueType?: ParameterValueType; +} + +/** + * Interface representing a Remote Config parameter group. + * Grouping parameters is only for management purposes and does not affect client-side + * fetching of parameter values. + */ +export interface RemoteConfigParameterGroup { + /** + * A description for the group. Its length must be less than or equal to 256 characters. + * A description may contain any Unicode characters. + */ + description?: string; + + /** + * Map of parameter keys to their optional default values and optional conditional values for + * parameters that belong to this group. A parameter only appears once per + * Remote Config template. An ungrouped parameter appears at the top level, whereas a + * parameter organized within a group appears within its group's map of parameters. + */ + parameters: { [key: string]: RemoteConfigParameter }; +} + +/** + * Interface representing a Remote Config template. + */ +export interface RemoteConfigTemplate { + /** + * A list of conditions in descending order by priority. + */ + conditions: RemoteConfigCondition[]; + + /** + * Map of parameter keys to their optional default values and optional conditional values. + */ + parameters: { [key: string]: RemoteConfigParameter }; + + /** + * Map of parameter group names to their parameter group objects. + * A group's name is mutable but must be unique among groups in the Remote Config template. + * The name is limited to 256 characters and intended to be human-readable. Any Unicode + * characters are allowed. + */ + parameterGroups: { [key: string]: RemoteConfigParameterGroup }; + + /** + * ETag of the current Remote Config template (readonly). + */ + readonly etag: string; + + /** + * Version information for the current Remote Config template. + */ + version?: Version; +} + +/** + * Interface representing a Remote Config user. + */ +export interface RemoteConfigUser { + /** + * Email address. Output only. + */ + email: string; + + /** + * Display name. Output only. + */ + name?: string; + + /** + * Image URL. Output only. + */ + imageUrl?: string; +} + +/** + * Interface representing a Remote Config template version. + * Output only, except for the version description. Contains metadata about a particular + * version of the Remote Config template. All fields are set at the time the specified Remote + * Config template is published. A version's description field may be specified in + * `publishTemplate` calls. + */ +export interface Version { + /** + * The version number of a Remote Config template. + */ + versionNumber?: string; + + /** + * The timestamp of when this version of the Remote Config template was written to the + * Remote Config backend. + */ + updateTime?: string; + + /** + * The origin of the template update action. + */ + updateOrigin?: ('REMOTE_CONFIG_UPDATE_ORIGIN_UNSPECIFIED' | 'CONSOLE' | + 'REST_API' | 'ADMIN_SDK_NODE'); + + /** + * The type of the template update action. + */ + updateType?: ('REMOTE_CONFIG_UPDATE_TYPE_UNSPECIFIED' | + 'INCREMENTAL_UPDATE' | 'FORCED_UPDATE' | 'ROLLBACK'); + + /** + * Aggregation of all metadata fields about the account that performed the update. + */ + updateUser?: RemoteConfigUser; + + /** + * The user-provided description of the corresponding Remote Config template. + */ + description?: string; + + /** + * The version number of the Remote Config template that has become the current version + * due to a rollback. Only present if this version is the result of a rollback. + */ + rollbackSource?: string; + + /** + * Indicates whether this Remote Config template was published before version history was + * supported. + */ + isLegacy?: boolean; +} + +/** + * Interface representing a list of Remote Config template versions. + */ +export interface ListVersionsResult { + /** + * A list of version metadata objects, sorted in reverse chronological order. + */ + versions: Version[]; + + /** + * Token to retrieve the next page of results, or empty if there are no more results + * in the list. + */ + nextPageToken?: string; +} + +/** + * Interface representing options for Remote Config list versions operation. + */ +export interface ListVersionsOptions { + /** + * The maximum number of items to return per page. + */ + pageSize?: number; + + /** + * The `nextPageToken` value returned from a previous list versions request, if any. + */ + pageToken?: string; + + /** + * Specifies the newest version number to include in the results. + * If specified, must be greater than zero. Defaults to the newest version. + */ + endVersionNumber?: string | number; + + /** + * Specifies the earliest update time to include in the results. Any entries updated before this + * time are omitted. + */ + startTime?: Date | string; + + /** + * Specifies the latest update time to include in the results. Any entries updated on or after + * this time are omitted. + */ + endTime?: Date | string; +} diff --git a/src/remote-config/remote-config-namespace.ts b/src/remote-config/remote-config-namespace.ts new file mode 100644 index 0000000000..40b2698f66 --- /dev/null +++ b/src/remote-config/remote-config-namespace.ts @@ -0,0 +1,135 @@ +/*! + * Copyright 2021 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 { App } from '../app'; +import { + ExplicitParameterValue as TExplicitParameterValue, + InAppDefaultValue as TInAppDefaultValue, + ListVersionsOptions as TListVersionsOptions, + ListVersionsResult as TListVersionsResult, + ParameterValueType as TParameterValueType, + RemoteConfigCondition as TRemoteConfigCondition, + RemoteConfigParameter as TRemoteConfigParameter, + RemoteConfigParameterGroup as TRemoteConfigParameterGroup, + RemoteConfigParameterValue as TRemoteConfigParameterValue, + RemoteConfigTemplate as TRemoteConfigTemplate, + RemoteConfigUser as TRemoteConfigUser, + TagColor as TTagColor, + Version as TVersion, +} from './remote-config-api'; +import { RemoteConfig as TRemoteConfig } from './remote-config'; + +/** + * Gets the {@link firebase-admin.remote-config#RemoteConfig} service for the + * default app or a given app. + * + * `admin.remoteConfig()` can be called with no arguments to access the default + * app's `RemoteConfig` service or as `admin.remoteConfig(app)` to access the + * `RemoteConfig` service associated with a specific app. + * + * @example + * ```javascript + * // Get the `RemoteConfig` service for the default app + * var defaultRemoteConfig = admin.remoteConfig(); + * ``` + * + * @example + * ```javascript + * // Get the `RemoteConfig` service for a given app + * var otherRemoteConfig = admin.remoteConfig(otherApp); + * ``` + * + * @param app Optional app for which to return the `RemoteConfig` service. + * If not provided, the default `RemoteConfig` service is returned. + * + * @returns The default `RemoteConfig` service if no + * app is provided, or the `RemoteConfig` service associated with the provided + * app. + */ +export declare function remoteConfig(app?: App): remoteConfig.RemoteConfig; + +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace remoteConfig { + /** + * Type alias to {@link firebase-admin.remote-config#ExplicitParameterValue}. + */ + export type ExplicitParameterValue = TExplicitParameterValue; + + /** + * Type alias to {@link firebase-admin.remote-config#InAppDefaultValue}. + */ + export type InAppDefaultValue = TInAppDefaultValue; + + /** + * Type alias to {@link firebase-admin.remote-config#ListVersionsOptions}. + */ + export type ListVersionsOptions = TListVersionsOptions; + + /** + * Type alias to {@link firebase-admin.remote-config#ListVersionsResult}. + */ + export type ListVersionsResult = TListVersionsResult; + + /** + * Type alias to {@link firebase-admin.remote-config#ParameterValueType}. + */ + export type ParameterValueType = TParameterValueType; + + /** + * Type alias to {@link firebase-admin.remote-config#RemoteConfig}. + */ + export type RemoteConfig = TRemoteConfig; + + /** + * Type alias to {@link firebase-admin.remote-config#RemoteConfigCondition}. + */ + export type RemoteConfigCondition = TRemoteConfigCondition; + + /** + * Type alias to {@link firebase-admin.remote-config#RemoteConfigParameter}. + */ + export type RemoteConfigParameter = TRemoteConfigParameter; + + /** + * Type alias to {@link firebase-admin.remote-config#RemoteConfigParameterGroup}. + */ + export type RemoteConfigParameterGroup = TRemoteConfigParameterGroup; + + /** + * Type alias to {@link firebase-admin.remote-config#RemoteConfigParameterValue}. + */ + export type RemoteConfigParameterValue = TRemoteConfigParameterValue; + + /** + * Type alias to {@link firebase-admin.remote-config#RemoteConfigTemplate}. + */ + export type RemoteConfigTemplate = TRemoteConfigTemplate; + + /** + * Type alias to {@link firebase-admin.remote-config#RemoteConfigUser}. + */ + export type RemoteConfigUser = TRemoteConfigUser; + + /** + * Type alias to {@link firebase-admin.remote-config#TagColor}. + */ + export type TagColor = TTagColor; + + /** + * Type alias to {@link firebase-admin.remote-config#Version}. + */ + export type Version = TVersion; +} diff --git a/src/remote-config/remote-config.ts b/src/remote-config/remote-config.ts index d0b0046832..2b35a216a2 100644 --- a/src/remote-config/remote-config.ts +++ b/src/remote-config/remote-config.ts @@ -14,41 +14,40 @@ * limitations under the License. */ -import { FirebaseApp } from '../firebase-app'; +import { App } from '../app'; import * as validator from '../utils/validator'; -import { remoteConfig } from './index'; import { FirebaseRemoteConfigError, RemoteConfigApiClient } from './remote-config-api-client-internal'; - -import RemoteConfigTemplate = remoteConfig.RemoteConfigTemplate; -import RemoteConfigParameter = remoteConfig.RemoteConfigParameter; -import RemoteConfigCondition = remoteConfig.RemoteConfigCondition; -import RemoteConfigParameterGroup = remoteConfig.RemoteConfigParameterGroup; -import ListVersionsOptions = remoteConfig.ListVersionsOptions; -import ListVersionsResult = remoteConfig.ListVersionsResult; -import RemoteConfigUser = remoteConfig.RemoteConfigUser; -import Version = remoteConfig.Version; -import RemoteConfigInterface = remoteConfig.RemoteConfig; +import { + ListVersionsOptions, + ListVersionsResult, + RemoteConfigCondition, + RemoteConfigParameter, + RemoteConfigParameterGroup, + RemoteConfigTemplate, + RemoteConfigUser, + Version, +} from './remote-config-api'; /** - * Remote Config service bound to the provided app. + * The Firebase `RemoteConfig` service interface. */ -export class RemoteConfig implements RemoteConfigInterface { +export class RemoteConfig { private readonly client: RemoteConfigApiClient; /** * @param app The app for this RemoteConfig service. * @constructor + * @internal */ - constructor(readonly app: FirebaseApp) { + constructor(readonly app: App) { this.client = new RemoteConfigApiClient(app); } /** - * Gets the current active version of the {@link remoteConfig.RemoteConfigTemplate - * `RemoteConfigTemplate`} of the project. + * Gets the current active version of the {@link RemoteConfigTemplate} of the project. * - * @return A promise that fulfills with a `RemoteConfigTemplate`. + * @returns A promise that fulfills with a `RemoteConfigTemplate`. */ public getTemplate(): Promise { return this.client.getTemplate() @@ -58,12 +57,11 @@ export class RemoteConfig implements RemoteConfigInterface { } /** - * Gets the requested version of the {@link remoteConfig.RemoteConfigTemplate - * `RemoteConfigTemplate`} of the project. + * Gets the requested version of the {@link RemoteConfigTemplate} of the project. * * @param versionNumber Version number of the Remote Config template to look up. * - * @return A promise that fulfills with a `RemoteConfigTemplate`. + * @returns A promise that fulfills with a `RemoteConfigTemplate`. */ public getTemplateAtVersion(versionNumber: number | string): Promise { return this.client.getTemplateAtVersion(versionNumber) @@ -73,7 +71,7 @@ export class RemoteConfig implements RemoteConfigInterface { } /** - * Validates a {@link remoteConfig.RemoteConfigTemplate `RemoteConfigTemplate`}. + * Validates a {@link RemoteConfigTemplate}. * * @param template The Remote Config template to be validated. * @returns A promise that fulfills with the validated `RemoteConfigTemplate`. @@ -90,14 +88,14 @@ export class RemoteConfig implements RemoteConfigInterface { * * @param template The Remote Config template to be published. * @param options Optional options object when publishing a Remote Config template: - * - {boolean} `force` Setting this to `true` forces the Remote Config template to + * - `force`: Setting this to `true` forces the Remote Config template to * be updated and circumvent the ETag. This approach is not recommended * because it risks causing the loss of updates to your Remote Config * template if multiple clients are updating the Remote Config template. - * See {@link https://firebase.google.com/docs/remote-config/use-config-rest#etag_usage_and_forced_updates + * See {@link https://firebase.google.com/docs/remote-config/use-config-rest#etag_usage_and_forced_updates | * ETag usage and forced updates}. * - * @return A Promise that fulfills with the published `RemoteConfigTemplate`. + * @returns A Promise that fulfills with the published `RemoteConfigTemplate`. */ public publishTemplate(template: RemoteConfigTemplate, options?: { force: boolean }): Promise { return this.client.publishTemplate(template, options) @@ -116,7 +114,7 @@ export class RemoteConfig implements RemoteConfigInterface { * been deleted due to staleness. Only the last 300 versions are stored. * All versions that correspond to non-active Remote Config templates (that is, all except the * template that is being fetched by clients) are also deleted if they are more than 90 days old. - * @return A promise that fulfills with the published `RemoteConfigTemplate`. + * @returns A promise that fulfills with the published `RemoteConfigTemplate`. */ public rollback(versionNumber: number | string): Promise { return this.client.rollback(versionNumber) @@ -132,7 +130,7 @@ export class RemoteConfig implements RemoteConfigInterface { * template that is being fetched by clients) are also deleted if they are older than 90 days. * * @param options Optional options object for getting a list of versions. - * @return A promise that fulfills with a `ListVersionsResult`. + * @returns A promise that fulfills with a `ListVersionsResult`. */ public listVersions(options?: ListVersionsOptions): Promise { return this.client.listVersions(options) @@ -149,7 +147,7 @@ export class RemoteConfig implements RemoteConfigInterface { * * @param json The JSON string to populate a Remote Config template. * - * @return A new template instance. + * @returns A new template instance. */ public createTemplateFromJSON(json: string): RemoteConfigTemplate { if (!validator.isNonEmptyString(json)) { @@ -234,16 +232,18 @@ class RemoteConfigTemplateImpl implements RemoteConfigTemplate { /** * Gets the ETag of the template. * - * @return {string} The ETag of the Remote Config template. + * @returns The ETag of the Remote Config template. */ get etag(): string { return this.etagInternal; } /** - * @return {RemoteConfigTemplate} A JSON-serializable representation of this object. + * Returns a JSON-serializable representation of this object. + * + * @returns A JSON-serializable representation of this object. */ - public toJSON(): RemoteConfigTemplate { + public toJSON(): object { return { conditions: this.conditions, parameters: this.parameters, @@ -359,9 +359,9 @@ class VersionImpl implements Version { } /** - * @return {Version} A JSON-serializable representation of this object. + * @returns A JSON-serializable representation of this object. */ - public toJSON(): Version { + public toJSON(): object { return { versionNumber: this.versionNumber, updateOrigin: this.updateOrigin, diff --git a/src/security-rules/index.ts b/src/security-rules/index.ts index 2871f7873b..0f8c846b7e 100644 --- a/src/security-rules/index.ts +++ b/src/security-rules/index.ts @@ -14,223 +14,54 @@ * limitations under the License. */ -import { app } from '../firebase-namespace-api'; +/** + * Security Rules for Cloud Firestore and Cloud Storage. + * + * @packageDocumentation + */ + +import { App, getApp } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; +import { SecurityRules } from './security-rules'; + +export { + RulesFile, + Ruleset, + RulesetMetadata, + RulesetMetadataList, + SecurityRules, +} from './security-rules'; /** - * Gets the {@link securityRules.SecurityRules - * `SecurityRules`} service for the default app or a given app. + * Gets the {@link SecurityRules} service for the default app or a given app. * * `admin.securityRules()` can be called with no arguments to access the - * default app's {@link securityRules.SecurityRules - * `SecurityRules`} service, or as `admin.securityRules(app)` to access - * the {@link securityRules.SecurityRules `SecurityRules`} - * service associated with a specific app. + * default app's `SecurityRules` service, or as `admin.securityRules(app)` to access + * the `SecurityRules` service associated with a specific app. * * @example * ```javascript * // Get the SecurityRules service for the default app - * var defaultSecurityRules = admin.securityRules(); + * const defaultSecurityRules = getSecurityRules(); * ``` * * @example - * ```javascript + * ```javascript * // Get the SecurityRules service for a given app - * var otherSecurityRules = admin.securityRules(otherApp); + * const otherSecurityRules = getSecurityRules(otherApp); * ``` * * @param app Optional app to return the `SecurityRules` service * for. If not provided, the default `SecurityRules` service * is returned. - * @return The default `SecurityRules` service if no app is provided, or the + * @returns The default `SecurityRules` service if no app is provided, or the * `SecurityRules` service associated with the provided app. */ -export declare function securityRules(app?: app.App): securityRules.SecurityRules; - -/* eslint-disable @typescript-eslint/no-namespace */ -export namespace securityRules { - /** - * A source file containing some Firebase security rules. The content includes raw - * source code including text formatting, indentation and comments. Use the - * [`securityRules.createRulesFileFromSource()`](securityRules.SecurityRules#createRulesFileFromSource) - * method to create new instances of this type. - */ - export interface RulesFile { - readonly name: string; - readonly content: string; - } - - /** - * Required metadata associated with a ruleset. - */ - export interface RulesetMetadata { - /** - * Name of the `Ruleset` as a short string. This can be directly passed into APIs - * like {@link securityRules.SecurityRules.getRuleset `securityRules.getRuleset()`} - * and {@link securityRules.SecurityRules.deleteRuleset `securityRules.deleteRuleset()`}. - */ - readonly name: string; - /** - * Creation time of the `Ruleset` as a UTC timestamp string. - */ - readonly createTime: string; - } - - /** - * A page of ruleset metadata. - */ - export interface RulesetMetadataList { - /** - * A batch of ruleset metadata. - */ - readonly rulesets: RulesetMetadata[]; - /** - * The next page token if available. This is needed to retrieve the next batch. - */ - readonly nextPageToken?: string; - } - - /** - * A set of Firebase security rules. - */ - export interface Ruleset extends RulesetMetadata { - readonly source: RulesFile[]; +export function getSecurityRules(app?: App): SecurityRules { + if (typeof app === 'undefined') { + app = getApp(); } - /** - * The Firebase `SecurityRules` service interface. - */ - export interface SecurityRules { - app: app.App; - - /** - * Creates a {@link securityRules.RulesFile `RuleFile`} with the given name - * and source. Throws an error if any of the arguments are invalid. This is a local - * operation, and does not involve any network API calls. - * - * @example - * ```javascript - * const source = '// Some rules source'; - * const rulesFile = admin.securityRules().createRulesFileFromSource( - * 'firestore.rules', source); - * ``` - * - * @param name Name to assign to the rules file. This is usually a short file name that - * helps identify the file in a ruleset. - * @param source Contents of the rules file. - * @return A new rules file instance. - */ - createRulesFileFromSource(name: string, source: string | Buffer): RulesFile; - - /** - * Creates a new {@link securityRules.Ruleset `Ruleset`} from the given - * {@link securityRules.RulesFile `RuleFile`}. - * - * @param file Rules file to include in the new `Ruleset`. - * @returns A promise that fulfills with the newly created `Ruleset`. - */ - createRuleset(file: RulesFile): Promise; - - /** - * Gets the {@link securityRules.Ruleset `Ruleset`} identified by the given - * name. The input name should be the short name string without the project ID - * prefix. For example, to retrieve the `projects/project-id/rulesets/my-ruleset`, - * pass the short name "my-ruleset". Rejects with a `not-found` error if the - * specified `Ruleset` cannot be found. - * - * @param name Name of the `Ruleset` to retrieve. - * @return A promise that fulfills with the specified `Ruleset`. - */ - getRuleset(name: string): Promise; - - /** - * Deletes the {@link securityRules.Ruleset `Ruleset`} identified by the given - * name. The input name should be the short name string without the project ID - * prefix. For example, to delete the `projects/project-id/rulesets/my-ruleset`, - * pass the short name "my-ruleset". Rejects with a `not-found` error if the - * specified `Ruleset` cannot be found. - * - * @param name Name of the `Ruleset` to delete. - * @return A promise that fulfills when the `Ruleset` is deleted. - */ - deleteRuleset(name: string): Promise; - - /** - * Retrieves a page of ruleset metadata. - * - * @param pageSize The page size, 100 if undefined. This is also the maximum allowed - * limit. - * @param nextPageToken The next page token. If not specified, returns rulesets - * starting without any offset. - * @return A promise that fulfills with a page of rulesets. - */ - listRulesetMetadata( - pageSize?: number, nextPageToken?: string): Promise; - - /** - * Gets the {@link securityRules.Ruleset `Ruleset`} currently applied to - * Cloud Firestore. Rejects with a `not-found` error if no ruleset is applied - * on Firestore. - * - * @return A promise that fulfills with the Firestore ruleset. - */ - getFirestoreRuleset(): Promise; - - /** - * Creates a new {@link securityRules.Ruleset `Ruleset`} from the given - * source, and applies it to Cloud Firestore. - * - * @param source Rules source to apply. - * @return A promise that fulfills when the ruleset is created and released. - */ - releaseFirestoreRulesetFromSource(source: string | Buffer): Promise; - - /** - * Applies the specified {@link securityRules.Ruleset `Ruleset`} ruleset - * to Cloud Firestore. - * - * @param ruleset Name of the ruleset to apply or a `RulesetMetadata` object - * containing the name. - * @return A promise that fulfills when the ruleset is released. - */ - releaseFirestoreRuleset(ruleset: string | RulesetMetadata): Promise; - - /** - * Gets the {@link securityRules.Ruleset `Ruleset`} currently applied to a - * Cloud Storage bucket. Rejects with a `not-found` error if no ruleset is applied - * on the bucket. - * - * @param bucket Optional name of the Cloud Storage bucket to be retrieved. If not - * specified, retrieves the ruleset applied on the default bucket configured via - * `AppOptions`. - * @return A promise that fulfills with the Cloud Storage ruleset. - */ - getStorageRuleset(bucket?: string): Promise; - - /** - * Creates a new {@link securityRules.Ruleset `Ruleset`} from the given - * source, and applies it to a Cloud Storage bucket. - * - * @param source Rules source to apply. - * @param bucket Optional name of the Cloud Storage bucket to apply the rules on. If - * not specified, applies the ruleset on the default bucket configured via - * {@link AppOptions `AppOptions`}. - * @return A promise that fulfills when the ruleset is created and released. - */ - releaseStorageRulesetFromSource( - source: string | Buffer, bucket?: string): Promise; - - /** - * Applies the specified {@link securityRules.Ruleset `Ruleset`} ruleset - * to a Cloud Storage bucket. - * - * @param ruleset Name of the ruleset to apply or a `RulesetMetadata` object - * containing the name. - * @param bucket Optional name of the Cloud Storage bucket to apply the rules on. If - * not specified, applies the ruleset on the default bucket configured via - * {@link AppOptions `AppOptions`}. - * @return A promise that fulfills when the ruleset is released. - */ - releaseStorageRuleset( - ruleset: string | RulesetMetadata, bucket?: string): Promise; - } + const firebaseApp: FirebaseApp = app as FirebaseApp; + return firebaseApp.getOrInitService('securityRules', (app) => new SecurityRules(app)); } diff --git a/src/security-rules/security-rules-api-client-internal.ts b/src/security-rules/security-rules-api-client-internal.ts index 50fdbc9cb8..73369f0918 100644 --- a/src/security-rules/security-rules-api-client-internal.ts +++ b/src/security-rules/security-rules-api-client-internal.ts @@ -19,7 +19,8 @@ import { PrefixedFirebaseError } from '../utils/error'; import { FirebaseSecurityRulesError, SecurityRulesErrorCode } from './security-rules-internal'; import * as utils from '../utils/index'; import * as validator from '../utils/validator'; -import { FirebaseApp } from '../firebase-app'; +import { FirebaseApp } from '../app/firebase-app'; +import { App } from '../app'; const RULES_V1_API = 'https://firebaserules.googleapis.com/v1'; const FIREBASE_VERSION_HEADER = { @@ -59,7 +60,7 @@ export class SecurityRulesApiClient { private readonly httpClient: HttpClient; private projectIdPrefix?: string; - constructor(private readonly app: FirebaseApp) { + constructor(private readonly app: App) { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseSecurityRulesError( 'invalid-argument', @@ -67,7 +68,7 @@ export class SecurityRulesApiClient { + 'instance.'); } - this.httpClient = new AuthorizedHttpClient(app); + this.httpClient = new AuthorizedHttpClient(app as FirebaseApp); } public getRuleset(name: string): Promise { diff --git a/src/security-rules/security-rules-namespace.ts b/src/security-rules/security-rules-namespace.ts new file mode 100644 index 0000000000..f418fcc883 --- /dev/null +++ b/src/security-rules/security-rules-namespace.ts @@ -0,0 +1,82 @@ +/*! + * Copyright 2021 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 { App } from '../app'; +import { + RulesFile as TRulesFile, + Ruleset as TRuleset, + RulesetMetadata as TRulesetMetadata, + RulesetMetadataList as TRulesetMetadataList, + SecurityRules as TSecurityRules, +} from './security-rules'; + +/** + * Gets the {@link securityRules.SecurityRules + * `SecurityRules`} service for the default app or a given app. + * + * `admin.securityRules()` can be called with no arguments to access the + * default app's {@link securityRules.SecurityRules + * `SecurityRules`} service, or as `admin.securityRules(app)` to access + * the {@link securityRules.SecurityRules `SecurityRules`} + * service associated with a specific app. + * + * @example + * ```javascript + * // Get the SecurityRules service for the default app + * var defaultSecurityRules = admin.securityRules(); + * ``` + * + * @example + * ```javascript + * // Get the SecurityRules service for a given app + * var otherSecurityRules = admin.securityRules(otherApp); + * ``` + * + * @param app Optional app to return the `SecurityRules` service + * for. If not provided, the default `SecurityRules` service + * is returned. + * @returns The default `SecurityRules` service if no app is provided, or the + * `SecurityRules` service associated with the provided app. + */ +export declare function securityRules(app?: App): securityRules.SecurityRules; + +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace securityRules { + /** + * Type alias to {@link firebase-admin.security-rules#RulesFile}. + */ + export type RulesFile = TRulesFile; + + /** + * Type alias to {@link firebase-admin.security-rules#Ruleset}. + */ + export type Ruleset = TRuleset; + + /** + * Type alias to {@link firebase-admin.security-rules#RulesetMetadata}. + */ + export type RulesetMetadata = TRulesetMetadata; + + /** + * Type alias to {@link firebase-admin.security-rules#RulesetMetadataList}. + */ + export type RulesetMetadataList = TRulesetMetadataList; + + /** + * Type alias to {@link firebase-admin.security-rules#SecurityRules}. + */ + export type SecurityRules = TSecurityRules; +} diff --git a/src/security-rules/security-rules.ts b/src/security-rules/security-rules.ts index 2206410df0..6a5eafa3d9 100644 --- a/src/security-rules/security-rules.ts +++ b/src/security-rules/security-rules.ts @@ -14,25 +14,56 @@ * limitations under the License. */ -import { FirebaseApp } from '../firebase-app'; +import { App } from '../app'; import * as validator from '../utils/validator'; import { SecurityRulesApiClient, RulesetResponse, RulesetContent, ListRulesetsResponse, } from './security-rules-api-client-internal'; import { FirebaseSecurityRulesError } from './security-rules-internal'; -import { securityRules } from './index'; -import RulesFile = securityRules.RulesFile; -import RulesetMetadata = securityRules.RulesetMetadata; -import RulesetMetadataList = securityRules.RulesetMetadataList; -import RulesetInterface = securityRules.Ruleset; -import SecurityRulesInterface = securityRules.SecurityRules; +/** + * A source file containing some Firebase security rules. The content includes raw + * source code including text formatting, indentation and comments. Use the + * {@link SecurityRules.createRulesFileFromSource} method to create new instances of this type. + */ +export interface RulesFile { + readonly name: string; + readonly content: string; +} + +/** + * Required metadata associated with a ruleset. + */ +export interface RulesetMetadata { + /** + * Name of the `Ruleset` as a short string. This can be directly passed into APIs + * like {@link SecurityRules.getRuleset} and {@link SecurityRules.deleteRuleset}. + */ + readonly name: string; + /** + * Creation time of the `Ruleset` as a UTC timestamp string. + */ + readonly createTime: string; +} -class RulesetMetadataListImpl implements RulesetMetadataList { +/** + * A page of ruleset metadata. + */ +export class RulesetMetadataList { + /** + * A batch of ruleset metadata. + */ public readonly rulesets: RulesetMetadata[]; + + /** + * The next page token if available. This is needed to retrieve the next batch. + */ public readonly nextPageToken?: string; + /** + * @internal + */ constructor(response: ListRulesetsResponse) { if (!validator.isNonNullObject(response) || !validator.isArray(response.rulesets)) { throw new FirebaseSecurityRulesError( @@ -54,14 +85,25 @@ class RulesetMetadataListImpl implements RulesetMetadataList { } /** - * Represents a set of Firebase security rules. + * A set of Firebase security rules. */ -export class Ruleset implements RulesetInterface { +export class Ruleset implements RulesetMetadata { + /** + * {@inheritdoc RulesetMetadata.name} + */ public readonly name: string; + + /** + * {@inheritdoc RulesetMetadata.createTime} + */ public readonly createTime: string; + public readonly source: RulesFile[]; + /** + * @internal + */ constructor(ruleset: RulesetResponse) { if (!validator.isNonNullObject(ruleset) || !validator.isNonEmptyString(ruleset.name) || @@ -80,11 +122,8 @@ export class Ruleset implements RulesetInterface { /** * The Firebase `SecurityRules` service interface. - * - * Do not call this constructor directly. Instead, use - * [`admin.securityRules()`](securityRules#securityRules). */ -export class SecurityRules implements SecurityRulesInterface { +export class SecurityRules { private static readonly CLOUD_FIRESTORE = 'cloud.firestore'; private static readonly FIREBASE_STORAGE = 'firebase.storage'; @@ -92,20 +131,23 @@ export class SecurityRules implements SecurityRulesInterface { private readonly client: SecurityRulesApiClient; /** - * @param {object} app The app for this SecurityRules service. + * @param app The app for this SecurityRules service. * @constructor + * @internal */ - constructor(readonly app: FirebaseApp) { + constructor(readonly app: App) { this.client = new SecurityRulesApiClient(app); } /** - * Gets the Ruleset identified by the given name. The input name should be the short name string without - * the project ID prefix. For example, to retrieve the `projects/project-id/rulesets/my-ruleset`, pass the - * short name "my-ruleset". Rejects with a `not-found` error if the specified Ruleset cannot be found. + * Gets the {@link Ruleset} identified by the given + * name. The input name should be the short name string without the project ID + * prefix. For example, to retrieve the `projects/project-id/rulesets/my-ruleset`, + * pass the short name "my-ruleset". Rejects with a `not-found` error if the + * specified `Ruleset` cannot be found. * - * @param {string} name Name of the Ruleset to retrieve. - * @returns {Promise} A promise that fulfills with the specified Ruleset. + * @param name Name of the `Ruleset` to retrieve. + * @returns A promise that fulfills with the specified `Ruleset`. */ public getRuleset(name: string): Promise { return this.client.getRuleset(name) @@ -115,20 +157,22 @@ export class SecurityRules implements SecurityRulesInterface { } /** - * Gets the Ruleset currently applied to Cloud Firestore. Rejects with a `not-found` error if no Ruleset is - * applied on Firestore. + * Gets the {@link Ruleset} currently applied to + * Cloud Firestore. Rejects with a `not-found` error if no ruleset is applied + * on Firestore. * - * @returns {Promise} A promise that fulfills with the Firestore Ruleset. + * @returns A promise that fulfills with the Firestore ruleset. */ public getFirestoreRuleset(): Promise { return this.getRulesetForRelease(SecurityRules.CLOUD_FIRESTORE); } /** - * Creates a new ruleset from the given source, and applies it to Cloud Firestore. + * Creates a new {@link Ruleset} from the given + * source, and applies it to Cloud Firestore. * - * @param {string|Buffer} source Rules source to apply. - * @returns {Promise} A promise that fulfills when the ruleset is created and released. + * @param source Rules source to apply. + * @returns A promise that fulfills when the ruleset is created and released. */ public releaseFirestoreRulesetFromSource(source: string | Buffer): Promise { return Promise.resolve() @@ -145,23 +189,26 @@ export class SecurityRules implements SecurityRulesInterface { } /** - * Makes the specified ruleset the currently applied ruleset for Cloud Firestore. + * Applies the specified {@link Ruleset} ruleset + * to Cloud Firestore. * - * @param {string|RulesetMetadata} ruleset Name of the ruleset to apply or a RulesetMetadata object containing - * the name. - * @returns {Promise} A promise that fulfills when the ruleset is released. + * @param ruleset Name of the ruleset to apply or a `RulesetMetadata` object + * containing the name. + * @returns A promise that fulfills when the ruleset is released. */ public releaseFirestoreRuleset(ruleset: string | RulesetMetadata): Promise { return this.releaseRuleset(ruleset, SecurityRules.CLOUD_FIRESTORE); } /** - * Gets the Ruleset currently applied to a Cloud Storage bucket. Rejects with a `not-found` error if no Ruleset is - * applied on the bucket. + * Gets the {@link Ruleset} currently applied to a + * Cloud Storage bucket. Rejects with a `not-found` error if no ruleset is applied + * on the bucket. * - * @param {string=} bucket Optional name of the Cloud Storage bucket to be retrieved. If not specified, - * retrieves the ruleset applied on the default bucket configured via `AppOptions`. - * @returns {Promise} A promise that fulfills with the Cloud Storage Ruleset. + * @param bucket Optional name of the Cloud Storage bucket to be retrieved. If not + * specified, retrieves the ruleset applied on the default bucket configured via + * `AppOptions`. + * @returns A promise that fulfills with the Cloud Storage ruleset. */ public getStorageRuleset(bucket?: string): Promise { return Promise.resolve() @@ -174,12 +221,14 @@ export class SecurityRules implements SecurityRulesInterface { } /** - * Creates a new ruleset from the given source, and applies it to a Cloud Storage bucket. + * Creates a new {@link Ruleset} from the given + * source, and applies it to a Cloud Storage bucket. * - * @param {string|Buffer} source Rules source to apply. - * @param {string=} bucket Optional name of the Cloud Storage bucket to apply the rules on. If not specified, - * applies the ruleset on the default bucket configured via `AppOptions`. - * @returns {Promise} A promise that fulfills when the ruleset is created and released. + * @param source Rules source to apply. + * @param bucket Optional name of the Cloud Storage bucket to apply the rules on. If + * not specified, applies the ruleset on the default bucket configured via + * {@link firebase-admin.app#AppOptions}. + * @returns A promise that fulfills when the ruleset is created and released. */ public releaseStorageRulesetFromSource(source: string | Buffer, bucket?: string): Promise { return Promise.resolve() @@ -199,13 +248,15 @@ export class SecurityRules implements SecurityRulesInterface { } /** - * Makes the specified ruleset the currently applied ruleset for a Cloud Storage bucket. + * Applies the specified {@link Ruleset} ruleset + * to a Cloud Storage bucket. * - * @param {string|RulesetMetadata} ruleset Name of the ruleset to apply or a RulesetMetadata object containing - * the name. - * @param {string=} bucket Optional name of the Cloud Storage bucket to apply the rules on. If not specified, - * applies the ruleset on the default bucket configured via `AppOptions`. - * @returns {Promise} A promise that fulfills when the ruleset is released. + * @param ruleset Name of the ruleset to apply or a `RulesetMetadata` object + * containing the name. + * @param bucket Optional name of the Cloud Storage bucket to apply the rules on. If + * not specified, applies the ruleset on the default bucket configured via + * {@link firebase-admin.app#AppOptions}. + * @returns A promise that fulfills when the ruleset is released. */ public releaseStorageRuleset(ruleset: string | RulesetMetadata, bucket?: string): Promise { return Promise.resolve() @@ -218,7 +269,7 @@ export class SecurityRules implements SecurityRulesInterface { } /** - * Creates a {@link securityRules.RulesFile `RuleFile`} with the given name + * Creates a {@link RulesFile} with the given name * and source. Throws an error if any of the arguments are invalid. This is a local * operation, and does not involve any network API calls. * @@ -232,7 +283,7 @@ export class SecurityRules implements SecurityRulesInterface { * @param name Name to assign to the rules file. This is usually a short file name that * helps identify the file in a ruleset. * @param source Contents of the rules file. - * @return A new rules file instance. + * @returns A new rules file instance. */ public createRulesFileFromSource(name: string, source: string | Buffer): RulesFile { if (!validator.isNonEmptyString(name)) { @@ -257,8 +308,7 @@ export class SecurityRules implements SecurityRulesInterface { } /** - * Creates a new {@link securityRules.Ruleset `Ruleset`} from the given - * {@link securityRules.RulesFile `RuleFile`}. + * Creates a new {@link Ruleset} from the given {@link RulesFile}. * * @param file Rules file to include in the new `Ruleset`. * @returns A promise that fulfills with the newly created `Ruleset`. @@ -277,14 +327,14 @@ export class SecurityRules implements SecurityRulesInterface { } /** - * Deletes the {@link securityRules.Ruleset `Ruleset`} identified by the given + * Deletes the {@link Ruleset} identified by the given * name. The input name should be the short name string without the project ID * prefix. For example, to delete the `projects/project-id/rulesets/my-ruleset`, * pass the short name "my-ruleset". Rejects with a `not-found` error if the * specified `Ruleset` cannot be found. * * @param name Name of the `Ruleset` to delete. - * @return A promise that fulfills when the `Ruleset` is deleted. + * @returns A promise that fulfills when the `Ruleset` is deleted. */ public deleteRuleset(name: string): Promise { return this.client.deleteRuleset(name); @@ -297,12 +347,12 @@ export class SecurityRules implements SecurityRulesInterface { * limit. * @param nextPageToken The next page token. If not specified, returns rulesets * starting without any offset. - * @return A promise that fulfills with a page of rulesets. + * @returns A promise that fulfills with a page of rulesets. */ public listRulesetMetadata(pageSize = 100, nextPageToken?: string): Promise { return this.client.listRulesets(pageSize, nextPageToken) .then((response) => { - return new RulesetMetadataListImpl(response); + return new RulesetMetadataList(response); }); } diff --git a/src/storage/index.ts b/src/storage/index.ts index 109f54431d..bbab751584 100644 --- a/src/storage/index.ts +++ b/src/storage/index.ts @@ -14,50 +14,42 @@ * limitations under the License. */ -import { Bucket } from '@google-cloud/storage'; -import { app } from '../firebase-namespace-api'; +/** + * Cloud Storage for Firebase. + * + * @packageDocumentation + */ + +import { App, getApp } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; +import { Storage } from './storage'; + +export { Storage } from './storage'; /** - * Gets the {@link storage.Storage `Storage`} service for the - * default app or a given app. + * Gets the {@link Storage} service for the default app or a given app. * - * `admin.storage()` can be called with no arguments to access the default - * app's {@link storage.Storage `Storage`} service or as - * `admin.storage(app)` to access the - * {@link storage.Storage `Storage`} service associated with a - * specific app. + * `getStorage()` can be called with no arguments to access the default + * app's `Storage` service or as `getStorage(app)` to access the + * `Storage` service associated with a specific app. * * @example * ```javascript * // Get the Storage service for the default app - * var defaultStorage = admin.storage(); + * const defaultStorage = getStorage(); * ``` * * @example * ```javascript * // Get the Storage service for a given app - * var otherStorage = admin.storage(otherApp); + * const otherStorage = getStorage(otherApp); * ``` */ -export declare function storage(app?: app.App): storage.Storage; - -/* eslint-disable @typescript-eslint/no-namespace */ -export namespace storage { - /** - * The default `Storage` service if no - * app is provided or the `Storage` service associated with the provided - * app. - */ - export interface Storage { - /** - * Optional app whose `Storage` service to - * return. If not provided, the default `Storage` service will be returned. - */ - app: app.App; - /** - * @returns A [Bucket](https://cloud.google.com/nodejs/docs/reference/storage/latest/Bucket) - * instance as defined in the `@google-cloud/storage` package. - */ - bucket(name?: string): Bucket; +export function getStorage(app?: App): Storage { + if (typeof app === 'undefined') { + app = getApp(); } + + const firebaseApp: FirebaseApp = app as FirebaseApp; + return firebaseApp.getOrInitService('storage', (app) => new Storage(app)); } diff --git a/src/storage/storage-namespace.ts b/src/storage/storage-namespace.ts new file mode 100644 index 0000000000..f0dd80725d --- /dev/null +++ b/src/storage/storage-namespace.ts @@ -0,0 +1,48 @@ +/*! + * Copyright 2021 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 { App } from '../app'; +import { Storage as TStorage } from './storage'; + +/** + * Gets the {@link firebase-admin.storage#Storage} service for the + * default app or a given app. + * + * `admin.storage()` can be called with no arguments to access the default + * app's `Storage` service or as `admin.storage(app)` to access the + * `Storage` service associated with a specific app. + * + * @example + * ```javascript + * // Get the Storage service for the default app + * var defaultStorage = admin.storage(); + * ``` + * + * @example + * ```javascript + * // Get the Storage service for a given app + * var otherStorage = admin.storage(otherApp); + * ``` + */ +export declare function storage(app?: App): storage.Storage; + +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace storage { + /** + * Type alias to {@link firebase-admin.storage#Storage}. + */ + export type Storage = TStorage; +} diff --git a/src/storage/storage.ts b/src/storage/storage.ts index 7bac81ff46..3c7994fb27 100644 --- a/src/storage/storage.ts +++ b/src/storage/storage.ts @@ -15,32 +15,29 @@ * limitations under the License. */ -import { FirebaseApp } from '../firebase-app'; +import { App } from '../app'; import { FirebaseError } from '../utils/error'; -import { ServiceAccountCredential, isApplicationDefault } from '../credential/credential-internal'; +import { ServiceAccountCredential, isApplicationDefault } from '../app/credential-internal'; import { Bucket, Storage as StorageClient } from '@google-cloud/storage'; import * as utils from '../utils/index'; import * as validator from '../utils/validator'; -import { storage } from './index'; - -import StorageInterface = storage.Storage; /** * The default `Storage` service if no * app is provided or the `Storage` service associated with the provided * app. */ -export class Storage implements StorageInterface { +export class Storage { - private readonly appInternal: FirebaseApp; + private readonly appInternal: App; private readonly storageClient: StorageClient; /** - * @param {FirebaseApp} app The app for this Storage service. + * @param app The app for this Storage service. * @constructor * @internal */ - constructor(app: FirebaseApp) { + constructor(app: App) { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseError({ code: 'storage/invalid-argument', @@ -100,9 +97,11 @@ export class Storage implements StorageInterface { } /** + * Gets a reference to a Cloud Storage bucket. + * * @param name Optional name of the bucket to be retrieved. If name is not specified, * retrieves a reference to the default bucket. - * @returns A [Bucket](https://cloud.google.com/nodejs/docs/reference/storage/latest/Bucket) + * @returns A {@link https://cloud.google.com/nodejs/docs/reference/storage/latest/Bucket | Bucket} * instance as defined in the `@google-cloud/storage` package. */ public bucket(name?: string): Bucket { @@ -120,9 +119,10 @@ export class Storage implements StorageInterface { } /** - * @return The app associated with this Storage instance. + * Optional app whose `Storage` service to + * return. If not provided, the default `Storage` service will be returned. */ - get app(): FirebaseApp { + get app(): App { return this.appInternal; } } diff --git a/src/utils/api-request.ts b/src/utils/api-request.ts index 5b1b153288..129f2f5c83 100644 --- a/src/utils/api-request.ts +++ b/src/utils/api-request.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { FirebaseApp } from '../firebase-app'; +import { FirebaseApp } from '../app/firebase-app'; import { AppErrorCodes, FirebaseAppError } from './error'; import * as validator from './validator'; @@ -260,8 +260,8 @@ export class HttpClient { * header should be explicitly set by the caller. To send a JSON leaf value (e.g. "foo", 5), parse it into JSON, * and pass as a string or a Buffer along with the appropriate content-type header. * - * @param {HttpRequest} config HTTP request to be sent. - * @return {Promise} A promise that resolves with the response details. + * @param config HTTP request to be sent. + * @returns A promise that resolves with the response details. */ public send(config: HttpRequestConfig): Promise { return this.sendWithRetry(config); @@ -271,9 +271,9 @@ export class HttpClient { * Sends an HTTP request. In the event of an error, retries the HTTP request according to the * RetryConfig set on the HttpClient. * - * @param {HttpRequestConfig} config HTTP request to be sent. - * @param {number} retryAttempts Number of retries performed up to now. - * @return {Promise} A promise that resolves with the response details. + * @param config HTTP request to be sent. + * @param retryAttempts Number of retries performed up to now. + * @returns A promise that resolves with the response details. */ private sendWithRetry(config: HttpRequestConfig, retryAttempts = 0): Promise { return AsyncHttpCall.invoke(config) @@ -323,9 +323,9 @@ export class HttpClient { * Checks if a failed request is eligible for a retry, and if so returns the duration to wait before initiating * the retry. * - * @param {number} retryAttempts Number of retries completed up to now. - * @param {LowLevelError} err The last encountered error. - * @returns {[number, boolean]} A 2-tuple where the 1st element is the duration to wait before another retry, and the + * @param retryAttempts Number of retries completed up to now. + * @param err The last encountered error. + * @returns A 2-tuple where the 1st element is the duration to wait before another retry, and the * 2nd element is a boolean indicating whether the request is eligible for a retry or not. */ private getRetryDelayMillis(retryAttempts: number, err: LowLevelError): [number, boolean] { @@ -401,9 +401,9 @@ export class HttpClient { /** * Parses a full HTTP response message containing both a header and a body. * - * @param {string|Buffer} response The HTTP response to be parsed. - * @param {HttpRequestConfig} config The request configuration that resulted in the HTTP response. - * @return {HttpResponse} An object containing the parsed HTTP status, headers and the body. + * @param response The HTTP response to be parsed. + * @param config The request configuration that resulted in the HTTP response. + * @returns An object containing the parsed HTTP status, headers and the body. */ export function parseHttpResponse( response: string | Buffer, config: HttpRequestConfig): HttpResponse { @@ -839,8 +839,8 @@ export class AuthorizedHttpClient extends HttpClient { /** * Class that defines all the settings for the backend API endpoint. * - * @param {string} endpoint The Firebase Auth backend endpoint. - * @param {HttpMethod} httpMethod The http method for that endpoint. + * @param endpoint The Firebase Auth backend endpoint. + * @param httpMethod The http method for that endpoint. * @constructor */ export class ApiSettings { @@ -852,19 +852,19 @@ export class ApiSettings { .setResponseValidator(null); } - /** @return {string} The backend API endpoint. */ + /** @returns The backend API endpoint. */ public getEndpoint(): string { return this.endpoint; } - /** @return {HttpMethod} The request HTTP method. */ + /** @returns The request HTTP method. */ public getHttpMethod(): HttpMethod { return this.httpMethod; } /** - * @param {ApiCallbackFunction} requestValidator The request validator. - * @return {ApiSettings} The current API settings instance. + * @param requestValidator The request validator. + * @returns The current API settings instance. */ public setRequestValidator(requestValidator: ApiCallbackFunction | null): ApiSettings { const nullFunction: ApiCallbackFunction = () => undefined; @@ -872,14 +872,14 @@ export class ApiSettings { return this; } - /** @return {ApiCallbackFunction} The request validator. */ + /** @returns The request validator. */ public getRequestValidator(): ApiCallbackFunction { return this.requestValidator; } /** - * @param {ApiCallbackFunction} responseValidator The response validator. - * @return {ApiSettings} The current API settings instance. + * @param responseValidator The response validator. + * @returns The current API settings instance. */ public setResponseValidator(responseValidator: ApiCallbackFunction | null): ApiSettings { const nullFunction: ApiCallbackFunction = () => undefined; @@ -887,7 +887,7 @@ export class ApiSettings { return this; } - /** @return {ApiCallbackFunction} The response validator. */ + /** @returns The response validator. */ public getResponseValidator(): ApiCallbackFunction { return this.responseValidator; } @@ -938,10 +938,10 @@ export class ExponentialBackoffPoller extends EventEmitter { /** * Poll the provided callback with exponential backoff. * - * @param {() => Promise} callback The callback to be called for each poll. If the + * @param callback The callback to be called for each poll. If the * callback resolves to a falsey value, polling will continue. Otherwise, the truthy * resolution will be used to resolve the promise returned by this method. - * @return {Promise} A Promise which resolves to the truthy value returned by the provided + * @returns A Promise which resolves to the truthy value returned by the provided * callback when polling is complete. */ public poll(callback: () => Promise): Promise { diff --git a/src/utils/crypto-signer.ts b/src/utils/crypto-signer.ts index e8bb8b79ae..789e2c81f3 100644 --- a/src/utils/crypto-signer.ts +++ b/src/utils/crypto-signer.ts @@ -15,8 +15,9 @@ * limitations under the License. */ -import { FirebaseApp } from '../firebase-app'; -import { ServiceAccountCredential } from '../credential/credential-internal'; +import { App } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; +import { ServiceAccountCredential } from '../app/credential-internal'; import { AuthorizedHttpClient, HttpRequestConfig, HttpClient, HttpError } from './api-request'; import { Algorithm } from 'jsonwebtoken'; @@ -38,15 +39,15 @@ export interface CryptoSigner { /** * Cryptographically signs a buffer of data. * - * @param {Buffer} buffer The data to be signed. - * @return {Promise} A promise that resolves with the raw bytes of a signature. + * @param buffer The data to be signed. + * @returns A promise that resolves with the raw bytes of a signature. */ sign(buffer: Buffer): Promise; /** * Returns the ID of the service account used to sign tokens. * - * @return {Promise} A promise that resolves with a service account ID. + * @returns A promise that resolves with a service account ID. */ getAccountId(): Promise; } @@ -62,7 +63,7 @@ export class ServiceAccountSigner implements CryptoSigner { /** * Creates a new CryptoSigner instance from the given service account credential. * - * @param {ServiceAccountCredential} credential A service account credential. + * @param credential A service account credential. */ constructor(private readonly credential: ServiceAccountCredential) { if (!credential) { @@ -187,16 +188,16 @@ export class IAMSigner implements CryptoSigner { * Creates a new CryptoSigner instance for the given app. If the app has been initialized with a * service account credential, creates a ServiceAccountSigner. * - * @param {FirebaseApp} app A FirebaseApp instance. - * @return {CryptoSigner} A CryptoSigner instance. + * @param app A FirebaseApp instance. + * @returns A CryptoSigner instance. */ -export function cryptoSignerFromApp(app: FirebaseApp): CryptoSigner { +export function cryptoSignerFromApp(app: App): CryptoSigner { const credential = app.options.credential; if (credential instanceof ServiceAccountCredential) { return new ServiceAccountSigner(credential); } - return new IAMSigner(new AuthorizedHttpClient(app), app.options.serviceAccountId); + return new IAMSigner(new AuthorizedHttpClient(app as FirebaseApp), app.options.serviceAccountId); } /** @@ -209,7 +210,7 @@ export interface ExtendedErrorInfo extends ErrorInfo { /** * CryptoSigner error code structure. * - * @param {ErrorInfo} errorInfo The error information (code and message). + * @param errorInfo The error information (code and message). * @constructor */ export class CryptoSignerError extends Error { @@ -223,17 +224,17 @@ export class CryptoSignerError extends Error { (this as any).__proto__ = CryptoSignerError.prototype; } - /** @return {string} The error code. */ + /** @returns The error code. */ public get code(): string { return this.errorInfo.code; } - /** @return {string} The error message. */ + /** @returns The error message. */ public get message(): string { return this.errorInfo.message; } - /** @return {object} The error data. */ + /** @returns The error data. */ public get cause(): Error | undefined { return this.errorInfo.cause; } diff --git a/src/utils/deep-copy.ts b/src/utils/deep-copy.ts index 8e5bee9d8b..4b73e7da8f 100644 --- a/src/utils/deep-copy.ts +++ b/src/utils/deep-copy.ts @@ -18,8 +18,8 @@ /** * Returns a deep copy of an object or array. * - * @param {object|array} value The object or array to deep copy. - * @return {object|array} A deep copy of the provided object or array. + * @param value The object or array to deep copy. + * @returns A deep copy of the provided object or array. */ export function deepCopy(value: T): T { return deepExtend(undefined, value); @@ -37,9 +37,9 @@ export function deepCopy(value: T): T { * Note that the target can be a function, in which case the properties in the source object are * copied onto it as static properties of the function. * - * @param {any} target The value which is being extended. - * @param {any} source The value whose properties are extending the target. - * @return {any} The target value. + * @param target The value which is being extended. + * @param source The value whose properties are extending the target. + * @returns The target value. */ export function deepExtend(target: any, source: any): any { if (!(source instanceof Object)) { diff --git a/src/utils/error.ts b/src/utils/error.ts index de22a4b9ca..cde0d24335 100644 --- a/src/utils/error.ts +++ b/src/utils/error.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { FirebaseError as FirebaseErrorInterface } from '../firebase-namespace-api'; +import { FirebaseError as FirebaseErrorInterface } from '../app'; import { deepCopy } from '../utils/deep-copy'; /** @@ -36,7 +36,7 @@ interface ServerToClientCode { /** * Firebase error code structure. This extends Error. * - * @param {ErrorInfo} errorInfo The error information (code and message). + * @param errorInfo The error information (code and message). * @constructor */ export class FirebaseError extends Error implements FirebaseErrorInterface { @@ -50,17 +50,17 @@ export class FirebaseError extends Error implements FirebaseErrorInterface { (this as any).__proto__ = FirebaseError.prototype; } - /** @return {string} The error code. */ + /** @returns The error code. */ public get code(): string { return this.errorInfo.code; } - /** @return {string} The error message. */ + /** @returns The error message. */ public get message(): string { return this.errorInfo.message; } - /** @return {object} The object representation of the error. */ + /** @returns The object representation of the error. */ public toJSON(): object { return { code: this.code, @@ -72,9 +72,9 @@ export class FirebaseError extends Error implements FirebaseErrorInterface { /** * A FirebaseError with a prefix in front of the error code. * - * @param {string} codePrefix The prefix to apply to the error code. - * @param {string} code The error code. - * @param {string} message The error message. + * @param codePrefix The prefix to apply to the error code. + * @param code The error code. + * @param message The error message. * @constructor */ export class PrefixedFirebaseError extends FirebaseError { @@ -95,8 +95,8 @@ export class PrefixedFirebaseError extends FirebaseError { * Allows the error type to be checked without needing to know implementation details * of the code prefixing. * - * @param {string} code The non-prefixed error code to test against. - * @return {boolean} True if the code matches, false otherwise. + * @param code The non-prefixed error code to test against. + * @returns True if the code matches, false otherwise. */ public hasCode(code: string): boolean { return `${this.codePrefix}/${code}` === this.code; @@ -106,8 +106,8 @@ export class PrefixedFirebaseError extends FirebaseError { /** * Firebase App error code structure. This extends PrefixedFirebaseError. * - * @param {string} code The error code. - * @param {string} message The error message. + * @param code The error code. + * @param message The error message. * @constructor */ export class FirebaseAppError extends PrefixedFirebaseError { @@ -125,8 +125,8 @@ export class FirebaseAppError extends PrefixedFirebaseError { /** * Firebase Auth error code structure. This extends PrefixedFirebaseError. * - * @param {ErrorInfo} info The error code info. - * @param {string} [message] The error message. This will override the default + * @param info The error code info. + * @param [message] The error message. This will override the default * message if provided. * @constructor */ @@ -134,11 +134,11 @@ export class FirebaseAuthError extends PrefixedFirebaseError { /** * Creates the developer-facing error corresponding to the backend error code. * - * @param {string} serverErrorCode The server error code. - * @param {string} [message] The error message. The default message is used + * @param serverErrorCode The server error code. + * @param [message] The error message. The default message is used * if not provided. - * @param {object} [rawServerResponse] The error's raw server response. - * @return {FirebaseAuthError} The corresponding developer-facing error. + * @param [rawServerResponse] The error's raw server response. + * @returns The corresponding developer-facing error. */ public static fromServerError( serverErrorCode: string, @@ -185,8 +185,8 @@ export class FirebaseAuthError extends PrefixedFirebaseError { /** * Firebase Database error code structure. This extends FirebaseError. * - * @param {ErrorInfo} info The error code info. - * @param {string} [message] The error message. This will override the default + * @param info The error code info. + * @param [message] The error message. This will override the default * message if provided. * @constructor */ @@ -200,8 +200,8 @@ export class FirebaseDatabaseError extends FirebaseError { /** * Firebase Firestore error code structure. This extends FirebaseError. * - * @param {ErrorInfo} info The error code info. - * @param {string} [message] The error message. This will override the default + * @param info The error code info. + * @param [message] The error message. This will override the default * message if provided. * @constructor */ @@ -215,8 +215,8 @@ export class FirebaseFirestoreError extends FirebaseError { /** * Firebase instance ID error code structure. This extends FirebaseError. * - * @param {ErrorInfo} info The error code info. - * @param {string} [message] The error message. This will override the default + * @param info The error code info. + * @param [message] The error message. This will override the default * message if provided. * @constructor */ @@ -248,19 +248,19 @@ export class FirebaseInstallationsError extends FirebaseError { /** * Firebase Messaging error code structure. This extends PrefixedFirebaseError. * - * @param {ErrorInfo} info The error code info. - * @param {string} [message] The error message. This will override the default message if provided. + * @param info The error code info. + * @param [message] The error message. This will override the default message if provided. * @constructor */ export class FirebaseMessagingError extends PrefixedFirebaseError { /** * Creates the developer-facing error corresponding to the backend error code. * - * @param {string} serverErrorCode The server error code. - * @param {string} [message] The error message. The default message is used + * @param serverErrorCode The server error code. + * @param [message] The error message. The default message is used * if not provided. - * @param {object} [rawServerResponse] The error's raw server response. - * @return {FirebaseMessagingError} The corresponding developer-facing error. + * @param [rawServerResponse] The error's raw server response. + * @returns The corresponding developer-facing error. */ public static fromServerError( serverErrorCode: string | null, @@ -322,8 +322,8 @@ export class FirebaseMessagingError extends PrefixedFirebaseError { /** * Firebase project management error code structure. This extends PrefixedFirebaseError. * - * @param {ProjectManagementErrorCode} code The error code. - * @param {string} message The error message. + * @param code The error code. + * @param message The error message. * @constructor */ export class FirebaseProjectManagementError extends PrefixedFirebaseError { diff --git a/src/utils/index.ts b/src/utils/index.ts index d047397667..a3b1b45bfc 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -15,14 +15,15 @@ * limitations under the License. */ -import { app as _app } from '../firebase-namespace-api'; +import { App } from '../app/index'; import { ServiceAccountCredential, ComputeEngineCredential -} from '../credential/credential-internal'; +} from '../app/credential-internal'; import * as validator from './validator'; let sdkVersion: string; +// TODO: Move to firebase-admin/app as an internal member. export function getSdkVersion(): string { if (!sdkVersion) { const { version } = require('../../package.json'); // eslint-disable-line @typescript-eslint/no-var-requires @@ -36,8 +37,8 @@ export function getSdkVersion(): string { * * For example, this can be used to map underscore_cased properties to camelCase. * - * @param {object} obj The object whose properties to rename. - * @param {object} keyMap The mapping from old to new property names. + * @param obj The object whose properties to rename. + * @param keyMap The mapping from old to new property names. */ export function renameProperties(obj: {[key: string]: any}, keyMap: { [key: string]: string }): void { Object.keys(keyMap).forEach((oldKey) => { @@ -53,9 +54,9 @@ export function renameProperties(obj: {[key: string]: any}, keyMap: { [key: stri /** * Defines a new read-only property directly on an object and returns the object. * - * @param {object} obj The object on which to define the property. - * @param {string} prop The name of the property to be defined or modified. - * @param {any} value The value associated with the property. + * @param obj The object on which to define the property. + * @param prop The name of the property to be defined or modified. + * @param value The value associated with the property. */ export function addReadonlyGetter(obj: object, prop: string, value: any): void { Object.defineProperty(obj, prop, { @@ -74,9 +75,9 @@ export function addReadonlyGetter(obj: object, prop: string, value: any): void { * * @param app A Firebase app to get the project ID from. * - * @return A project ID string or null. + * @returns A project ID string or null. */ -export function getExplicitProjectId(app: _app.App): string | null { +export function getExplicitProjectId(app: App): string | null { const options = app.options; if (validator.isNonEmptyString(options.projectId)) { return options.projectId; @@ -103,9 +104,9 @@ export function getExplicitProjectId(app: _app.App): string | null { * * @param app A Firebase app to get the project ID from. * - * @return A project ID string or null. + * @returns A project ID string or null. */ -export function findProjectId(app: _app.App): Promise { +export function findProjectId(app: App): Promise { const projectId = getExplicitProjectId(app); if (projectId) { return Promise.resolve(projectId); @@ -122,8 +123,8 @@ export function findProjectId(app: _app.App): Promise { /** * Encodes data using web-safe-base64. * - * @param {Buffer} data The raw data byte input. - * @return {string} The base64-encoded result. + * @param data The raw data byte input. + * @returns The base64-encoded result. */ export function toWebSafeBase64(data: Buffer): string { return data.toString('base64').replace(/\//g, '_').replace(/\+/g, '-'); @@ -134,11 +135,11 @@ export function toWebSafeBase64(data: Buffer): string { * with corresponding arguments {projectId: '1234', api: 'resource'} * and returns output: 'project/1234/resource'. * - * @param {string} str The original string where the param need to be + * @param str The original string where the param need to be * replaced. - * @param {object=} params The optional parameters to replace in the + * @param params The optional parameters to replace in the * string. - * @return {string} The resulting formatted string. + * @returns The resulting formatted string. */ export function formatString(str: string, params?: object): string { let formatted = str; @@ -159,7 +160,7 @@ export function formatString(str: string, params?: object): string { * Nested objects beyond that path will be ignored. This is useful for * keys with variable object values. * @param root The path so far. - * @return The computed update mask list. + * @returns The computed update mask list. */ export function generateUpdateMask( obj: any, terminalPaths: string[] = [], root = '' diff --git a/src/utils/jwt.ts b/src/utils/jwt.ts index fe711b97d6..ec54193b32 100644 --- a/src/utils/jwt.ts +++ b/src/utils/jwt.ts @@ -111,7 +111,7 @@ export class UrlKeyFetcher implements KeyFetcher { /** * Fetches the public keys for the Google certs. * - * @return A promise fulfilled with public keys for the Google certs. + * @returns A promise fulfilled with public keys for the Google certs. */ public fetchPublicKeys(): Promise<{ [key: string]: string }> { if (this.shouldRefresh()) { @@ -122,7 +122,7 @@ export class UrlKeyFetcher implements KeyFetcher { /** * Checks if the cached public keys need to be refreshed. - * + * * @returns Whether the keys should be fetched from the client certs url or not. */ private shouldRefresh(): boolean { @@ -177,7 +177,7 @@ export class UrlKeyFetcher implements KeyFetcher { } /** - * Class for verifing JWT signature with a public key. + * Class for verifying JWT signature with a public key. */ export class PublicKeySignatureVerifier implements SignatureVerifier { constructor(private keyFetcher: KeyFetcher) { @@ -239,7 +239,7 @@ export class PublicKeySignatureVerifier implements SignatureVerifier { } /** - * Class for verifing unsigned (emulator) JWTs. + * Class for verifying unsigned (emulator) JWTs. */ export class EmulatorSignatureVerifier implements SignatureVerifier { public verify(token: string): Promise { @@ -250,7 +250,7 @@ export class EmulatorSignatureVerifier implements SignatureVerifier { /** * Provides a callback to fetch public keys. - * + * * @param fetcher KeyFetcher to fetch the keys from. * @returns A callback function that can be used to get keys in `jsonwebtoken`. */ @@ -276,8 +276,8 @@ function getKeyCallback(fetcher: KeyFetcher): jwt.GetPublicKeyOrSecret { /** * Verifies the signature of a JWT using the provided secret or a function to fetch * the secret or public key. - * - * @param token The JWT to be verfied. + * + * @param token The JWT to be verified. * @param secretOrPublicKey The secret or a function to fetch the secret or public key. * @param options JWT verification options. * @returns A Promise resolving for a token with a valid signature. @@ -318,7 +318,7 @@ export function verifyJwtSignature(token: string, secretOrPublicKey: jwt.Secret /** * Decodes general purpose Firebase JWTs. - * + * * @param jwtToken JWT token to be decoded. * @returns Decoded token containing the header and payload. */ diff --git a/src/utils/validator.ts b/src/utils/validator.ts index bca0c660ab..523f91f33c 100644 --- a/src/utils/validator.ts +++ b/src/utils/validator.ts @@ -20,8 +20,8 @@ import url = require('url'); /** * Validates that a value is a byte buffer. * - * @param {any} value The value to validate. - * @return {boolean} Whether the value is byte buffer or not. + * @param value The value to validate. + * @returns Whether the value is byte buffer or not. */ export function isBuffer(value: any): value is Buffer { return value instanceof Buffer; @@ -30,8 +30,8 @@ export function isBuffer(value: any): value is Buffer { /** * Validates that a value is an array. * - * @param {any} value The value to validate. - * @return {boolean} Whether the value is an array or not. + * @param value The value to validate. + * @returns Whether the value is an array or not. */ export function isArray(value: any): value is T[] { return Array.isArray(value); @@ -40,8 +40,8 @@ export function isArray(value: any): value is T[] { /** * Validates that a value is a non-empty array. * - * @param {any} value The value to validate. - * @return {boolean} Whether the value is a non-empty array or not. + * @param value The value to validate. + * @returns Whether the value is a non-empty array or not. */ export function isNonEmptyArray(value: any): value is T[] { return isArray(value) && value.length !== 0; @@ -51,8 +51,8 @@ export function isNonEmptyArray(value: any): value is T[] { /** * Validates that a value is a boolean. * - * @param {any} value The value to validate. - * @return {boolean} Whether the value is a boolean or not. + * @param value The value to validate. + * @returns Whether the value is a boolean or not. */ export function isBoolean(value: any): boolean { return typeof value === 'boolean'; @@ -62,8 +62,8 @@ export function isBoolean(value: any): boolean { /** * Validates that a value is a number. * - * @param {any} value The value to validate. - * @return {boolean} Whether the value is a number or not. + * @param value The value to validate. + * @returns Whether the value is a number or not. */ export function isNumber(value: any): boolean { return typeof value === 'number' && !isNaN(value); @@ -73,8 +73,8 @@ export function isNumber(value: any): boolean { /** * Validates that a value is a string. * - * @param {any} value The value to validate. - * @return {boolean} Whether the value is a string or not. + * @param value The value to validate. + * @returns Whether the value is a string or not. */ export function isString(value: any): value is string { return typeof value === 'string'; @@ -84,8 +84,8 @@ export function isString(value: any): value is string { /** * Validates that a value is a base64 string. * - * @param {any} value The value to validate. - * @return {boolean} Whether the value is a base64 string or not. + * @param value The value to validate. + * @returns Whether the value is a base64 string or not. */ export function isBase64String(value: any): boolean { if (!isString(value)) { @@ -98,8 +98,8 @@ export function isBase64String(value: any): boolean { /** * Validates that a value is a non-empty string. * - * @param {any} value The value to validate. - * @return {boolean} Whether the value is a non-empty string or not. + * @param value The value to validate. + * @returns Whether the value is a non-empty string or not. */ export function isNonEmptyString(value: any): value is string { return isString(value) && value !== ''; @@ -109,8 +109,8 @@ export function isNonEmptyString(value: any): value is string { /** * Validates that a value is a nullable object. * - * @param {any} value The value to validate. - * @return {boolean} Whether the value is an object or not. + * @param value The value to validate. + * @returns Whether the value is an object or not. */ export function isObject(value: any): boolean { return typeof value === 'object' && !isArray(value); @@ -120,8 +120,8 @@ export function isObject(value: any): boolean { /** * Validates that a value is a non-null object. * - * @param {any} value The value to validate. - * @return {boolean} Whether the value is a non-null object or not. + * @param value The value to validate. + * @returns Whether the value is a non-null object or not. */ export function isNonNullObject(value: T | null | undefined): value is T { return isObject(value) && value !== null; @@ -131,8 +131,8 @@ export function isNonNullObject(value: T | null | undefined): value is T { /** * Validates that a string is a valid Firebase Auth uid. * - * @param {any} uid The string to validate. - * @return {boolean} Whether the string is a valid Firebase Auth uid. + * @param uid The string to validate. + * @returns Whether the string is a valid Firebase Auth uid. */ export function isUid(uid: any): boolean { return typeof uid === 'string' && uid.length > 0 && uid.length <= 128; @@ -142,8 +142,8 @@ export function isUid(uid: any): boolean { /** * Validates that a string is a valid Firebase Auth password. * - * @param {any} password The password string to validate. - * @return {boolean} Whether the string is a valid Firebase Auth password. + * @param password The password string to validate. + * @returns Whether the string is a valid Firebase Auth password. */ export function isPassword(password: any): boolean { // A password must be a string of at least 6 characters. @@ -154,8 +154,8 @@ export function isPassword(password: any): boolean { /** * Validates that a string is a valid email. * - * @param {any} email The string to validate. - * @return {boolean} Whether the string is valid email or not. + * @param email The string to validate. + * @returns Whether the string is valid email or not. */ export function isEmail(email: any): boolean { if (typeof email !== 'string') { @@ -170,8 +170,8 @@ export function isEmail(email: any): boolean { /** * Validates that a string is a valid phone number. * - * @param {any} phoneNumber The string to validate. - * @return {boolean} Whether the string is a valid phone number or not. + * @param phoneNumber The string to validate. + * @returns Whether the string is a valid phone number or not. */ export function isPhoneNumber(phoneNumber: any): boolean { if (typeof phoneNumber !== 'string') { @@ -190,7 +190,7 @@ export function isPhoneNumber(phoneNumber: any): boolean { * Validates that a string is a valid ISO date string. * * @param dateString The string to validate. - * @return Whether the string is a valid ISO date string. + * @returns Whether the string is a valid ISO date string. */ export function isISODateString(dateString: any): boolean { try { @@ -206,7 +206,7 @@ export function isISODateString(dateString: any): boolean { * Validates that a string is a valid UTC date string. * * @param dateString The string to validate. - * @return Whether the string is a valid UTC date string. + * @returns Whether the string is a valid UTC date string. */ export function isUTCDateString(dateString: any): boolean { try { @@ -221,8 +221,8 @@ export function isUTCDateString(dateString: any): boolean { /** * Validates that a string is a valid web URL. * - * @param {any} urlStr The string to validate. - * @return {boolean} Whether the string is valid web URL or not. + * @param urlStr The string to validate. + * @returns Whether the string is valid web URL or not. */ export function isURL(urlStr: any): boolean { if (typeof urlStr !== 'string') { @@ -267,8 +267,8 @@ export function isURL(urlStr: any): boolean { /** * Validates that the provided topic is a valid FCM topic name. * - * @param {any} topic The topic to validate. - * @return {boolean} Whether the provided topic is a valid FCM topic name. + * @param topic The topic to validate. + * @returns Whether the provided topic is a valid FCM topic name. */ export function isTopic(topic: any): boolean { if (typeof topic !== 'string') { diff --git a/test/integration/app.spec.ts b/test/integration/app.spec.ts index cf2829878c..3470ae088a 100644 --- a/test/integration/app.spec.ts +++ b/test/integration/app.spec.ts @@ -15,6 +15,8 @@ */ import * as admin from '../../lib/index'; +import { App, deleteApp, getApp, initializeApp } from '../../lib/app/index'; +import { getAuth } from '../../lib/auth/index'; import { expect } from 'chai'; import { defaultApp, nullApp, nonNullApp, databaseUrl, projectId, storageBucket, @@ -27,30 +29,56 @@ describe('admin', () => { expect(storageBucket).to.be.not.empty; }); - it('does not load RTDB by default', () => { - const firebaseRtdb = require.cache[require.resolve('@firebase/database-compat/standalone')]; - expect(firebaseRtdb).to.be.undefined; - const rtdbInternal = require.cache[require.resolve('../../lib/database/database-internal')]; - expect(rtdbInternal).to.be.undefined; - }); + describe('Dependency lazy loading', () => { + const tempCache: {[key: string]: any} = {}; + const dependencies = ['@firebase/database-compat/standalone', '@google-cloud/firestore']; + let lazyLoadingApp: App; - it('loads RTDB when calling admin.database', () => { - const rtdbNamespace = admin.database; - expect(rtdbNamespace).to.not.be.null; - const firebaseRtdb = require.cache[require.resolve('@firebase/database-compat/standalone')]; - expect(firebaseRtdb).to.not.be.undefined; - }); + before(() => { + // Unload dependencies if already loaded. Some of the other test files have imports + // to firebase-admin/database and firebase-admin/firestore, which cause the corresponding + // dependencies to get loaded before the tests are executed. + dependencies.forEach((name) => { + const resolvedName = require.resolve(name); + tempCache[name] = require.cache[resolvedName]; + delete require.cache[resolvedName]; + }); - it('does not load Firestore by default', () => { - const gcloud = require.cache[require.resolve('@google-cloud/firestore')]; - expect(gcloud).to.be.undefined; - }); + // Initialize the SDK + lazyLoadingApp = initializeApp(defaultApp.options, 'lazyLoadingApp'); + }); + + it('does not load RTDB by default', () => { + const firebaseRtdb = require.cache[require.resolve('@firebase/database-compat/standalone')]; + expect(firebaseRtdb).to.be.undefined; + }); + + it('loads RTDB when calling admin.database', () => { + const rtdbNamespace = admin.database; + expect(rtdbNamespace).to.not.be.null; + const firebaseRtdb = require.cache[require.resolve('@firebase/database-compat/standalone')]; + expect(firebaseRtdb).to.not.be.undefined; + }); - it('loads Firestore when calling admin.firestore', () => { - const firestoreNamespace = admin.firestore; - expect(firestoreNamespace).to.not.be.null; - const gcloud = require.cache[require.resolve('@google-cloud/firestore')]; - expect(gcloud).to.not.be.undefined; + it('does not load Firestore by default', () => { + const gcloud = require.cache[require.resolve('@google-cloud/firestore')]; + expect(gcloud).to.be.undefined; + }); + + it('loads Firestore when calling admin.firestore', () => { + const firestoreNamespace = admin.firestore; + expect(firestoreNamespace).to.not.be.null; + const gcloud = require.cache[require.resolve('@google-cloud/firestore')]; + expect(gcloud).to.not.be.undefined; + }); + + after(() => { + dependencies.forEach((name) => { + const resolvedName = require.resolve(name); + require.cache[resolvedName] = tempCache[name]; + }); + return deleteApp(lazyLoadingApp); + }) }); }); @@ -98,3 +126,42 @@ describe('admin.app', () => { expect(admin.storage(app).app).to.deep.equal(app); }); }); + +describe('getApp', () => { + it('getApp() returns the default App', () => { + const app = getApp(); + expect(app).to.deep.equal(defaultApp); + expect(app.name).to.equal('[DEFAULT]'); + expect(app.options.databaseURL).to.equal(databaseUrl); + expect(app.options.databaseAuthVariableOverride).to.be.undefined; + expect(app.options.storageBucket).to.equal(storageBucket); + }); + + it('getApp("null") returns the App named "null"', () => { + const app = getApp('null'); + expect(app).to.deep.equal(nullApp); + expect(app.name).to.equal('null'); + expect(app.options.databaseURL).to.equal(databaseUrl); + expect(app.options.databaseAuthVariableOverride).to.be.null; + expect(app.options.storageBucket).to.equal(storageBucket); + }); + + it('getApp("nonNull") returns the App named "nonNull"', () => { + const app = getApp('nonNull'); + expect(app).to.deep.equal(nonNullApp); + expect(app.name).to.equal('nonNull'); + expect(app.options.databaseURL).to.equal(databaseUrl); + expect((app.options.databaseAuthVariableOverride as any).uid).to.be.ok; + expect(app.options.storageBucket).to.equal(storageBucket); + }); + + it('namespace services are attached to the default App', () => { + const app = getApp(); + expect(getAuth(app).app).to.deep.equal(app); + }); + + it('namespace services are attached to the named App', () => { + const app = getApp('null'); + expect(getAuth(app).app).to.deep.equal(app); + }); +}); diff --git a/test/integration/auth.spec.ts b/test/integration/auth.spec.ts index 2cf7780cd5..946d43e34b 100644 --- a/test/integration/auth.spec.ts +++ b/test/integration/auth.spec.ts @@ -14,21 +14,25 @@ * limitations under the License. */ -import * as admin from '../../lib/index'; -import * as chai from 'chai'; -import * as chaiAsPromised from 'chai-as-promised'; +import url = require('url'); import * as crypto from 'crypto'; import * as bcrypt from 'bcrypt'; +import * as chai from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; import firebase from '@firebase/app-compat'; import '@firebase/auth-compat'; import { clone } from 'lodash'; +import { User, FirebaseAuth } from '@firebase/auth-types'; import { generateRandomString, projectId, apiKey, noServiceAccountApp, cmdArgs, } from './setup'; -import url = require('url'); import * as mocks from '../resources/mocks'; import { deepExtend, deepCopy } from '../../src/utils/deep-copy'; -import { User, FirebaseAuth } from '@firebase/auth-types'; +import { + AuthProviderConfig, CreateTenantRequest, DeleteUsersResult, PhoneMultiFactorInfo, + TenantAwareAuth, UpdatePhoneMultiFactorInfoRequest, UpdateTenantRequest, UserImportOptions, + UserImportRecord, UserRecord, getAuth, +} from '../../lib/auth/index'; const chalk = require('chalk'); // eslint-disable-line @typescript-eslint/no-var-requires @@ -74,7 +78,7 @@ let deleteQueue = Promise.resolve(); interface UserImportTest { name: string; - importOptions: admin.auth.UserImportOptions; + importOptions: UserImportOptions; rawPassword: string; rawSalt?: string; computePasswordHash(userImportTest: UserImportTest): Buffer; @@ -118,7 +122,7 @@ describe('admin.auth', () => { const newUserData = clone(mockUserData); newUserData.email = generateRandomString(20).toLowerCase() + '@example.com'; newUserData.phoneNumber = testPhoneNumber2; - return admin.auth().createUser(newUserData) + return getAuth().createUser(newUserData) .then((userRecord) => { uidFromCreateUserWithoutUid = userRecord.uid; expect(typeof userRecord.uid).to.equal('string'); @@ -132,7 +136,7 @@ describe('admin.auth', () => { it('createUser() creates a new user with the specified UID', () => { const newUserData: any = clone(mockUserData); newUserData.uid = newUserUid; - return admin.auth().createUser(newUserData) + return getAuth().createUser(newUserData) .then((userRecord) => { expect(userRecord.uid).to.equal(newUserUid); // Confirm expected email. @@ -167,7 +171,7 @@ describe('admin.auth', () => { enrolledFactors, }, }; - return admin.auth().createUser(newUserData) + return getAuth().createUser(newUserData) .then((userRecord) => { expect(userRecord.uid).to.equal(newMultiFactorUserUid); // Confirm expected email. @@ -178,7 +182,7 @@ describe('admin.auth', () => { const firstMultiFactor = userRecord.multiFactor!.enrolledFactors[0]; expect(firstMultiFactor.uid).not.to.be.undefined; expect(firstMultiFactor.enrollmentTime).not.to.be.undefined; - expect((firstMultiFactor as admin.auth.PhoneMultiFactorInfo).phoneNumber).to.equal( + expect((firstMultiFactor as PhoneMultiFactorInfo).phoneNumber).to.equal( enrolledFactors[0].phoneNumber); expect(firstMultiFactor.displayName).to.equal(enrolledFactors[0].displayName); expect(firstMultiFactor.factorId).to.equal(enrolledFactors[0].factorId); @@ -186,7 +190,7 @@ describe('admin.auth', () => { const secondMultiFactor = userRecord.multiFactor!.enrolledFactors[1]; expect(secondMultiFactor.uid).not.to.be.undefined; expect(secondMultiFactor.enrollmentTime).not.to.be.undefined; - expect((secondMultiFactor as admin.auth.PhoneMultiFactorInfo).phoneNumber).to.equal( + expect((secondMultiFactor as PhoneMultiFactorInfo).phoneNumber).to.equal( enrolledFactors[1].phoneNumber); expect(secondMultiFactor.displayName).to.equal(enrolledFactors[1].displayName); expect(secondMultiFactor.factorId).to.equal(enrolledFactors[1].factorId); @@ -196,26 +200,26 @@ describe('admin.auth', () => { it('createUser() fails when the UID is already in use', () => { const newUserData: any = clone(mockUserData); newUserData.uid = newUserUid; - return admin.auth().createUser(newUserData) + return getAuth().createUser(newUserData) .should.eventually.be.rejected.and.have.property('code', 'auth/uid-already-exists'); }); it('getUser() returns a user record with the matching UID', () => { - return admin.auth().getUser(newUserUid) + return getAuth().getUser(newUserUid) .then((userRecord) => { expect(userRecord.uid).to.equal(newUserUid); }); }); it('getUserByEmail() returns a user record with the matching email', () => { - return admin.auth().getUserByEmail(mockUserData.email) + return getAuth().getUserByEmail(mockUserData.email) .then((userRecord) => { expect(userRecord.uid).to.equal(newUserUid); }); }); it('getUserByPhoneNumber() returns a user record with the matching phone number', () => { - return admin.auth().getUserByPhoneNumber(mockUserData.phoneNumber) + return getAuth().getUserByPhoneNumber(mockUserData.phoneNumber) .then((userRecord) => { expect(userRecord.uid).to.equal(newUserUid); }); @@ -225,7 +229,7 @@ describe('admin.auth', () => { // TODO(rsgowman): Once we can link a provider id with a user, just do that // here instead of creating a new user. const randomUid = 'import_' + generateRandomString(20).toLowerCase(); - const importUser: admin.auth.UserImportRecord = { + const importUser: UserImportRecord = { uid: randomUid, email: 'user@example.com', phoneNumber: '+15555550000', @@ -245,10 +249,10 @@ describe('admin.auth', () => { }], }; - await admin.auth().importUsers([importUser]); + await getAuth().importUsers([importUser]); try { - await admin.auth().getUserByProviderUid('google.com', 'google_uid') + await getAuth().getUserByProviderUid('google.com', 'google_uid') .then((userRecord) => { expect(userRecord.uid).to.equal(importUser.uid); }); @@ -258,14 +262,164 @@ describe('admin.auth', () => { }); it('getUserByProviderUid() redirects to getUserByEmail if given an email', () => { - return admin.auth().getUserByProviderUid('email', mockUserData.email) + return getAuth().getUserByProviderUid('email', mockUserData.email) .then((userRecord) => { expect(userRecord.uid).to.equal(newUserUid); }); }); it('getUserByProviderUid() redirects to getUserByPhoneNumber if given a phone number', () => { - return admin.auth().getUserByProviderUid('phone', mockUserData.phoneNumber) + return getAuth().getUserByProviderUid('phone', mockUserData.phoneNumber) + .then((userRecord) => { + expect(userRecord.uid).to.equal(newUserUid); + }); + }); + + it('getUserByProviderUid() returns a user record with the matching provider id', async () => { + // TODO(rsgowman): Once we can link a provider id with a user, just do that + // here instead of creating a new user. + const randomUid = 'import_' + generateRandomString(20).toLowerCase(); + const importUser: UserImportRecord = { + uid: randomUid, + email: 'user@example.com', + phoneNumber: '+15555550000', + emailVerified: true, + disabled: false, + metadata: { + lastSignInTime: 'Thu, 01 Jan 1970 00:00:00 UTC', + creationTime: 'Thu, 01 Jan 1970 00:00:00 UTC', + }, + providerData: [{ + displayName: 'User Name', + email: 'user@example.com', + phoneNumber: '+15555550000', + photoURL: 'http://example.com/user', + providerId: 'google.com', + uid: 'google_uid', + }], + }; + + await getAuth().importUsers([importUser]); + + try { + await getAuth().getUserByProviderUid('google.com', 'google_uid') + .then((userRecord) => { + expect(userRecord.uid).to.equal(importUser.uid); + }); + } finally { + await safeDelete(importUser.uid); + } + }); + + it('getUserByProviderUid() redirects to getUserByEmail if given an email', () => { + return getAuth().getUserByProviderUid('email', mockUserData.email) + .then((userRecord) => { + expect(userRecord.uid).to.equal(newUserUid); + }); + }); + + it('getUserByProviderUid() redirects to getUserByPhoneNumber if given a phone number', () => { + return getAuth().getUserByProviderUid('phone', mockUserData.phoneNumber) + .then((userRecord) => { + expect(userRecord.uid).to.equal(newUserUid); + }); + }); + + it('getUserByProviderUid() returns a user record with the matching provider id', async () => { + // TODO(rsgowman): Once we can link a provider id with a user, just do that + // here instead of creating a new user. + const randomUid = 'import_' + generateRandomString(20).toLowerCase(); + const importUser: UserImportRecord = { + uid: randomUid, + email: 'user@example.com', + phoneNumber: '+15555550000', + emailVerified: true, + disabled: false, + metadata: { + lastSignInTime: 'Thu, 01 Jan 1970 00:00:00 UTC', + creationTime: 'Thu, 01 Jan 1970 00:00:00 UTC', + }, + providerData: [{ + displayName: 'User Name', + email: 'user@example.com', + phoneNumber: '+15555550000', + photoURL: 'http://example.com/user', + providerId: 'google.com', + uid: 'google_uid', + }], + }; + + await getAuth().importUsers([importUser]); + + try { + await getAuth().getUserByProviderUid('google.com', 'google_uid') + .then((userRecord) => { + expect(userRecord.uid).to.equal(importUser.uid); + }); + } finally { + await safeDelete(importUser.uid); + } + }); + + it('getUserByProviderUid() redirects to getUserByEmail if given an email', () => { + return getAuth().getUserByProviderUid('email', mockUserData.email) + .then((userRecord) => { + expect(userRecord.uid).to.equal(newUserUid); + }); + }); + + it('getUserByProviderUid() redirects to getUserByPhoneNumber if given a phone number', () => { + return getAuth().getUserByProviderUid('phone', mockUserData.phoneNumber) + .then((userRecord) => { + expect(userRecord.uid).to.equal(newUserUid); + }); + }); + + it('getUserByProviderUid() returns a user record with the matching provider id', async () => { + // TODO(rsgowman): Once we can link a provider id with a user, just do that + // here instead of creating a new user. + const randomUid = 'import_' + generateRandomString(20).toLowerCase(); + const importUser: UserImportRecord = { + uid: randomUid, + email: 'user@example.com', + phoneNumber: '+15555550000', + emailVerified: true, + disabled: false, + metadata: { + lastSignInTime: 'Thu, 01 Jan 1970 00:00:00 UTC', + creationTime: 'Thu, 01 Jan 1970 00:00:00 UTC', + }, + providerData: [{ + displayName: 'User Name', + email: 'user@example.com', + phoneNumber: '+15555550000', + photoURL: 'http://example.com/user', + providerId: 'google.com', + uid: 'google_uid', + }], + }; + + await getAuth().importUsers([importUser]); + + try { + await getAuth().getUserByProviderUid('google.com', 'google_uid') + .then((userRecord) => { + expect(userRecord.uid).to.equal(importUser.uid); + }); + } finally { + await safeDelete(importUser.uid); + } + }); + + it('getUserByProviderUid() redirects to getUserByEmail if given an email', () => { + return getAuth().getUserByProviderUid('email', mockUserData.email) + .then((userRecord) => { + expect(userRecord.uid).to.equal(newUserUid); + }); + }); + + it('getUserByProviderUid() redirects to getUserByPhoneNumber if given a phone number', () => { + return getAuth().getUserByProviderUid('phone', mockUserData.phoneNumber) .then((userRecord) => { expect(userRecord.uid).to.equal(newUserUid); }); @@ -290,7 +444,7 @@ describe('admin.auth', () => { // Also create a user with a provider config. (You can't create a user with // a provider config. But you *can* import one.) - const importUser1: admin.auth.UserImportRecord = { + const importUser1: UserImportRecord = { uid: 'uid4', email: 'user4@example.com', phoneNumber: '+15555550004', @@ -320,8 +474,8 @@ describe('admin.auth', () => { await deleteUsersWithDelay(uidsToDelete); // Create/import users required by these tests - await Promise.all(usersToCreate.map((user) => admin.auth().createUser(user))); - await admin.auth().importUsers([importUser1]); + await Promise.all(usersToCreate.map((user) => getAuth().createUser(user))); + await getAuth().importUsers([importUser1]); }); after(async () => { @@ -331,7 +485,7 @@ describe('admin.auth', () => { }); it('returns users by various identifier types in a single call', async () => { - const users = await admin.auth().getUsers([ + const users = await getAuth().getUsers([ { uid: 'uid1' }, { email: 'user2@example.com' }, { phoneNumber: '+15555550003' }, @@ -344,7 +498,7 @@ describe('admin.auth', () => { }); it('returns found users and ignores non-existing users', async () => { - const users = await admin.auth().getUsers([ + const users = await getAuth().getUsers([ { uid: 'uid1' }, { uid: 'uid_that_doesnt_exist' }, { uid: 'uid3' }, @@ -357,14 +511,14 @@ describe('admin.auth', () => { it('returns nothing when queried for only non-existing users', async () => { const notFoundIds = [{ uid: 'non-existing user' }]; - const users = await admin.auth().getUsers(notFoundIds); + const users = await getAuth().getUsers(notFoundIds); expect(users.users).to.be.empty; expect(users.notFound).to.deep.equal(notFoundIds); }); it('de-dups duplicate users', async () => { - const users = await admin.auth().getUsers([ + const users = await getAuth().getUsers([ { uid: 'uid1' }, { uid: 'uid1' }, ]) @@ -379,7 +533,7 @@ describe('admin.auth', () => { return new Date(s).toUTCString() === s; }; - const newUserRecord = await admin.auth().createUser({ + const newUserRecord = await getAuth().createUser({ uid: 'lastRefreshTimeUser', email: 'lastRefreshTimeUser@example.com', password: 'p4ssword', @@ -399,7 +553,7 @@ describe('admin.auth', () => { let userRecord = null; for (let i = 0; i < 3; i++) { - userRecord = await admin.auth().getUser('lastRefreshTimeUser'); + userRecord = await getAuth().getUser('lastRefreshTimeUser'); if (userRecord.metadata.lastRefreshTime) { break; } @@ -418,25 +572,25 @@ describe('admin.auth', () => { expect(lastRefreshTime).lte(creationTime + 3600 * 1000); }); } finally { - admin.auth().deleteUser('lastRefreshTimeUser'); + getAuth().deleteUser('lastRefreshTimeUser'); } }); }); it('listUsers() returns up to the specified number of users', () => { - const promises: Array> = []; + const promises: Array> = []; uids.forEach((uid) => { const tempUserData = { uid, password: 'password', }; - promises.push(admin.auth().createUser(tempUserData)); + promises.push(getAuth().createUser(tempUserData)); }); return Promise.all(promises) .then(() => { // Return 2 users with the provided page token. // This test will fail if other users are created in between. - return admin.auth().listUsers(2, uids[0]); + return getAuth().listUsers(2, uids[0]); }) .then((listUsersResult) => { // Confirm expected number of users. @@ -482,16 +636,16 @@ describe('admin.auth', () => { .then((idToken) => { currentIdToken = idToken; // Verify that user's ID token while checking for revocation. - return admin.auth().verifyIdToken(currentIdToken, true); + return getAuth().verifyIdToken(currentIdToken, true); }) .then((decodedIdToken) => { // Verification should succeed. Revoke that user's session. return new Promise((resolve) => setTimeout(() => resolve( - admin.auth().revokeRefreshTokens(decodedIdToken.sub), + getAuth().revokeRefreshTokens(decodedIdToken.sub), ), 1000)); }) .then(() => { - const verifyingIdToken = admin.auth().verifyIdToken(currentIdToken) + const verifyingIdToken = getAuth().verifyIdToken(currentIdToken) if (authEmulatorHost) { // Check revocation is forced in emulator-mode and this should throw. return verifyingIdToken.should.eventually.be.rejected; @@ -502,7 +656,7 @@ describe('admin.auth', () => { }) .then(() => { // verifyIdToken while checking for revocation should fail. - return admin.auth().verifyIdToken(currentIdToken, true) + return getAuth().verifyIdToken(currentIdToken, true) .should.eventually.be.rejected.and.have.property('code', 'auth/id-token-revoked'); }) .then(() => { @@ -522,16 +676,16 @@ describe('admin.auth', () => { }) .then((idToken) => { // ID token for new session should be valid even with revocation check. - return admin.auth().verifyIdToken(idToken, true) + return getAuth().verifyIdToken(idToken, true) .should.eventually.be.fulfilled; }); }); it('setCustomUserClaims() sets claims that are accessible via user\'s ID token', () => { // Set custom claims on the user. - return admin.auth().setCustomUserClaims(newUserUid, customClaims) + return getAuth().setCustomUserClaims(newUserUid, customClaims) .then(() => { - return admin.auth().getUser(newUserUid); + return getAuth().getUser(newUserUid); }) .then((userRecord) => { // Confirm custom claims set on the UserRecord. @@ -547,7 +701,7 @@ describe('admin.auth', () => { }) .then((idToken) => { // Verify ID token contents. - return admin.auth().verifyIdToken(idToken); + return getAuth().verifyIdToken(idToken); }) .then((decodedIdToken: {[key: string]: any}) => { // Confirm expected claims set on the user's ID token. @@ -557,10 +711,10 @@ describe('admin.auth', () => { } } // Test clearing of custom claims. - return admin.auth().setCustomUserClaims(newUserUid, null); + return getAuth().setCustomUserClaims(newUserUid, null); }) .then(() => { - return admin.auth().getUser(newUserUid); + return getAuth().getUser(newUserUid); }) .then((userRecord) => { // Custom claims should be cleared. @@ -571,7 +725,7 @@ describe('admin.auth', () => { }) .then((idToken) => { // Verify ID token contents. - return admin.auth().verifyIdToken(idToken); + return getAuth().verifyIdToken(idToken); }) .then((decodedIdToken: {[key: string]: any}) => { // Confirm all custom claims are cleared. @@ -590,16 +744,16 @@ describe('admin.auth', () => { * '$name_$tenRandomChars@example.com'. */ // TODO(rsgowman): This function could usefully be employed throughout this file. - function createTestUser(name: string): Promise { + function createTestUser(name: string): Promise { const tenRandomChars = generateRandomString(10); - return admin.auth().createUser({ + return getAuth().createUser({ uid: name + '_' + tenRandomChars, displayName: name, email: name + '_' + tenRandomChars + '@example.com', }); } - let updateUser: admin.auth.UserRecord; + let updateUser: UserRecord; before(async () => { updateUser = await createTestUser('UpdateUser'); }); @@ -610,7 +764,7 @@ describe('admin.auth', () => { it('updates the user record with the given parameters', () => { const updatedDisplayName = 'Updated User ' + updateUser.uid; - return admin.auth().updateUser(updateUser.uid, { + return getAuth().updateUser(updateUser.uid, { email: updatedEmail, phoneNumber: updatedPhone, emailVerified: true, @@ -649,7 +803,7 @@ describe('admin.auth', () => { enrollmentTime: now, }, ]; - return admin.auth().updateUser(updateUser.uid, { + return getAuth().updateUser(updateUser.uid, { multiFactor: { enrolledFactors, }, @@ -660,7 +814,7 @@ describe('admin.auth', () => { expect(actualUserRecord.multiFactor.enrolledFactors.length).to.equal(2); expect(actualUserRecord.multiFactor.enrolledFactors).to.deep.equal(enrolledFactors); // Update list of second factors. - return admin.auth().updateUser(updateUser.uid, { + return getAuth().updateUser(updateUser.uid, { multiFactor: { enrolledFactors: [enrolledFactors[0]], }, @@ -671,7 +825,7 @@ describe('admin.auth', () => { const actualUserRecord: {[key: string]: any} = userRecord.toJSON(); expect(actualUserRecord.multiFactor.enrolledFactors[0]).to.deep.equal(enrolledFactors[0]); // Remove all second factors. - return admin.auth().updateUser(updateUser.uid, { + return getAuth().updateUser(updateUser.uid, { multiFactor: { enrolledFactors: null, }, @@ -688,7 +842,7 @@ describe('admin.auth', () => { return this.skip(); // Not yet supported in Auth Emulator. } const googleFederatedUid = 'google_uid_' + generateRandomString(10); - let userRecord = await admin.auth().updateUser(updateUser.uid, { + let userRecord = await getAuth().updateUser(updateUser.uid, { providerToLink: { providerId: 'google.com', uid: googleFederatedUid, @@ -700,7 +854,7 @@ describe('admin.auth', () => { expect(providerUids).to.deep.include(googleFederatedUid); expect(providerIds).to.deep.include('google.com'); - userRecord = await admin.auth().updateUser(updateUser.uid, { + userRecord = await getAuth().updateUser(updateUser.uid, { providersToUnlink: ['google.com'], }); @@ -719,14 +873,14 @@ describe('admin.auth', () => { const googleFederatedUid = 'google_uid_' + generateRandomString(10); const facebookFederatedUid = 'facebook_uid_' + generateRandomString(10); - let userRecord = await admin.auth().updateUser(updateUser.uid, { + let userRecord = await getAuth().updateUser(updateUser.uid, { phoneNumber: '+15555550001', providerToLink: { providerId: 'google.com', uid: googleFederatedUid, }, }); - userRecord = await admin.auth().updateUser(updateUser.uid, { + userRecord = await getAuth().updateUser(updateUser.uid, { providerToLink: { providerId: 'facebook.com', uid: facebookFederatedUid, @@ -738,7 +892,7 @@ describe('admin.auth', () => { expect(providerUids).to.deep.include.members([googleFederatedUid, facebookFederatedUid, '+15555550001']); expect(providerIds).to.deep.include.members(['google.com', 'facebook.com', 'phone']); - userRecord = await admin.auth().updateUser(updateUser.uid, { + userRecord = await getAuth().updateUser(updateUser.uid, { providersToUnlink: ['google.com', 'facebook.com', 'phone'], }); @@ -751,7 +905,7 @@ describe('admin.auth', () => { it('noops successfully when given an empty providersToUnlink list', async () => { const userRecord = await createTestUser('NoopWithEmptyProvidersToDeleteUser'); try { - const updatedUserRecord = await admin.auth().updateUser(userRecord.uid, { + const updatedUserRecord = await getAuth().updateUser(userRecord.uid, { providersToUnlink: [], }); @@ -764,7 +918,7 @@ describe('admin.auth', () => { it('A user with user record disabled is unable to sign in', async () => { const password = 'password'; const email = 'updatedEmail@example.com'; - return admin.auth().updateUser(updateUser.uid, { disabled : true , password, email }) + return getAuth().updateUser(updateUser.uid, { disabled : true , password, email }) .then(() => { return clientAuth().signInWithEmailAndPassword(email, password); }) @@ -777,38 +931,58 @@ describe('admin.auth', () => { }); it('getUser() fails when called with a non-existing UID', () => { - return admin.auth().getUser(nonexistentUid) + return getAuth().getUser(nonexistentUid) .should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); }); it('getUserByEmail() fails when called with a non-existing email', () => { - return admin.auth().getUserByEmail(nonexistentUid + '@example.com') + return getAuth().getUserByEmail(nonexistentUid + '@example.com') .should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); }); it('getUserByPhoneNumber() fails when called with a non-existing phone number', () => { - return admin.auth().getUserByPhoneNumber(nonexistentPhoneNumber) + return getAuth().getUserByPhoneNumber(nonexistentPhoneNumber) + .should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); + }); + + it('getUserByProviderUid() fails when called with a non-existing provider id', () => { + return getAuth().getUserByProviderUid('google.com', nonexistentUid) + .should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); + }); + + it('getUserByProviderUid() fails when called with a non-existing provider id', () => { + return getAuth().getUserByProviderUid('google.com', nonexistentUid) + .should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); + }); + + it('getUserByProviderUid() fails when called with a non-existing provider id', () => { + return getAuth().getUserByProviderUid('google.com', nonexistentUid) .should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); }); it('getUserByProviderUid() fails when called with a non-existing provider id', () => { - return admin.auth().getUserByProviderUid('google.com', nonexistentUid) + return getAuth().getUserByProviderUid('google.com', nonexistentUid) + .should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); + }); + + it('getUserByProviderUid() fails when called with a non-existing provider id', () => { + return getAuth().getUserByProviderUid('google.com', nonexistentUid) .should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); }); it('updateUser() fails when called with a non-existing UID', () => { - return admin.auth().updateUser(nonexistentUid, { + return getAuth().updateUser(nonexistentUid, { emailVerified: true, }).should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); }); it('deleteUser() fails when called with a non-existing UID', () => { - return admin.auth().deleteUser(nonexistentUid) + return getAuth().deleteUser(nonexistentUid) .should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); }); it('createCustomToken() mints a JWT that can be used to sign in', () => { - return admin.auth().createCustomToken(newUserUid, { + return getAuth().createCustomToken(newUserUid, { isAdmin: true, }) .then((customToken) => { @@ -819,7 +993,7 @@ describe('admin.auth', () => { return user!.getIdToken(); }) .then((idToken) => { - return admin.auth().verifyIdToken(idToken); + return getAuth().verifyIdToken(idToken); }) .then((token) => { expect(token.uid).to.equal(newUserUid); @@ -828,7 +1002,7 @@ describe('admin.auth', () => { }); it('createCustomToken() can mint JWTs without a service account', () => { - return admin.auth(noServiceAccountApp).createCustomToken(newUserUid, { + return getAuth(noServiceAccountApp).createCustomToken(newUserUid, { isAdmin: true, }) .then((customToken) => { @@ -839,7 +1013,7 @@ describe('admin.auth', () => { return user!.getIdToken(); }) .then((idToken) => { - return admin.auth(noServiceAccountApp).verifyIdToken(idToken); + return getAuth(noServiceAccountApp).verifyIdToken(idToken); }) .then((token) => { expect(token.uid).to.equal(newUserUid); @@ -847,113 +1021,53 @@ describe('admin.auth', () => { }); }); - describe('verifyIdToken()', () => { - const uid = generateRandomString(20).toLowerCase(); - const email = uid + '@example.com'; - const password = 'password'; - const userData = { - uid, - email, - emailVerified: false, - password, - }; - - // Create the test user before running this suite of tests. - before(() => { - return admin.auth().createUser(userData); - }); - - // Sign out after each test. - afterEach(() => { - return clientAuth().signOut(); - }); - - after(() => { - return safeDelete(uid); - }); - - it('verifyIdToken() fails when called with an invalid token', () => { - return admin.auth().verifyIdToken('invalid-token') - .should.eventually.be.rejected.and.have.property('code', 'auth/argument-error'); - }); - - it('verifyIdToken() fails with checkRevoked set to true and corresponding user disabled', async () => { - const { user } = await clientAuth().signInWithEmailAndPassword(email, password); - expect(user).to.exist; - expect(user!.email).to.equal(email); - - const idToken = await user!.getIdToken(); - let decodedIdToken = await admin.auth().verifyIdToken(idToken, true); - expect(decodedIdToken.uid).to.equal(uid); - expect(decodedIdToken.email).to.equal(email); - - const userRecord = await admin.auth().updateUser(uid, { disabled: true }); - expect(userRecord.uid).to.equal(uid); - expect(userRecord.email).to.equal(email); - expect(userRecord.disabled).to.equal(true); - - try { - // If it is in emulator mode, a user-disabled error will be thrown. - decodedIdToken = await admin.auth().verifyIdToken(idToken, false); - expect(decodedIdToken.uid).to.equal(uid); - } catch (error) { - if (authEmulatorHost) { - expect(error).to.have.property('code', 'auth/user-disabled'); - } else { - throw error; - } - } - - try { - await admin.auth().verifyIdToken(idToken, true); - } catch (error) { - expect(error).to.have.property('code', 'auth/user-disabled'); - } - }); + it('verifyIdToken() fails when called with an invalid token', () => { + return getAuth().verifyIdToken('invalid-token') + .should.eventually.be.rejected.and.have.property('code', 'auth/argument-error'); + }); - if (authEmulatorHost) { - describe('Auth emulator support', () => { - const uid = 'authEmulatorUser'; - before(() => { - return admin.auth().createUser({ - uid, - email: 'lastRefreshTimeUser@example.com', - password: 'p4ssword', - }); - }); - after(() => { - return admin.auth().deleteUser(uid); + if (authEmulatorHost) { + describe('Auth emulator support', () => { + const uid = 'authEmulatorUser'; + before(() => { + return getAuth().createUser({ + uid, + email: 'lastRefreshTimeUser@example.com', + password: 'p4ssword', }); + }); + after(() => { + return getAuth().deleteUser(uid); + }); - it('verifyIdToken() succeeds when called with an unsigned token', () => { - const unsignedToken = mocks.generateIdToken({ - algorithm: 'none', - audience: projectId, - issuer: 'https://securetoken.google.com/' + projectId, - subject: uid, - }); - return admin.auth().verifyIdToken(unsignedToken); + it('verifyIdToken() succeeds when called with an unsigned token', () => { + const unsignedToken = mocks.generateIdToken({ + algorithm: 'none', + audience: projectId, + issuer: 'https://securetoken.google.com/' + projectId, + subject: uid, }); + return getAuth().verifyIdToken(unsignedToken); + }); - it('verifyIdToken() fails when called with a token with wrong project', () => { - const unsignedToken = mocks.generateIdToken({ algorithm: 'none', audience: 'nosuch' }); - return admin.auth().verifyIdToken(unsignedToken) - .should.eventually.be.rejected.and.have.property('code', 'auth/argument-error'); - }); + it('verifyIdToken() fails when called with a token with wrong project', () => { + const unsignedToken = mocks.generateIdToken({ algorithm: 'none', audience: 'nosuch' }); + return getAuth().verifyIdToken(unsignedToken) + .should.eventually.be.rejected.and.have.property('code', 'auth/argument-error'); + }); - it('verifyIdToken() fails when called with a token that does not belong to a user', () => { - const unsignedToken = mocks.generateIdToken({ - algorithm: 'none', - audience: projectId, - issuer: 'https://securetoken.google.com/' + projectId, - subject: 'nosuch', - }); - return admin.auth().verifyIdToken(unsignedToken) - .should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); + it('verifyIdToken() fails when called with a token that does not belong to a user', () => { + const unsignedToken = mocks.generateIdToken({ + algorithm: 'none', + audience: projectId, + issuer: 'https://securetoken.google.com/' + projectId, + subject: 'nosuch', }); + return getAuth().verifyIdToken(unsignedToken) + .should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); }); - } - }); + }); + } describe('Link operations', () => { const uid = generateRandomString(20).toLowerCase(); @@ -968,7 +1082,7 @@ describe('admin.auth', () => { // Create the test user before running this suite of tests. before(() => { - return admin.auth().createUser(userData); + return getAuth().createUser(userData); }); // Sign out after each test. @@ -983,9 +1097,9 @@ describe('admin.auth', () => { it('generatePasswordResetLink() should return a password reset link', () => { // Ensure old password set on created user. - return admin.auth().updateUser(uid, { password: 'password' }) + return getAuth().updateUser(uid, { password: 'password' }) .then(() => { - return admin.auth().generatePasswordResetLink(email, actionCodeSettings); + return getAuth().generatePasswordResetLink(email, actionCodeSettings); }) .then((link) => { const code = getActionCode(link); @@ -1005,10 +1119,10 @@ describe('admin.auth', () => { it('generateEmailVerificationLink() should return a verification link', () => { // Ensure the user's email is unverified. - return admin.auth().updateUser(uid, { password: 'password', emailVerified: false }) + return getAuth().updateUser(uid, { password: 'password', emailVerified: false }) .then((userRecord) => { expect(userRecord.emailVerified).to.be.false; - return admin.auth().generateEmailVerificationLink(email, actionCodeSettings); + return getAuth().generateEmailVerificationLink(email, actionCodeSettings); }) .then((link) => { const code = getActionCode(link); @@ -1026,7 +1140,7 @@ describe('admin.auth', () => { }); it('generateSignInWithEmailLink() should return a sign-in link', () => { - return admin.auth().generateSignInWithEmailLink(email, actionCodeSettings) + return getAuth().generateSignInWithEmailLink(email, actionCodeSettings) .then((link) => { expect(getContinueUrl(link)).equal(actionCodeSettings.url); return clientAuth().signInWithEmailLink(email, link); @@ -1042,7 +1156,7 @@ describe('admin.auth', () => { describe('Tenant management operations', () => { let createdTenantId: string; const createdTenants: string[] = []; - const tenantOptions: admin.auth.CreateTenantRequest = { + const tenantOptions: CreateTenantRequest = { displayName: 'testTenant1', emailSignInConfig: { enabled: true, @@ -1122,14 +1236,14 @@ describe('admin.auth', () => { const promises: Array> = []; createdTenants.forEach((tenantId) => { promises.push( - admin.auth().tenantManager().deleteTenant(tenantId) + getAuth().tenantManager().deleteTenant(tenantId) .catch(() => {/** Ignore. */})); }); return Promise.all(promises); }); it('createTenant() should resolve with a new tenant', () => { - return admin.auth().tenantManager().createTenant(tenantOptions) + return getAuth().tenantManager().createTenant(tenantOptions) .then((actualTenant) => { createdTenantId = actualTenant.tenantId; createdTenants.push(createdTenantId); @@ -1139,7 +1253,7 @@ describe('admin.auth', () => { }); it('createTenant() can enable anonymous users', async () => { - const tenant = await admin.auth().tenantManager().createTenant({ + const tenant = await getAuth().tenantManager().createTenant({ displayName: 'testTenantWithAnon', emailSignInConfig: { enabled: false, @@ -1155,7 +1269,7 @@ describe('admin.auth', () => { // Sanity check user management + email link generation + custom attribute APIs. // TODO: Confirm behavior in client SDK when it starts supporting it. describe('supports user management, email link generation, custom attribute and token revocation APIs', () => { - let tenantAwareAuth: admin.auth.TenantAwareAuth; + let tenantAwareAuth: TenantAwareAuth; let createdUserUid: string; let lastValidSinceTime: number; const newUserData = clone(mockUserData); @@ -1174,7 +1288,7 @@ describe('admin.auth', () => { if (!createdTenantId) { this.skip(); } else { - tenantAwareAuth = admin.auth().tenantManager().authForTenant(createdTenantId); + tenantAwareAuth = getAuth().tenantManager().authForTenant(createdTenantId); } }); @@ -1335,7 +1449,7 @@ describe('admin.auth', () => { // Sanity check OIDC/SAML config management API. describe('SAML management APIs', () => { - let tenantAwareAuth: admin.auth.TenantAwareAuth; + let tenantAwareAuth: TenantAwareAuth; const authProviderConfig = { providerId: randomSamlProviderId(), displayName: 'SAML_DISPLAY_NAME1', @@ -1362,7 +1476,7 @@ describe('admin.auth', () => { if (!createdTenantId) { this.skip(); } else { - tenantAwareAuth = admin.auth().tenantManager().authForTenant(createdTenantId); + tenantAwareAuth = getAuth().tenantManager().authForTenant(createdTenantId); } }); @@ -1401,7 +1515,7 @@ describe('admin.auth', () => { }); describe('OIDC management APIs', () => { - let tenantAwareAuth: admin.auth.TenantAwareAuth; + let tenantAwareAuth: TenantAwareAuth; const authProviderConfig = { providerId: randomOidcProviderId(), displayName: 'OIDC_DISPLAY_NAME1', @@ -1439,7 +1553,7 @@ describe('admin.auth', () => { if (!createdTenantId) { this.skip(); } else { - tenantAwareAuth = admin.auth().tenantManager().authForTenant(createdTenantId); + tenantAwareAuth = getAuth().tenantManager().authForTenant(createdTenantId); } }); @@ -1476,7 +1590,7 @@ describe('admin.auth', () => { }); it('getTenant() should resolve with expected tenant', () => { - return admin.auth().tenantManager().getTenant(createdTenantId) + return getAuth().tenantManager().getTenant(createdTenantId) .then((actualTenant) => { expect(actualTenant.toJSON()).to.deep.equal(expectedCreatedTenant); }); @@ -1485,7 +1599,7 @@ describe('admin.auth', () => { it('updateTenant() should resolve with the updated tenant', () => { expectedUpdatedTenant.tenantId = createdTenantId; expectedUpdatedTenant2.tenantId = createdTenantId; - const updatedOptions: admin.auth.UpdateTenantRequest = { + const updatedOptions: UpdateTenantRequest = { displayName: expectedUpdatedTenant.displayName, emailSignInConfig: { enabled: false, @@ -1493,7 +1607,7 @@ describe('admin.auth', () => { multiFactorConfig: deepCopy(expectedUpdatedTenant.multiFactorConfig), testPhoneNumbers: deepCopy(expectedUpdatedTenant.testPhoneNumbers), }; - const updatedOptions2: admin.auth.UpdateTenantRequest = { + const updatedOptions2: UpdateTenantRequest = { emailSignInConfig: { enabled: true, passwordRequired: false, @@ -1502,10 +1616,10 @@ describe('admin.auth', () => { // Test clearing of phone numbers. testPhoneNumbers: null, }; - return admin.auth().tenantManager().updateTenant(createdTenantId, updatedOptions) + return getAuth().tenantManager().updateTenant(createdTenantId, updatedOptions) .then((actualTenant) => { expect(actualTenant.toJSON()).to.deep.equal(expectedUpdatedTenant); - return admin.auth().tenantManager().updateTenant(createdTenantId, updatedOptions2); + return getAuth().tenantManager().updateTenant(createdTenantId, updatedOptions2); }) .then((actualTenant) => { expect(actualTenant.toJSON()).to.deep.equal(expectedUpdatedTenant2); @@ -1513,7 +1627,7 @@ describe('admin.auth', () => { }); it('updateTenant() should be able to enable/disable anon provider', async () => { - const tenantManager = admin.auth().tenantManager(); + const tenantManager = getAuth().tenantManager(); let tenant = await tenantManager.createTenant({ displayName: 'testTenantUpdateAnon', }); @@ -1536,7 +1650,7 @@ describe('admin.auth', () => { const tenantOptions2 = deepCopy(tenantOptions); tenantOptions2.displayName = 'testTenant2'; const listAllTenantIds = (tenantIds: string[], nextPageToken?: string): Promise => { - return admin.auth().tenantManager().listTenants(100, nextPageToken) + return getAuth().tenantManager().listTenants(100, nextPageToken) .then((result) => { result.tenants.forEach((tenant) => { tenantIds.push(tenant.tenantId); @@ -1546,7 +1660,7 @@ describe('admin.auth', () => { } }); }; - return admin.auth().tenantManager().createTenant(tenantOptions2) + return getAuth().tenantManager().createTenant(tenantOptions2) .then((actualTenant) => { createdTenants.push(actualTenant.tenantId); // Test listTenants returns the expected tenants. @@ -1561,9 +1675,9 @@ describe('admin.auth', () => { }); it('deleteTenant() should successfully delete the provided tenant', () => { - return admin.auth().tenantManager().deleteTenant(createdTenantId) + return getAuth().tenantManager().deleteTenant(createdTenantId) .then(() => { - return admin.auth().tenantManager().getTenant(createdTenantId); + return getAuth().tenantManager().getTenant(createdTenantId); }) .then(() => { throw new Error('unexpected success'); @@ -1600,8 +1714,8 @@ describe('admin.auth', () => { const removeTempConfigs = (): Promise => { return Promise.all([ - admin.auth().deleteProviderConfig(authProviderConfig1.providerId).catch(() => {/* empty */}), - admin.auth().deleteProviderConfig(authProviderConfig2.providerId).catch(() => {/* empty */}), + getAuth().deleteProviderConfig(authProviderConfig1.providerId).catch(() => {/* empty */}), + getAuth().deleteProviderConfig(authProviderConfig2.providerId).catch(() => {/* empty */}), ]); }; @@ -1610,7 +1724,7 @@ describe('admin.auth', () => { if (authEmulatorHost) { return this.skip(); // Not implemented. } - return removeTempConfigs().then(() => admin.auth().createProviderConfig(authProviderConfig1)); + return removeTempConfigs().then(() => getAuth().createProviderConfig(authProviderConfig1)); }); after(() => { @@ -1618,25 +1732,25 @@ describe('admin.auth', () => { }); it('createProviderConfig() successfully creates a SAML config', () => { - return admin.auth().createProviderConfig(authProviderConfig2) + return getAuth().createProviderConfig(authProviderConfig2) .then((config) => { assertDeepEqualUnordered(authProviderConfig2, config); }); }); it('getProviderConfig() successfully returns the expected SAML config', () => { - return admin.auth().getProviderConfig(authProviderConfig1.providerId) + return getAuth().getProviderConfig(authProviderConfig1.providerId) .then((config) => { assertDeepEqualUnordered(authProviderConfig1, config); }); }); it('listProviderConfig() successfully returns the list of SAML providers', () => { - const configs: admin.auth.AuthProviderConfig[] = []; + const configs: AuthProviderConfig[] = []; const listProviders: any = (type: 'saml' | 'oidc', maxResults?: number, pageToken?: string) => { - return admin.auth().listProviderConfigs({ type, maxResults, pageToken }) + return getAuth().listProviderConfigs({ type, maxResults, pageToken }) .then((result) => { - result.providerConfigs.forEach((config: admin.auth.AuthProviderConfig) => { + result.providerConfigs.forEach((config: AuthProviderConfig) => { configs.push(config); }); if (result.pageToken) { @@ -1673,7 +1787,7 @@ describe('admin.auth', () => { callbackURL: 'https://projectId3.firebaseapp.com/__/auth/handler', enableRequestSigning: false, }; - return admin.auth().updateProviderConfig(authProviderConfig1.providerId, modifiedConfigOptions) + return getAuth().updateProviderConfig(authProviderConfig1.providerId, modifiedConfigOptions) .then((config) => { const modifiedConfig = deepExtend( { providerId: authProviderConfig1.providerId }, modifiedConfigOptions); @@ -1701,7 +1815,7 @@ describe('admin.auth', () => { callbackURL: 'https://projectId3.firebaseapp.com/__/auth/handler', enableRequestSigning: false, }; - return admin.auth().updateProviderConfig(authProviderConfig1.providerId, deltaChanges) + return getAuth().updateProviderConfig(authProviderConfig1.providerId, deltaChanges) .then((config) => { const modifiedConfig = deepExtend( { providerId: authProviderConfig1.providerId }, modifiedConfigOptions); @@ -1710,8 +1824,8 @@ describe('admin.auth', () => { }); it('deleteProviderConfig() successfully deletes an existing SAML config', () => { - return admin.auth().deleteProviderConfig(authProviderConfig1.providerId).then(() => { - return admin.auth().getProviderConfig(authProviderConfig1.providerId) + return getAuth().deleteProviderConfig(authProviderConfig1.providerId).then(() => { + return getAuth().getProviderConfig(authProviderConfig1.providerId) .should.eventually.be.rejected.and.have.property('code', 'auth/configuration-not-found'); }); }); @@ -1742,8 +1856,8 @@ describe('admin.auth', () => { const removeTempConfigs = (): Promise => { return Promise.all([ - admin.auth().deleteProviderConfig(authProviderConfig1.providerId).catch(() => {/* empty */}), - admin.auth().deleteProviderConfig(authProviderConfig2.providerId).catch(() => {/* empty */}), + getAuth().deleteProviderConfig(authProviderConfig1.providerId).catch(() => {/* empty */}), + getAuth().deleteProviderConfig(authProviderConfig2.providerId).catch(() => {/* empty */}), ]); }; @@ -1752,7 +1866,7 @@ describe('admin.auth', () => { if (authEmulatorHost) { return this.skip(); // Not implemented. } - return removeTempConfigs().then(() => admin.auth().createProviderConfig(authProviderConfig1)); + return removeTempConfigs().then(() => getAuth().createProviderConfig(authProviderConfig1)); }); after(() => { @@ -1760,25 +1874,25 @@ describe('admin.auth', () => { }); it('createProviderConfig() successfully creates an OIDC config', () => { - return admin.auth().createProviderConfig(authProviderConfig2) + return getAuth().createProviderConfig(authProviderConfig2) .then((config) => { assertDeepEqualUnordered(authProviderConfig2, config); }); }); it('getProviderConfig() successfully returns the expected OIDC config', () => { - return admin.auth().getProviderConfig(authProviderConfig1.providerId) + return getAuth().getProviderConfig(authProviderConfig1.providerId) .then((config) => { assertDeepEqualUnordered(authProviderConfig1, config); }); }); it('listProviderConfig() successfully returns the list of OIDC providers', () => { - const configs: admin.auth.AuthProviderConfig[] = []; + const configs: AuthProviderConfig[] = []; const listProviders: any = (type: 'saml' | 'oidc', maxResults?: number, pageToken?: string) => { - return admin.auth().listProviderConfigs({ type, maxResults, pageToken }) + return getAuth().listProviderConfigs({ type, maxResults, pageToken }) .then((result) => { - result.providerConfigs.forEach((config: admin.auth.AuthProviderConfig) => { + result.providerConfigs.forEach((config: AuthProviderConfig) => { configs.push(config); }); if (result.pageToken) { @@ -1828,7 +1942,7 @@ describe('admin.auth', () => { code: true, }, }; - return admin.auth().updateProviderConfig(authProviderConfig1.providerId, deltaChanges) + return getAuth().updateProviderConfig(authProviderConfig1.providerId, deltaChanges) .then((config) => { assertDeepEqualUnordered(modifiedConfigOptions, config); }); @@ -1846,7 +1960,7 @@ describe('admin.auth', () => { code: false, }, }; - return admin.auth().updateProviderConfig(authProviderConfig1.providerId, deltaChanges). + return getAuth().updateProviderConfig(authProviderConfig1.providerId, deltaChanges). should.eventually.be.rejected.and.have.property('code', 'auth/invalid-oauth-responsetype'); }); @@ -1861,13 +1975,13 @@ describe('admin.auth', () => { code: true, }, }; - return admin.auth().updateProviderConfig(authProviderConfig1.providerId, deltaChanges). + return getAuth().updateProviderConfig(authProviderConfig1.providerId, deltaChanges). should.eventually.be.rejected.and.have.property('code', 'auth/missing-oauth-client-secret'); }); it('deleteProviderConfig() successfully deletes an existing OIDC config', () => { - return admin.auth().deleteProviderConfig(authProviderConfig1.providerId).then(() => { - return admin.auth().getProviderConfig(authProviderConfig1.providerId) + return getAuth().deleteProviderConfig(authProviderConfig1.providerId).then(() => { + return getAuth().getProviderConfig(authProviderConfig1.providerId) .should.eventually.be.rejected.and.have.property('code', 'auth/configuration-not-found'); }); }); @@ -1875,16 +1989,16 @@ describe('admin.auth', () => { it('deleteUser() deletes the user with the given UID', () => { return Promise.all([ - admin.auth().deleteUser(newUserUid), - admin.auth().deleteUser(uidFromCreateUserWithoutUid), + getAuth().deleteUser(newUserUid), + getAuth().deleteUser(uidFromCreateUserWithoutUid), ]).should.eventually.be.fulfilled; }); describe('deleteUsers()', () => { it('deletes users', async () => { - const uid1 = await admin.auth().createUser({}).then((ur) => ur.uid); - const uid2 = await admin.auth().createUser({}).then((ur) => ur.uid); - const uid3 = await admin.auth().createUser({}).then((ur) => ur.uid); + const uid1 = await getAuth().createUser({}).then((ur) => ur.uid); + const uid2 = await getAuth().createUser({}).then((ur) => ur.uid); + const uid3 = await getAuth().createUser({}).then((ur) => ur.uid); const ids = [{ uid: uid1 }, { uid: uid2 }, { uid: uid3 }]; return deleteUsersWithDelay([uid1, uid2, uid3]) @@ -1893,7 +2007,7 @@ describe('admin.auth', () => { expect(deleteUsersResult.failureCount).to.equal(0); expect(deleteUsersResult.errors).to.have.length(0); - return admin.auth().getUsers(ids); + return getAuth().getUsers(ids); }) .then((getUsersResult) => { expect(getUsersResult.users).to.have.length(0); @@ -1902,7 +2016,7 @@ describe('admin.auth', () => { }); it('deletes users that exist even when non-existing users also specified', async () => { - const uid1 = await admin.auth().createUser({}).then((ur) => ur.uid); + const uid1 = await getAuth().createUser({}).then((ur) => ur.uid); const uid2 = 'uid-that-doesnt-exist'; const ids = [{ uid: uid1 }, { uid: uid2 }]; @@ -1912,7 +2026,7 @@ describe('admin.auth', () => { expect(deleteUsersResult.failureCount).to.equal(0); expect(deleteUsersResult.errors).to.have.length(0); - return admin.auth().getUsers(ids); + return getAuth().getUsers(ids); }) .then((getUsersResult) => { expect(getUsersResult.users).to.have.length(0); @@ -1921,7 +2035,7 @@ describe('admin.auth', () => { }); it('is idempotent', async () => { - const uid = await admin.auth().createUser({}).then((ur) => ur.uid); + const uid = await getAuth().createUser({}).then((ur) => ur.uid); return deleteUsersWithDelay([uid]) .then((deleteUsersResult) => { @@ -1948,7 +2062,7 @@ describe('admin.auth', () => { const uid3 = sessionCookieUids[2]; it('creates a valid Firebase session cookie', () => { - return admin.auth().createCustomToken(uid, { admin: true, groupId: '1234' }) + return getAuth().createCustomToken(uid, { admin: true, groupId: '1234' }) .then((customToken) => clientAuth().signInWithCustomToken(customToken)) .then(({ user }) => { expect(user).to.exist; @@ -1956,7 +2070,7 @@ describe('admin.auth', () => { }) .then((idToken) => { currentIdToken = idToken; - return admin.auth().verifyIdToken(idToken); + return getAuth().verifyIdToken(idToken); }).then((decodedIdTokenClaims) => { expectedExp = Math.floor((new Date().getTime() + expiresIn) / 1000); payloadClaims = decodedIdTokenClaims; @@ -1966,9 +2080,9 @@ describe('admin.auth', () => { delete payloadClaims.iat; expectedIat = Math.floor(new Date().getTime() / 1000); // One day long session cookie. - return admin.auth().createSessionCookie(currentIdToken, { expiresIn }); + return getAuth().createSessionCookie(currentIdToken, { expiresIn }); }) - .then((sessionCookie) => admin.auth().verifySessionCookie(sessionCookie)) + .then((sessionCookie) => getAuth().verifySessionCookie(sessionCookie)) .then((decodedIdToken) => { // Check for expected expiration with +/-5 seconds of variation. expect(decodedIdToken.exp).to.be.within(expectedExp - 5, expectedExp + 5); @@ -1984,7 +2098,7 @@ describe('admin.auth', () => { it('creates a revocable session cookie', () => { let currentSessionCookie: string; - return admin.auth().createCustomToken(uid2) + return getAuth().createCustomToken(uid2) .then((customToken) => clientAuth().signInWithCustomToken(customToken)) .then(({ user }) => { expect(user).to.exist; @@ -1992,16 +2106,16 @@ describe('admin.auth', () => { }) .then((idToken) => { // One day long session cookie. - return admin.auth().createSessionCookie(idToken, { expiresIn }); + return getAuth().createSessionCookie(idToken, { expiresIn }); }) .then((sessionCookie) => { currentSessionCookie = sessionCookie; return new Promise((resolve) => setTimeout(() => resolve( - admin.auth().revokeRefreshTokens(uid2), + getAuth().revokeRefreshTokens(uid2), ), 1000)); }) .then(() => { - const verifyingSessionCookie = admin.auth().verifySessionCookie(currentSessionCookie); + const verifyingSessionCookie = getAuth().verifySessionCookie(currentSessionCookie); if (authEmulatorHost) { // Check revocation is forced in emulator-mode and this should throw. return verifyingSessionCookie.should.eventually.be.rejected; @@ -2011,13 +2125,13 @@ describe('admin.auth', () => { } }) .then(() => { - return admin.auth().verifySessionCookie(currentSessionCookie, true) + return getAuth().verifySessionCookie(currentSessionCookie, true) .should.eventually.be.rejected.and.have.property('code', 'auth/session-cookie-revoked'); }); }); it('fails when called with a revoked ID token', () => { - return admin.auth().createCustomToken(uid3, { admin: true, groupId: '1234' }) + return getAuth().createCustomToken(uid3, { admin: true, groupId: '1234' }) .then((customToken) => clientAuth().signInWithCustomToken(customToken)) .then(({ user }) => { expect(user).to.exist; @@ -2026,11 +2140,11 @@ describe('admin.auth', () => { .then((idToken) => { currentIdToken = idToken; return new Promise((resolve) => setTimeout(() => resolve( - admin.auth().revokeRefreshTokens(uid3), + getAuth().revokeRefreshTokens(uid3), ), 1000)); }) .then(() => { - return admin.auth().createSessionCookie(currentIdToken, { expiresIn }) + return getAuth().createSessionCookie(currentIdToken, { expiresIn }) .should.eventually.be.rejected.and.have.property('code', 'auth/id-token-expired'); }); }); @@ -2040,45 +2154,45 @@ describe('admin.auth', () => { describe('verifySessionCookie()', () => { const uid = sessionCookieUids[0]; it('fails when called with an invalid session cookie', () => { - return admin.auth().verifySessionCookie('invalid-token') + return getAuth().verifySessionCookie('invalid-token') .should.eventually.be.rejected.and.have.property('code', 'auth/argument-error'); }); it('fails when called with a Firebase ID token', () => { - return admin.auth().createCustomToken(uid) + return getAuth().createCustomToken(uid) .then((customToken) => clientAuth().signInWithCustomToken(customToken)) .then(({ user }) => { expect(user).to.exist; return user!.getIdToken(); }) .then((idToken) => { - return admin.auth().verifySessionCookie(idToken) + return getAuth().verifySessionCookie(idToken) .should.eventually.be.rejected.and.have.property('code', 'auth/argument-error'); }); }); it('fails with checkRevoked set to true and corresponding user disabled', async () => { const expiresIn = 24 * 60 * 60 * 1000; - const customToken = await admin.auth().createCustomToken(uid, { admin: true, groupId: '1234' }); + const customToken = await getAuth().createCustomToken(uid, { admin: true, groupId: '1234' }); const { user } = await clientAuth().signInWithCustomToken(customToken); expect(user).to.exist; const idToken = await user!.getIdToken(); - const decodedIdTokenClaims = await admin.auth().verifyIdToken(idToken); + const decodedIdTokenClaims = await getAuth().verifyIdToken(idToken); expect(decodedIdTokenClaims.uid).to.be.equal(uid); - const sessionCookie = await admin.auth().createSessionCookie(idToken, { expiresIn }); - let decodedIdToken = await admin.auth().verifySessionCookie(sessionCookie, true); + const sessionCookie = await getAuth().createSessionCookie(idToken, { expiresIn }); + let decodedIdToken = await getAuth().verifySessionCookie(sessionCookie, true); expect(decodedIdToken.uid).to.equal(uid); - const userRecord = await admin.auth().updateUser(uid, { disabled : true }); + const userRecord = await getAuth().updateUser(uid, { disabled : true }); // Ensure disabled field has been updated. expect(userRecord.uid).to.equal(uid); expect(userRecord.disabled).to.equal(true); try { // If it is in emulator mode, a user-disabled error will be thrown. - decodedIdToken = await admin.auth().verifySessionCookie(sessionCookie, false); + decodedIdToken = await getAuth().verifySessionCookie(sessionCookie, false); expect(decodedIdToken.uid).to.equal(uid); } catch (error) { if (authEmulatorHost) { @@ -2089,7 +2203,7 @@ describe('admin.auth', () => { } try { - await admin.auth().verifySessionCookie(sessionCookie, true); + await getAuth().verifySessionCookie(sessionCookie, true); } catch (error) { expect(error).to.have.property('code', 'auth/user-disabled'); } @@ -2098,7 +2212,7 @@ describe('admin.auth', () => { describe('importUsers()', () => { const randomUid = 'import_' + generateRandomString(20).toLowerCase(); - let importUserRecord: admin.auth.UserImportRecord; + let importUserRecord: UserImportRecord; const rawPassword = 'password'; const rawSalt = 'NaCl'; // Simulate a user stored using SCRYPT being migrated to Firebase Auth via importUsers. @@ -2313,12 +2427,12 @@ describe('admin.auth', () => { ], }; uids.push(importUserRecord.uid); - return admin.auth().importUsers([importUserRecord]) + return getAuth().importUsers([importUserRecord]) .then((result) => { expect(result.failureCount).to.equal(0); expect(result.successCount).to.equal(1); expect(result.errors.length).to.equal(0); - return admin.auth().getUser(uid); + return getAuth().getUser(uid); }).then((userRecord) => { // The phone number provider will be appended to the list of accounts. importUserRecord.providerData?.push({ @@ -2341,7 +2455,7 @@ describe('admin.auth', () => { const uid = generateRandomString(20).toLowerCase(); const email = uid + '@example.com'; const now = new Date(1476235905000).toUTCString(); - const enrolledFactors: admin.auth.UpdatePhoneMultiFactorInfoRequest[] = [ + const enrolledFactors: UpdatePhoneMultiFactorInfoRequest[] = [ { uid: 'mfaUid1', phoneNumber: '+16505550001', @@ -2382,12 +2496,12 @@ describe('admin.auth', () => { }; uids.push(importUserRecord.uid); - return admin.auth().importUsers([importUserRecord]) + return getAuth().importUsers([importUserRecord]) .then((result) => { expect(result.failureCount).to.equal(0); expect(result.successCount).to.equal(1); expect(result.errors.length).to.equal(0); - return admin.auth().getUser(uid); + return getAuth().getUser(uid); }).then((userRecord) => { // Confirm second factors added to user. const actualUserRecord: {[key: string]: any} = userRecord.toJSON(); @@ -2402,7 +2516,7 @@ describe('admin.auth', () => { { uid: generateRandomString(20).toLowerCase(), email: 'invalid' }, { uid: generateRandomString(20).toLowerCase(), emailVerified: 'invalid' } as any, ]; - return admin.auth().importUsers(users) + return getAuth().importUsers(users) .then((result) => { expect(result.successCount).to.equal(0); expect(result.failureCount).to.equal(2); @@ -2424,7 +2538,7 @@ describe('admin.auth', () => { { uid: generateRandomString(20).toLowerCase(), phoneNumber: '+1error' }, { uid: generateRandomString(20).toLowerCase(), phoneNumber: '+1invalid' }, ]; - return admin.auth().importUsers(users) + return getAuth().importUsers(users) .then((result) => { expect(result.successCount).to.equal(0); expect(result.failureCount).to.equal(2); @@ -2442,18 +2556,18 @@ describe('admin.auth', () => { * Imports the provided user record with the specified hashing options and then * validates the import was successful by signing in to the imported account using * the corresponding plain text password. - * @param {admin.auth.UserImportRecord} importUserRecord The user record to import. - * @param {admin.auth.UserImportOptions} importOptions The import hashing options. - * @param {string} rawPassword The plain unhashed password string. - * @retunr {Promise} A promise that resolved on success. + * @param importUserRecord The user record to import. + * @param importOptions The import hashing options. + * @param rawPassword The plain unhashed password string. + * @retunr A promise that resolved on success. */ function testImportAndSignInUser( - importUserRecord: admin.auth.UserImportRecord, + importUserRecord: UserImportRecord, importOptions: any, rawPassword: string): Promise { const users = [importUserRecord]; // Import the user record. - return admin.auth().importUsers(users, importOptions) + return getAuth().importUsers(users, importOptions) .then((result) => { // Verify the import result. expect(result.failureCount).to.equal(0); @@ -2479,7 +2593,7 @@ function testImportAndSignInUser( * or is found not to exist. */ function deletePhoneNumberUser(phoneNumber: string): Promise { - return admin.auth().getUserByPhoneNumber(phoneNumber) + return getAuth().getUserByPhoneNumber(phoneNumber) .then((userRecord) => { return safeDelete(userRecord.uid); }) @@ -2495,7 +2609,7 @@ function deletePhoneNumberUser(phoneNumber: string): Promise { * Runs cleanup routine that could affect outcome of tests and removes any * intermediate users created. * - * @return {Promise} A promise that resolves when test preparations are ready. + * @return A promise that resolves when test preparations are ready. */ function cleanup(): Promise { // Delete any existing users that could affect the test outcome. @@ -2518,8 +2632,8 @@ function cleanup(): Promise { /** * Returns the action code corresponding to the link. * - * @param {string} link The link to parse for the action code. - * @return {string} The link's corresponding action code. + * @param link The link to parse for the action code. + * @return The link's corresponding action code. */ function getActionCode(link: string): string { const parsedUrl = new url.URL(link); @@ -2531,8 +2645,8 @@ function getActionCode(link: string): string { /** * Returns the continue URL corresponding to the link. * - * @param {string} link The link to parse for the continue URL. - * @return {string} The link's corresponding continue URL. + * @param link The link to parse for the continue URL. + * @return The link's corresponding continue URL. */ function getContinueUrl(link: string): string { const parsedUrl = new url.URL(link); @@ -2544,8 +2658,8 @@ function getContinueUrl(link: string): string { /** * Returns the tenant ID corresponding to the link. * - * @param {string} link The link to parse for the tenant ID. - * @return {string} The link's corresponding tenant ID. + * @param link The link to parse for the tenant ID. + * @return The link's corresponding tenant ID. */ function getTenantId(link: string): string { const parsedUrl = new url.URL(link); @@ -2559,14 +2673,14 @@ function getTenantId(link: string): string { * requests and throttles them as the Auth backend rate limits this endpoint. * A bulk delete API is being designed to help solve this issue. * - * @param {string} uid The identifier of the user to delete. - * @return {Promise} A promise that resolves when delete operation resolves. + * @param uid The identifier of the user to delete. + * @return A promise that resolves when delete operation resolves. */ function safeDelete(uid: string): Promise { // Wait for delete queue to empty. const deletePromise = deleteQueue .then(() => { - return admin.auth().deleteUser(uid); + return getAuth().deleteUser(uid); }) .catch((error) => { // Suppress user not found error. @@ -2586,22 +2700,22 @@ function safeDelete(uid: string): Promise { * API is rate limited at 1 QPS, and therefore this helper function staggers * subsequent invocations by adding 1 second delay to each call. * - * @param {string[]} uids The list of user identifiers to delete. - * @return {Promise} A promise that resolves when delete operation resolves. + * @param uids The list of user identifiers to delete. + * @return A promise that resolves when delete operation resolves. */ -async function deleteUsersWithDelay(uids: string[]): Promise { +async function deleteUsersWithDelay(uids: string[]): Promise { if (!authEmulatorHost) { await new Promise((resolve) => { setTimeout(resolve, 1000); }); } - return admin.auth().deleteUsers(uids); + return getAuth().deleteUsers(uids); } /** * Asserts actual object is equal to expected object while ignoring key order. * This is useful since to.deep.equal fails when order differs. * - * @param {[key: string]: any} expected object. - * @param {[key: string]: any} actual object. + * @param expected object. + * @param actual object. */ function assertDeepEqualUnordered(expected: {[key: string]: any}, actual: {[key: string]: any}): void { for (const key in expected) { diff --git a/test/integration/database.spec.ts b/test/integration/database.spec.ts index 77204a1839..40b35484eb 100644 --- a/test/integration/database.spec.ts +++ b/test/integration/database.spec.ts @@ -14,9 +14,12 @@ * limitations under the License. */ -import * as admin from '../../lib/index'; import * as chai from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; +import * as admin from '../../lib/index'; +import { + Database, DataSnapshot, EventType, Reference, ServerValue, getDatabase, getDatabaseWithUrl, +} from '../../lib/database/index'; import { defaultApp, nullApp, nonNullApp, cmdArgs, databaseUrl, isEmulator } from './setup'; // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -46,12 +49,18 @@ describe('admin.database', () => { '.write': 'auth != null', }, }; - return admin.database().setRules(defaultRules); + return getDatabase().setRules(defaultRules); + }); + + it('getDatabase() returns a database client', () => { + const db: Database = getDatabase(); + expect(db).to.not.be.undefined; }); it('admin.database() returns a database client', () => { - const db = admin.database(); - expect(db).to.be.instanceOf((admin.database as any).Database); + const db: admin.database.Database = admin.database(); + expect(db).to.not.be.undefined; + expect(db).to.equal(getDatabase()); }); it('admin.database.ServerValue type is defined', () => { @@ -60,7 +69,7 @@ describe('admin.database', () => { }); it('default App is not blocked by security rules', () => { - return defaultApp.database().ref('blocked').set(admin.database.ServerValue.TIMESTAMP) + return getDatabase(defaultApp).ref('blocked').set(ServerValue.TIMESTAMP) .should.eventually.be.fulfilled; }); @@ -71,32 +80,31 @@ describe('admin.database', () => { // remove this once updating security rules through admin is in place. return this.skip(); } - return nullApp.database().ref('blocked').set(admin.database.ServerValue.TIMESTAMP) + return getDatabase(nullApp).ref('blocked').set(admin.database.ServerValue.TIMESTAMP) .should.eventually.be.rejectedWith('PERMISSION_DENIED: Permission denied'); }); it('App with non-null auth override is not blocked by security rules', () => { - return nonNullApp.database().ref('blocked').set(admin.database.ServerValue.TIMESTAMP) + return getDatabase(nonNullApp).ref('blocked').set(ServerValue.TIMESTAMP) .should.eventually.be.fulfilled; }); - describe('admin.database().ref()', () => { - let ref: admin.database.Reference; + describe('Reference', () => { + let ref: Reference; before(() => { - ref = admin.database().ref(path); + ref = getDatabase().ref(path); }); it('ref() can be called with ref', () => { - const copy = admin.database().ref(ref); - expect(copy).to.be.instanceof((admin.database as any).Reference); + const copy: Reference = getDatabase().ref(ref); expect(copy.key).to.equal(ref.key); }); it('set() completes successfully', () => { return ref.set({ success: true, - timestamp: admin.database.ServerValue.TIMESTAMP, + timestamp: ServerValue.TIMESTAMP, }).should.eventually.be.fulfilled; }); @@ -121,24 +129,29 @@ describe('admin.database', () => { }); }); - describe('app.database(url).ref()', () => { + describe('getDatabaseWithUrl()', () => { - let refWithUrl: admin.database.Reference; + let refWithUrl: Reference; before(() => { - const app = admin.app(); - refWithUrl = app.database(databaseUrl).ref(path); + refWithUrl = getDatabaseWithUrl(databaseUrl).ref(path); + }); + + it('getDatabaseWithUrl(url) returns a Database client for URL', () => { + const db: Database = getDatabaseWithUrl(databaseUrl); + expect(db).to.not.be.undefined; }); it('app.database(url) returns a Database client for URL', () => { - const db = admin.app().database(databaseUrl); - expect(db).to.be.instanceOf((admin.database as any).Database); + const db: Database = admin.app().database(databaseUrl); + expect(db).to.not.be.undefined; + expect(db).to.equal(getDatabaseWithUrl(databaseUrl)); }); it('set() completes successfully', () => { return refWithUrl.set({ success: true, - timestamp: admin.database.ServerValue.TIMESTAMP, + timestamp: ServerValue.TIMESTAMP, }).should.eventually.be.fulfilled; }); @@ -168,7 +181,7 @@ describe('admin.database', () => { // https://github.com/firebase/firebase-admin-node/issues/1149 return this.skip(); } - return admin.database().getRules().then((result) => { + return getDatabase().getRules().then((result) => { return expect(result).to.be.not.empty; }); }); @@ -178,7 +191,7 @@ describe('admin.database', () => { // https://github.com/firebase/firebase-admin-node/issues/1149 return this.skip(); } - return admin.database().getRulesJSON().then((result) => { + return getDatabase().getRulesJSON().then((result) => { return expect(result).to.be.not.undefined; }); }); @@ -188,8 +201,8 @@ describe('admin.database', () => { // will trigger a TS compilation failure if the RTDB typings were not loaded // correctly. (Marked as export to avoid compilation warning.) export function addValueEventListener( - db: admin.database.Database, - callback: (s: admin.database.DataSnapshot | null) => any): void { - const eventType: admin.database.EventType = 'value'; + db: Database, + callback: (s: DataSnapshot | null) => any): void { + const eventType: EventType = 'value'; db.ref().on(eventType, callback); } diff --git a/test/integration/firestore.spec.ts b/test/integration/firestore.spec.ts index 13ef9131dd..1695bab9af 100644 --- a/test/integration/firestore.spec.ts +++ b/test/integration/firestore.spec.ts @@ -14,10 +14,14 @@ * limitations under the License. */ -import * as admin from '../../lib/index'; import * as chai from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import { clone } from 'lodash'; +import * as admin from '../../lib/index'; +import { + DocumentReference, DocumentSnapshot, FieldValue, Firestore, FirestoreDataConverter, + QueryDocumentSnapshot, Timestamp, getFirestore, setLogFunction, +} from '../../lib/firestore/index'; chai.should(); chai.use(chaiAsPromised); @@ -31,21 +35,27 @@ const mountainView = { describe('admin.firestore', () => { - let reference: admin.firestore.DocumentReference; + let reference: DocumentReference; before(() => { - const db = admin.firestore(); + const db = getFirestore(); reference = db.collection('cities').doc(); }); + it('getFirestore() returns a Firestore client', () => { + const firestore: Firestore = getFirestore(); + expect(firestore).to.not.be.undefined; + }); + it('admin.firestore() returns a Firestore client', () => { - const firestore = admin.firestore(); - expect(firestore).to.be.instanceOf(admin.firestore.Firestore); + const firestore: admin.firestore.Firestore = admin.firestore(); + expect(firestore).to.not.be.undefined; + expect(firestore).to.equal(getFirestore()); }); it('app.firestore() returns a Firestore client', () => { - const firestore = admin.app().firestore(); - expect(firestore).to.be.instanceOf(admin.firestore.Firestore); + const firestore: admin.firestore.Firestore = admin.app().firestore(); + expect(firestore).to.not.be.undefined; }); it('supports basic data access', () => { @@ -66,9 +76,9 @@ describe('admin.firestore', () => { }); }); - it('admin.firestore.FieldValue.serverTimestamp() provides a server-side timestamp', () => { + it('FieldValue.serverTimestamp() provides a server-side timestamp', () => { const expected: any = clone(mountainView); - expected.timestamp = admin.firestore.FieldValue.serverTimestamp(); + expected.timestamp = FieldValue.serverTimestamp(); return reference.set(expected) .then(() => { return reference.get(); @@ -77,7 +87,7 @@ describe('admin.firestore', () => { const data = snapshot.data(); expect(data).to.exist; expect(data!.timestamp).is.not.null; - expect(data!.timestamp).to.be.instanceOf(admin.firestore.Timestamp); + expect(data!.timestamp).to.be.instanceOf(Timestamp); return reference.delete(); }) .should.eventually.be.fulfilled; @@ -118,20 +128,20 @@ describe('admin.firestore', () => { }); it('supports operations with custom type converters', () => { - const converter: admin.firestore.FirestoreDataConverter = { + const converter: FirestoreDataConverter = { toFirestore: (city: City) => { return { name: city.localId, population: city.people, }; }, - fromFirestore: (snap: admin.firestore.QueryDocumentSnapshot) => { + fromFirestore: (snap: QueryDocumentSnapshot) => { return new City(snap.data().name, snap.data().population); } }; const expected: City = new City('Sunnyvale', 153185); - const refWithConverter: admin.firestore.DocumentReference = admin.firestore() + const refWithConverter: DocumentReference = getFirestore() .collection('cities') .doc() .withConverter(converter); @@ -139,15 +149,15 @@ describe('admin.firestore', () => { .then(() => { return refWithConverter.get(); }) - .then((snapshot: admin.firestore.DocumentSnapshot) => { + .then((snapshot: DocumentSnapshot) => { expect(snapshot.data()).to.be.instanceOf(City); return refWithConverter.delete(); }); }); it('supports saving references in documents', () => { - const source = admin.firestore().collection('cities').doc(); - const target = admin.firestore().collection('cities').doc(); + const source = getFirestore().collection('cities').doc(); + const target = getFirestore().collection('cities').doc(); return source.set(mountainView) .then(() => { return target.set({ name: 'Palo Alto', sisterCity: source }); @@ -167,10 +177,10 @@ describe('admin.firestore', () => { .should.eventually.be.fulfilled; }); - it('admin.firestore.setLogFunction() enables logging for the Firestore module', () => { + it('setLogFunction() enables logging for the Firestore module', () => { const logs: string[] = []; - const source = admin.firestore().collection('cities').doc(); - admin.firestore.setLogFunction((log) => { + const source = getFirestore().collection('cities').doc(); + setLogFunction((log) => { logs.push(log); }); return source.set({ name: 'San Francisco' }) diff --git a/test/integration/installations.spec.ts b/test/integration/installations.spec.ts index 982bd3a218..98eb3ad72a 100644 --- a/test/integration/installations.spec.ts +++ b/test/integration/installations.spec.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import * as admin from '../../lib/index'; +import { getInstallations } from '../../lib/installations/index'; import * as chai from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; @@ -24,7 +24,7 @@ chai.use(chaiAsPromised); describe('admin.installations', () => { it('deleteInstallation() fails when called with fictive-ID0 instance ID', () => { // instance ids have to conform to /[cdef][A-Za-z0-9_-]{9}[AEIMQUYcgkosw048]/ - return admin.installations().deleteInstallation('fictive-ID0') + return getInstallations().deleteInstallation('fictive-ID0') .should.eventually.be .rejectedWith('Installation ID "fictive-ID0": Failed to find the installation ID.'); }); diff --git a/test/integration/instance-id.spec.ts b/test/integration/instance-id.spec.ts index d98c2802f6..2155205990 100644 --- a/test/integration/instance-id.spec.ts +++ b/test/integration/instance-id.spec.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import * as admin from '../../lib/index'; import * as chai from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; +import { getInstanceId } from '../../lib/instance-id/index'; chai.should(); chai.use(chaiAsPromised); @@ -24,7 +24,7 @@ chai.use(chaiAsPromised); describe('admin.instanceId', () => { it('deleteInstanceId() fails when called with fictive-ID0 instance ID', () => { // instance ids have to conform to /[cdef][A-Za-z0-9_-]{9}[AEIMQUYcgkosw048]/ - return admin.instanceId().deleteInstanceId('fictive-ID0') + return getInstanceId().deleteInstanceId('fictive-ID0') .should.eventually.be .rejectedWith('Installation ID "fictive-ID0": Failed to find the installation ID.'); }); diff --git a/test/integration/machine-learning.spec.ts b/test/integration/machine-learning.spec.ts index f767b7d0ad..900bbaa699 100644 --- a/test/integration/machine-learning.spec.ts +++ b/test/integration/machine-learning.spec.ts @@ -17,12 +17,12 @@ import path = require('path'); import * as chai from 'chai'; -import * as admin from '../../lib/index'; import { projectId } from './setup'; import { Bucket } from '@google-cloud/storage'; - -import AutoMLTfliteModelOptions = admin.machineLearning.AutoMLTfliteModelOptions; -import GcsTfliteModelOptions = admin.machineLearning.GcsTfliteModelOptions; +import { getStorage } from '../../lib/storage/index'; +import { + AutoMLTfliteModelOptions, GcsTfliteModelOptions, Model, ModelOptions, getMachineLearning, +} from '../../lib/machine-learning/index'; const expect = chai.expect; @@ -30,32 +30,31 @@ describe('admin.machineLearning', () => { const modelsToDelete: string[] = []; - function scheduleForDelete(model: admin.machineLearning.Model): void { + function scheduleForDelete(model: Model): void { modelsToDelete.push(model.modelId); } - function unscheduleForDelete(model: admin.machineLearning.Model): void { + function unscheduleForDelete(model: Model): void { modelsToDelete.splice(modelsToDelete.indexOf(model.modelId), 1); } function deleteTempModels(): Promise { const promises: Array> = []; modelsToDelete.forEach((modelId) => { - promises.push(admin.machineLearning().deleteModel(modelId)); + promises.push(getMachineLearning().deleteModel(modelId)); }); modelsToDelete.splice(0, modelsToDelete.length); // Clear out the array. return Promise.all(promises); } - function createTemporaryModel(options?: admin.machineLearning.ModelOptions): - Promise { - let modelOptions: admin.machineLearning.ModelOptions = { + function createTemporaryModel(options?: ModelOptions): Promise { + let modelOptions: ModelOptions = { displayName: 'nodejs_integration_temp_model', }; if (options) { modelOptions = options; } - return admin.machineLearning().createModel(modelOptions) + return getMachineLearning().createModel(modelOptions) .then((model) => { scheduleForDelete(model); return model; @@ -63,7 +62,7 @@ describe('admin.machineLearning', () => { } function uploadModelToGcs(localFileName: string, gcsFileName: string): Promise { - const bucket: Bucket = admin.storage().bucket(); + const bucket: Bucket = getStorage().bucket(); const tfliteFileName = path.join(__dirname, `../resources/${localFileName}`); return bucket.upload(tfliteFileName, { destination: gcsFileName }) .then(() => { @@ -77,11 +76,11 @@ describe('admin.machineLearning', () => { describe('createModel()', () => { it('creates a new Model without ModelFormat', () => { - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { displayName: 'node-integ-test-create-1', tags: ['tag123', 'tag345'] }; - return admin.machineLearning().createModel(modelOptions) + return getMachineLearning().createModel(modelOptions) .then((model) => { scheduleForDelete(model); verifyModel(model, modelOptions); @@ -89,7 +88,7 @@ describe('admin.machineLearning', () => { }); it('creates a new Model with valid GCS TFLite ModelFormat', () => { - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { displayName: 'node-integ-test-create-2', tags: ['tag234', 'tag456'], tfliteModel: { gcsTfliteUri: 'this will be replaced below' }, @@ -97,7 +96,7 @@ describe('admin.machineLearning', () => { return uploadModelToGcs('model1.tflite', 'valid_model.tflite') .then((fileName: string) => { modelOptions.tfliteModel!.gcsTfliteUri = fileName; - return admin.machineLearning().createModel(modelOptions) + return getMachineLearning().createModel(modelOptions) .then((model) => { scheduleForDelete(model); verifyModel(model, modelOptions); @@ -114,12 +113,12 @@ describe('admin.machineLearning', () => { this.skip(); return; } - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { displayName: 'node-integ-test-create-automl', tags: ['tagAutoml'], tfliteModel: { automlModel: automlRef } }; - return admin.machineLearning().createModel(modelOptions) + return getMachineLearning().createModel(modelOptions) .then((model) => { return model.waitForUnlocked(55000) .then(() => { @@ -132,7 +131,7 @@ describe('admin.machineLearning', () => { it('creates a new Model with invalid ModelFormat', () => { // Upload a file to default gcs bucket - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { displayName: 'node-integ-test-create-3', tags: ['tag234', 'tag456'], tfliteModel: { gcsTfliteUri: 'this will be replaced below' }, @@ -140,7 +139,7 @@ describe('admin.machineLearning', () => { return uploadModelToGcs('invalid_model.tflite', 'invalid_model.tflite') .then((fileName: string) => { modelOptions.tfliteModel!.gcsTfliteUri = fileName; - return admin.machineLearning().createModel(modelOptions) + return getMachineLearning().createModel(modelOptions) .then((model) => { scheduleForDelete(model); verifyModel(model, modelOptions); @@ -149,39 +148,39 @@ describe('admin.machineLearning', () => { }); it ('rejects with invalid-argument when modelOptions are invalid', () => { - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { displayName: 'Invalid Name#*^!', }; - return admin.machineLearning().createModel(modelOptions) + return getMachineLearning().createModel(modelOptions) .should.eventually.be.rejected.and.have.property('code', 'machine-learning/invalid-argument'); }); }); describe('updateModel()', () => { - const UPDATE_NAME: admin.machineLearning.ModelOptions = { + const UPDATE_NAME: ModelOptions = { displayName: 'update-model-new-name', }; it('rejects with not-found when the Model does not exist', () => { const nonExistingId = '00000000'; - return admin.machineLearning().updateModel(nonExistingId, UPDATE_NAME) + return getMachineLearning().updateModel(nonExistingId, UPDATE_NAME) .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/not-found'); }); it('rejects with invalid-argument when the ModelId is invalid', () => { - return admin.machineLearning().updateModel('invalid-model-id', UPDATE_NAME) + return getMachineLearning().updateModel('invalid-model-id', UPDATE_NAME) .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/invalid-argument'); }); it ('rejects with invalid-argument when modelOptions are invalid', () => { - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { displayName: 'Invalid Name#*^!', }; return createTemporaryModel({ displayName: 'node-integ-invalid-argument' }) - .then((model) => admin.machineLearning().updateModel(model.modelId, modelOptions) + .then((model) => getMachineLearning().updateModel(model.modelId, modelOptions) .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/invalid-argument')); }); @@ -190,10 +189,10 @@ describe('admin.machineLearning', () => { const DISPLAY_NAME = 'node-integ-test-update-1b'; return createTemporaryModel({ displayName: 'node-integ-test-update-1a' }) .then((model) => { - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { displayName: DISPLAY_NAME, }; - return admin.machineLearning().updateModel(model.modelId, modelOptions) + return getMachineLearning().updateModel(model.modelId, modelOptions) .then((updatedModel) => { verifyModel(updatedModel, modelOptions); }); @@ -208,10 +207,10 @@ describe('admin.machineLearning', () => { displayName: 'node-integ-test-update-2', tags: ORIGINAL_TAGS, }).then((expectedModel) => { - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { tags: NEW_TAGS, }; - return admin.machineLearning().updateModel(expectedModel.modelId, modelOptions) + return getMachineLearning().updateModel(expectedModel.modelId, modelOptions) .then((actualModel) => { expect(actualModel.tags!.length).to.equal(2); expect(actualModel.tags).to.have.same.members(NEW_TAGS); @@ -224,10 +223,10 @@ describe('admin.machineLearning', () => { createTemporaryModel(), uploadModelToGcs('model1.tflite', 'valid_model.tflite')]) .then(([model, fileName]) => { - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { tfliteModel: { gcsTfliteUri: fileName }, }; - return admin.machineLearning().updateModel(model.modelId, modelOptions) + return getMachineLearning().updateModel(model.modelId, modelOptions) .then((updatedModel) => { verifyModel(updatedModel, modelOptions); }); @@ -247,10 +246,10 @@ describe('admin.machineLearning', () => { this.skip(); return; } - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { tfliteModel: { automlModel: automlRef }, }; - return admin.machineLearning().updateModel(model.modelId, modelOptions) + return getMachineLearning().updateModel(model.modelId, modelOptions) .then((updatedModel) => { return updatedModel.waitForUnlocked(55000) .then(() => { @@ -266,11 +265,11 @@ describe('admin.machineLearning', () => { const TAGS = ['node-integ-tag-1', 'node-integ-tag-2']; return createTemporaryModel({ displayName: 'node-integ-test-update-3a' }) .then((model) => { - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { displayName: DISPLAY_NAME, tags: TAGS, }; - return admin.machineLearning().updateModel(model.modelId, modelOptions) + return getMachineLearning().updateModel(model.modelId, modelOptions) .then((updatedModel) => { expect(updatedModel.displayName).to.equal(DISPLAY_NAME); expect(updatedModel.tags).to.have.same.members(TAGS); @@ -282,19 +281,19 @@ describe('admin.machineLearning', () => { describe('publishModel()', () => { it('should reject when model does not exist', () => { const nonExistingName = '00000000'; - return admin.machineLearning().publishModel(nonExistingName) + return getMachineLearning().publishModel(nonExistingName) .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/not-found'); }); it('rejects with invalid-argument when the ModelId is invalid', () => { - return admin.machineLearning().publishModel('invalid-model-id') + return getMachineLearning().publishModel('invalid-model-id') .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/invalid-argument'); }); it('publishes the model successfully', () => { - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { displayName: 'node-integ-test-publish-1', tfliteModel: { gcsTfliteUri: 'this will be replaced below' }, }; @@ -305,7 +304,7 @@ describe('admin.machineLearning', () => { .then((createdModel) => { expect(createdModel.validationError).to.be.undefined; expect(createdModel.published).to.be.false; - return admin.machineLearning().publishModel(createdModel.modelId) + return getMachineLearning().publishModel(createdModel.modelId) .then((publishedModel) => { expect(publishedModel.published).to.be.true; }); @@ -317,19 +316,19 @@ describe('admin.machineLearning', () => { describe('unpublishModel()', () => { it('should reject when model does not exist', () => { const nonExistingName = '00000000'; - return admin.machineLearning().unpublishModel(nonExistingName) + return getMachineLearning().unpublishModel(nonExistingName) .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/not-found'); }); it('rejects with invalid-argument when the ModelId is invalid', () => { - return admin.machineLearning().unpublishModel('invalid-model-id') + return getMachineLearning().unpublishModel('invalid-model-id') .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/invalid-argument'); }); it('unpublishes the model successfully', () => { - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { displayName: 'node-integ-test-unpublish-1', tfliteModel: { gcsTfliteUri: 'this will be replaced below' }, }; @@ -340,10 +339,10 @@ describe('admin.machineLearning', () => { .then((createdModel) => { expect(createdModel.validationError).to.be.undefined; expect(createdModel.published).to.be.false; - return admin.machineLearning().publishModel(createdModel.modelId) + return getMachineLearning().publishModel(createdModel.modelId) .then((publishedModel) => { expect(publishedModel.published).to.be.true; - return admin.machineLearning().unpublishModel(publishedModel.modelId) + return getMachineLearning().unpublishModel(publishedModel.modelId) .then((unpublishedModel) => { expect(unpublishedModel.published).to.be.false; }); @@ -357,13 +356,13 @@ describe('admin.machineLearning', () => { describe('getModel()', () => { it('rejects with not-found when the Model does not exist', () => { const nonExistingName = '00000000'; - return admin.machineLearning().getModel(nonExistingName) + return getMachineLearning().getModel(nonExistingName) .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/not-found'); }); it('rejects with invalid-argument when the ModelId is invalid', () => { - return admin.machineLearning().getModel('invalid-model-id') + return getMachineLearning().getModel('invalid-model-id') .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/invalid-argument'); }); @@ -371,7 +370,7 @@ describe('admin.machineLearning', () => { it('resolves with existing Model', () => { return createTemporaryModel() .then((expectedModel) => - admin.machineLearning().getModel(expectedModel.modelId) + getMachineLearning().getModel(expectedModel.modelId) .then((actualModel) => { expect(actualModel).to.deep.equal(expectedModel); }), @@ -380,25 +379,25 @@ describe('admin.machineLearning', () => { }); describe('listModels()', () => { - let model1: admin.machineLearning.Model; - let model2: admin.machineLearning.Model; - let model3: admin.machineLearning.Model; + let model1: Model; + let model2: Model; + let model3: Model; before(() => { return Promise.all([ - admin.machineLearning().createModel({ + getMachineLearning().createModel({ displayName: 'node-integ-list1', tags: ['node-integ-tag-1'], }), - admin.machineLearning().createModel({ + getMachineLearning().createModel({ displayName: 'node-integ-list2', tags: ['node-integ-tag-1'], }), - admin.machineLearning().createModel({ + getMachineLearning().createModel({ displayName: 'node-integ-list3', tags: ['node-integ-tag-1'], })]) - .then(([m1, m2, m3]: admin.machineLearning.Model[]) => { + .then(([m1, m2, m3]: Model[]) => { model1 = m1; model2 = m2; model3 = m3; @@ -407,14 +406,14 @@ describe('admin.machineLearning', () => { after(() => { return Promise.all([ - admin.machineLearning().deleteModel(model1.modelId), - admin.machineLearning().deleteModel(model2.modelId), - admin.machineLearning().deleteModel(model3.modelId), + getMachineLearning().deleteModel(model1.modelId), + getMachineLearning().deleteModel(model2.modelId), + getMachineLearning().deleteModel(model3.modelId), ]); }); it('resolves with a list of models', () => { - return admin.machineLearning().listModels({ pageSize: 100 }) + return getMachineLearning().listModels({ pageSize: 100 }) .then((modelList) => { expect(modelList.models.length).to.be.at.least(2); expect(modelList.models).to.deep.include(model1); @@ -424,7 +423,7 @@ describe('admin.machineLearning', () => { }); it('respects page size', () => { - return admin.machineLearning().listModels({ pageSize: 2 }) + return getMachineLearning().listModels({ pageSize: 2 }) .then((modelList) => { expect(modelList.models.length).to.equal(2); expect(modelList.pageToken).not.to.be.empty; @@ -432,7 +431,7 @@ describe('admin.machineLearning', () => { }); it('filters by exact displayName', () => { - return admin.machineLearning().listModels({ filter: 'displayName=node-integ-list1' }) + return getMachineLearning().listModels({ filter: 'displayName=node-integ-list1' }) .then((modelList) => { expect(modelList.models.length).to.equal(1); expect(modelList.models[0]).to.deep.equal(model1); @@ -441,7 +440,7 @@ describe('admin.machineLearning', () => { }); it('filters by displayName prefix', () => { - return admin.machineLearning().listModels({ filter: 'displayName:node-integ-list*', pageSize: 100 }) + return getMachineLearning().listModels({ filter: 'displayName:node-integ-list*', pageSize: 100 }) .then((modelList) => { expect(modelList.models.length).to.be.at.least(3); expect(modelList.models).to.deep.include(model1); @@ -452,7 +451,7 @@ describe('admin.machineLearning', () => { }); it('filters by tag', () => { - return admin.machineLearning().listModels({ filter: 'tags:node-integ-tag-1', pageSize: 100 }) + return getMachineLearning().listModels({ filter: 'tags:node-integ-tag-1', pageSize: 100 }) .then((modelList) => { expect(modelList.models.length).to.be.at.least(3); expect(modelList.models).to.deep.include(model1); @@ -463,11 +462,11 @@ describe('admin.machineLearning', () => { }); it('handles pageTokens properly', () => { - return admin.machineLearning().listModels({ filter: 'displayName:node-integ-list*', pageSize: 2 }) + return getMachineLearning().listModels({ filter: 'displayName:node-integ-list*', pageSize: 2 }) .then((modelList) => { expect(modelList.models.length).to.equal(2); expect(modelList.pageToken).not.to.be.undefined; - return admin.machineLearning().listModels({ + return getMachineLearning().listModels({ filter: 'displayName:node-integ-list*', pageSize: 2, pageToken: modelList.pageToken @@ -480,7 +479,7 @@ describe('admin.machineLearning', () => { }); it('successfully returns an empty list of models', () => { - return admin.machineLearning().listModels({ filter: 'displayName=non-existing-model' }) + return getMachineLearning().listModels({ filter: 'displayName=non-existing-model' }) .then((modelList) => { expect(modelList.models.length).to.equal(0); expect(modelList.pageToken).to.be.undefined; @@ -488,7 +487,7 @@ describe('admin.machineLearning', () => { }); it('rejects with invalid argument if the filter is invalid', () => { - return admin.machineLearning().listModels({ filter: 'invalidFilterItem=foo' }) + return getMachineLearning().listModels({ filter: 'invalidFilterItem=foo' }) .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/invalid-argument'); }); @@ -497,22 +496,22 @@ describe('admin.machineLearning', () => { describe('deleteModel()', () => { it('rejects with not-found when the Model does not exist', () => { const nonExistingName = '00000000'; - return admin.machineLearning().deleteModel(nonExistingName) + return getMachineLearning().deleteModel(nonExistingName) .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/not-found'); }); it('rejects with invalid-argument when the Model ID is invalid', () => { - return admin.machineLearning().deleteModel('invalid-model-id') + return getMachineLearning().deleteModel('invalid-model-id') .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/invalid-argument'); }); it('deletes existing Model', () => { return createTemporaryModel().then((model) => { - return admin.machineLearning().deleteModel(model.modelId) + return getMachineLearning().deleteModel(model.modelId) .then(() => { - return admin.machineLearning().getModel(model.modelId) + return getMachineLearning().getModel(model.modelId) .should.eventually.be.rejected.and.have.property('code', 'machine-learning/not-found'); }) .then(() => { @@ -524,7 +523,7 @@ describe('admin.machineLearning', () => { }); -function verifyModel(model: admin.machineLearning.Model, expectedOptions: admin.machineLearning.ModelOptions): void { +function verifyModel(model: Model, expectedOptions: ModelOptions): void { if (expectedOptions.displayName) { expect(model.displayName).to.equal(expectedOptions.displayName); } else { @@ -548,7 +547,7 @@ function verifyModel(model: admin.machineLearning.Model, expectedOptions: admin. } } -function verifyGcsTfliteModel(model: admin.machineLearning.Model, expectedOptions: GcsTfliteModelOptions): void { +function verifyGcsTfliteModel(model: Model, expectedOptions: GcsTfliteModelOptions): void { const expectedGcsTfliteUri = expectedOptions.tfliteModel.gcsTfliteUri; expect(model.tfliteModel!.gcsTfliteUri).to.equal(expectedGcsTfliteUri); if (expectedGcsTfliteUri.endsWith('invalid_model.tflite')) { @@ -560,7 +559,7 @@ function verifyGcsTfliteModel(model: admin.machineLearning.Model, expectedOption } } -function verifyAutomlTfliteModel(model: admin.machineLearning.Model, expectedOptions: AutoMLTfliteModelOptions): void { +function verifyAutomlTfliteModel(model: Model, expectedOptions: AutoMLTfliteModelOptions): void { const expectedAutomlReference = expectedOptions.tfliteModel.automlModel; expect(model.tfliteModel!.automlModel).to.equal(expectedAutomlReference); expect(model.validationError).to.be.undefined; diff --git a/test/integration/messaging.spec.ts b/test/integration/messaging.spec.ts index d8703b5335..bc3533893e 100644 --- a/test/integration/messaging.spec.ts +++ b/test/integration/messaging.spec.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import * as admin from '../../lib/index'; import * as chai from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; +import { Message, MulticastMessage, getMessaging } from '../../lib/messaging/index'; chai.should(); chai.use(chaiAsPromised); @@ -38,7 +38,7 @@ const condition = '"test0" in topics || ("test1" in topics && "test2" in topics) const invalidTopic = 'topic-$%#^'; -const message: admin.messaging.Message = { +const message: Message = { data: { foo: 'bar', }, @@ -102,15 +102,15 @@ const options = { describe('admin.messaging', () => { it('send(message, dryRun) returns a message ID', () => { - return admin.messaging().send(message, true) + return getMessaging().send(message, true) .then((name) => { expect(name).matches(/^projects\/.*\/messages\/.*$/); }); }); it('sendAll()', () => { - const messages: admin.messaging.Message[] = [message, message, message]; - return admin.messaging().sendAll(messages, true) + const messages: Message[] = [message, message, message]; + return getMessaging().sendAll(messages, true) .then((response) => { expect(response.responses.length).to.equal(messages.length); expect(response.successCount).to.equal(messages.length); @@ -123,11 +123,11 @@ describe('admin.messaging', () => { }); it('sendAll(500)', () => { - const messages: admin.messaging.Message[] = []; + const messages: Message[] = []; for (let i = 0; i < 500; i++) { messages.push({ topic: `foo-bar-${i % 10}` }); } - return admin.messaging().sendAll(messages, true) + return getMessaging().sendAll(messages, true) .then((response) => { expect(response.responses.length).to.equal(messages.length); expect(response.successCount).to.equal(messages.length); @@ -140,12 +140,12 @@ describe('admin.messaging', () => { }); it('sendMulticast()', () => { - const multicastMessage: admin.messaging.MulticastMessage = { + const multicastMessage: MulticastMessage = { data: message.data, android: message.android, tokens: ['not-a-token', 'also-not-a-token'], }; - return admin.messaging().sendMulticast(multicastMessage, true) + return getMessaging().sendMulticast(multicastMessage, true) .then((response) => { expect(response.responses.length).to.equal(2); expect(response.successCount).to.equal(0); @@ -159,86 +159,86 @@ describe('admin.messaging', () => { }); it('sendToDevice(token) returns a response with multicast ID', () => { - return admin.messaging().sendToDevice(registrationToken, payload, options) + return getMessaging().sendToDevice(registrationToken, payload, options) .then((response) => { expect(typeof response.multicastId).to.equal('number'); }); }); it('sendToDevice(token-list) returns a response with multicat ID', () => { - return admin.messaging().sendToDevice(registrationTokens, payload, options) + return getMessaging().sendToDevice(registrationTokens, payload, options) .then((response) => { expect(typeof response.multicastId).to.equal('number'); }); }); xit('sendToDeviceGroup() returns a response with success count', () => { - return admin.messaging().sendToDeviceGroup(notificationKey, payload, options) + return getMessaging().sendToDeviceGroup(notificationKey, payload, options) .then((response) => { expect(typeof response.successCount).to.equal('number'); }); }); it('sendToTopic() returns a response with message ID', () => { - return admin.messaging().sendToTopic(topic, payload, options) + return getMessaging().sendToTopic(topic, payload, options) .then((response) => { expect(typeof response.messageId).to.equal('number'); }); }); it('sendToCondition() returns a response with message ID', () => { - return admin.messaging().sendToCondition(condition, payload, options) + return getMessaging().sendToCondition(condition, payload, options) .then((response) => { expect(typeof response.messageId).to.equal('number'); }); }); it('sendToDevice(token) fails when called with invalid payload', () => { - return admin.messaging().sendToDevice(registrationToken, invalidPayload, options) + return getMessaging().sendToDevice(registrationToken, invalidPayload, options) .should.eventually.be.rejected.and.have.property('code', 'messaging/invalid-payload'); }); it('sendToDevice(token-list) fails when called with invalid payload', () => { - return admin.messaging().sendToDevice(registrationTokens, invalidPayload, options) + return getMessaging().sendToDevice(registrationTokens, invalidPayload, options) .should.eventually.be.rejected.and.have.property('code', 'messaging/invalid-payload'); }); it('sendToDeviceGroup() fails when called with invalid payload', () => { - return admin.messaging().sendToDeviceGroup(notificationKey, invalidPayload, options) + return getMessaging().sendToDeviceGroup(notificationKey, invalidPayload, options) .should.eventually.be.rejected.and.have.property('code', 'messaging/invalid-payload'); }); it('sendToTopic() fails when called with invalid payload', () => { - return admin.messaging().sendToTopic(topic, invalidPayload, options) + return getMessaging().sendToTopic(topic, invalidPayload, options) .should.eventually.be.rejected.and.have.property('code', 'messaging/invalid-payload'); }); it('sendToCondition() fails when called with invalid payload', () => { - return admin.messaging().sendToCondition(condition, invalidPayload, options) + return getMessaging().sendToCondition(condition, invalidPayload, options) .should.eventually.be.rejected.and.have.property('code', 'messaging/invalid-payload'); }); it('subscribeToTopic() returns a response with success count', () => { - return admin.messaging().subscribeToTopic(registrationToken, topic) + return getMessaging().subscribeToTopic(registrationToken, topic) .then((response) => { expect(typeof response.successCount).to.equal('number'); }); }); it('unsubscribeFromTopic() returns a response with success count', () => { - return admin.messaging().unsubscribeFromTopic(registrationToken, topic) + return getMessaging().unsubscribeFromTopic(registrationToken, topic) .then((response) => { expect(typeof response.successCount).to.equal('number'); }); }); it('subscribeToTopic() fails when called with invalid topic', () => { - return admin.messaging().subscribeToTopic(registrationToken, invalidTopic) + return getMessaging().subscribeToTopic(registrationToken, invalidTopic) .should.eventually.be.rejected.and.have.property('code', 'messaging/invalid-argument'); }); it('unsubscribeFromTopic() fails when called with invalid topic', () => { - return admin.messaging().unsubscribeFromTopic(registrationToken, invalidTopic) + return getMessaging().unsubscribeFromTopic(registrationToken, invalidTopic) .should.eventually.be.rejected.and.have.property('code', 'messaging/invalid-argument'); }); }); diff --git a/test/integration/postcheck/esm/example.test.js b/test/integration/postcheck/esm/example.test.js new file mode 100644 index 0000000000..c731577f66 --- /dev/null +++ b/test/integration/postcheck/esm/example.test.js @@ -0,0 +1,127 @@ +/*! + * @license + * Copyright 2021 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 { expect } from 'chai'; + +import { cert, deleteApp, initializeApp } from 'firebase-admin/app'; +import { getAppCheck, AppCheck } from 'firebase-admin/app-check'; +import { getAuth, Auth } from 'firebase-admin/auth'; +import { getDatabase, getDatabaseWithUrl, ServerValue } from 'firebase-admin/database'; +import { getFirestore, DocumentReference, Firestore, FieldValue } from 'firebase-admin/firestore'; +import { getInstanceId, InstanceId } from 'firebase-admin/instance-id'; +import { getMachineLearning, MachineLearning } from 'firebase-admin/machine-learning'; +import { getMessaging, Messaging } from 'firebase-admin/messaging'; +import { getProjectManagement, ProjectManagement } from 'firebase-admin/project-management'; +import { getRemoteConfig, RemoteConfig } from 'firebase-admin/remote-config'; +import { getSecurityRules, SecurityRules } from 'firebase-admin/security-rules'; +import { getStorage, Storage } from 'firebase-admin/storage'; + +describe('ESM entry points', () => { + let app; + + before(() => { + app = initializeApp({ + credential: cert('mock.key.json'), + databaseURL: 'https://mock.firebaseio.com' + }, 'TestApp'); + }); + + after(() => { + return deleteApp(app); + }); + + it('Should return an initialized App', () => { + expect(app.name).to.equal('TestApp'); + }); + + it('Should return an AppCheck client', () => { + const client = getAppCheck(app); + expect(client).to.be.instanceOf(AppCheck); + }); + + it('Should return an Auth client', () => { + const client = getAuth(app); + expect(client).to.be.instanceOf(Auth); + }); + + it('Should return a Messaging client', () => { + const client = getMessaging(app); + expect(client).to.be.instanceOf(Messaging); + }); + + it('Should return a ProjectManagement client', () => { + const client = getProjectManagement(app); + expect(client).to.be.instanceOf(ProjectManagement); + }); + + it('Should return a SecurityRules client', () => { + const client = getSecurityRules(app); + expect(client).to.be.instanceOf(SecurityRules); + }); + + it('Should return a Database client', () => { + const db = getDatabase(app); + expect(db).to.be.not.undefined; + expect(typeof db.getRules).to.equal('function'); + }); + + it('Should return a Database client for URL', () => { + const db = getDatabaseWithUrl('https://other-mock.firebaseio.com', app); + expect(db).to.be.not.undefined; + expect(typeof db.getRules).to.equal('function'); + }); + + it('Should return a Database ServerValue', () => { + expect(ServerValue.increment(1)).to.be.not.undefined; + }); + + it('Should return a Cloud Storage client', () => { + const storage = getStorage(app); + expect(storage).to.be.instanceOf(Storage) + const bucket = storage.bucket('TestBucket'); + expect(bucket.name).to.equal('TestBucket'); + }); + + it('Should return a Firestore client', () => { + const firestore = getFirestore(app); + expect(firestore).to.be.instanceOf(Firestore); + }); + + it('Should return a Firestore FieldValue', () => { + expect(FieldValue.increment(1)).to.be.not.undefined; + }); + + it('Should return a DocumentReference', () => { + const ref = getFirestore(app).collection('test').doc(); + expect(ref).to.be.instanceOf(DocumentReference); + }); + + it('Should return an InstanceId client', () => { + const client = getInstanceId(app); + expect(client).to.be.instanceOf(InstanceId); + }); + + it('Should return a MachineLearning client', () => { + const client = getMachineLearning(app); + expect(client).to.be.instanceOf(MachineLearning); + }); + + it('Should return a RemoteConfig client', () => { + const client = getRemoteConfig(app); + expect(client).to.be.instanceOf(RemoteConfig); + }); +}); diff --git a/test/integration/postcheck/esm/package.json b/test/integration/postcheck/esm/package.json new file mode 100644 index 0000000000..3dbc1ca591 --- /dev/null +++ b/test/integration/postcheck/esm/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/test/integration/typescript/package.json b/test/integration/postcheck/package.json similarity index 63% rename from test/integration/typescript/package.json rename to test/integration/postcheck/package.json index 8f467c2a41..6345381702 100644 --- a/test/integration/typescript/package.json +++ b/test/integration/postcheck/package.json @@ -1,5 +1,5 @@ { - "name": "firebase-admin-typescript-test", + "name": "firebase-admin-postcheck", "version": "1.0.0", "description": "Firebase Admin SDK post package test cases", "license": "Apache-2.0", @@ -8,12 +8,12 @@ "url": "https://github.com/firebase/firebase-admin-node" }, "devDependencies": { - "@types/chai": "^3.4.35", + "@types/chai": "^4.0.0", "@types/mocha": "^2.2.48", - "@types/node": "^8.10.59", - "chai": "^3.5.0", - "mocha": "^5.2.0", - "ts-node": "^3.3.0", + "@types/node": "^10.10.0", + "chai": "^4.2.0", + "mocha": "^8.0.0", + "ts-node": "^9.0.0", "typescript": "^3.7.3" } } diff --git a/test/integration/typescript/tsconfig.json b/test/integration/postcheck/tsconfig.json similarity index 89% rename from test/integration/typescript/tsconfig.json rename to test/integration/postcheck/tsconfig.json index 6820c83387..85001669ec 100644 --- a/test/integration/typescript/tsconfig.json +++ b/test/integration/postcheck/tsconfig.json @@ -11,6 +11,6 @@ ] }, "files": [ - "src/example.ts" + "./typescript/example.ts" ] } diff --git a/test/integration/postcheck/typescript/example-modular.test.ts b/test/integration/postcheck/typescript/example-modular.test.ts new file mode 100644 index 0000000000..0d67abade4 --- /dev/null +++ b/test/integration/postcheck/typescript/example-modular.test.ts @@ -0,0 +1,133 @@ +/*! + * @license + * Copyright 2021 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 { expect } from 'chai'; + +import { cert, deleteApp, initializeApp, App } from 'firebase-admin/app'; +import { getAppCheck, AppCheck } from 'firebase-admin/app-check'; +import { getAuth, Auth } from 'firebase-admin/auth'; +import { getDatabase, getDatabaseWithUrl, Database, ServerValue } from 'firebase-admin/database'; +import { getFirestore, DocumentReference, Firestore, FieldValue } from 'firebase-admin/firestore'; +import { getInstanceId, InstanceId } from 'firebase-admin/instance-id'; +import { getMachineLearning, MachineLearning } from 'firebase-admin/machine-learning'; +import { getMessaging, Messaging } from 'firebase-admin/messaging'; +import { getProjectManagement, ProjectManagement } from 'firebase-admin/project-management'; +import { getRemoteConfig, RemoteConfig } from 'firebase-admin/remote-config'; +import { getSecurityRules, SecurityRules } from 'firebase-admin/security-rules'; +import { getStorage, Storage } from 'firebase-admin/storage'; + +import { Bucket } from '@google-cloud/storage'; + + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const serviceAccount = require('../mock.key.json'); + +describe('Modular API', () => { + let app: App; + + before(() => { + app = initializeApp({ + credential: cert(serviceAccount), + databaseURL: 'https://mock.firebaseio.com' + }, 'TestApp'); + }); + + after(() => { + return deleteApp(app); + }); + + it('Should return an initialized App', () => { + expect(app.name).to.equal('TestApp'); + }); + + it('Should return an AppCheck client', () => { + const client = getAppCheck(app); + expect(client).to.be.instanceOf(AppCheck); + }); + + it('Should return an Auth client', () => { + const client = getAuth(app); + expect(client).to.be.instanceOf(Auth); + }); + + it('Should return a Messaging client', () => { + const client = getMessaging(app); + expect(client).to.be.instanceOf(Messaging); + }); + + it('Should return a ProjectManagement client', () => { + const client = getProjectManagement(app); + expect(client).to.be.instanceOf(ProjectManagement); + }); + + it('Should return a SecurityRules client', () => { + const client = getSecurityRules(app); + expect(client).to.be.instanceOf(SecurityRules); + }); + + it('Should return a Database client', () => { + const db: Database = getDatabase(app); + expect(db).to.be.not.undefined; + expect(typeof db.getRules).to.equal('function'); + }); + + it('Should return a Database client for URL', () => { + const db: Database = getDatabaseWithUrl('https://other-mock.firebaseio.com', app); + expect(db).to.be.not.undefined; + expect(typeof db.getRules).to.equal('function'); + }); + + it('Should return a Database ServerValue', () => { + expect(ServerValue.increment(1)).to.be.not.undefined; + }); + + it('Should return a Cloud Storage client', () => { + const storage = getStorage(app); + expect(storage).to.be.instanceOf(Storage) + const bucket: Bucket = storage.bucket('TestBucket'); + expect(bucket.name).to.equal('TestBucket'); + }); + + it('Should return a Firestore client', () => { + const firestore = getFirestore(app); + expect(firestore).to.be.instanceOf(Firestore); + }); + + it('Should return a Firestore FieldValue', () => { + expect(FieldValue.increment(1)).to.be.not.undefined; + }); + + it('Should return a DocumentReference', () => { + const ref = getFirestore(app).collection('test').doc(); + expect(ref).to.be.instanceOf(DocumentReference); + }); + + it('Should return an InstanceId client', () => { + const client = getInstanceId(app); + expect(client).to.be.instanceOf(InstanceId); + }); + + it('Should return a MachineLearning client', () => { + const client = getMachineLearning(app); + expect(client).to.be.instanceOf(MachineLearning); + }); + + it('Should return a RemoteConfig client', () => { + const client = getRemoteConfig(app); + expect(client).to.be.instanceOf(RemoteConfig); + }); +}); diff --git a/test/integration/typescript/src/example.test.ts b/test/integration/postcheck/typescript/example.test.ts similarity index 96% rename from test/integration/typescript/src/example.test.ts rename to test/integration/postcheck/typescript/example.test.ts index 5aa63c083d..2d97196477 100644 --- a/test/integration/typescript/src/example.test.ts +++ b/test/integration/postcheck/typescript/example.test.ts @@ -25,8 +25,12 @@ import * as admin from 'firebase-admin'; // eslint-disable-next-line @typescript-eslint/no-var-requires const serviceAccount = require('../mock.key.json'); -describe('Init App', () => { - const app: admin.app.App = initApp(serviceAccount, 'TestApp'); +describe('Legacy API', () => { + let app: admin.app.App; + + before(() => { + app = initApp(serviceAccount, 'TestApp'); + }); after(() => { return app.delete(); diff --git a/test/integration/typescript/src/example.ts b/test/integration/postcheck/typescript/example.ts similarity index 100% rename from test/integration/typescript/src/example.ts rename to test/integration/postcheck/typescript/example.ts diff --git a/test/integration/project-management.spec.ts b/test/integration/project-management.spec.ts index de26f4aaf2..0d24566394 100644 --- a/test/integration/project-management.spec.ts +++ b/test/integration/project-management.spec.ts @@ -17,8 +17,10 @@ import * as _ from 'lodash'; import * as chai from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; -import * as admin from '../../lib/index'; import { projectId } from './setup'; +import { + AndroidApp, IosApp, ShaCertificate, getProjectManagement, +} from '../../lib/project-management/index'; const APP_NAMESPACE_PREFIX = 'com.adminsdkintegrationtest.a'; const APP_NAMESPACE_SUFFIX_LENGTH = 15; @@ -37,8 +39,8 @@ chai.use(chaiAsPromised); describe('admin.projectManagement', () => { - let androidApp: admin.projectManagement.AndroidApp; - let iosApp: admin.projectManagement.IosApp; + let androidApp: AndroidApp; + let iosApp: IosApp; before(() => { const androidPromise = ensureAndroidApp() @@ -55,7 +57,7 @@ describe('admin.projectManagement', () => { describe('listAndroidApps()', () => { it('successfully lists Android apps', () => { - return admin.projectManagement().listAndroidApps() + return getProjectManagement().listAndroidApps() .then((apps) => Promise.all(apps.map((app) => app.getMetadata()))) .then((metadatas) => { expect(metadatas.length).to.be.at.least(1); @@ -69,7 +71,7 @@ describe('admin.projectManagement', () => { describe('listIosApps()', () => { it('successfully lists iOS apps', () => { - return admin.projectManagement().listIosApps() + return getProjectManagement().listIosApps() .then((apps) => Promise.all(apps.map((app) => app.getMetadata()))) .then((metadatas) => { expect(metadatas.length).to.be.at.least(1); @@ -86,14 +88,14 @@ describe('admin.projectManagement', () => { const newDisplayName = generateUniqueProjectDisplayName(); // TODO(caot): verify that project name has been renamed successfully after adding the ability // to get project metadata. - return admin.projectManagement().setDisplayName(newDisplayName) + return getProjectManagement().setDisplayName(newDisplayName) .should.eventually.be.fulfilled; }); }); describe('listAppMetadata()', () => { it('successfully lists metadata of all apps', () => { - return admin.projectManagement().listAppMetadata() + return getProjectManagement().listAppMetadata() .then((metadatas) => { expect(metadatas.length).to.be.at.least(2); const testAppMetadatas = metadatas.filter((metadata) => @@ -158,7 +160,7 @@ describe('admin.projectManagement', () => { .then((certs) => { expect(certs.length).to.equal(0); - const shaCertificate = admin.projectManagement().shaCertificate(SHA_256_HASH); + const shaCertificate = getProjectManagement().shaCertificate(SHA_256_HASH); return androidApp.addShaCertificate(shaCertificate); }) .then(() => androidApp.getShaCertificates()) @@ -179,7 +181,7 @@ describe('admin.projectManagement', () => { it('add a cert and then remove it fails due to missing resourceName', () => { const shaCertificate = - admin.projectManagement().shaCertificate(SHA_256_HASH); + getProjectManagement().shaCertificate(SHA_256_HASH); return androidApp.addShaCertificate(shaCertificate) .then(() => androidApp.deleteShaCertificate(shaCertificate)) .should.eventually.be @@ -211,20 +213,20 @@ describe('admin.projectManagement', () => { /** * Ensures that an Android app owned by these integration tests exist. If not one will be created. * - * @return {Promise} Android app owned by these integration tests. + * @return Android app owned by these integration tests. */ -function ensureAndroidApp(): Promise { - return admin.projectManagement().listAndroidApps() +function ensureAndroidApp(): Promise { + return getProjectManagement().listAndroidApps() .then((apps) => Promise.all(apps.map((app) => app.getMetadata()))) .then((metadatas) => { const metadataOwnedByTest = metadatas.find((metadata) => isIntegrationTestApp(metadata.packageName)); if (metadataOwnedByTest) { - return admin.projectManagement().androidApp(metadataOwnedByTest.appId); + return getProjectManagement().androidApp(metadataOwnedByTest.appId); } // If no Android app owned by these integration tests was found, then create one. - return admin.projectManagement() + return getProjectManagement() .createAndroidApp(generateUniqueAppNamespace(), generateUniqueAppDisplayName()); }); } @@ -232,20 +234,20 @@ function ensureAndroidApp(): Promise { /** * Ensures that an iOS app owned by these integration tests exist. If not one will be created. * - * @return {Promise} iOS app owned by these integration tests. + * @return iOS app owned by these integration tests. */ -function ensureIosApp(): Promise { - return admin.projectManagement().listIosApps() +function ensureIosApp(): Promise { + return getProjectManagement().listIosApps() .then((apps) => Promise.all(apps.map((app) => app.getMetadata()))) .then((metadatas) => { const metadataOwnedByTest = metadatas.find((metadata) => isIntegrationTestApp(metadata.bundleId)); if (metadataOwnedByTest) { - return admin.projectManagement().iosApp(metadataOwnedByTest.appId); + return getProjectManagement().iosApp(metadataOwnedByTest.appId); } // If no iOS app owned by these integration tests was found, then create one. - return admin.projectManagement() + return getProjectManagement() .createIosApp(generateUniqueAppNamespace(), generateUniqueAppDisplayName()); }); } @@ -253,51 +255,51 @@ function ensureIosApp(): Promise { /** * Deletes all SHA certificates from the specified Android app. */ -function deleteAllShaCertificates(androidApp: admin.projectManagement.AndroidApp): Promise { +function deleteAllShaCertificates(androidApp: AndroidApp): Promise { return androidApp.getShaCertificates() - .then((shaCertificates: admin.projectManagement.ShaCertificate[]) => { + .then((shaCertificates: ShaCertificate[]) => { return Promise.all(shaCertificates.map((cert) => androidApp.deleteShaCertificate(cert))); }) .then(() => undefined); } /** - * @return {string} Dot-separated string that can be used as a unique package name or bundle ID. + * @return Dot-separated string that can be used as a unique package name or bundle ID. */ function generateUniqueAppNamespace(): string { return APP_NAMESPACE_PREFIX + generateRandomString(APP_NAMESPACE_SUFFIX_LENGTH); } /** - * @return {string} Dot-separated string that can be used as a unique app display name. + * @return Dot-separated string that can be used as a unique app display name. */ function generateUniqueAppDisplayName(): string { return APP_DISPLAY_NAME_PREFIX + generateRandomString(APP_DISPLAY_NAME_SUFFIX_LENGTH); } /** - * @return {string} string that can be used as a unique project display name. + * @return string that can be used as a unique project display name. */ function generateUniqueProjectDisplayName(): string { return PROJECT_DISPLAY_NAME_PREFIX + generateRandomString(PROJECT_DISPLAY_NAME_SUFFIX_LENGTH); } /** - * @return {boolean} True if the specified appNamespace belongs to these integration tests. + * @return True if the specified appNamespace belongs to these integration tests. */ function isIntegrationTestApp(appNamespace: string): boolean { return appNamespace ? appNamespace.startsWith(APP_NAMESPACE_PREFIX) : false; } /** - * @return {boolean} True if the specified appDisplayName belongs to these integration tests. + * @return True if the specified appDisplayName belongs to these integration tests. */ function isIntegrationTestAppDisplayName(appDisplayName: string | undefined): boolean { return appDisplayName ? appDisplayName.startsWith(APP_DISPLAY_NAME_PREFIX) : false; } /** - * @return {string} A randomly generated alphanumeric string, of the specified length. + * @return A randomly generated alphanumeric string, of the specified length. */ function generateRandomString(stringLength: number): string { return _.times(stringLength, () => _.random(35).toString(36)).join(''); diff --git a/test/integration/remote-config.spec.ts b/test/integration/remote-config.spec.ts index 29078e0d3f..b8b75500af 100644 --- a/test/integration/remote-config.spec.ts +++ b/test/integration/remote-config.spec.ts @@ -14,10 +14,15 @@ * limitations under the License. */ -import * as admin from '../../lib/index'; import * as chai from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import { deepCopy } from '../../src/utils/deep-copy'; +import { + getRemoteConfig, + ParameterValueType, + RemoteConfigCondition, + RemoteConfigTemplate, +} from '../../lib/remote-config/index'; chai.should(); chai.use(chaiAsPromised); @@ -29,12 +34,12 @@ const VALID_PARAMETERS = { holiday_promo_enabled: { defaultValue: { useInAppDefault: true }, description: 'promo indicator', - valueType: 'STRING' as admin.remoteConfig.ParameterValueType, + valueType: 'STRING' as ParameterValueType, }, // eslint-disable-next-line @typescript-eslint/camelcase welcome_message: { defaultValue: { value: `welcome text ${Date.now()}` }, - valueType: 'STRING' as admin.remoteConfig.ParameterValueType, + valueType: 'STRING' as ParameterValueType, conditionalValues: { ios: { value: 'welcome ios text' }, android: { value: 'welcome android text' }, @@ -54,13 +59,13 @@ const VALID_PARAMETER_GROUPS = { 'android': { value: 'A Droid must love a pumpkin spice latte.' }, }, description: 'Description of the parameter.', - valueType: 'STRING' as admin.remoteConfig.ParameterValueType, + valueType: 'STRING' as ParameterValueType, }, }, }, }; -const VALID_CONDITIONS: admin.remoteConfig.RemoteConfigCondition[] = [ +const VALID_CONDITIONS: RemoteConfigCondition[] = [ { name: 'ios', expression: 'device.os == \'ios\'', @@ -77,12 +82,12 @@ const VALID_VERSION = { description: `template description ${Date.now()}`, } -let currentTemplate: admin.remoteConfig.RemoteConfigTemplate; +let currentTemplate: RemoteConfigTemplate; describe('admin.remoteConfig', () => { before(async () => { // obtain the most recent template (etag) to perform operations - currentTemplate = await admin.remoteConfig().getTemplate(); + currentTemplate = await getRemoteConfig().getTemplate(); }); it('verify that the etag is read-only', () => { @@ -98,7 +103,7 @@ describe('admin.remoteConfig', () => { currentTemplate.parameters = VALID_PARAMETERS; currentTemplate.parameterGroups = VALID_PARAMETER_GROUPS; currentTemplate.version = VALID_VERSION; - return admin.remoteConfig().validateTemplate(currentTemplate) + return getRemoteConfig().validateTemplate(currentTemplate) .then((template) => { expect(template.etag).matches(/^etag-[0-9]*-[0-9]*$/); expect(template.conditions.length).to.equal(2); @@ -116,7 +121,7 @@ describe('admin.remoteConfig', () => { currentTemplate.parameters = VALID_PARAMETERS; currentTemplate.parameterGroups = VALID_PARAMETER_GROUPS; currentTemplate.version = VALID_VERSION; - return admin.remoteConfig().validateTemplate(currentTemplate) + return getRemoteConfig().validateTemplate(currentTemplate) .should.eventually.be.rejected.and.have.property('code', 'remote-config/invalid-argument'); }); }); @@ -128,7 +133,7 @@ describe('admin.remoteConfig', () => { currentTemplate.parameters = VALID_PARAMETERS; currentTemplate.parameterGroups = VALID_PARAMETER_GROUPS; currentTemplate.version = VALID_VERSION; - return admin.remoteConfig().publishTemplate(currentTemplate) + return getRemoteConfig().publishTemplate(currentTemplate) .then((template) => { expect(template.etag).matches(/^etag-[0-9]*-[0-9]*$/); expect(template.conditions.length).to.equal(2); @@ -146,14 +151,14 @@ describe('admin.remoteConfig', () => { currentTemplate.parameters = VALID_PARAMETERS; currentTemplate.parameterGroups = VALID_PARAMETER_GROUPS; currentTemplate.version = VALID_VERSION; - return admin.remoteConfig().publishTemplate(currentTemplate) + return getRemoteConfig().publishTemplate(currentTemplate) .should.eventually.be.rejected.and.have.property('code', 'remote-config/invalid-argument'); }); }); describe('getTemplate', () => { it('should return the most recently published template', () => { - return admin.remoteConfig().getTemplate() + return getRemoteConfig().getTemplate() .then((template) => { expect(template.etag).matches(/^etag-[0-9]*-[0-9]*$/); expect(template.conditions.length).to.equal(2); @@ -174,23 +179,23 @@ describe('admin.remoteConfig', () => { describe('getTemplateAtVersion', () => { before(async () => { // obtain the current active template - let activeTemplate = await admin.remoteConfig().getTemplate(); + let activeTemplate = await getRemoteConfig().getTemplate(); // publish a new template to create a new version number activeTemplate.version = { description: versionOneDescription }; - activeTemplate = await admin.remoteConfig().publishTemplate(activeTemplate) + activeTemplate = await getRemoteConfig().publishTemplate(activeTemplate) expect(activeTemplate.version).to.be.not.undefined; versionOneNumber = activeTemplate.version!.versionNumber!; // publish another template to create a second version number activeTemplate.version = { description: versionTwoDescription }; - activeTemplate = await admin.remoteConfig().publishTemplate(activeTemplate) + activeTemplate = await getRemoteConfig().publishTemplate(activeTemplate) expect(activeTemplate.version).to.be.not.undefined; versionTwoNumber = activeTemplate.version!.versionNumber!; }); it('should return the requested template version v1', () => { - return admin.remoteConfig().getTemplateAtVersion(versionOneNumber) + return getRemoteConfig().getTemplateAtVersion(versionOneNumber) .then((template) => { expect(template.etag).matches(/^etag-[0-9]*-[0-9]*$/); expect(template.version).to.be.not.undefined; @@ -202,7 +207,7 @@ describe('admin.remoteConfig', () => { describe('listVersions', () => { it('should return the most recently published 2 versions', () => { - return admin.remoteConfig().listVersions({ + return getRemoteConfig().listVersions({ pageSize: 2, }) .then((response) => { @@ -218,7 +223,7 @@ describe('admin.remoteConfig', () => { describe('rollback', () => { it('verify the most recent template version before rollback to the one prior', () => { - return admin.remoteConfig().getTemplate() + return getRemoteConfig().getTemplate() .then((template) => { expect(template.version).to.be.not.undefined; expect(template.version!.versionNumber).equals(versionTwoNumber); @@ -226,7 +231,7 @@ describe('admin.remoteConfig', () => { }); it('should rollback to the requested version', () => { - return admin.remoteConfig().rollback(versionOneNumber) + return getRemoteConfig().rollback(versionOneNumber) .then((template) => { expect(template.version).to.be.not.undefined; expect(template.version!.updateType).equals('ROLLBACK'); @@ -241,14 +246,14 @@ describe('admin.remoteConfig', () => { INVALID_STRINGS.forEach((invalidJson) => { it(`should throw if the json string is ${JSON.stringify(invalidJson)}`, () => { - expect(() => admin.remoteConfig().createTemplateFromJSON(invalidJson)) + expect(() => getRemoteConfig().createTemplateFromJSON(invalidJson)) .to.throw('JSON string must be a valid non-empty string'); }); }); INVALID_JSON_STRINGS.forEach((invalidJson) => { it(`should throw if the json string is ${JSON.stringify(invalidJson)}`, () => { - expect(() => admin.remoteConfig().createTemplateFromJSON(invalidJson)) + expect(() => getRemoteConfig().createTemplateFromJSON(invalidJson)) .to.throw(/Failed to parse the JSON string/); }); }); @@ -266,14 +271,14 @@ describe('admin.remoteConfig', () => { invalidEtagTemplate.etag = invalidEtag; const jsonString = JSON.stringify(invalidEtagTemplate); it(`should throw if the ETag is ${JSON.stringify(invalidEtag)}`, () => { - expect(() => admin.remoteConfig().createTemplateFromJSON(jsonString)) + expect(() => getRemoteConfig().createTemplateFromJSON(jsonString)) .to.throw(`Invalid Remote Config template: ${jsonString}`); }); }); it('should succeed when a valid json string is provided', () => { const jsonString = JSON.stringify(sourceTemplate); - const newTemplate = admin.remoteConfig().createTemplateFromJSON(jsonString); + const newTemplate = getRemoteConfig().createTemplateFromJSON(jsonString); expect(newTemplate.etag).to.equal(sourceTemplate.etag); expect(() => { (currentTemplate as any).etag = 'new-etag'; diff --git a/test/integration/security-rules.spec.ts b/test/integration/security-rules.spec.ts index 648b74c787..eda5f000f4 100644 --- a/test/integration/security-rules.spec.ts +++ b/test/integration/security-rules.spec.ts @@ -15,8 +15,7 @@ */ import * as chai from 'chai'; - -import * as admin from '../../lib/index'; +import { Ruleset, RulesetMetadata, getSecurityRules } from '../../lib/security-rules/index'; const expect = chai.expect; @@ -47,27 +46,27 @@ describe('admin.securityRules', () => { const rulesetsToDelete: string[] = []; - function scheduleForDelete(ruleset: admin.securityRules.Ruleset): void { + function scheduleForDelete(ruleset: Ruleset): void { rulesetsToDelete.push(ruleset.name); } - function unscheduleForDelete(ruleset: admin.securityRules.Ruleset): void { + function unscheduleForDelete(ruleset: Ruleset): void { rulesetsToDelete.splice(rulesetsToDelete.indexOf(ruleset.name), 1); } function deleteTempRulesets(): Promise { const promises: Array> = []; rulesetsToDelete.forEach((rs) => { - promises.push(admin.securityRules().deleteRuleset(rs)); + promises.push(getSecurityRules().deleteRuleset(rs)); }); rulesetsToDelete.splice(0, rulesetsToDelete.length); // Clear out the array. return Promise.all(promises); } - function createTemporaryRuleset(): Promise { + function createTemporaryRuleset(): Promise { const name = 'firestore.rules'; - const rulesFile = admin.securityRules().createRulesFileFromSource(name, SAMPLE_FIRESTORE_RULES); - return admin.securityRules().createRuleset(rulesFile) + const rulesFile = getSecurityRules().createRulesFileFromSource(name, SAMPLE_FIRESTORE_RULES); + return getSecurityRules().createRuleset(rulesFile) .then((ruleset) => { scheduleForDelete(ruleset); return ruleset; @@ -80,14 +79,14 @@ describe('admin.securityRules', () => { describe('createRulesFileFromSource()', () => { it('creates a RulesFile from the source string', () => { - const rulesFile = admin.securityRules().createRulesFileFromSource( + const rulesFile = getSecurityRules().createRulesFileFromSource( RULES_FILE_NAME, SAMPLE_FIRESTORE_RULES); expect(rulesFile.name).to.equal(RULES_FILE_NAME); expect(rulesFile.content).to.equal(SAMPLE_FIRESTORE_RULES); }); it('creates a RulesFile from the source Buffer', () => { - const rulesFile = admin.securityRules().createRulesFileFromSource( + const rulesFile = getSecurityRules().createRulesFileFromSource( 'firestore.rules', Buffer.from(SAMPLE_FIRESTORE_RULES, 'utf-8')); expect(rulesFile.name).to.equal(RULES_FILE_NAME); expect(rulesFile.content).to.equal(SAMPLE_FIRESTORE_RULES); @@ -96,9 +95,9 @@ describe('admin.securityRules', () => { describe('createRuleset()', () => { it('creates a new Ruleset from a given RulesFile', () => { - const rulesFile = admin.securityRules().createRulesFileFromSource( + const rulesFile = getSecurityRules().createRulesFileFromSource( RULES_FILE_NAME, SAMPLE_FIRESTORE_RULES); - return admin.securityRules().createRuleset(rulesFile) + return getSecurityRules().createRuleset(rulesFile) .then((ruleset) => { scheduleForDelete(ruleset); verifyFirestoreRuleset(ruleset); @@ -106,9 +105,9 @@ describe('admin.securityRules', () => { }); it('rejects with invalid-argument when the source is invalid', () => { - const rulesFile = admin.securityRules().createRulesFileFromSource( + const rulesFile = getSecurityRules().createRulesFileFromSource( RULES_FILE_NAME, 'invalid syntax'); - return admin.securityRules().createRuleset(rulesFile) + return getSecurityRules().createRuleset(rulesFile) .should.eventually.be.rejected.and.have.property('code', 'security-rules/invalid-argument'); }); }); @@ -116,19 +115,19 @@ describe('admin.securityRules', () => { describe('getRuleset()', () => { it('rejects with not-found when the Ruleset does not exist', () => { const nonExistingName = '00000000-1111-2222-3333-444444444444'; - return admin.securityRules().getRuleset(nonExistingName) + return getSecurityRules().getRuleset(nonExistingName) .should.eventually.be.rejected.and.have.property('code', 'security-rules/not-found'); }); it('rejects with invalid-argument when the Ruleset name is invalid', () => { - return admin.securityRules().getRuleset('invalid uuid') + return getSecurityRules().getRuleset('invalid uuid') .should.eventually.be.rejected.and.have.property('code', 'security-rules/invalid-argument'); }); it('resolves with existing Ruleset', () => { return createTemporaryRuleset() .then((expectedRuleset) => - admin.securityRules().getRuleset(expectedRuleset.name) + getSecurityRules().getRuleset(expectedRuleset.name) .then((actualRuleset) => { expect(actualRuleset).to.deep.equal(expectedRuleset); }), @@ -137,15 +136,15 @@ describe('admin.securityRules', () => { }); describe('Cloud Firestore', () => { - let oldRuleset: admin.securityRules.Ruleset | null = null; - let newRuleset: admin.securityRules.Ruleset | null = null; + let oldRuleset: Ruleset | null = null; + let newRuleset: Ruleset | null = null; function revertFirestoreRulesetIfModified(): Promise { if (!newRuleset || !oldRuleset) { return Promise.resolve(); } - return admin.securityRules().releaseFirestoreRuleset(oldRuleset); + return getSecurityRules().releaseFirestoreRuleset(oldRuleset); } afterEach(() => { @@ -153,7 +152,7 @@ describe('admin.securityRules', () => { }); it('getFirestoreRuleset() returns the Ruleset currently in effect', () => { - return admin.securityRules().getFirestoreRuleset() + return getSecurityRules().getFirestoreRuleset() .then((ruleset) => { expect(ruleset.name).to.match(RULESET_NAME_PATTERN); const createTime = new Date(ruleset.createTime); @@ -164,10 +163,10 @@ describe('admin.securityRules', () => { }); it('releaseFirestoreRulesetFromSource() applies the specified Ruleset to Firestore', () => { - return admin.securityRules().getFirestoreRuleset() + return getSecurityRules().getFirestoreRuleset() .then((ruleset) => { oldRuleset = ruleset; - return admin.securityRules().releaseFirestoreRulesetFromSource(SAMPLE_FIRESTORE_RULES); + return getSecurityRules().releaseFirestoreRulesetFromSource(SAMPLE_FIRESTORE_RULES); }) .then((ruleset) => { scheduleForDelete(ruleset); @@ -175,7 +174,7 @@ describe('admin.securityRules', () => { expect(ruleset.name).to.not.equal(oldRuleset!.name); verifyFirestoreRuleset(ruleset); - return admin.securityRules().getFirestoreRuleset(); + return getSecurityRules().getFirestoreRuleset(); }) .then((ruleset) => { expect(ruleset.name).to.equal(newRuleset!.name); @@ -185,15 +184,15 @@ describe('admin.securityRules', () => { }); describe('Cloud Storage', () => { - let oldRuleset: admin.securityRules.Ruleset | null = null; - let newRuleset: admin.securityRules.Ruleset | null = null; + let oldRuleset: Ruleset | null = null; + let newRuleset: Ruleset | null = null; function revertStorageRulesetIfModified(): Promise { if (!newRuleset || !oldRuleset) { return Promise.resolve(); } - return admin.securityRules().releaseStorageRuleset(oldRuleset); + return getSecurityRules().releaseStorageRuleset(oldRuleset); } afterEach(() => { @@ -201,7 +200,7 @@ describe('admin.securityRules', () => { }); it('getStorageRuleset() returns the currently applied Storage rules', () => { - return admin.securityRules().getStorageRuleset() + return getSecurityRules().getStorageRuleset() .then((ruleset) => { expect(ruleset.name).to.match(RULESET_NAME_PATTERN); const createTime = new Date(ruleset.createTime); @@ -212,10 +211,10 @@ describe('admin.securityRules', () => { }); it('releaseStorageRulesetFromSource() applies the specified Ruleset to Storage', () => { - return admin.securityRules().getStorageRuleset() + return getSecurityRules().getStorageRuleset() .then((ruleset) => { oldRuleset = ruleset; - return admin.securityRules().releaseStorageRulesetFromSource(SAMPLE_STORAGE_RULES); + return getSecurityRules().releaseStorageRulesetFromSource(SAMPLE_STORAGE_RULES); }) .then((ruleset) => { scheduleForDelete(ruleset); @@ -225,7 +224,7 @@ describe('admin.securityRules', () => { expect(ruleset.name).to.match(RULESET_NAME_PATTERN); const createTime = new Date(ruleset.createTime); expect(ruleset.createTime).equals(createTime.toUTCString()); - return admin.securityRules().getStorageRuleset(); + return getSecurityRules().getStorageRuleset(); }) .then((ruleset) => { expect(ruleset.name).to.equal(newRuleset!.name); @@ -235,12 +234,10 @@ describe('admin.securityRules', () => { describe('listRulesetMetadata()', () => { it('lists all available Rulesets in pages', () => { - type RulesetMetadata = admin.securityRules.RulesetMetadata; - function listAllRulesets( pageToken?: string, results: RulesetMetadata[] = []): Promise { - return admin.securityRules().listRulesetMetadata(100, pageToken) + return getSecurityRules().listRulesetMetadata(100, pageToken) .then((page) => { results.push(...page.rulesets); if (page.nextPageToken) { @@ -262,7 +259,7 @@ describe('admin.securityRules', () => { }); it('lists the specified number of Rulesets', () => { - return admin.securityRules().listRulesetMetadata(2) + return getSecurityRules().listRulesetMetadata(2) .then((page) => { expect(page.rulesets.length).to.be.at.most(2); expect(page.rulesets.length).to.be.at.least(1); @@ -273,20 +270,20 @@ describe('admin.securityRules', () => { describe('deleteRuleset()', () => { it('rejects with not-found when the Ruleset does not exist', () => { const nonExistingName = '00000000-1111-2222-3333-444444444444'; - return admin.securityRules().deleteRuleset(nonExistingName) + return getSecurityRules().deleteRuleset(nonExistingName) .should.eventually.be.rejected.and.have.property('code', 'security-rules/not-found'); }); it('rejects with invalid-argument when the Ruleset name is invalid', () => { - return admin.securityRules().deleteRuleset('invalid uuid') + return getSecurityRules().deleteRuleset('invalid uuid') .should.eventually.be.rejected.and.have.property('code', 'security-rules/invalid-argument'); }); it('deletes existing Ruleset', () => { return createTemporaryRuleset().then((ruleset) => { - return admin.securityRules().deleteRuleset(ruleset.name) + return getSecurityRules().deleteRuleset(ruleset.name) .then(() => { - return admin.securityRules().getRuleset(ruleset.name) + return getSecurityRules().getRuleset(ruleset.name) .should.eventually.be.rejected.and.have.property('code', 'security-rules/not-found'); }) .then(() => { @@ -296,7 +293,7 @@ describe('admin.securityRules', () => { }); }); - function verifyFirestoreRuleset(ruleset: admin.securityRules.Ruleset): void { + function verifyFirestoreRuleset(ruleset: Ruleset): void { expect(ruleset.name).to.match(RULESET_NAME_PATTERN); const createTime = new Date(ruleset.createTime); expect(ruleset.createTime).equals(createTime.toUTCString()); diff --git a/test/integration/setup.ts b/test/integration/setup.ts index 5880a6cd0d..fa217120f2 100644 --- a/test/integration/setup.ts +++ b/test/integration/setup.ts @@ -14,12 +14,13 @@ * limitations under the License. */ -import * as admin from '../../lib/index'; import fs = require('fs'); import minimist = require('minimist'); import path = require('path'); import { random } from 'lodash'; -import { GoogleOAuthAccessToken } from '../../src/credential/index'; +import { + App, Credential, GoogleOAuthAccessToken, cert, deleteApp, initializeApp, +} from '../../lib/app/index' // eslint-disable-next-line @typescript-eslint/no-var-requires const chalk = require('chalk'); @@ -29,17 +30,17 @@ export let storageBucket: string; export let projectId: string; export let apiKey: string; -export let defaultApp: admin.app.App; -export let nullApp: admin.app.App; -export let nonNullApp: admin.app.App; -export let noServiceAccountApp: admin.app.App; +export let defaultApp: App; +export let nullApp: App; +export let nonNullApp: App; +export let noServiceAccountApp: App; export let cmdArgs: any; export const isEmulator = !!process.env.FIREBASE_EMULATOR_HUB; before(() => { - let getCredential: () => {credential?: admin.credential.Credential}; + let getCredential: () => {credential?: Credential}; let serviceAccountId: string; /* tslint:disable:no-console */ @@ -75,7 +76,7 @@ before(() => { )); throw error; } - getCredential = () => ({ credential: admin.credential.cert(serviceAccount) }); + getCredential = () => ({ credential: cert(serviceAccount) }); projectId = serviceAccount.project_id; serviceAccountId = serviceAccount.client_email; } @@ -84,14 +85,14 @@ before(() => { databaseUrl = 'https://' + projectId + '.firebaseio.com'; storageBucket = projectId + '.appspot.com'; - defaultApp = admin.initializeApp({ + defaultApp = initializeApp({ ...getCredential(), projectId, databaseURL: databaseUrl, storageBucket, }); - nullApp = admin.initializeApp({ + nullApp = initializeApp({ ...getCredential(), projectId, databaseURL: databaseUrl, @@ -99,7 +100,7 @@ before(() => { storageBucket, }, 'null'); - nonNullApp = admin.initializeApp({ + nonNullApp = initializeApp({ ...getCredential(), projectId, databaseURL: databaseUrl, @@ -114,7 +115,7 @@ before(() => { noServiceAccountAppCreds.credential = new CertificatelessCredential( noServiceAccountAppCreds.credential) } - noServiceAccountApp = admin.initializeApp({ + noServiceAccountApp = initializeApp({ ...noServiceAccountAppCreds, serviceAccountId, projectId, @@ -125,17 +126,17 @@ before(() => { after(() => { return Promise.all([ - defaultApp.delete(), - nullApp.delete(), - nonNullApp.delete(), - noServiceAccountApp.delete(), + deleteApp(defaultApp), + deleteApp(nullApp), + deleteApp(nonNullApp), + deleteApp(noServiceAccountApp), ]); }); -class CertificatelessCredential implements admin.credential.Credential { - private readonly delegate: admin.credential.Credential; +class CertificatelessCredential implements Credential { + private readonly delegate: Credential; - constructor(delegate: admin.credential.Credential) { + constructor(delegate: Credential) { this.delegate = delegate; } diff --git a/test/integration/storage.spec.ts b/test/integration/storage.spec.ts index 25b7a39e43..f7467ac9fc 100644 --- a/test/integration/storage.spec.ts +++ b/test/integration/storage.spec.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import * as admin from '../../lib/index'; import * as chai from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import { Bucket, File } from '@google-cloud/storage'; import { projectId } from './setup'; +import { getStorage } from '../../lib/storage/index'; chai.should(); chai.use(chaiAsPromised); @@ -28,19 +28,19 @@ const expect = chai.expect; describe('admin.storage', () => { it('bucket() returns a handle to the default bucket', () => { - const bucket: Bucket = admin.storage().bucket(); + const bucket: Bucket = getStorage().bucket(); return verifyBucket(bucket, 'storage().bucket()') .should.eventually.be.fulfilled; }); it('bucket(string) returns a handle to the specified bucket', () => { - const bucket: Bucket = admin.storage().bucket(projectId + '.appspot.com'); + const bucket: Bucket = getStorage().bucket(projectId + '.appspot.com'); return verifyBucket(bucket, 'storage().bucket(string)') .should.eventually.be.fulfilled; }); it('bucket(non-existing) returns a handle which can be queried for existence', () => { - const bucket: Bucket = admin.storage().bucket('non.existing'); + const bucket: Bucket = getStorage().bucket('non.existing'); return bucket.exists() .then((data) => { expect(data[0]).to.be.false; diff --git a/test/resources/mocks.ts b/test/resources/mocks.ts index 152364163e..026faa4f01 100644 --- a/test/resources/mocks.ts +++ b/test/resources/mocks.ts @@ -26,10 +26,8 @@ import * as _ from 'lodash'; import * as jwt from 'jsonwebtoken'; import { AppOptions } from '../../src/firebase-namespace-api'; -import { FirebaseNamespace } from '../../src/firebase-namespace'; -import { FirebaseApp } from '../../src/firebase-app'; -import { credential as _credential, GoogleOAuthAccessToken } from '../../src/credential/index'; -import { ServiceAccountCredential } from '../../src/credential/credential-internal'; +import { FirebaseApp } from '../../src/app/firebase-app'; +import { Credential, GoogleOAuthAccessToken, cert } from '../../src/app/index'; const ALGORITHM = 'RS256' as const; const ONE_HOUR_IN_SECONDS = 60 * 60; @@ -53,7 +51,7 @@ export const databaseAuthVariableOverride = { 'some#string': 'some#val' }; export const storageBucket = 'bucketName.appspot.com'; -export const credential = new ServiceAccountCredential(path.resolve(__dirname, './mock.key.json')); +export const credential = cert(path.resolve(__dirname, './mock.key.json')); export const appOptions: AppOptions = { credential, @@ -82,7 +80,7 @@ export const appOptionsAuthDB: AppOptions = { databaseURL, }; -export class MockCredential implements _credential.Credential { +export class MockCredential implements Credential { public getAccessToken(): Promise { return Promise.resolve({ access_token: 'mock-token', // eslint-disable-line @typescript-eslint/camelcase @@ -92,22 +90,18 @@ export class MockCredential implements _credential.Credential { } export function app(): FirebaseApp { - const namespaceInternals = new FirebaseNamespace().INTERNAL; - namespaceInternals.removeApp = _.noop; - return new FirebaseApp(appOptions, appName, namespaceInternals); + return new FirebaseApp(appOptions, appName); } export function mockCredentialApp(): FirebaseApp { return new FirebaseApp({ credential: new MockCredential(), databaseURL, - }, appName, new FirebaseNamespace().INTERNAL); + }, appName); } export function appWithOptions(options: AppOptions): FirebaseApp { - const namespaceInternals = new FirebaseNamespace().INTERNAL; - namespaceInternals.removeApp = _.noop; - return new FirebaseApp(options, appName, namespaceInternals); + return new FirebaseApp(options, appName); } export function appReturningNullAccessToken(): FirebaseApp { @@ -118,7 +112,7 @@ export function appReturningNullAccessToken(): FirebaseApp { } as any, databaseURL, projectId, - }, appName, new FirebaseNamespace().INTERNAL); + }, appName); } export function appReturningMalformedAccessToken(): FirebaseApp { @@ -128,7 +122,7 @@ export function appReturningMalformedAccessToken(): FirebaseApp { } as any, databaseURL, projectId, - }, appName, new FirebaseNamespace().INTERNAL); + }, appName); } export function appRejectedWhileFetchingAccessToken(): FirebaseApp { @@ -138,7 +132,7 @@ export function appRejectedWhileFetchingAccessToken(): FirebaseApp { } as any, databaseURL, projectId, - }, appName, new FirebaseNamespace().INTERNAL); + }, appName); } export const refreshToken = { diff --git a/test/unit/app-check/app-check-api-client-internal.spec.ts b/test/unit/app-check/app-check-api-client-internal.spec.ts index fba1fba20d..b846fd1c68 100644 --- a/test/unit/app-check/app-check-api-client-internal.spec.ts +++ b/test/unit/app-check/app-check-api-client-internal.spec.ts @@ -25,7 +25,7 @@ import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; import { getSdkVersion } from '../../../src/utils'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { AppCheckApiClient, FirebaseAppCheckError } from '../../../src/app-check/app-check-api-client-internal'; import { FirebaseAppError } from '../../../src/utils/error'; import { deepCopy } from '../../../src/utils/deep-copy'; diff --git a/test/unit/app-check/app-check.spec.ts b/test/unit/app-check/app-check.spec.ts index 05c598f301..5b8b48cc6c 100644 --- a/test/unit/app-check/app-check.spec.ts +++ b/test/unit/app-check/app-check.spec.ts @@ -22,8 +22,8 @@ import * as chai from 'chai'; import * as sinon from 'sinon'; import * as mocks from '../../resources/mocks'; -import { FirebaseApp } from '../../../src/firebase-app'; -import { AppCheck } from '../../../src/app-check/app-check'; +import { FirebaseApp } from '../../../src/app/firebase-app'; +import { AppCheck } from '../../../src/app-check/index'; import { AppCheckApiClient, FirebaseAppCheckError } from '../../../src/app-check/app-check-api-client-internal'; import { AppCheckTokenGenerator } from '../../../src/app-check/token-generator'; import { HttpClient } from '../../../src/utils/api-request'; @@ -181,7 +181,7 @@ describe('AppCheck', () => { }); it('should resolve with VerifyAppCheckTokenResponse on success', () => { - const response = { + const response = { sub: 'app-id', iss: 'https://firebaseappcheck.googleapis.com/123456', // eslint-disable-next-line @typescript-eslint/camelcase diff --git a/test/unit/app-check/token-generator.spec.ts b/test/unit/app-check/token-generator.spec.ts index b7a1b87622..b90341c2ed 100644 --- a/test/unit/app-check/token-generator.spec.ts +++ b/test/unit/app-check/token-generator.spec.ts @@ -32,7 +32,7 @@ import { import { CryptoSignerError, CryptoSignerErrorCode, ServiceAccountSigner } from '../../../src/utils/crypto-signer'; -import { ServiceAccountCredential } from '../../../src/credential/credential-internal'; +import { ServiceAccountCredential } from '../../../src/app/credential-internal'; import { FirebaseAppCheckError } from '../../../src/app-check/app-check-api-client-internal'; import * as utils from '../utils'; @@ -43,7 +43,7 @@ chai.use(chaiAsPromised); const expect = chai.expect; const ALGORITHM = 'RS256'; -const FIVE_MIN_IN_SECONDS = 60 * 5; +const FIVE_MIN_IN_SECONDS = 5 * 60; const FIREBASE_APP_CHECK_AUDIENCE = 'https://firebaseappcheck.googleapis.com/google.firebase.appcheck.v1beta.TokenExchangeService'; /** diff --git a/test/unit/credential/credential.spec.ts b/test/unit/app/credential-internal.spec.ts similarity index 99% rename from test/unit/credential/credential.spec.ts rename to test/unit/app/credential-internal.spec.ts index d34b569bda..4ac283947c 100644 --- a/test/unit/credential/credential.spec.ts +++ b/test/unit/app/credential-internal.spec.ts @@ -32,12 +32,12 @@ import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; import { - GoogleOAuthAccessToken, credential -} from '../../../src/credential/index'; + GoogleOAuthAccessToken, Credential +} from '../../../src/app/index'; import { RefreshTokenCredential, ServiceAccountCredential, ComputeEngineCredential, getApplicationDefault, isApplicationDefault -} from '../../../src/credential/credential-internal'; +} from '../../../src/app/credential-internal'; import { HttpClient } from '../../../src/utils/api-request'; import { Agent } from 'https'; import { FirebaseAppError } from '../../../src/utils/error'; @@ -556,7 +556,7 @@ describe('Credential', () => { }); it('should return false for custom credential', () => { - const c: credential.Credential = { + const c: Credential = { getAccessToken: () => { throw new Error(); }, diff --git a/test/unit/firebase-app.spec.ts b/test/unit/app/firebase-app.spec.ts similarity index 95% rename from test/unit/firebase-app.spec.ts rename to test/unit/app/firebase-app.spec.ts index 1929637f94..c6ec9eaaf6 100644 --- a/test/unit/firebase-app.spec.ts +++ b/test/unit/app/firebase-app.spec.ts @@ -23,27 +23,19 @@ import * as sinon from 'sinon'; import * as sinonChai from 'sinon-chai'; import * as chaiAsPromised from 'chai-as-promised'; -import * as utils from './utils'; -import * as mocks from '../resources/mocks'; - -import { GoogleOAuthAccessToken } from '../../src/credential/index'; -import { ServiceAccountCredential } from '../../src/credential/credential-internal'; -import { FirebaseApp, FirebaseAccessToken } from '../../src/firebase-app'; -import { FirebaseNamespace, FirebaseNamespaceInternals, FIREBASE_CONFIG_VAR } from '../../src/firebase-namespace'; - -import { auth } from '../../src/auth/index'; -import { messaging } from '../../src/messaging/index'; -import { machineLearning } from '../../src/machine-learning/index'; -import { storage } from '../../src/storage/index'; -import { firestore } from '../../src/firestore/index'; -import { database } from '../../src/database/index'; -import { installations } from '../../src/installations/index'; -import { instanceId } from '../../src/instance-id/index'; -import { projectManagement } from '../../src/project-management/index'; -import { securityRules } from '../../src/security-rules/index'; -import { remoteConfig } from '../../src/remote-config/index'; -import { appCheck } from '../../src/app-check/index'; -import { FirebaseAppError, AppErrorCodes } from '../../src/utils/error'; +import * as utils from '../utils'; +import * as mocks from '../../resources/mocks'; + +import { GoogleOAuthAccessToken } from '../../../src/app/index'; +import { ServiceAccountCredential } from '../../../src/app/credential-internal'; +import { FirebaseApp, FirebaseAccessToken } from '../../../src/app/firebase-app'; +import { FirebaseNamespace } from '../../../src/app/firebase-namespace'; +import { AppStore, FIREBASE_CONFIG_VAR } from '../../../src/app/lifecycle'; +import { + auth, messaging, machineLearning, storage, firestore, database, + instanceId, installations, projectManagement, securityRules , remoteConfig, appCheck, +} from '../../../src/firebase-namespace-api'; +import { FirebaseAppError, AppErrorCodes } from '../../../src/utils/error'; import Auth = auth.Auth; import Database = database.Database; @@ -84,7 +76,6 @@ describe('FirebaseApp', () => { let clock: sinon.SinonFakeTimers; let getTokenStub: sinon.SinonStub; let firebaseNamespace: FirebaseNamespace; - let firebaseNamespaceInternals: FirebaseNamespaceInternals; let firebaseConfigVar: string | undefined; beforeEach(() => { @@ -97,10 +88,7 @@ describe('FirebaseApp', () => { firebaseConfigVar = process.env[FIREBASE_CONFIG_VAR]; delete process.env[FIREBASE_CONFIG_VAR]; firebaseNamespace = new FirebaseNamespace(); - firebaseNamespaceInternals = firebaseNamespace.INTERNAL; - - sinon.stub(firebaseNamespaceInternals, 'removeApp'); - mockApp = new FirebaseApp(mocks.appOptions, mocks.appName, firebaseNamespaceInternals); + mockApp = new FirebaseApp(mocks.appOptions, mocks.appName); }); afterEach(() => { @@ -113,7 +101,6 @@ describe('FirebaseApp', () => { } deleteSpy.resetHistory(); - (firebaseNamespaceInternals.removeApp as any).restore(); }); describe('#name', () => { @@ -131,14 +118,14 @@ describe('FirebaseApp', () => { it('should be case sensitive', () => { const newMockAppName = mocks.appName.toUpperCase(); - mockApp = new FirebaseApp(mocks.appOptions, newMockAppName, firebaseNamespaceInternals); + mockApp = new FirebaseApp(mocks.appOptions, newMockAppName); expect(mockApp.name).to.not.equal(mocks.appName); expect(mockApp.name).to.equal(newMockAppName); }); it('should respect leading and trailing whitespace', () => { const newMockAppName = ' ' + mocks.appName + ' '; - mockApp = new FirebaseApp(mocks.appOptions, newMockAppName, firebaseNamespaceInternals); + mockApp = new FirebaseApp(mocks.appOptions, newMockAppName); expect(mockApp.name).to.not.equal(mocks.appName); expect(mockApp.name).to.equal(newMockAppName); }); @@ -335,10 +322,11 @@ describe('FirebaseApp', () => { }); it('should call removeApp() on the Firebase namespace internals', () => { - return mockApp.delete().then(() => { - expect(firebaseNamespaceInternals.removeApp) - .to.have.been.calledOnce - .and.calledWith(mocks.appName); + const store = new AppStore(); + const stub = sinon.stub(store, 'removeApp').resolves(); + const app = new FirebaseApp(mockApp.options, mockApp.name, store); + return app.delete().then(() => { + expect(stub).to.have.been.calledOnce.and.calledWith(mocks.appName); }); }); diff --git a/test/unit/firebase-namespace.spec.ts b/test/unit/app/firebase-namespace.spec.ts similarity index 87% rename from test/unit/firebase-namespace.spec.ts rename to test/unit/app/firebase-namespace.spec.ts index 21fb5ad86a..467854d124 100644 --- a/test/unit/firebase-namespace.spec.ts +++ b/test/unit/app/firebase-namespace.spec.ts @@ -17,14 +17,16 @@ 'use strict'; +import path = require('path'); + import * as _ from 'lodash'; import * as chai from 'chai'; import * as sinonChai from 'sinon-chai'; import * as chaiAsPromised from 'chai-as-promised'; -import * as mocks from '../resources/mocks'; +import * as mocks from '../../resources/mocks'; -import { FirebaseNamespace } from '../../src/firebase-namespace'; +import { FirebaseNamespace } from '../../../src/app/firebase-namespace'; import { enableLogging, Database as DatabaseImpl, @@ -43,32 +45,24 @@ import { v1beta1, setLogFunction, } from '@google-cloud/firestore'; -import { getSdkVersion } from '../../src/utils/index'; - -import { app } from '../../src/firebase-namespace-api'; -import { auth } from '../../src/auth/index'; -import { messaging } from '../../src/messaging/index'; -import { machineLearning } from '../../src/machine-learning/index'; -import { storage } from '../../src/storage/index'; -import { firestore } from '../../src/firestore/index'; -import { database } from '../../src/database/index'; -import { installations } from '../../src/installations/index'; -import { instanceId } from '../../src/instance-id/index'; -import { projectManagement } from '../../src/project-management/index'; -import { securityRules } from '../../src/security-rules/index'; -import { remoteConfig } from '../../src/remote-config/index'; -import { appCheck } from '../../src/app-check/index'; - -import { AppCheck as AppCheckImpl } from '../../src/app-check/app-check'; -import { Auth as AuthImpl } from '../../src/auth/auth'; -import { Installations as InstallationsImpl } from '../../src/installations/installations'; -import { InstanceId as InstanceIdImpl } from '../../src/instance-id/instance-id'; -import { MachineLearning as MachineLearningImpl } from '../../src/machine-learning/machine-learning'; -import { Messaging as MessagingImpl } from '../../src/messaging/messaging'; -import { ProjectManagement as ProjectManagementImpl } from '../../src/project-management/project-management'; -import { RemoteConfig as RemoteConfigImpl } from '../../src/remote-config/remote-config'; -import { SecurityRules as SecurityRulesImpl } from '../../src/security-rules/security-rules'; -import { Storage as StorageImpl } from '../../src/storage/storage'; +import { getSdkVersion } from '../../../src/utils/index'; + +import { + app, auth, messaging, machineLearning, storage, firestore, database, + instanceId, installations, projectManagement, securityRules , remoteConfig, appCheck, +} from '../../../src/firebase-namespace-api'; +import { AppCheck as AppCheckImpl } from '../../../src/app-check/app-check'; +import { Auth as AuthImpl } from '../../../src/auth/auth'; +import { InstanceId as InstanceIdImpl } from '../../../src/instance-id/instance-id'; +import { Installations as InstallationsImpl } from '../../../src/installations/installations'; +import { MachineLearning as MachineLearningImpl } from '../../../src/machine-learning/machine-learning'; +import { Messaging as MessagingImpl } from '../../../src/messaging/messaging'; +import { ProjectManagement as ProjectManagementImpl } from '../../../src/project-management/project-management'; +import { RemoteConfig as RemoteConfigImpl } from '../../../src/remote-config/remote-config'; +import { SecurityRules as SecurityRulesImpl } from '../../../src/security-rules/security-rules'; +import { Storage as StorageImpl } from '../../../src/storage/storage'; + +import { clearGlobalAppDefaultCred } from '../../../src/app/credential-factory'; import App = app.App; import AppCheck = appCheck.AppCheck; @@ -266,66 +260,6 @@ describe('FirebaseNamespace', () => { }); }); - describe('#INTERNAL.removeApp()', () => { - const invalidAppNames = [null, NaN, 0, 1, true, false, [], ['a'], {}, { a: 1 }, _.noop]; - invalidAppNames.forEach((invalidAppName) => { - it('should throw given non-string app name: ' + JSON.stringify(invalidAppName), () => { - expect(() => { - firebaseNamespace.INTERNAL.removeApp(invalidAppName as any); - }).to.throw(`Invalid Firebase app name "${invalidAppName}" provided. App name must be a non-empty string.`); - }); - }); - - it('should throw given empty string app name', () => { - expect(() => { - firebaseNamespace.INTERNAL.removeApp(''); - }).to.throw('Invalid Firebase app name "" provided. App name must be a non-empty string.'); - }); - - it('should throw given an app name which does not correspond to an existing app', () => { - expect(() => { - firebaseNamespace.INTERNAL.removeApp(mocks.appName); - }).to.throw(`Firebase app named "${mocks.appName}" does not exist.`); - }); - - it('should throw given no app name if the default app does not exist', () => { - expect(() => { - (firebaseNamespace as any).INTERNAL.removeApp(); - }).to.throw('No Firebase app name provided. App name must be a non-empty string.'); - }); - - it('should throw given no app name even if the default app exists', () => { - firebaseNamespace.initializeApp(mocks.appOptions); - expect(() => { - (firebaseNamespace as any).INTERNAL.removeApp(); - }).to.throw('No Firebase app name provided. App name must be a non-empty string.'); - }); - - it('should remove the app corresponding to the provided app name from the namespace\'s app list', () => { - firebaseNamespace.initializeApp(mocks.appOptions, mocks.appName); - firebaseNamespace.INTERNAL.removeApp(mocks.appName); - expect(() => { - return firebaseNamespace.app(mocks.appName); - }).to.throw(`Firebase app named "${mocks.appName}" does not exist.`); - }); - - it('should remove the default app from the namespace\'s app list if the default app name is provided', () => { - firebaseNamespace.initializeApp(mocks.appOptions); - firebaseNamespace.INTERNAL.removeApp(DEFAULT_APP_NAME); - expect(() => { - return firebaseNamespace.app(); - }).to.throw('The default Firebase app does not exist.'); - }); - - it('should not be idempotent', () => { - firebaseNamespace.initializeApp(mocks.appOptions, mocks.appName); - firebaseNamespace.INTERNAL.removeApp(mocks.appName); - expect(() => { - firebaseNamespace.INTERNAL.removeApp(mocks.appName); - }).to.throw(`Firebase app named "${mocks.appName}" does not exist.`); - }); - }); - describe('#auth()', () => { it('should throw when called before initializing an app', () => { expect(() => { @@ -467,7 +401,7 @@ describe('FirebaseNamespace', () => { }); }); - describe('#machine-learning()', () => { + describe('#machineLearning()', () => { it('should throw when called before initializating an app', () => { expect(() => { firebaseNamespace.machineLearning(); @@ -806,6 +740,41 @@ describe('FirebaseNamespace', () => { }); }); + describe('credentials', () => { + it('should create a service account credential from object', () => { + const mockCertificateObject = mocks.certificateObject; + const credential = firebaseNamespace.credential.cert(mockCertificateObject); + expect(credential).to.deep.include({ + projectId: mockCertificateObject.project_id, + clientEmail: mockCertificateObject.client_email, + privateKey: mockCertificateObject.private_key, + implicit: false, + }); + }); + + it('should create a refresh token credential from object', () => { + const mockRefreshToken = mocks.refreshToken; + const credential = firebaseNamespace.credential.refreshToken(mockRefreshToken); + expect(credential).to.deep.include({ + implicit: false, + }); + }); + + it('should create application default credentials from environment', () => { + process.env.GOOGLE_APPLICATION_CREDENTIALS = path.resolve(__dirname, '../../resources/mock.key.json'); + const mockCertificateObject = mocks.certificateObject; + const credential = firebaseNamespace.credential.applicationDefault(); + expect(credential).to.deep.include({ + projectId: mockCertificateObject.project_id, + clientEmail: mockCertificateObject.client_email, + privateKey: mockCertificateObject.private_key, + implicit: true, + }); + }); + + after(clearGlobalAppDefaultCred); + }); + describe('#appCheck()', () => { it('should throw when called before initializing an app', () => { expect(() => { diff --git a/test/unit/app/index.spec.ts b/test/unit/app/index.spec.ts new file mode 100644 index 0000000000..9de58baf84 --- /dev/null +++ b/test/unit/app/index.spec.ts @@ -0,0 +1,251 @@ +/*! + * @license + * Copyright 2021 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. + */ + +'use strict'; + +import path = require('path'); + +import * as _ from 'lodash'; +import * as chai from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import * as mocks from '../../resources/mocks'; +import * as sinon from 'sinon'; + +import { + initializeApp, getApp, getApps, deleteApp, SDK_VERSION, + Credential, applicationDefault, cert, refreshToken, +} from '../../../src/app/index'; +import { clearGlobalAppDefaultCred } from '../../../src/app/credential-factory'; +import { defaultAppStore } from '../../../src/app/lifecycle'; + +chai.should(); +chai.use(chaiAsPromised); + +const expect = chai.expect; + + +describe('firebase-admin/app', () => { + afterEach(() => { + return defaultAppStore.clearAllApps(); + }); + + describe('#initializeApp()', () => { + const invalidOptions: any[] = [null, NaN, 0, 1, true, false, '', 'a', [], _.noop]; + invalidOptions.forEach((invalidOption: any) => { + it('should throw given invalid options object: ' + JSON.stringify(invalidOption), () => { + expect(() => { + initializeApp(invalidOption); + }).to.throw('Invalid Firebase app options'); + }); + }); + + it('should use application default credentials when no credentials are explicitly specified', () => { + const app = initializeApp(mocks.appOptionsNoAuth); + expect(app.options).to.have.property('credential'); + expect(app.options.credential).to.not.be.undefined; + }); + + it('should not modify the provided options object', () => { + const optionsClone = _.clone(mocks.appOptions); + initializeApp(mocks.appOptions); + expect(optionsClone).to.deep.equal(mocks.appOptions); + }); + + const invalidCredentials = [undefined, null, NaN, 0, 1, '', 'a', true, false, '', _.noop]; + invalidCredentials.forEach((invalidCredential) => { + it('should throw given non-object credential: ' + JSON.stringify(invalidCredential), () => { + expect(() => { + initializeApp({ + credential: invalidCredential as any, + }); + }).to.throw('Invalid Firebase app options'); + }); + }); + + it('should throw given a credential which doesn\'t implement the Credential interface', () => { + expect(() => { + initializeApp({ + credential: {}, + } as any); + }).to.throw('Invalid Firebase app options'); + + expect(() => { + initializeApp({ + credential: { + getAccessToken: true, + }, + } as any); + }).to.throw('Invalid Firebase app options'); + }); + + it('should initialize App instance without extended service methods', () => { + const app = initializeApp(mocks.appOptions); + expect((app as any).__extended).to.be.undefined; + expect((app as any).auth).to.be.undefined; + }); + }); + + describe('#getApp()', () => { + const invalidOptions: any[] = [null, NaN, 0, 1, true, false, '', [], _.noop]; + invalidOptions.forEach((invalidOption: any) => { + it('should throw given invalid app name: ' + JSON.stringify(invalidOption), () => { + expect(() => { + getApp(invalidOption); + }).to.throw('Invalid Firebase app name'); + }); + }); + + it('should return default app when name not specified', () => { + initializeApp(mocks.appOptionsNoAuth); + const defaulApp = getApp(); + expect(defaulApp.name).to.equal('[DEFAULT]'); + }); + + it('should return named app when available', () => { + initializeApp(mocks.appOptionsNoAuth, 'testApp'); + const testApp = getApp('testApp'); + expect(testApp.name).to.equal('testApp'); + }); + + it('should throw when the default app does not exist', () => { + expect(() => getApp()).to.throw('The default Firebase app does not exist'); + }); + + it('should throw when the specified app does not exist', () => { + expect(() => getApp('testApp')).to.throw('Firebase app named "testApp" does not exist'); + }); + }); + + describe('#getApps()', () => { + it('should return empty array when no apps available', () => { + const apps = getApps(); + expect(apps).to.be.empty; + }); + + it('should return a non-empty array of apps', () => { + initializeApp(mocks.appOptionsNoAuth); + initializeApp(mocks.appOptionsNoAuth, 'testApp'); + const apps = getApps(); + expect(apps.length).to.equal(2); + + const appNames = apps.map((a) => a.name); + expect(appNames).to.contain('[DEFAULT]'); + expect(appNames).to.contain('testApp'); + }); + + it('apps array is immutable', () => { + initializeApp(mocks.appOptionsNoAuth); + const apps = getApps(); + expect(apps.length).to.equal(1); + apps.push({} as any); + + expect(getApps().length).to.equal(1); + }); + }); + + describe('#deleteApp()', () => { + it('should delete the specified app', () => { + const app = initializeApp(mocks.appOptionsNoAuth); + const spy = sinon.spy(app as any, 'delete'); + deleteApp(app); + expect(getApps()).to.be.empty; + expect(spy.calledOnce); + }); + + it('should throw if the app is already deleted', () => { + const app = initializeApp(mocks.appOptionsNoAuth); + deleteApp(app); + expect(() => deleteApp(app)).to.throw('The default Firebase app does not exist'); + }); + + const invalidOptions: any[] = [null, NaN, 0, 1, true, false, '', [], _.noop]; + invalidOptions.forEach((invalidOption: any) => { + it('should throw given invalid app: ' + JSON.stringify(invalidOption), () => { + expect(() => { + deleteApp(invalidOption); + }).to.throw('Invalid app argument'); + }); + }); + }); + + describe('SDK_VERSION', () => { + it('should indicate the current version of the SDK', () => { + const { version } = require('../../../package.json'); // eslint-disable-line @typescript-eslint/no-var-requires + expect(SDK_VERSION).to.equal(version); + }); + }); + + describe('#cert()', () => { + it('should create a service account credential from object', () => { + const mockCertificateObject = mocks.certificateObject; + const credential: Credential = cert(mockCertificateObject); + expect(credential).to.deep.include({ + projectId: mockCertificateObject.project_id, + clientEmail: mockCertificateObject.client_email, + privateKey: mockCertificateObject.private_key, + implicit: false, + }); + }); + + it('should create a service account credential from file path', () => { + const filePath = path.resolve(__dirname, '../../resources/mock.key.json'); + const mockCertificateObject = mocks.certificateObject; + const credential: Credential = cert(filePath); + expect(credential).to.deep.include({ + projectId: mockCertificateObject.project_id, + clientEmail: mockCertificateObject.client_email, + privateKey: mockCertificateObject.private_key, + implicit: false, + }); + }); + }); + + describe('#refreshToken()', () => { + it('should create a refresh token credential from object', () => { + const mockRefreshToken = mocks.refreshToken; + const credential: Credential = refreshToken(mockRefreshToken); + expect(credential).to.deep.include({ + implicit: false, + }); + }); + }); + + describe('#applicationDefault()', () => { + before(() => { + process.env.GOOGLE_APPLICATION_CREDENTIALS = path.resolve(__dirname, '../../resources/mock.key.json'); + }); + + it('should create application default credentials from environment', () => { + const mockCertificateObject = mocks.certificateObject; + const credential: Credential = applicationDefault(); + expect(credential).to.deep.include({ + projectId: mockCertificateObject.project_id, + clientEmail: mockCertificateObject.client_email, + privateKey: mockCertificateObject.private_key, + implicit: true, + }); + }); + + it('should cache application default credentials globally', () => { + const credential1: Credential = applicationDefault(); + const credential2: Credential = applicationDefault(); + expect(credential1).to.equal(credential2); + }); + + after(clearGlobalAppDefaultCred); + }); +}); diff --git a/test/unit/auth/auth-api-request.spec.ts b/test/unit/auth/auth-api-request.spec.ts index 29ccf6eb4d..75000300e7 100644 --- a/test/unit/auth/auth-api-request.spec.ts +++ b/test/unit/auth/auth-api-request.spec.ts @@ -27,7 +27,7 @@ import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; import { deepCopy, deepExtend } from '../../../src/utils/deep-copy'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { HttpClient, HttpRequestConfig } from '../../../src/utils/api-request'; import * as validator from '../../../src/utils/validator'; import { @@ -43,18 +43,11 @@ import { ActionCodeSettingsBuilder } from '../../../src/auth/action-code-setting import { SAMLConfigServerResponse } from '../../../src/auth/auth-config'; import { expectUserImportResult } from './user-import-builder.spec'; import { getSdkVersion } from '../../../src/utils/index'; -import { auth } from '../../../src/auth/index'; - -import UserImportRecord = auth.UserImportRecord; -import OIDCAuthProviderConfig = auth.OIDCAuthProviderConfig; -import SAMLAuthProviderConfig = auth.SAMLAuthProviderConfig; -import OIDCUpdateAuthProviderRequest = auth.OIDCUpdateAuthProviderRequest; -import SAMLUpdateAuthProviderRequest = auth.SAMLUpdateAuthProviderRequest; -import UserIdentifier = auth.UserIdentifier; -import UpdateRequest = auth.UpdateRequest; -import UpdateMultiFactorInfoRequest = auth.UpdateMultiFactorInfoRequest; -import CreateTenantRequest = auth.CreateTenantRequest; -import UpdateTenantRequest = auth.UpdateTenantRequest; +import { + UserImportRecord, OIDCAuthProviderConfig, SAMLAuthProviderConfig, OIDCUpdateAuthProviderRequest, + SAMLUpdateAuthProviderRequest, UserIdentifier, UpdateRequest, UpdateMultiFactorInfoRequest, + CreateTenantRequest, UpdateTenantRequest, +} from '../../../src/auth/index'; chai.should(); chai.use(sinonChai); diff --git a/test/unit/auth/auth-config.spec.ts b/test/unit/auth/auth-config.spec.ts index d9e6ce4abc..62e7d4e3f6 100644 --- a/test/unit/auth/auth-config.spec.ts +++ b/test/unit/auth/auth-config.spec.ts @@ -27,12 +27,10 @@ import { EmailSignInConfig, MultiFactorAuthConfig, validateTestPhoneNumbers, MAXIMUM_TEST_PHONE_NUMBERS, } from '../../../src/auth/auth-config'; -import { auth } from '../../../src/auth/index'; - -import SAMLUpdateAuthProviderRequest = auth.SAMLUpdateAuthProviderRequest; -import OIDCUpdateAuthProviderRequest = auth.OIDCUpdateAuthProviderRequest; -import SAMLAuthProviderConfig = auth.SAMLAuthProviderConfig; -import OIDCAuthProviderConfig = auth.OIDCAuthProviderConfig; +import { + SAMLUpdateAuthProviderRequest, OIDCUpdateAuthProviderRequest, + SAMLAuthProviderConfig, OIDCAuthProviderConfig, +} from '../../../src/auth/index'; chai.should(); chai.use(sinonChai); diff --git a/test/unit/auth/auth.spec.ts b/test/unit/auth/auth.spec.ts index 836a7f29ba..4c112dfe21 100644 --- a/test/unit/auth/auth.spec.ts +++ b/test/unit/auth/auth.spec.ts @@ -27,9 +27,7 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; -import { Auth, TenantAwareAuth, BaseAuth } from '../../../src/auth/auth'; -import { UserRecord } from '../../../src/auth/user-record'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { AuthRequestHandler, TenantAwareAuthRequestHandler, AbstractAuthRequestHandler, } from '../../../src/auth/auth-api-request'; @@ -41,14 +39,12 @@ import { OIDCConfig, SAMLConfig, OIDCConfigServerResponse, SAMLConfigServerResponse, } from '../../../src/auth/auth-config'; import { deepCopy } from '../../../src/utils/deep-copy'; -import { TenantManager } from '../../../src/auth/tenant-manager'; -import { ServiceAccountCredential } from '../../../src/credential/credential-internal'; +import { ServiceAccountCredential } from '../../../src/app/credential-internal'; import { HttpClient } from '../../../src/utils/api-request'; -import { auth } from '../../../src/auth/index'; - -import DecodedIdToken = auth.DecodedIdToken; -import UpdateRequest = auth.UpdateRequest; -import AuthProviderConfigFilter = auth.AuthProviderConfigFilter; +import { + Auth, TenantAwareAuth, BaseAuth, UserRecord, DecodedIdToken, + UpdateRequest, AuthProviderConfigFilter, TenantManager, +} from '../../../src/auth/index'; chai.should(); chai.use(sinonChai); @@ -60,9 +56,9 @@ const expect = chai.expect; interface AuthTest { name: string; supportsTenantManagement: boolean; - Auth: new (...args: any[]) => BaseAuth; + Auth: new (...args: any[]) => BaseAuth; RequestHandler: new (...args: any[]) => AbstractAuthRequestHandler; - init(app: FirebaseApp): BaseAuth; + init(app: FirebaseApp): BaseAuth; } @@ -266,13 +262,13 @@ const AUTH_CONFIGS: AuthTest[] = [ ]; AUTH_CONFIGS.forEach((testConfig) => { describe(testConfig.name, () => { - let auth: BaseAuth; + let auth: BaseAuth; let mockApp: FirebaseApp; let getTokenStub: sinon.SinonStub; let oldProcessEnv: NodeJS.ProcessEnv; - let nullAccessTokenAuth: BaseAuth; - let malformedAccessTokenAuth: BaseAuth; - let rejectedPromiseAccessTokenAuth: BaseAuth; + let nullAccessTokenAuth: BaseAuth; + let malformedAccessTokenAuth: BaseAuth; + let rejectedPromiseAccessTokenAuth: BaseAuth; beforeEach(() => { mockApp = mocks.app(); @@ -498,7 +494,7 @@ AUTH_CONFIGS.forEach((testConfig) => { expect(getUserStub).to.have.been.calledOnce.and.calledWith(uid); // Confirm expected error returned. expect(error).to.have.property('code', 'auth/user-disabled'); - }); + }); }); it('verifyIdToken() should reject user disabled before ID tokens revoked', () => { @@ -901,7 +897,7 @@ AUTH_CONFIGS.forEach((testConfig) => { expect(getUserStub).to.have.been.calledOnce.and.calledWith(uid); // Confirm expected error returned. expect(error).to.have.property('code', 'auth/user-disabled'); - }); + }); }); it('verifySessionCookie() should reject user disabled before ID tokens revoked', () => { @@ -1960,6 +1956,109 @@ AUTH_CONFIGS.forEach((testConfig) => { }); }); + describe('non-federated providers', () => { + let invokeRequestHandlerStub: sinon.SinonStub; + let getAccountInfoByUidStub: sinon.SinonStub; + beforeEach(() => { + invokeRequestHandlerStub = sinon.stub(testConfig.RequestHandler.prototype, 'invokeRequestHandler') + .resolves({ + // nothing here is checked; we just need enough to not crash. + users: [{ + localId: 1, + }], + }); + + getAccountInfoByUidStub = sinon.stub(testConfig.RequestHandler.prototype, 'getAccountInfoByUid') + .resolves({ + // nothing here is checked; we just need enough to not crash. + users: [{ + localId: 1, + }], + }); + }); + afterEach(() => { + invokeRequestHandlerStub.restore(); + getAccountInfoByUidStub.restore(); + }); + + it('specifying both email and providerId=email should be rejected', () => { + expect(() => { + auth.updateUser(uid, { + email: 'user@example.com', + providerToLink: { + providerId: 'email', + uid: 'user@example.com', + }, + }); + }).to.throw(FirebaseAuthError).with.property('code', 'auth/argument-error'); + }); + + it('specifying both phoneNumber and providerId=phone should be rejected', () => { + expect(() => { + auth.updateUser(uid, { + phoneNumber: '+15555550001', + providerToLink: { + providerId: 'phone', + uid: '+15555550001', + }, + }); + }).to.throw(FirebaseAuthError).with.property('code', 'auth/argument-error'); + }); + + it('email linking should use email field', async () => { + await auth.updateUser(uid, { + providerToLink: { + providerId: 'email', + uid: 'user@example.com', + }, + }); + expect(invokeRequestHandlerStub).to.have.been.calledOnce.and.calledWith( + sinon.match.any, sinon.match.any, { + localId: uid, + email: 'user@example.com', + }); + }); + + it('phone linking should use phoneNumber field', async () => { + await auth.updateUser(uid, { + providerToLink: { + providerId: 'phone', + uid: '+15555550001', + }, + }); + expect(invokeRequestHandlerStub).to.have.been.calledOnce.and.calledWith( + sinon.match.any, sinon.match.any, { + localId: uid, + phoneNumber: '+15555550001', + }); + }); + + it('specifying both phoneNumber=null and providersToUnlink=phone should be rejected', () => { + expect(() => { + auth.updateUser(uid, { + phoneNumber: null, + providersToUnlink: ['phone'], + }); + }).to.throw(FirebaseAuthError).with.property('code', 'auth/argument-error'); + }); + + it('doesnt mutate the properties parameter', async () => { + const properties: UpdateRequest = { + providerToLink: { + providerId: 'email', + uid: 'user@example.com', + }, + }; + await auth.updateUser(uid, properties); + expect(properties).to.deep.equal({ + providerToLink: { + providerId: 'email', + uid: 'user@example.com', + }, + }); + }); + }); + it('should be rejected given an app which returns null access tokens', () => { return nullAccessTokenAuth.updateUser(uid, propertiesToEdit) .should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential'); diff --git a/test/unit/auth/index.spec.ts b/test/unit/auth/index.spec.ts new file mode 100644 index 0000000000..099bb00cd4 --- /dev/null +++ b/test/unit/auth/index.spec.ts @@ -0,0 +1,75 @@ +/*! + * @license + * Copyright 2021 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. + */ + +'use strict'; + +import * as chai from 'chai'; +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +import * as mocks from '../../resources/mocks'; +import { App } from '../../../src/app/index'; +import { getAuth, Auth } from '../../../src/auth/index'; + +chai.should(); +chai.use(sinonChai); +chai.use(chaiAsPromised); + +const expect = chai.expect; + +describe('Auth', () => { + let mockApp: App; + let mockCredentialApp: App; + + const noProjectIdError = 'Failed to determine project ID for Auth. Initialize the SDK ' + + 'with service account credentials or set project ID as an app option. Alternatively set the ' + + 'GOOGLE_CLOUD_PROJECT environment variable.'; + + beforeEach(() => { + mockApp = mocks.app(); + mockCredentialApp = mocks.mockCredentialApp(); + }); + + describe('getAuth()', () => { + it('should throw when default app is not available', () => { + expect(() => { + return getAuth(); + }).to.throw('The default Firebase app does not exist.'); + }); + + it('should reject given an invalid credential without project ID', () => { + // Project ID not set in the environment. + delete process.env.GOOGLE_CLOUD_PROJECT; + delete process.env.GCLOUD_PROJECT; + const auth = getAuth(mockCredentialApp); + return auth.getUser('uid') + .should.eventually.rejectedWith(noProjectIdError); + }); + + it('should not throw given a valid app', () => { + expect(() => { + return getAuth(mockApp); + }).not.to.throw(); + }); + + it('should return the same instance for a given app instance', () => { + const auth1: Auth = getAuth(mockApp); + const auth2: Auth = getAuth(mockApp); + expect(auth1).to.equal(auth2); + }); + }); +}); diff --git a/test/unit/auth/tenant-manager.spec.ts b/test/unit/auth/tenant-manager.spec.ts index 52bf955ccd..8a8f0617f2 100644 --- a/test/unit/auth/tenant-manager.spec.ts +++ b/test/unit/auth/tenant-manager.spec.ts @@ -23,16 +23,13 @@ import * as sinonChai from 'sinon-chai'; import * as chaiAsPromised from 'chai-as-promised'; import * as mocks from '../../resources/mocks'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { AuthRequestHandler } from '../../../src/auth/auth-api-request'; -import { Tenant, TenantServerResponse } from '../../../src/auth/tenant'; -import { TenantManager } from '../../../src/auth/tenant-manager'; +import { TenantServerResponse } from '../../../src/auth/tenant'; import { AuthClientErrorCode, FirebaseAuthError } from '../../../src/utils/error'; -import { auth } from '../../../src/auth/index'; - -import CreateTenantRequest = auth.CreateTenantRequest; -import UpdateTenantRequest = auth.UpdateTenantRequest; -import ListTenantsResult = auth.ListTenantsResult; +import { + CreateTenantRequest, UpdateTenantRequest, ListTenantsResult, Tenant, TenantManager, +} from '../../../src/auth/index'; chai.should(); chai.use(sinonChai); diff --git a/test/unit/auth/tenant.spec.ts b/test/unit/auth/tenant.spec.ts index b2ebe6a5d1..0f14856faa 100644 --- a/test/unit/auth/tenant.spec.ts +++ b/test/unit/auth/tenant.spec.ts @@ -21,12 +21,10 @@ import * as chaiAsPromised from 'chai-as-promised'; import { deepCopy } from '../../../src/utils/deep-copy'; import { EmailSignInConfig, MultiFactorAuthConfig } from '../../../src/auth/auth-config'; -import { Tenant, TenantServerResponse } from '../../../src/auth/tenant'; -import { auth } from '../../../src/auth/index'; - -import EmailSignInProviderConfig = auth.EmailSignInProviderConfig; -import CreateTenantRequest = auth.CreateTenantRequest; -import UpdateTenantRequest = auth.UpdateTenantRequest; +import { TenantServerResponse } from '../../../src/auth/tenant'; +import { + CreateTenantRequest, UpdateTenantRequest, EmailSignInProviderConfig, Tenant, +} from '../../../src/auth/index'; chai.should(); chai.use(sinonChai); diff --git a/test/unit/auth/token-generator.spec.ts b/test/unit/auth/token-generator.spec.ts index 6a6d148b09..70df21b4ef 100644 --- a/test/unit/auth/token-generator.spec.ts +++ b/test/unit/auth/token-generator.spec.ts @@ -30,7 +30,7 @@ import { } from '../../../src/auth/token-generator'; import { CryptoSignerError, CryptoSignerErrorCode, ServiceAccountSigner } from '../../../src/utils/crypto-signer'; -import { ServiceAccountCredential } from '../../../src/credential/credential-internal'; +import { ServiceAccountCredential } from '../../../src/app/credential-internal'; import { FirebaseAuthError } from '../../../src/utils/error'; import * as utils from '../utils'; diff --git a/test/unit/auth/token-verifier.spec.ts b/test/unit/auth/token-verifier.spec.ts index a8afe7167b..6a4b67db6a 100644 --- a/test/unit/auth/token-verifier.spec.ts +++ b/test/unit/auth/token-verifier.spec.ts @@ -30,10 +30,9 @@ import * as mocks from '../../resources/mocks'; import { FirebaseTokenGenerator } from '../../../src/auth/token-generator'; import { ServiceAccountSigner } from '../../../src/utils/crypto-signer'; import * as verifier from '../../../src/auth/token-verifier'; - -import { ServiceAccountCredential } from '../../../src/credential/credential-internal'; +import { ServiceAccountCredential } from '../../../src/app/credential-internal'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { AuthClientErrorCode } from '../../../src/utils/error'; -import { FirebaseApp } from '../../../src/firebase-app'; import { JwtError, JwtErrorCode, PublicKeySignatureVerifier } from '../../../src/utils/jwt'; chai.should(); @@ -449,7 +448,6 @@ describe('FirebaseTokenVerifier', () => { createTokenVerifier(mockAppWithAgent); expect(verifierSpy.args[0][1]).to.equal(agentForApp); - verifierSpy.restore(); }); diff --git a/test/unit/auth/user-import-builder.spec.ts b/test/unit/auth/user-import-builder.spec.ts index 14deeaeb64..859265a03a 100644 --- a/test/unit/auth/user-import-builder.spec.ts +++ b/test/unit/auth/user-import-builder.spec.ts @@ -24,11 +24,9 @@ import { } from '../../../src/auth/user-import-builder'; import { AuthClientErrorCode, FirebaseAuthError } from '../../../src/utils/error'; import { toWebSafeBase64 } from '../../../src/utils'; -import { auth } from '../../../src/auth/index'; - -import UpdatePhoneMultiFactorInfoRequest = auth.UpdatePhoneMultiFactorInfoRequest; -import UserImportResult = auth.UserImportResult; -import UserImportRecord = auth.UserImportRecord; +import { + UpdatePhoneMultiFactorInfoRequest, UserImportResult, UserImportRecord, +} from '../../../src/auth'; chai.should(); chai.use(sinonChai); diff --git a/test/unit/auth/user-record.spec.ts b/test/unit/auth/user-record.spec.ts index 0a42de92cb..cca0af6185 100644 --- a/test/unit/auth/user-record.spec.ts +++ b/test/unit/auth/user-record.spec.ts @@ -21,9 +21,11 @@ import * as chaiAsPromised from 'chai-as-promised'; import { deepCopy } from '../../../src/utils/deep-copy'; import { - UserInfo, UserMetadata, UserRecord, GetAccountInfoUserResponse, ProviderUserInfoResponse, - MultiFactor, PhoneMultiFactorInfo, MultiFactorInfo, MultiFactorInfoResponse, + GetAccountInfoUserResponse, ProviderUserInfoResponse, MultiFactorInfoResponse, } from '../../../src/auth/user-record'; +import { + UserInfo, UserMetadata, UserRecord, MultiFactorSettings, MultiFactorInfo, PhoneMultiFactorInfo, +} from '../../../src/auth/index'; chai.should(); @@ -395,7 +397,7 @@ describe('MultiFactorInfo', () => { }); }); -describe('MultiFactor', () => { +describe('MultiFactorSettings', () => { const serverResponse = { localId: 'uid123', mfaInfo: [ @@ -440,18 +442,18 @@ describe('MultiFactor', () => { describe('constructor', () => { it('should throw when a non object is provided', () => { expect(() => { - return new MultiFactor(undefined as any); + return new MultiFactorSettings(undefined as any); }).to.throw('INTERNAL ASSERT FAILED: Invalid multi-factor response'); }); it('should populate an empty enrolledFactors array when given an empty object', () => { - const multiFactor = new MultiFactor({} as any); + const multiFactor = new MultiFactorSettings({} as any); expect(multiFactor.enrolledFactors.length).to.equal(0); }); it('should populate expected enrolledFactors', () => { - const multiFactor = new MultiFactor(serverResponse); + const multiFactor = new MultiFactorSettings(serverResponse); expect(multiFactor.enrolledFactors.length).to.equal(2); expect(multiFactor.enrolledFactors[0]).to.deep.equal(expectedMultiFactorInfo[0]); @@ -461,7 +463,7 @@ describe('MultiFactor', () => { describe('getter', () => { it('should throw when modifying readonly enrolledFactors property', () => { - const multiFactor = new MultiFactor(serverResponse); + const multiFactor = new MultiFactorSettings(serverResponse); expect(() => { (multiFactor as any).enrolledFactors = [ @@ -471,7 +473,7 @@ describe('MultiFactor', () => { }); it('should throw when modifying readonly enrolledFactors internals', () => { - const multiFactor = new MultiFactor(serverResponse); + const multiFactor = new MultiFactorSettings(serverResponse); expect(() => { (multiFactor.enrolledFactors as any)[0] = new PhoneMultiFactorInfo({ @@ -486,7 +488,7 @@ describe('MultiFactor', () => { describe('toJSON', () => { it('should return expected JSON object when given an empty response', () => { - const multiFactor = new MultiFactor({} as any); + const multiFactor = new MultiFactorSettings({} as any); expect(multiFactor.toJSON()).to.deep.equal({ enrolledFactors: [], @@ -494,7 +496,7 @@ describe('MultiFactor', () => { }); it('should return expected JSON object when given a populated response', () => { - const multiFactor = new MultiFactor(serverResponse); + const multiFactor = new MultiFactorSettings(serverResponse); expect(multiFactor.toJSON()).to.deep.equal({ enrolledFactors: [ @@ -683,6 +685,15 @@ describe('UserMetadata', () => { it('should return expected lastRefreshTime', () => { expect(actualMetadata.lastRefreshTime).to.equal(new Date(expectedLastRefreshAt).toUTCString()) }); + + it('should return null when lastRefreshTime is not available', () => { + const metadata: UserMetadata = new UserMetadata({ + localId: 'uid123', + lastLoginAt: expectedLastLoginAt.toString(), + createdAt: expectedCreatedAt.toString(), + }); + expect(metadata.lastRefreshTime).to.be.null; + }); }); describe('toJSON', () => { @@ -971,7 +982,7 @@ describe('UserRecord', () => { }); it('should return expected multiFactor', () => { - const multiFactor = new MultiFactor({ + const multiFactor = new MultiFactorSettings({ localId: 'uid123', mfaInfo: [ { @@ -1001,7 +1012,7 @@ describe('UserRecord', () => { it('should throw when modifying readonly multiFactor property', () => { expect(() => { - (userRecord as any).multiFactor = new MultiFactor({ + (userRecord as any).multiFactor = new MultiFactorSettings({ localId: 'uid123', mfaInfo: [{ mfaEnrollmentId: 'enrollmentId3', diff --git a/test/unit/database/database.spec.ts b/test/unit/database/database.spec.ts index 763041262e..e299ae0fe0 100644 --- a/test/unit/database/database.spec.ts +++ b/test/unit/database/database.spec.ts @@ -22,15 +22,12 @@ import { expect } from 'chai'; import * as sinon from 'sinon'; import * as mocks from '../../resources/mocks'; -import { FirebaseApp } from '../../../src/firebase-app'; -import { DatabaseService } from '../../../src/database/database-internal'; -import { database } from '../../../src/database/index'; -import { ServiceAccountCredential } from '../../../src/credential/credential-internal'; +import { FirebaseApp } from '../../../src/app/firebase-app'; +import { Database, DatabaseService } from '../../../src/database/database'; +import { ServiceAccountCredential } from '../../../src/app/credential-internal'; import * as utils from '../utils'; import { HttpClient, HttpRequestConfig } from '../../../src/utils/api-request'; -import Database = database.Database; - describe('Database', () => { let mockApp: FirebaseApp; let database: DatabaseService; diff --git a/test/unit/database/index.spec.ts b/test/unit/database/index.spec.ts new file mode 100644 index 0000000000..382a1d96d3 --- /dev/null +++ b/test/unit/database/index.spec.ts @@ -0,0 +1,100 @@ +/*! + * @license + * Copyright 2021 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. + */ + +'use strict'; + +import * as chai from 'chai'; +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +import * as mocks from '../../resources/mocks'; +import { App } from '../../../src/app/index'; +import { + getDatabase, getDatabaseWithUrl, Database, ServerValue, enableLogging , +} from '../../../src/database/index'; +import { FirebaseApp } from '../../../src/app/firebase-app'; + +chai.should(); +chai.use(sinonChai); +chai.use(chaiAsPromised); + +const expect = chai.expect; + +describe('Database', () => { + let mockApp: App; + + beforeEach(() => { + mockApp = mocks.app(); + }); + + afterEach(() => { + return (mockApp as FirebaseApp).delete(); + }); + + describe('getDatabase()', () => { + it('should throw when default app is not available', () => { + expect(() => { + return getDatabase(); + }).to.throw('The default Firebase app does not exist.'); + }); + + it('should not throw given a valid app', () => { + expect(() => { + return getDatabase(mockApp); + }).not.to.throw(); + }); + + it('should return the same instance for a given app instance', () => { + const db1: Database = getDatabase(mockApp); + const db2: Database = getDatabase(mockApp); + expect(db1).to.equal(db2); + }); + }); + + describe('getDatabaseWithUrl()', () => { + it('should throw when default app is not available', () => { + expect(() => { + return getDatabaseWithUrl('https://test.firebaseio.com'); + }).to.throw('The default Firebase app does not exist.'); + }); + + it('should not throw given a valid app', () => { + expect(() => { + return getDatabaseWithUrl('https://test.firebaseio.com', mockApp); + }).not.to.throw(); + }); + + it('should return the same instance for a given app instance', () => { + const db1: Database = getDatabaseWithUrl('https://test.firebaseio.com', mockApp); + const db2: Database = getDatabaseWithUrl('https://test.firebaseio.com', mockApp); + const db3: Database = getDatabaseWithUrl('https://other.firebaseio.com', mockApp); + expect(db1).to.equal(db2); + expect(db1).to.not.equal(db3); + }); + }); + + it('should expose ServerValue sentinel', () => { + expect(() => ServerValue.increment(1)).to.not.throw(); + }); + + it('should expose enableLogging global function', () => { + expect(() => { + enableLogging(console.log); + enableLogging(false); + }).to.not.throw(); + }); +}); diff --git a/test/unit/firebase.spec.ts b/test/unit/firebase.spec.ts index da1256a2c2..3313499b5f 100644 --- a/test/unit/firebase.spec.ts +++ b/test/unit/firebase.spec.ts @@ -28,10 +28,11 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as mocks from '../resources/mocks'; import * as firebaseAdmin from '../../src/index'; -import { FirebaseApp, FirebaseAppInternals } from '../../src/firebase-app'; +import { FirebaseApp, FirebaseAppInternals } from '../../src/app/firebase-app'; import { RefreshTokenCredential, ServiceAccountCredential, isApplicationDefault -} from '../../src/credential/credential-internal'; +} from '../../src/app/credential-internal'; +import { defaultAppStore, initializeApp } from '../../src/app/lifecycle'; chai.should(); chai.use(chaiAsPromised); @@ -54,12 +55,7 @@ describe('Firebase', () => { }); afterEach(() => { - const deletePromises: Array> = []; - firebaseAdmin.apps.forEach((app) => { - deletePromises.push(app.delete()); - }); - - return Promise.all(deletePromises); + return defaultAppStore.clearAllApps(); }); describe('#initializeApp()', () => { @@ -165,6 +161,23 @@ describe('Firebase', () => { return getAppInternals().getToken() .should.eventually.have.keys(['accessToken', 'expirationTime']); }); + + it('should initialize App instance with extended service methods', () => { + const app = firebaseAdmin.initializeApp(mocks.appOptions); + expect((app as any).__extended).to.be.true; + expect(app.auth).to.be.not.undefined; + }); + + it('should add extended service methods when retrieved via namespace', () => { + const app = initializeApp(mocks.appOptions); + expect((app as any).__extended).to.be.undefined; + expect((app as any).auth).to.be.undefined; + + const extendedApp = firebaseAdmin.app(); + expect(app).to.equal(extendedApp); + expect((app as any).__extended).to.be.true; + expect((app as any).auth).to.be.not.undefined; + }); }); describe('#database()', () => { @@ -266,6 +279,6 @@ describe('Firebase', () => { }); function getAppInternals(): FirebaseAppInternals { - return (firebaseAdmin.app() as FirebaseApp).INTERNAL; + return (firebaseAdmin.app() as unknown as FirebaseApp).INTERNAL; } }); diff --git a/test/unit/firestore/firestore.spec.ts b/test/unit/firestore/firestore.spec.ts index 197e8dfde6..f8d6f188a0 100644 --- a/test/unit/firestore/firestore.spec.ts +++ b/test/unit/firestore/firestore.spec.ts @@ -21,10 +21,10 @@ import * as _ from 'lodash'; import { expect } from 'chai'; import * as mocks from '../../resources/mocks'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { ComputeEngineCredential, RefreshTokenCredential -} from '../../../src/credential/credential-internal'; +} from '../../../src/app/credential-internal'; import { FirestoreService, getFirestoreOptions } from '../../../src/firestore/firestore-internal'; describe('Firestore', () => { diff --git a/test/unit/firestore/index.spec.ts b/test/unit/firestore/index.spec.ts new file mode 100644 index 0000000000..5cfd800507 --- /dev/null +++ b/test/unit/firestore/index.spec.ts @@ -0,0 +1,73 @@ +/*! + * @license + * Copyright 2021 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. + */ + +'use strict'; + +import * as chai from 'chai'; +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +import * as mocks from '../../resources/mocks'; +import { App } from '../../../src/app/index'; +import { getFirestore, Firestore } from '../../../src/firestore/index'; + +chai.should(); +chai.use(sinonChai); +chai.use(chaiAsPromised); + +const expect = chai.expect; + +describe('Firestore', () => { + let mockApp: App; + let mockCredentialApp: App; + + const noProjectIdError = 'Failed to initialize Google Cloud Firestore client with the ' + + 'available credentials. Must initialize the SDK with a certificate credential or ' + + 'application default credentials to use Cloud Firestore API.'; + + beforeEach(() => { + mockApp = mocks.app(); + mockCredentialApp = mocks.mockCredentialApp(); + }); + + describe('getFirestore()', () => { + it('should throw when default app is not available', () => { + expect(() => { + return getFirestore(); + }).to.throw('The default Firebase app does not exist.'); + }); + + it('should reject given an invalid credential without project ID', () => { + // Project ID not set in the environment. + delete process.env.GOOGLE_CLOUD_PROJECT; + delete process.env.GCLOUD_PROJECT; + expect(() => getFirestore(mockCredentialApp)).to.throw(noProjectIdError); + }); + + it('should not throw given a valid app', () => { + expect(() => { + return getFirestore(mockApp); + }).not.to.throw(); + }); + + it('should return the same instance for a given app instance', () => { + const db1: Firestore = getFirestore(mockApp); + const db2: Firestore = getFirestore(mockApp); + expect(db1).to.equal(db2); + }); + }); +}); diff --git a/test/unit/index.spec.ts b/test/unit/index.spec.ts index 1cccc1def5..ade3503069 100644 --- a/test/unit/index.spec.ts +++ b/test/unit/index.spec.ts @@ -17,8 +17,10 @@ // General import './firebase.spec'; -import './firebase-app.spec'; -import './firebase-namespace.spec'; +import './app/credential-internal.spec'; +import './app/index.spec'; +import './app/firebase-app.spec'; +import './app/firebase-namespace.spec'; // Utilities import './utils/index.spec'; @@ -30,6 +32,7 @@ import './utils/crypto-signer.spec'; // Auth import './auth/auth.spec'; +import './auth/index.spec'; import './auth/user-record.spec'; import './auth/token-generator.spec'; import './auth/token-verifier.spec'; @@ -40,44 +43,58 @@ import './auth/auth-config.spec'; import './auth/tenant.spec'; import './auth/tenant-manager.spec'; -// Credential -import './credential/credential.spec'; - // Database import './database/database.spec'; +import './database/index.spec'; // Messaging +import './messaging/index.spec'; import './messaging/messaging.spec'; import './messaging/batch-requests.spec'; // Machine Learning +import './machine-learning/index.spec'; import './machine-learning/machine-learning.spec'; import './machine-learning/machine-learning-api-client.spec'; // Storage import './storage/storage.spec'; +import './storage/index.spec'; // Firestore import './firestore/firestore.spec'; +import './firestore/index.spec'; + +// Installations +import './installations/installations.spec'; +import './installations/installations-request-handler.spec'; + +// Installations +import './installations/installations.spec'; +import './installations/installations-request-handler.spec'; // Installations import './installations/installations.spec'; import './installations/installations-request-handler.spec'; // InstanceId +import './instance-id/index.spec'; import './instance-id/instance-id.spec'; // ProjectManagement +import './project-management/index.spec'; import './project-management/project-management.spec'; import './project-management/project-management-api-request.spec'; import './project-management/android-app.spec'; import './project-management/ios-app.spec'; // SecurityRules +import './security-rules/index.spec'; import './security-rules/security-rules.spec'; import './security-rules/security-rules-api-client.spec'; // RemoteConfig +import './remote-config/index.spec'; import './remote-config/remote-config.spec'; import './remote-config/remote-config-api-client.spec'; diff --git a/test/unit/installations/installations-request-handler.spec.ts b/test/unit/installations/installations-request-handler.spec.ts index dcc32b464b..36e696dd2d 100644 --- a/test/unit/installations/installations-request-handler.spec.ts +++ b/test/unit/installations/installations-request-handler.spec.ts @@ -26,7 +26,7 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { HttpClient } from '../../../src/utils/api-request'; import { FirebaseInstallationsRequestHandler } from '../../../src/installations/installations-request-handler'; diff --git a/test/unit/installations/installations.spec.ts b/test/unit/installations/installations.spec.ts index e7816fa556..6a38c8413c 100644 --- a/test/unit/installations/installations.spec.ts +++ b/test/unit/installations/installations.spec.ts @@ -28,7 +28,7 @@ import * as mocks from '../../resources/mocks'; import { Installations } from '../../../src/installations/installations'; import { FirebaseInstallationsRequestHandler } from '../../../src/installations/installations-request-handler'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { FirebaseInstallationsError, InstallationsClientErrorCode } from '../../../src/utils/error'; chai.should(); diff --git a/test/unit/instance-id/index.spec.ts b/test/unit/instance-id/index.spec.ts new file mode 100644 index 0000000000..2f1d690e6a --- /dev/null +++ b/test/unit/instance-id/index.spec.ts @@ -0,0 +1,75 @@ +/*! + * @license + * Copyright 2021 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. + */ + +'use strict'; + +import * as chai from 'chai'; +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +import * as mocks from '../../resources/mocks'; +import { App } from '../../../src/app/index'; +import { getInstanceId, InstanceId } from '../../../src/instance-id/index'; + +chai.should(); +chai.use(sinonChai); +chai.use(chaiAsPromised); + +const expect = chai.expect; + +describe('InstanceId', () => { + let mockApp: App; + let mockCredentialApp: App; + + const noProjectIdError = 'Failed to determine project ID for Installations. Initialize the SDK ' + + 'with service account credentials or set project ID as an app option. Alternatively set the ' + + 'GOOGLE_CLOUD_PROJECT environment variable.'; + + beforeEach(() => { + mockApp = mocks.app(); + mockCredentialApp = mocks.mockCredentialApp(); + }); + + describe('getInstanceId()', () => { + it('should throw when default app is not available', () => { + expect(() => { + return getInstanceId(); + }).to.throw('The default Firebase app does not exist.'); + }); + + it('should reject given an invalid credential without project ID', () => { + // Project ID not set in the environment. + delete process.env.GOOGLE_CLOUD_PROJECT; + delete process.env.GCLOUD_PROJECT; + const iid = getInstanceId(mockCredentialApp); + return iid.deleteInstanceId('iid') + .should.eventually.rejectedWith(noProjectIdError); + }); + + it('should not throw given a valid app', () => { + expect(() => { + return getInstanceId(mockApp); + }).not.to.throw(); + }); + + it('should return the same instance for a given app instance', () => { + const iid1: InstanceId = getInstanceId(mockApp); + const iid2: InstanceId = getInstanceId(mockApp); + expect(iid1).to.equal(iid2); + }); + }); +}); diff --git a/test/unit/instance-id/instance-id.spec.ts b/test/unit/instance-id/instance-id.spec.ts index a35d2a01e2..a610d7678d 100644 --- a/test/unit/instance-id/instance-id.spec.ts +++ b/test/unit/instance-id/instance-id.spec.ts @@ -26,12 +26,12 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; -import { InstanceId } from '../../../src/instance-id/instance-id'; -import { Installations } from '../../../src/installations/installations'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { InstanceId } from '../../../src/instance-id/index'; +import { Installations } from '../../../src/installations/index'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { - FirebaseInstallationsError, FirebaseInstanceIdError, - InstallationsClientErrorCode, InstanceIdClientErrorCode, + FirebaseInstanceIdError, InstanceIdClientErrorCode, + FirebaseInstallationsError, InstallationsClientErrorCode, } from '../../../src/utils/error'; chai.should(); @@ -86,7 +86,7 @@ describe('InstanceId', () => { expect(() => { const iidAny: any = InstanceId; return new iidAny(invalidApp); - }).to.throw('First argument passed to admin.instanceId() must be a valid Firebase app instance.'); + }).to.throw('First argument passed to instanceId() must be a valid Firebase app instance.'); }); }); @@ -94,7 +94,7 @@ describe('InstanceId', () => { expect(() => { const iidAny: any = InstanceId; return new iidAny(); - }).to.throw('First argument passed to admin.instanceId() must be a valid Firebase app instance.'); + }).to.throw('First argument passed to instanceId() must be a valid Firebase app instance.'); }); it('should reject given an invalid credential without project ID', () => { diff --git a/test/unit/machine-learning/index.spec.ts b/test/unit/machine-learning/index.spec.ts new file mode 100644 index 0000000000..1937f5387d --- /dev/null +++ b/test/unit/machine-learning/index.spec.ts @@ -0,0 +1,75 @@ +/*! + * @license + * Copyright 2021 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. + */ + +'use strict'; + +import * as chai from 'chai'; +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +import * as mocks from '../../resources/mocks'; +import { App } from '../../../src/app/index'; +import { getMachineLearning, MachineLearning } from '../../../src/machine-learning/index'; + +chai.should(); +chai.use(sinonChai); +chai.use(chaiAsPromised); + +const expect = chai.expect; + +describe('MachineLearning', () => { + let mockApp: App; + let mockCredentialApp: App; + + const noProjectIdError = 'Failed to determine project ID. Initialize the SDK ' + + 'with service account credentials, or set project ID as an app option. Alternatively, set the ' + + 'GOOGLE_CLOUD_PROJECT environment variable.'; + + beforeEach(() => { + mockApp = mocks.app(); + mockCredentialApp = mocks.mockCredentialApp(); + }); + + describe('getMachineLearning()', () => { + it('should throw when default app is not available', () => { + expect(() => { + return getMachineLearning(); + }).to.throw('The default Firebase app does not exist.'); + }); + + it('should reject given an invalid credential without project ID', () => { + // Project ID not set in the environment. + delete process.env.GOOGLE_CLOUD_PROJECT; + delete process.env.GCLOUD_PROJECT; + const client = getMachineLearning(mockCredentialApp); + return client.getModel('test') + .should.eventually.rejectedWith(noProjectIdError); + }); + + it('should not throw given a valid app', () => { + expect(() => { + return getMachineLearning(mockApp); + }).not.to.throw(); + }); + + it('should return the same instance for a given app instance', () => { + const client1: MachineLearning = getMachineLearning(mockApp); + const client2: MachineLearning = getMachineLearning(mockApp); + expect(client1).to.equal(client2); + }); + }); +}); diff --git a/test/unit/machine-learning/machine-learning-api-client.spec.ts b/test/unit/machine-learning/machine-learning-api-client.spec.ts index 3399dd0f52..01f6936869 100644 --- a/test/unit/machine-learning/machine-learning-api-client.spec.ts +++ b/test/unit/machine-learning/machine-learning-api-client.spec.ts @@ -24,13 +24,10 @@ import { HttpClient } from '../../../src/utils/api-request'; import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; import { FirebaseAppError } from '../../../src/utils/error'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { getSdkVersion } from '../../../src/utils/index'; import { MachineLearningApiClient } from '../../../src/machine-learning/machine-learning-api-client'; -import { machineLearning } from '../../../src/machine-learning/index'; - -import ListModelsOptions = machineLearning.ListModelsOptions; -import ModelOptions = machineLearning.ModelOptions; +import { ListModelsOptions, ModelOptions } from '../../../src/machine-learning/index'; const expect = chai.expect; diff --git a/test/unit/machine-learning/machine-learning.spec.ts b/test/unit/machine-learning/machine-learning.spec.ts index 96493d298b..8791ca9c87 100644 --- a/test/unit/machine-learning/machine-learning.spec.ts +++ b/test/unit/machine-learning/machine-learning.spec.ts @@ -19,9 +19,8 @@ import * as _ from 'lodash'; import * as chai from 'chai'; import * as sinon from 'sinon'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import * as mocks from '../../resources/mocks'; -import { MachineLearning, Model } from '../../../src/machine-learning/machine-learning'; import { MachineLearningApiClient, StatusErrorResponse, @@ -30,9 +29,7 @@ import { } from '../../../src/machine-learning/machine-learning-api-client'; import { FirebaseMachineLearningError } from '../../../src/machine-learning/machine-learning-utils'; import { deepCopy } from '../../../src/utils/deep-copy'; -import { machineLearning } from '../../../src/machine-learning/index'; - -import ModelOptions = machineLearning.ModelOptions; +import { MachineLearning, Model, ModelOptions } from '../../../src/machine-learning/index'; const expect = chai.expect; diff --git a/test/unit/messaging/index.spec.ts b/test/unit/messaging/index.spec.ts new file mode 100644 index 0000000000..56374ff1a6 --- /dev/null +++ b/test/unit/messaging/index.spec.ts @@ -0,0 +1,75 @@ +/*! + * @license + * Copyright 2021 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. + */ + +'use strict'; + +import * as chai from 'chai'; +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +import * as mocks from '../../resources/mocks'; +import { App } from '../../../src/app/index'; +import { getMessaging, Messaging } from '../../../src/messaging/index'; + +chai.should(); +chai.use(sinonChai); +chai.use(chaiAsPromised); + +const expect = chai.expect; + +describe('Messaging', () => { + let mockApp: App; + let mockCredentialApp: App; + + const noProjectIdError = 'Failed to determine project ID for Messaging. Initialize the SDK ' + + 'with service account credentials or set project ID as an app option. Alternatively set the ' + + 'GOOGLE_CLOUD_PROJECT environment variable.'; + + beforeEach(() => { + mockApp = mocks.app(); + mockCredentialApp = mocks.mockCredentialApp(); + }); + + describe('getMessaging()', () => { + it('should throw when default app is not available', () => { + expect(() => { + return getMessaging(); + }).to.throw('The default Firebase app does not exist.'); + }); + + it('should reject given an invalid credential without project ID', () => { + // Project ID not set in the environment. + delete process.env.GOOGLE_CLOUD_PROJECT; + delete process.env.GCLOUD_PROJECT; + const messaging = getMessaging(mockCredentialApp); + return messaging.send({ topic: 'test' }) + .should.eventually.rejectedWith(noProjectIdError); + }); + + it('should not throw given a valid app', () => { + expect(() => { + return getMessaging(mockApp); + }).not.to.throw(); + }); + + it('should return the same instance for a given app instance', () => { + const fcm1: Messaging = getMessaging(mockApp); + const fcm2: Messaging = getMessaging(mockApp); + expect(fcm1).to.equal(fcm2); + }); + }); +}); diff --git a/test/unit/messaging/messaging.spec.ts b/test/unit/messaging/messaging.spec.ts index 236c18213a..215d9b4472 100644 --- a/test/unit/messaging/messaging.spec.ts +++ b/test/unit/messaging/messaging.spec.ts @@ -24,15 +24,17 @@ import * as sinon from 'sinon'; import * as sinonChai from 'sinon-chai'; import * as chaiAsPromised from 'chai-as-promised'; -import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; - -import { FirebaseApp } from '../../../src/firebase-app'; -import { messaging } from '../../../src/messaging/index'; -import { Messaging } from '../../../src/messaging/messaging'; +import { FirebaseApp } from '../../../src/app/firebase-app'; +import { + Message, MessagingOptions, MessagingPayload, MessagingDevicesResponse, + MessagingDeviceGroupResponse, MessagingTopicManagementResponse, BatchResponse, + SendResponse, MulticastMessage, Messaging, TokenMessage, TopicMessage, ConditionMessage, +} from '../../../src/messaging/index'; import { BLACKLISTED_OPTIONS_KEYS, BLACKLISTED_DATA_PAYLOAD_KEYS } from '../../../src/messaging/messaging-internal'; import { HttpClient } from '../../../src/utils/api-request'; import { getSdkVersion } from '../../../src/utils/index'; +import * as utils from '../utils'; chai.should(); chai.use(sinonChai); @@ -40,19 +42,6 @@ chai.use(chaiAsPromised); const expect = chai.expect; -import Message = messaging.Message; -import TokenMessage = messaging.TokenMessage; -import TopicMessage = messaging.TopicMessage; -import ConditionMessage = messaging.ConditionMessage; -import MessagingOptions = messaging.MessagingOptions; -import MessagingPayload = messaging.MessagingPayload; -import MessagingDevicesResponse = messaging.MessagingDevicesResponse; -import MessagingDeviceGroupResponse = messaging.MessagingDeviceGroupResponse -import MessagingTopicManagementResponse = messaging.MessagingTopicManagementResponse; -import BatchResponse = messaging.BatchResponse; -import SendResponse = messaging.SendResponse; -import MulticastMessage = messaging.MulticastMessage; - // FCM endpoints const FCM_SEND_HOST = 'fcm.googleapis.com'; const FCM_SEND_PATH = '/fcm/send'; @@ -838,9 +827,9 @@ describe('Messaging', () => { const topicMessage: TopicMessage = { topic: 'test' }; const conditionMessage: ConditionMessage = { condition: 'test' }; const messages: Message[] = [tokenMessage, topicMessage, conditionMessage]; - + mockedRequests.push(mockBatchRequest(messageIds)); - + return messaging.sendAll(messages) .then((response: BatchResponse) => { expect(response.successCount).to.equal(3); @@ -851,7 +840,7 @@ describe('Messaging', () => { expect(resp.error).to.be.undefined; }); }); - }); + }); }); describe('sendMulticast()', () => { diff --git a/test/unit/project-management/android-app.spec.ts b/test/unit/project-management/android-app.spec.ts index 5feff560a9..94ccacbbd3 100644 --- a/test/unit/project-management/android-app.spec.ts +++ b/test/unit/project-management/android-app.spec.ts @@ -19,18 +19,16 @@ import * as chai from 'chai'; import * as _ from 'lodash'; import * as sinon from 'sinon'; -import { FirebaseApp } from '../../../src/firebase-app'; -import { AndroidApp, ShaCertificate } from '../../../src/project-management/android-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { ProjectManagementRequestHandler } from '../../../src/project-management/project-management-api-request-internal'; import { deepCopy } from '../../../src/utils/deep-copy'; import { FirebaseProjectManagementError } from '../../../src/utils/error'; import * as mocks from '../../resources/mocks'; -import { projectManagement } from '../../../src/project-management/index'; - -import AndroidAppMetadata = projectManagement.AndroidAppMetadata; -import AppPlatform = projectManagement.AppPlatform; +import { + AndroidApp, AndroidAppMetadata, AppPlatform, ShaCertificate, +} from '../../../src/project-management/index'; const expect = chai.expect; diff --git a/test/unit/project-management/index.spec.ts b/test/unit/project-management/index.spec.ts new file mode 100644 index 0000000000..6848494c96 --- /dev/null +++ b/test/unit/project-management/index.spec.ts @@ -0,0 +1,75 @@ +/*! + * @license + * Copyright 2021 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. + */ + +'use strict'; + +import * as chai from 'chai'; +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +import * as mocks from '../../resources/mocks'; +import { App } from '../../../src/app/index'; +import { getProjectManagement, ProjectManagement } from '../../../src/project-management/index'; + +chai.should(); +chai.use(sinonChai); +chai.use(chaiAsPromised); + +const expect = chai.expect; + +describe('ProjectManagement', () => { + let mockApp: App; + let mockCredentialApp: App; + + const noProjectIdError = 'Failed to determine project ID. Initialize the SDK ' + + 'with service account credentials, or set project ID as an app option. Alternatively, set the ' + + 'GOOGLE_CLOUD_PROJECT environment variable.'; + + beforeEach(() => { + mockApp = mocks.app(); + mockCredentialApp = mocks.mockCredentialApp(); + }); + + describe('getProjectManagement()', () => { + it('should throw when default app is not available', () => { + expect(() => { + return getProjectManagement(); + }).to.throw('The default Firebase app does not exist.'); + }); + + it('should reject given an invalid credential without project ID', () => { + // Project ID not set in the environment. + delete process.env.GOOGLE_CLOUD_PROJECT; + delete process.env.GCLOUD_PROJECT; + const client = getProjectManagement(mockCredentialApp); + return client.listAndroidApps() + .should.eventually.rejectedWith(noProjectIdError); + }); + + it('should not throw given a valid app', () => { + expect(() => { + return getProjectManagement(mockApp); + }).not.to.throw(); + }); + + it('should return the same instance for a given app instance', () => { + const client1: ProjectManagement = getProjectManagement(mockApp); + const client2: ProjectManagement = getProjectManagement(mockApp); + expect(client1).to.equal(client2); + }); + }); +}); diff --git a/test/unit/project-management/ios-app.spec.ts b/test/unit/project-management/ios-app.spec.ts index e0163d893f..f250ed5f10 100644 --- a/test/unit/project-management/ios-app.spec.ts +++ b/test/unit/project-management/ios-app.spec.ts @@ -19,18 +19,14 @@ import * as chai from 'chai'; import * as _ from 'lodash'; import * as sinon from 'sinon'; -import { FirebaseApp } from '../../../src/firebase-app'; -import { IosApp } from '../../../src/project-management/ios-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { ProjectManagementRequestHandler } from '../../../src/project-management/project-management-api-request-internal'; import { deepCopy } from '../../../src/utils/deep-copy'; import { FirebaseProjectManagementError } from '../../../src/utils/error'; import * as mocks from '../../resources/mocks'; -import { projectManagement } from '../../../src/project-management/index'; - -import IosAppMetadata = projectManagement.IosAppMetadata; -import AppPlatform = projectManagement.AppPlatform; +import { AppPlatform, IosApp, IosAppMetadata } from '../../../src/project-management/index'; const expect = chai.expect; diff --git a/test/unit/project-management/project-management-api-request.spec.ts b/test/unit/project-management/project-management-api-request.spec.ts index f4088e0a1f..772a33ad4c 100644 --- a/test/unit/project-management/project-management-api-request.spec.ts +++ b/test/unit/project-management/project-management-api-request.spec.ts @@ -21,7 +21,7 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as _ from 'lodash'; import * as sinon from 'sinon'; import * as sinonChai from 'sinon-chai'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { ProjectManagementRequestHandler } from '../../../src/project-management/project-management-api-request-internal'; @@ -29,10 +29,7 @@ import { HttpClient } from '../../../src/utils/api-request'; import * as mocks from '../../resources/mocks'; import * as utils from '../utils'; import { getSdkVersion } from '../../../src/utils/index'; -import { ShaCertificate } from '../../../src/project-management/android-app'; -import { projectManagement } from '../../../src/project-management/index'; - -import AppPlatform = projectManagement.AppPlatform; +import { AppPlatform, ShaCertificate } from '../../../src/project-management/index'; chai.should(); chai.use(sinonChai); diff --git a/test/unit/project-management/project-management.spec.ts b/test/unit/project-management/project-management.spec.ts index f7837389c7..b818bdb1c8 100644 --- a/test/unit/project-management/project-management.spec.ts +++ b/test/unit/project-management/project-management.spec.ts @@ -19,19 +19,15 @@ import * as chai from 'chai'; import * as _ from 'lodash'; import * as sinon from 'sinon'; -import { FirebaseApp } from '../../../src/firebase-app'; -import { AndroidApp } from '../../../src/project-management/android-app'; -import { ProjectManagement } from '../../../src/project-management/project-management'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { ProjectManagementRequestHandler } from '../../../src/project-management/project-management-api-request-internal'; import { FirebaseProjectManagementError } from '../../../src/utils/error'; import * as mocks from '../../resources/mocks'; -import { projectManagement } from '../../../src/project-management/index'; - -import AppMetadata = projectManagement.AppMetadata; -import AppPlatform = projectManagement.AppPlatform; -import IosApp = projectManagement.IosApp; +import { + AndroidApp, AppMetadata, AppPlatform, IosApp, ProjectManagement, +} from '../../../src/project-management/index'; const expect = chai.expect; diff --git a/test/unit/remote-config/index.spec.ts b/test/unit/remote-config/index.spec.ts new file mode 100644 index 0000000000..f2fd51a141 --- /dev/null +++ b/test/unit/remote-config/index.spec.ts @@ -0,0 +1,75 @@ +/*! + * @license + * Copyright 2021 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. + */ + +'use strict'; + +import * as chai from 'chai'; +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +import * as mocks from '../../resources/mocks'; +import { App } from '../../../src/app/index'; +import { getRemoteConfig, RemoteConfig } from '../../../src/remote-config/index'; + +chai.should(); +chai.use(sinonChai); +chai.use(chaiAsPromised); + +const expect = chai.expect; + +describe('RemoteConfig', () => { + let mockApp: App; + let mockCredentialApp: App; + + const noProjectIdError = 'Failed to determine project ID. Initialize the SDK ' + + 'with service account credentials, or set project ID as an app option. Alternatively, set the ' + + 'GOOGLE_CLOUD_PROJECT environment variable.'; + + beforeEach(() => { + mockApp = mocks.app(); + mockCredentialApp = mocks.mockCredentialApp(); + }); + + describe('getRemoteConfig()', () => { + it('should throw when default app is not available', () => { + expect(() => { + return getRemoteConfig(); + }).to.throw('The default Firebase app does not exist.'); + }); + + it('should reject given an invalid credential without project ID', () => { + // Project ID not set in the environment. + delete process.env.GOOGLE_CLOUD_PROJECT; + delete process.env.GCLOUD_PROJECT; + const remoteConfig = getRemoteConfig(mockCredentialApp); + return remoteConfig.getTemplate() + .should.eventually.rejectedWith(noProjectIdError); + }); + + it('should not throw given a valid app', () => { + expect(() => { + return getRemoteConfig(mockApp); + }).not.to.throw(); + }); + + it('should return the same instance for a given app instance', () => { + const rc1: RemoteConfig = getRemoteConfig(mockApp); + const rc2: RemoteConfig = getRemoteConfig(mockApp); + expect(rc1).to.equal(rc2); + }); + }); +}); diff --git a/test/unit/remote-config/remote-config-api-client.spec.ts b/test/unit/remote-config/remote-config-api-client.spec.ts index 8c5a68444f..79422b5c17 100644 --- a/test/unit/remote-config/remote-config-api-client.spec.ts +++ b/test/unit/remote-config/remote-config-api-client.spec.ts @@ -19,7 +19,6 @@ import * as _ from 'lodash'; import * as chai from 'chai'; import * as sinon from 'sinon'; -import { remoteConfig } from '../../../src/remote-config/index'; import { FirebaseRemoteConfigError, RemoteConfigApiClient @@ -28,13 +27,12 @@ import { HttpClient } from '../../../src/utils/api-request'; import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; import { FirebaseAppError } from '../../../src/utils/error'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { deepCopy } from '../../../src/utils/deep-copy'; import { getSdkVersion } from '../../../src/utils/index'; - -import RemoteConfigTemplate = remoteConfig.RemoteConfigTemplate; -import Version = remoteConfig.Version; -import ListVersionsResult = remoteConfig.ListVersionsResult; +import { + RemoteConfigTemplate, Version, ListVersionsResult, +} from '../../../src/remote-config/index'; const expect = chai.expect; diff --git a/test/unit/remote-config/remote-config.spec.ts b/test/unit/remote-config/remote-config.spec.ts index 3dfee808f0..90f24c2984 100644 --- a/test/unit/remote-config/remote-config.spec.ts +++ b/test/unit/remote-config/remote-config.spec.ts @@ -19,21 +19,22 @@ import * as _ from 'lodash'; import * as chai from 'chai'; import * as sinon from 'sinon'; -import { RemoteConfig } from '../../../src/remote-config/remote-config'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { + ParameterValueType, + RemoteConfig, + RemoteConfigTemplate, + RemoteConfigCondition, + TagColor, + ListVersionsResult, +} from '../../../src/remote-config/index'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import * as mocks from '../../resources/mocks'; -import { remoteConfig } from '../../../src/remote-config/index'; import { FirebaseRemoteConfigError, RemoteConfigApiClient } from '../../../src/remote-config/remote-config-api-client-internal'; import { deepCopy } from '../../../src/utils/deep-copy'; -import RemoteConfigTemplate = remoteConfig.RemoteConfigTemplate; -import RemoteConfigCondition = remoteConfig.RemoteConfigCondition; -import TagColor = remoteConfig.TagColor; -import ListVersionsResult = remoteConfig.ListVersionsResult; - const expect = chai.expect; describe('RemoteConfig', () => { @@ -51,7 +52,7 @@ describe('RemoteConfig', () => { 'android_en': { value: 'A Droid must love a pumpkin spice latte.' }, }, description: 'Description of the parameter.', - valueType: 'STRING' as remoteConfig.ParameterValueType, + valueType: 'STRING' as ParameterValueType, }, }, }, diff --git a/test/unit/security-rules/index.spec.ts b/test/unit/security-rules/index.spec.ts new file mode 100644 index 0000000000..ad6a0b05de --- /dev/null +++ b/test/unit/security-rules/index.spec.ts @@ -0,0 +1,75 @@ +/*! + * @license + * Copyright 2021 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. + */ + +'use strict'; + +import * as chai from 'chai'; +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +import * as mocks from '../../resources/mocks'; +import { App } from '../../../src/app/index'; +import { getSecurityRules, SecurityRules } from '../../../src/security-rules/index'; + +chai.should(); +chai.use(sinonChai); +chai.use(chaiAsPromised); + +const expect = chai.expect; + +describe('SecurityRules', () => { + let mockApp: App; + let mockCredentialApp: App; + + const noProjectIdError = 'Failed to determine project ID. Initialize the SDK ' + + 'with service account credentials, or set project ID as an app option. Alternatively, set the ' + + 'GOOGLE_CLOUD_PROJECT environment variable.'; + + beforeEach(() => { + mockApp = mocks.app(); + mockCredentialApp = mocks.mockCredentialApp(); + }); + + describe('getSecurityRules()', () => { + it('should throw when default app is not available', () => { + expect(() => { + return getSecurityRules(); + }).to.throw('The default Firebase app does not exist.'); + }); + + it('should reject given an invalid credential without project ID', () => { + // Project ID not set in the environment. + delete process.env.GOOGLE_CLOUD_PROJECT; + delete process.env.GCLOUD_PROJECT; + const rules = getSecurityRules(mockCredentialApp); + return rules.getFirestoreRuleset() + .should.eventually.rejectedWith(noProjectIdError); + }); + + it('should not throw given a valid app', () => { + expect(() => { + return getSecurityRules(mockApp); + }).not.to.throw(); + }); + + it('should return the same instance for a given app instance', () => { + const rules1: SecurityRules = getSecurityRules(mockApp); + const rules2: SecurityRules = getSecurityRules(mockApp); + expect(rules1).to.equal(rules2); + }); + }); +}); diff --git a/test/unit/security-rules/security-rules-api-client.spec.ts b/test/unit/security-rules/security-rules-api-client.spec.ts index f0cd1c4185..8b83a46fdf 100644 --- a/test/unit/security-rules/security-rules-api-client.spec.ts +++ b/test/unit/security-rules/security-rules-api-client.spec.ts @@ -25,7 +25,7 @@ import { HttpClient } from '../../../src/utils/api-request'; import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; import { FirebaseAppError } from '../../../src/utils/error'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { getSdkVersion } from '../../../src/utils/index'; const expect = chai.expect; diff --git a/test/unit/security-rules/security-rules.spec.ts b/test/unit/security-rules/security-rules.spec.ts index fd81fa7fd2..70611d7db5 100644 --- a/test/unit/security-rules/security-rules.spec.ts +++ b/test/unit/security-rules/security-rules.spec.ts @@ -19,8 +19,8 @@ import * as _ from 'lodash'; import * as chai from 'chai'; import * as sinon from 'sinon'; -import { SecurityRules } from '../../../src/security-rules/security-rules'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { SecurityRules } from '../../../src/security-rules/index'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import * as mocks from '../../resources/mocks'; import { SecurityRulesApiClient, RulesetContent } from '../../../src/security-rules/security-rules-api-client-internal'; import { FirebaseSecurityRulesError } from '../../../src/security-rules/security-rules-internal'; diff --git a/test/unit/storage/index.spec.ts b/test/unit/storage/index.spec.ts new file mode 100644 index 0000000000..8251207677 --- /dev/null +++ b/test/unit/storage/index.spec.ts @@ -0,0 +1,73 @@ +/*! + * @license + * Copyright 2021 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. + */ + +'use strict'; + +import * as chai from 'chai'; +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +import * as mocks from '../../resources/mocks'; +import { App } from '../../../src/app/index'; +import { getStorage, Storage } from '../../../src/storage/index'; + +chai.should(); +chai.use(sinonChai); +chai.use(chaiAsPromised); + +const expect = chai.expect; + +describe('Storage', () => { + let mockApp: App; + let mockCredentialApp: App; + + const noProjectIdError = 'Failed to initialize Google Cloud Storage client with the ' + + 'available credential. Must initialize the SDK with a certificate credential or ' + + 'application default credentials to use Cloud Storage API.'; + + beforeEach(() => { + mockApp = mocks.app(); + mockCredentialApp = mocks.mockCredentialApp(); + }); + + describe('getStorage()', () => { + it('should throw when default app is not available', () => { + expect(() => { + return getStorage(); + }).to.throw('The default Firebase app does not exist.'); + }); + + it('should reject given an invalid credential without project ID', () => { + // Project ID not set in the environment. + delete process.env.GOOGLE_CLOUD_PROJECT; + delete process.env.GCLOUD_PROJECT; + expect(() => getStorage(mockCredentialApp)).to.throw(noProjectIdError); + }); + + it('should not throw given a valid app', () => { + expect(() => { + return getStorage(mockApp); + }).not.to.throw(); + }); + + it('should return the same instance for a given app instance', () => { + const storage1: Storage = getStorage(mockApp); + const storage2: Storage = getStorage(mockApp); + expect(storage1).to.equal(storage2); + }); + }); +}); diff --git a/test/unit/storage/storage.spec.ts b/test/unit/storage/storage.spec.ts index e37bffd0e4..ea656a1e92 100644 --- a/test/unit/storage/storage.spec.ts +++ b/test/unit/storage/storage.spec.ts @@ -21,8 +21,8 @@ import * as _ from 'lodash'; import { expect } from 'chai'; import * as mocks from '../../resources/mocks'; -import { FirebaseApp } from '../../../src/firebase-app'; -import { Storage } from '../../../src/storage/storage'; +import { FirebaseApp } from '../../../src/app/firebase-app'; +import { Storage } from '../../../src/storage/index'; describe('Storage', () => { let mockApp: FirebaseApp; diff --git a/test/unit/utils.ts b/test/unit/utils.ts index 6eb845831a..2eb608397e 100644 --- a/test/unit/utils.ts +++ b/test/unit/utils.ts @@ -17,12 +17,9 @@ import * as _ from 'lodash'; import * as sinon from 'sinon'; - import * as mocks from '../resources/mocks'; - -import { FirebaseNamespace } from '../../src/firebase-namespace'; import { AppOptions } from '../../src/firebase-namespace-api'; -import { FirebaseApp, FirebaseAppInternals, FirebaseAccessToken } from '../../src/firebase-app'; +import { FirebaseApp, FirebaseAppInternals, FirebaseAccessToken } from '../../src/app/firebase-app'; import { HttpError, HttpResponse } from '../../src/utils/api-request'; /** @@ -32,8 +29,7 @@ import { HttpError, HttpResponse } from '../../src/utils/api-request'; * @return A new FirebaseApp instance with the provided options. */ export function createAppWithOptions(options: object): FirebaseApp { - const mockFirebaseNamespaceInternals = new FirebaseNamespace().INTERNAL; - return new FirebaseApp(options as AppOptions, mocks.appName, mockFirebaseNamespaceInternals); + return new FirebaseApp(options as AppOptions, mocks.appName); } diff --git a/test/unit/utils/api-request.spec.ts b/test/unit/utils/api-request.spec.ts index f997cfc9dd..fd92a9c191 100644 --- a/test/unit/utils/api-request.spec.ts +++ b/test/unit/utils/api-request.spec.ts @@ -26,7 +26,7 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { ApiSettings, HttpClient, HttpError, AuthorizedHttpClient, ApiCallbackFunction, HttpRequestConfig, HttpResponse, parseHttpResponse, RetryConfig, defaultRetryConfig, diff --git a/test/unit/utils/crypto-signer.spec.ts b/test/unit/utils/crypto-signer.spec.ts index 9a59fd10eb..efda058c02 100644 --- a/test/unit/utils/crypto-signer.spec.ts +++ b/test/unit/utils/crypto-signer.spec.ts @@ -25,9 +25,9 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as mocks from '../../resources/mocks'; import { ServiceAccountSigner, IAMSigner, CryptoSignerError } from '../../../src/utils/crypto-signer'; -import { ServiceAccountCredential } from '../../../src/credential/credential-internal'; +import { ServiceAccountCredential } from '../../../src/app/credential-internal'; import { AuthorizedHttpClient, HttpClient } from '../../../src/utils/api-request'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import * as utils from '../utils'; chai.should(); diff --git a/test/unit/utils/index.spec.ts b/test/unit/utils/index.spec.ts index 9729dc7817..7d007f2f78 100644 --- a/test/unit/utils/index.spec.ts +++ b/test/unit/utils/index.spec.ts @@ -25,8 +25,8 @@ import { toWebSafeBase64, formatString, generateUpdateMask, transformMillisecondsToSecondsString, } from '../../../src/utils/index'; import { isNonEmptyString } from '../../../src/utils/validator'; -import { FirebaseApp } from '../../../src/firebase-app'; -import { ComputeEngineCredential } from '../../../src/credential/credential-internal'; +import { FirebaseApp } from '../../../src/app/firebase-app'; +import { ComputeEngineCredential } from '../../../src/app/credential-internal'; import { HttpClient } from '../../../src/utils/api-request'; import * as utils from '../utils'; import { FirebaseAppError } from '../../../src/utils/error'; diff --git a/test/unit/utils/jwt.spec.ts b/test/unit/utils/jwt.spec.ts index a20b9030fb..6689c7ef7a 100644 --- a/test/unit/utils/jwt.spec.ts +++ b/test/unit/utils/jwt.spec.ts @@ -86,7 +86,7 @@ function mockFailedFetchPublicKeys(): nock.Scope { /** * Returns a mocked out success JWKS response. * - * @return {Object} A nock response object. + * @returns A nock response object. */ function mockFetchJsonWebKeys(path: string = jwksPath): nock.Scope { return nock('https://firebaseappcheck.googleapis.com') @@ -98,7 +98,7 @@ function mockFetchJsonWebKeys(path: string = jwksPath): nock.Scope { * Returns a mocked out error response for JWKS. * The status code is 200 but the response itself will contain an 'error' key. * - * @return {Object} A nock response object. + * @returns A nock response object. */ function mockFetchJsonWebKeysWithErrorResponse(): nock.Scope { return nock('https://firebaseappcheck.googleapis.com') @@ -113,7 +113,7 @@ function mockFetchJsonWebKeysWithErrorResponse(): nock.Scope { * Returns a mocked out failed JSON Web Keys response. * The status code is non-200 and the response itself will fail. * - * @return {Object} A nock response object. + * @returns A nock response object. */ function mockFailedFetchJsonWebKeys(): nock.Scope { return nock('https://firebaseappcheck.googleapis.com') @@ -401,7 +401,7 @@ describe('PublicKeySignatureVerifier', () => { .should.eventually.be.rejectedWith('jwt must be provided'); }); - it('should be fullfilled given a valid token', () => { + it('should be fulfilled given a valid token', () => { const keyFetcherStub = sinon.stub(UrlKeyFetcher.prototype, 'fetchPublicKeys') .resolves(VALID_PUBLIC_KEYS_RESPONSE); stubs.push(keyFetcherStub); @@ -410,7 +410,7 @@ describe('PublicKeySignatureVerifier', () => { return verifier.verify(mockIdToken).should.eventually.be.fulfilled; }); - it('should be fullfilled given a valid token without a kid (should check against all the keys)', () => { + it('should be fulfilled given a valid token without a kid (should check against all the keys)', () => { const keyFetcherStub = sinon.stub(UrlKeyFetcher.prototype, 'fetchPublicKeys') .resolves({ 'kid-other': 'key-other', ...VALID_PUBLIC_KEYS_RESPONSE }); stubs.push(keyFetcherStub); diff --git a/tsconfig.json b/tsconfig.json index 67f91761f3..f70690c510 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,6 +15,7 @@ //"strictPropertyInitialization": true, "lib": ["es2018"], "outDir": "lib", + "stripInternal": true, "rootDir": "." }, "files": [