diff --git a/.changeset/config.json b/.changeset/config.json index 41f4c0e6574..8a7e96564c6 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -1,6 +1,9 @@ { "$schema": "https://unpkg.com/@changesets/config@1.1.0/schema.json", - "changelog": ["../repo-scripts/changelog-generator", { "repo": "firebase/firebase-js-sdk"}], + "changelog": [ + "../repo-scripts/changelog-generator", + { "repo": "firebase/firebase-js-sdk" } + ], "commit": false, "linked": [], "access": "public", @@ -10,20 +13,27 @@ "firebase-namespace-integration-test", "firebase-firestore-integration-test", "firebase-messaging-integration-test", + "firebase-compat-interop-test", + "firebase-compat-typings-test", + "@firebase/app-compat", "@firebase/app-exp", - "@firebase/app-types-exp", + "@firebase/app-check-compat", + "@firebase/app-check-exp", + "@firebase/analytics-compat", + "@firebase/analytics-exp", "@firebase/auth-exp", "@firebase/auth-compat", - "@firebase/auth-types-exp", + "@firebase/functions-compat", "@firebase/functions-exp", - "@firebase/functions-types-exp", "@firebase/installations-exp", - "@firebase/installations-types-exp", + "@firebase/installations-compat", + "@firebase/messaging-exp", + "@firebase/messaging-compat", "@firebase/performance-exp", - "@firebase/performance-types-exp", - "@firebase/testing", + "@firebase/performance-compat", + "@firebase/remote-config-exp", + "@firebase/remote-config-compat", "firebase-exp", - "@firebase/app-compat", "@firebase/changelog-generator", "firebase-size-analysis" ], diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5462c0ea9e5..7266ad976e3 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -50,20 +50,17 @@ packages/storage @schmidt-sebastian @firebase/jssdk-global-approvers packages/storage-types @schmidt-sebastian @firebase/jssdk-global-approvers # Messaging Code -packages/messaging @zwu52 @firebase/jssdk-global-approvers -packages/messaging-types @zwu52 @firebase/jssdk-global-approvers -integration/messaging @zwu52 @firebase/jssdk-global-approvers +packages/messaging @zwu52 @chliangGoogle @ciarand @firebase/jssdk-global-approvers +packages/messaging-types @zwu52 @chliangGoogle @ciarand @firebase/jssdk-global-approvers +integration/messaging @zwu52 @chliangGoogle @ciarand @firebase/jssdk-global-approvers # Auth Code -packages/auth @bojeil-google @avolkovi @samhorlbeck @firebase/jssdk-global-approvers -packages/auth-types @bojeil-google @avolkovi @samhorlbeck @firebase/jssdk-global-approvers +packages/auth @bojeil-google @avolkovi @sam-gc @yuchenshi @firebase/jssdk-global-approvers +packages/auth-types @bojeil-google @avolkovi @sam-gc @yuchenshi @firebase/jssdk-global-approvers # Testing Code -packages/testing @avolkovi @samhorlbeck @yuchenshi @firebase/jssdk-global-approvers -packages/rules-unit-testing @avolkovi @samhorlbeck @yuchenshi @firebase/jssdk-global-approvers - -# RxFire Code -packages/rxfire @davideast @jamesdaniels @firebase/jssdk-global-approvers +packages/testing @avolkovi @sam-gc @yuchenshi @firebase/jssdk-global-approvers +packages/rules-unit-testing @avolkovi @sam-gc @yuchenshi @firebase/jssdk-global-approvers # Installations packages/installations @andirayo @ChaoqunCHEN @firebase/jssdk-global-approvers @@ -82,16 +79,15 @@ packages/remote-config @erikeldridge @firebase/jssdk-global-approvers packages/remote-config-types @erikeldridge @firebase/jssdk-global-approvers # Documentation Changes -packages/firebase/index.d.ts @firebase/firebase-techwriters @firebase/jssdk-global-approvers -scripts/docgen/content-sources/ @firebase/firebase-techwriters @firebase/jssdk-global-approvers +packages/firebase/index.d.ts @egilmorez @firebase/jssdk-global-approvers +scripts/docgen/content-sources/ @egilmorez @firebase/jssdk-global-approvers # Changeset -.changeset @firebase/firebase-techwriters @firebase/jssdk-changeset-approvers @firebase/firestore-js-team @firebase/jssdk-global-approvers +.changeset @egilmorez @firebase/jssdk-changeset-approvers @firebase/firestore-js-team @firebase/jssdk-global-approvers # Auth-Exp Code -packages-exp/auth-exp @avolkovi @samhorlbeck @firebase/jssdk-global-approvers -packages-exp/auth-types-exp @avolkovi @samhorlbeck @firebase/jssdk-global-approvers -packages-exp/auth-compat-exp @avolkovi @samhorlbeck @firebase/jssdk-global-approvers +packages-exp/auth-exp @avolkovi @sam-gc @yuchenshi @firebase/jssdk-global-approvers +packages-exp/auth-compat-exp @avolkovi @sam-gc @yuchenshi @firebase/jssdk-global-approvers # Installations-Exp Code packages/installations-exp @andirayo @ChaoqunCHEN @firebase/jssdk-global-approvers @@ -99,4 +95,8 @@ packages/installations-types-exp @andirayo @ChaoqunCHEN @firebase/jssdk-global-a # Perf-Exp Code packages/performance-exp @alikn @zijianjoy @firebase/jssdk-global-approvers -packages/performance-types-exp @alikn @zijianjoy @firebase/jssdk-global-approvers \ No newline at end of file +packages/performance-types-exp @alikn @zijianjoy @firebase/jssdk-global-approvers + +# RC-Exp Code +packages/remote-config-exp @erikeldridge @firebase/jssdk-global-approvers +packages/remote-config-compat @erikeldridge @firebase/jssdk-global-approvers \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 90b30d15ed9..00000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,44 +0,0 @@ - - - - - -### [REQUIRED] Describe your environment - - * Operating System version: _____ - * Browser version: _____ - * Firebase SDK version: _____ - * Firebase Product: _____ (auth, database, storage, etc) - - - -### [REQUIRED] Describe the problem - -#### Steps to reproduce: - -#### Relevant Code: - - -https://stackblitz.com/fork/firebase-issue-sandbox - -```javascript -// TODO(you): code here to reproduce the problem -``` diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000000..847e993c4dd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,50 @@ +--- +name: 🐞 Bug report +about: Found a bug in the JS SDK? File it here. +title: '' +labels: '' +assignees: '' + +--- + + + + + + + +### [REQUIRED] Describe your environment + + * Operating System version: _____ + * Browser version: _____ + * Firebase SDK version: _____ + * Firebase Product: _____ (auth, database, storage, etc) + + + +### [REQUIRED] Describe the problem + +#### Steps to reproduce: + +#### Relevant Code: + +```javascript +// TODO(you): code here to reproduce the problem +``` diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000000..82a99ad6b41 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: 🔥 Firebase Support + url: https://firebase.google.com/support/ + about: If you have an urgent issue with your app in production, please contact support. diff --git a/.github/auto_assign.yml b/.github/auto_assign.yml new file mode 100644 index 00000000000..c66465142ea --- /dev/null +++ b/.github/auto_assign.yml @@ -0,0 +1,26 @@ +# Set to true to add reviewers to pull requests +addReviewers: false + +# Set to true to add assignees to pull requests +addAssignees: true + +# A list of reviewers to be added to pull requests (GitHub user name) +# reviewers: +# - egilmorez + +# A number of reviewers added to the pull request +# Set 0 to add all the reviewers (default: 0) +# numberOfReviewers: 0 + +# A list of assignees, overrides reviewers if set +assignees: + - egilmorez + +# A number of assignees to add to the pull request +# Set to 0 to add all of the assignees. +# Uses numberOfReviewers if unset. +numberOfAssignees: 0 + +# A list of keywords to be skipped the process that add reviewers if pull requests include it +# skipKeywords: +# - wip \ No newline at end of file diff --git a/.github/workflows/assign-tech-writers.yml b/.github/workflows/assign-tech-writers.yml new file mode 100644 index 00000000000..f8a83cef238 --- /dev/null +++ b/.github/workflows/assign-tech-writers.yml @@ -0,0 +1,15 @@ +name: Assign tech writers + +on: + pull_request: + types: [labeled] +jobs: + assign_tech_writers: + if: ${{github.event.label.name == 'doc-changes'}} + runs-on: ubuntu-latest + + steps: + - name: assign techwriters to PR + uses: kentaro-m/auto-assign-action@v1.2.0 + with: + configuration-path: ".github/auto_assign.yml" diff --git a/.github/workflows/canary-deploy.yml b/.github/workflows/canary-deploy.yml index 35b7c92049a..2572029f6c8 100644 --- a/.github/workflows/canary-deploy.yml +++ b/.github/workflows/canary-deploy.yml @@ -16,10 +16,10 @@ jobs: with: # Canary release script requires git history and tags. fetch-depth: 0 - - name: Set up Node (10) - uses: actions/setup-node@v1 + - name: Set up Node (14) + uses: actions/setup-node@v2 with: - node-version: 10.x + node-version: 14.x - name: Yarn install run: yarn - name: Deploy canary @@ -30,6 +30,9 @@ jobs: NPM_TOKEN_ANALYTICS_TYPES: ${{secrets.NPM_TOKEN_ANALYTICS_TYPES}} NPM_TOKEN_APP: ${{secrets.NPM_TOKEN_APP}} NPM_TOKEN_APP_TYPES: ${{secrets.NPM_TOKEN_APP_TYPES}} + NPM_TOKEN_APP_CHECK: ${{secrets.NPM_TOKEN_APP_CHECK}} + NPM_TOKEN_APP_CHECK_INTEROP_TYPES: ${{secrets.NPM_TOKEN_APP_CHECK_INTEROP_TYPES}} + NPM_TOKEN_APP_CHECK_TYPES: ${{secrets.NPM_TOKEN_APP_CHECK_TYPES}} NPM_TOKEN_AUTH: ${{secrets.NPM_TOKEN_AUTH}} NPM_TOKEN_AUTH_INTEROP_TYPES: ${{secrets.NPM_TOKEN_AUTH_INTEROP_TYPES}} NPM_TOKEN_AUTH_TYPES: ${{secrets.NPM_TOKEN_AUTH_TYPES}} @@ -57,5 +60,4 @@ jobs: NPM_TOKEN_UTIL: ${{secrets.NPM_TOKEN_UTIL}} NPM_TOKEN_WEBCHANNEL_WRAPPER: ${{secrets.NPM_TOKEN_WEBCHANNEL_WRAPPER}} NPM_TOKEN_FIREBASE: ${{secrets.NPM_TOKEN_FIREBASE}} - NPM_TOKEN_RXFIRE: ${{secrets.NPM_TOKEN_RXFIRE}} CI: true diff --git a/.github/workflows/check-changeset.yml b/.github/workflows/check-changeset.yml index 286f4234bcd..3d35135df6f 100644 --- a/.github/workflows/check-changeset.yml +++ b/.github/workflows/check-changeset.yml @@ -2,6 +2,10 @@ name: Check Changeset on: pull_request +env: + GITHUB_PULL_REQUEST_HEAD_SHA: ${{ github.event.pull_request.head.sha }} + GITHUB_PULL_REQUEST_BASE_SHA: ${{ github.event.pull_request.base.sha }} + jobs: check_changeset: name: Check changeset vs changed files @@ -13,17 +17,19 @@ jobs: with: # This makes Actions fetch all Git history so check_changeset script can diff properly. fetch-depth: 0 - - name: Set up Node (10) - uses: actions/setup-node@v1 + - name: Set up Node (14) + uses: actions/setup-node@v2 with: - node-version: 10.x + node-version: 14.x - name: Yarn install run: yarn - name: Run changeset script run: yarn ts-node-script scripts/check_changeset.ts id: check-changeset - - name: Read output - run: echo "${{steps.check-changeset.outputs.MISSING_PACKAGES}}" + - name: Print changeset checker output + run: echo "${{steps.check-changeset.outputs.CHANGESET_ERROR_MESSAGE}}" + - name: Print blocking failure status + run: echo "${{steps.check-changeset.outputs.BLOCKING_FAILURE}}" - name: Find Comment uses: peter-evans/find-comment@v1 id: fc @@ -31,34 +37,33 @@ jobs: issue-number: ${{github.event.number}} body-includes: Changeset File Check - name: Create comment (missing packages) - if: ${{!steps.fc.outputs.comment-id && steps.check-changeset.outputs.MISSING_PACKAGES}} + if: ${{!steps.fc.outputs.comment-id && steps.check-changeset.outputs.CHANGESET_ERROR_MESSAGE}} uses: peter-evans/create-or-update-comment@v1 with: issue-number: ${{github.event.number}} body: | ### Changeset File Check :warning: - Warning: This PR modifies files in the following packages but they have not been included in the changeset file: - ${{steps.check-changeset.outputs.MISSING_PACKAGES}} - - Make sure this was intentional. + ${{steps.check-changeset.outputs.CHANGESET_ERROR_MESSAGE}} - name: Update comment (missing packages) if: ${{steps.fc.outputs.comment-id}} uses: peter-evans/create-or-update-comment@v1 with: - comment-id: ${{steps.fc.outputs.comment-id}} && steps.check-changeset.outputs.MISSING_PACKAGES}} + comment-id: ${{steps.fc.outputs.comment-id}} && steps.check-changeset.outputs.CHANGESET_ERROR_MESSAGE}} edit-mode: replace body: | ### Changeset File Check :warning: - Warning: This PR modifies files in the following packages but they have not been included in the changeset file: - ${{steps.check-changeset.outputs.MISSING_PACKAGES}} - - Make sure this was intentional. + ${{steps.check-changeset.outputs.CHANGESET_ERROR_MESSAGE}} - name: Update comment (no missing packages) - if: ${{steps.fc.outputs.comment-id && !steps.check-changeset.outputs.MISSING_PACKAGES}} + if: ${{steps.fc.outputs.comment-id && !steps.check-changeset.outputs.CHANGESET_ERROR_MESSAGE}} uses: peter-evans/create-or-update-comment@v1 with: comment-id: ${{steps.fc.outputs.comment-id}} edit-mode: replace body: | ### Changeset File Check :white_check_mark: - No modified packages are missing from the changeset file. \ No newline at end of file + - No modified packages are missing from the changeset file. + - No changeset formatting errors detected. + # Don't want it to throw before editing the comment. + - name: Fail if checker script logged a blocking failure + if: ${{steps.check-changeset.outputs.BLOCKING_FAILURE == 'true'}} + run: exit 1 \ No newline at end of file diff --git a/.github/workflows/cross-browser-test.yml b/.github/workflows/cross-browser-test.yml index 0e472ec1694..54259fb6ec1 100644 --- a/.github/workflows/cross-browser-test.yml +++ b/.github/workflows/cross-browser-test.yml @@ -15,10 +15,10 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Use Node.js 10.x - uses: actions/setup-node@v1 + - name: Use Node.js 14.x + uses: actions/setup-node@v2 with: - node-version: 10.x + node-version: 14.x - name: Test setup and yarn install run: | cp config/ci.config.json config/project.json diff --git a/.github/workflows/health-metrics-test.yml b/.github/workflows/health-metrics-test.yml index 2d95093e076..b28e51bebfe 100644 --- a/.github/workflows/health-metrics-test.yml +++ b/.github/workflows/health-metrics-test.yml @@ -6,6 +6,7 @@ env: METRICS_SERVICE_URL: ${{ secrets.METRICS_SERVICE_URL }} GITHUB_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} GITHUB_PULL_REQUEST_BASE_SHA: ${{ github.event.pull_request.base.sha }} + NODE_OPTIONS: "--max-old-space-size=4096" jobs: binary-size: @@ -14,9 +15,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 + - uses: actions/setup-node@v2 with: - node-version: 10.x + node-version: 14.x - uses: GoogleCloudPlatform/github-actions/setup-gcloud@master with: service_account_key: ${{ secrets.GCP_SA_KEY }} @@ -30,9 +31,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 + - uses: actions/setup-node@v2 with: - node-version: 10.x + node-version: 14.x - uses: GoogleCloudPlatform/github-actions/setup-gcloud@master with: service_account_key: ${{ secrets.GCP_SA_KEY }} diff --git a/.github/workflows/label-doc-changes.yml b/.github/workflows/label-doc-changes.yml new file mode 100644 index 00000000000..adde41c8d69 --- /dev/null +++ b/.github/workflows/label-doc-changes.yml @@ -0,0 +1,36 @@ +name: Label doc changes + +on: pull_request + +env: + GITHUB_PULL_REQUEST_HEAD_SHA: ${{ github.event.pull_request.head.sha }} + GITHUB_PULL_REQUEST_BASE_SHA: ${{ github.event.pull_request.base.sha }} + +jobs: + check_doc_changes: + name: Check if docs are being changed + runs-on: ubuntu-latest + + steps: + - name: Checkout Repo + uses: actions/checkout@master + with: + # This makes Actions fetch all Git history so check_changeset script can diff properly. + fetch-depth: 0 + - name: Set up Node (14) + uses: actions/setup-node@v2 + with: + node-version: 14.x + - name: Yarn install + run: yarn + - name: Run detect doc changes script + run: yarn ts-node-script scripts/exp/detect-doc-changes.ts + id: check-doc-changes + - name: Print if doc changed output + run: echo "${{steps.check-doc-changes.outputs.DOC_CHANGED}}" + - name: Add label if there are doc changes + uses: actions-ecosystem/action-add-labels@v1.1.0 + if: ${{steps.check-doc-changes.outputs.DOC_CHANGED == 'true'}} + with: + labels: doc-changes + github_token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 45422454a08..94ab40ad7b3 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,6 +1,6 @@ name: Lint All Packages -on: push +on: pull_request jobs: test: @@ -9,10 +9,10 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up Node (10) - uses: actions/setup-node@v1 + - name: Set up Node (14) + uses: actions/setup-node@v2 with: - node-version: 10.x + node-version: 14.x - name: yarn install run: yarn - name: yarn lint diff --git a/.github/workflows/prerelease-manual-deploy.yml b/.github/workflows/prerelease-manual-deploy.yml index d862d045652..64f15de5421 100644 --- a/.github/workflows/prerelease-manual-deploy.yml +++ b/.github/workflows/prerelease-manual-deploy.yml @@ -20,10 +20,10 @@ jobs: with: # Canary release script requires git history and tags. fetch-depth: 0 - - name: Set up Node (10) - uses: actions/setup-node@v1 + - name: Set up Node (14) + uses: actions/setup-node@v2 with: - node-version: 10.x + node-version: 14.x - name: Yarn install run: yarn - name: Deploy prerelease @@ -61,6 +61,5 @@ jobs: NPM_TOKEN_UTIL: ${{secrets.NPM_TOKEN_UTIL}} NPM_TOKEN_WEBCHANNEL_WRAPPER: ${{secrets.NPM_TOKEN_WEBCHANNEL_WRAPPER}} NPM_TOKEN_FIREBASE: ${{secrets.NPM_TOKEN_FIREBASE}} - NPM_TOKEN_RXFIRE: ${{secrets.NPM_TOKEN_RXFIRE}} CI: true \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1f566be4da1..256ee58db70 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,14 +16,17 @@ jobs: # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits fetch-depth: 0 - - name: Setup Node.js 12.x + - name: Setup Node.js 14.x uses: actions/setup-node@master with: - node-version: 12.x + node-version: 14.x - name: Install Dependencies run: yarn + - name: Add a changeset for @firebase/app + run: yarn ts-node-script scripts/add_changeset.ts + - name: Create Release Pull Request uses: changesets/action@master env: diff --git a/.github/workflows/test-all.yml b/.github/workflows/test-all.yml index 4a294bedfba..72edafe7741 100644 --- a/.github/workflows/test-all.yml +++ b/.github/workflows/test-all.yml @@ -9,14 +9,16 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up Node (10) - uses: actions/setup-node@v1 + - name: Set up Node (14) + uses: actions/setup-node@v2 with: - node-version: 10.x + node-version: 14.x - name: install Chrome stable run: | sudo apt-get update sudo apt-get install google-chrome-stable + - name: Bump Node memory limit + run: echo "NODE_OPTIONS=--max_old_space_size=4096" >> $GITHUB_ENV - name: Test setup and yarn install run: | cp config/ci.config.json config/project.json @@ -24,7 +26,7 @@ jobs: - name: yarn build run: yarn build - name: Set start timestamp env var - run: echo "::set-env name=FIREBASE_CI_TEST_START_TIME::$(date +%s)" + run: echo "FIREBASE_CI_TEST_START_TIME=$(date +%s)" >> $GITHUB_ENV - name: Run unit tests run: | xvfb-run yarn test:ci diff --git a/.github/workflows/test-changed-auth.yml b/.github/workflows/test-changed-auth.yml index a18ffa13b40..c3f1e7d0767 100644 --- a/.github/workflows/test-changed-auth.yml +++ b/.github/workflows/test-changed-auth.yml @@ -13,19 +13,21 @@ jobs: with: # This makes Actions fetch all Git history so run-changed script can diff properly. fetch-depth: 0 - - name: Set up Node (10) - uses: actions/setup-node@v1 + - name: Set up Node (14) + uses: actions/setup-node@v2 with: - node-version: 10.x + node-version: 14.x - name: install Chrome stable run: | sudo apt-get update sudo apt-get install google-chrome-stable + - name: Bump Node memory limit + run: echo "NODE_OPTIONS=--max_old_space_size=4096" >> $GITHUB_ENV - name: Test setup and yarn install run: | cp config/ci.config.json config/project.json yarn - name: build - run: yarn build + run: yarn build:changed auth --buildAll - name: Run tests on changed packages run: xvfb-run yarn test:changed auth \ No newline at end of file diff --git a/.github/workflows/test-changed-fcm-integration.yml b/.github/workflows/test-changed-fcm-integration.yml index 2a127d11a40..23d680cf6fe 100644 --- a/.github/workflows/test-changed-fcm-integration.yml +++ b/.github/workflows/test-changed-fcm-integration.yml @@ -13,14 +13,16 @@ jobs: with: # This makes Actions fetch all Git history so run-changed script can diff properly. fetch-depth: 0 - - name: Set up Node (10) - uses: actions/setup-node@v1 + - name: Set up Node (14) + uses: actions/setup-node@v2 with: - node-version: 10.x + node-version: 14.x - name: install Chrome stable run: | sudo apt-get update sudo apt-get install google-chrome-stable + - name: Bump Node memory limit + run: echo "NODE_OPTIONS=--max_old_space_size=4096" >> $GITHUB_ENV - name: Test setup and yarn install run: | cp config/ci.config.json config/project.json diff --git a/.github/workflows/test-changed-firestore-integration.yml b/.github/workflows/test-changed-firestore-integration.yml index 387701316b2..b686021953d 100644 --- a/.github/workflows/test-changed-firestore-integration.yml +++ b/.github/workflows/test-changed-firestore-integration.yml @@ -13,19 +13,21 @@ jobs: with: # This makes Actions fetch all Git history so run-changed script can diff properly. fetch-depth: 0 - - name: Set up Node (10) - uses: actions/setup-node@v1 + - name: Set up Node (14) + uses: actions/setup-node@v2 with: - node-version: 10.x + node-version: 14.x - name: install Chrome stable run: | sudo apt-get update sudo apt-get install google-chrome-stable + - name: Bump Node memory limit + run: echo "NODE_OPTIONS=--max_old_space_size=4096" >> $GITHUB_ENV - name: Test setup and yarn install run: | cp config/ci.config.json config/project.json yarn - name: build - run: yarn build:changed firestore-integration --buildAppExp + run: yarn build:changed firestore-integration --buildAppExp --buildAppCompat - name: Run tests if firestore or its dependencies has changed run: yarn test:changed firestore-integration diff --git a/.github/workflows/test-changed-firestore.yml b/.github/workflows/test-changed-firestore.yml index b4ced05b867..bc28ec6f4e0 100644 --- a/.github/workflows/test-changed-firestore.yml +++ b/.github/workflows/test-changed-firestore.yml @@ -13,19 +13,21 @@ jobs: with: # This makes Actions fetch all Git history so run-changed script can diff properly. fetch-depth: 0 - - name: Set up Node (10) - uses: actions/setup-node@v1 + - name: Set up Node (14) + uses: actions/setup-node@v2 with: - node-version: 10.x + node-version: 14.x - name: install Chrome stable run: | sudo apt-get update sudo apt-get install google-chrome-stable + - name: Bump Node memory limit + run: echo "NODE_OPTIONS=--max_old_space_size=4096" >> $GITHUB_ENV - name: Test setup and yarn install run: | cp config/ci.config.json config/project.json yarn - name: build - run: yarn build:changed firestore --buildAppExp + run: yarn build:changed firestore --buildAppExp --buildAppCompat - name: Run tests if firestore or its dependencies has changed run: yarn test:changed firestore diff --git a/.github/workflows/test-changed-misc.yml b/.github/workflows/test-changed-misc.yml index 6bdf4117ee0..13007a76cfb 100644 --- a/.github/workflows/test-changed-misc.yml +++ b/.github/workflows/test-changed-misc.yml @@ -1,4 +1,4 @@ -name: Test rxFire, @firebase/testing and @firebase/rules-unit-testing +name: Test @firebase/rules-unit-testing on: pull_request @@ -13,14 +13,16 @@ jobs: with: # This makes Actions fetch all Git history so run-changed script can diff properly. fetch-depth: 0 - - name: Set up Node (10) - uses: actions/setup-node@v1 + - name: Set up Node (14) + uses: actions/setup-node@v2 with: - node-version: 10.x + node-version: 14.x - name: install Chrome stable run: | sudo apt-get update sudo apt-get install google-chrome-stable + - name: Bump Node memory limit + run: echo "NODE_OPTIONS=--max_old_space_size=4096" >> $GITHUB_ENV - name: Test setup and yarn install run: | cp config/ci.config.json config/project.json diff --git a/.github/workflows/test-changed.yml b/.github/workflows/test-changed.yml index cb836d9c312..e818272a703 100644 --- a/.github/workflows/test-changed.yml +++ b/.github/workflows/test-changed.yml @@ -13,19 +13,21 @@ jobs: with: # This makes Actions fetch all Git history so run-changed script can diff properly. fetch-depth: 0 - - name: Set up Node (10) - uses: actions/setup-node@v1 + - name: Set up Node (14) + uses: actions/setup-node@v2 with: - node-version: 10.x + node-version: 14.x - name: install Chrome stable run: | sudo apt-get update sudo apt-get install google-chrome-stable + - name: Bump Node memory limit + run: echo "NODE_OPTIONS=--max_old_space_size=4096" >> $GITHUB_ENV - name: Test setup and yarn install run: | cp config/ci.config.json config/project.json yarn - name: build - run: yarn build:changed core + run: yarn build:changed core --buildAppExp - name: Run tests on changed packages run: xvfb-run yarn test:changed core \ No newline at end of file diff --git a/.github/workflows/test-firebase-integration.yml b/.github/workflows/test-firebase-integration.yml index c42c1c08687..154b2b7c12f 100644 --- a/.github/workflows/test-firebase-integration.yml +++ b/.github/workflows/test-firebase-integration.yml @@ -13,14 +13,16 @@ jobs: with: # This makes Actions fetch all Git history so run-changed script can diff properly. fetch-depth: 0 - - name: Set up Node (10) - uses: actions/setup-node@v1 + - name: Set up Node (14) + uses: actions/setup-node@v2 with: - node-version: 10.x + node-version: 14.x - name: install Chrome stable run: | sudo apt-get update sudo apt-get install google-chrome-stable + - name: Bump Node memory limit + run: echo "NODE_OPTIONS=--max_old_space_size=4096" >> $GITHUB_ENV - name: Test setup and yarn install run: | cp config/ci.config.json config/project.json diff --git a/.gitignore b/.gitignore index 93f49bb5bb0..dc9fe690372 100644 --- a/.gitignore +++ b/.gitignore @@ -88,6 +88,7 @@ packages-exp/**/temp # temp markdowns generated for individual SDKs packages-exp/**/docs +packages/**/docs # files generated by api-extractor that should not be tracked tsdoc-metadata.json \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 1a4875b99aa..338dd7801dc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,4 +11,4 @@ }, "typescript.tsdk": "node_modules/typescript/lib", "files.associations": { "*.json": "jsonc" } -} +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000000..ec4d0166d7e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +You can view the changelog for the packages within this monorepo in `packages//CHANGELOG.md`. For example, you will find the changelog for the Firestore package at [`packages/firestore/CHANGELOG.md`](./packages/firestore/CHANGELOG.md). + +Additionally, you can view our official release notes for the entire monorepo at the [firebase support page](https://firebase.google.com/support/release-notes/js). diff --git a/LICENSE b/LICENSE index d6456956733..d636db9ddaf 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,5 @@ +This source code is licensed under Apache-2.0 apart from the protobuf library +which is licensed under BSD-3-Clause, both licenses given below. Apache License Version 2.0, January 2004 @@ -200,3 +202,39 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +------------------------------------------------------------------------------ + +For Google protobuf source. + +file: [source code root]/packages/firestore/src/protos/google/protobuf/any.proto + +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/common/api-review/analytics-exp.api.md b/common/api-review/analytics-exp.api.md new file mode 100644 index 00000000000..35172fa0f8f --- /dev/null +++ b/common/api-review/analytics-exp.api.md @@ -0,0 +1,420 @@ +## API Report File for "@firebase/analytics-exp" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { FirebaseApp } from '@firebase/app-exp'; + +// @public +export interface Analytics { + app: FirebaseApp; +} + +// @public +export interface AnalyticsCallOptions { + global: boolean; +} + +// @public +export interface AnalyticsOptions { + config?: GtagConfigParams | EventParams; +} + +// @public +export interface ControlParams { + // (undocumented) + event_callback?: () => void; + // (undocumented) + event_timeout?: number; + // (undocumented) + groups?: string | string[]; + // (undocumented) + send_to?: string | string[]; +} + +// @public +export type Currency = string | number; + +// @public +export type CustomEventName = T extends EventNameString ? never : T; + +// @public +export interface CustomParams { + // (undocumented) + [key: string]: unknown; +} + +// @public +export type EventNameString = 'add_payment_info' | 'add_shipping_info' | 'add_to_cart' | 'add_to_wishlist' | 'begin_checkout' | 'checkout_progress' | 'exception' | 'generate_lead' | 'login' | 'page_view' | 'purchase' | 'refund' | 'remove_from_cart' | 'screen_view' | 'search' | 'select_content' | 'select_item' | 'select_promotion' | 'set_checkout_option' | 'share' | 'sign_up' | 'timing_complete' | 'view_cart' | 'view_item' | 'view_item_list' | 'view_promotion' | 'view_search_results'; + +// @public +export interface EventParams { + // (undocumented) + [key: string]: unknown; + // (undocumented) + affiliation?: string; + // (undocumented) + checkout_option?: string; + // (undocumented) + checkout_step?: number; + // (undocumented) + content_id?: string; + // (undocumented) + content_type?: string; + // (undocumented) + coupon?: string; + // (undocumented) + currency?: string; + // (undocumented) + description?: string; + // (undocumented) + event_category?: string; + // (undocumented) + event_label?: string; + // (undocumented) + fatal?: boolean; + // (undocumented) + item_list_id?: string; + // (undocumented) + item_list_name?: string; + // (undocumented) + items?: Item[]; + // (undocumented) + method?: string; + // (undocumented) + number?: string; + // (undocumented) + page_location?: string; + // (undocumented) + page_path?: string; + // (undocumented) + page_title?: string; + // (undocumented) + payment_type?: string; + // (undocumented) + promotion_id?: string; + // (undocumented) + promotion_name?: string; + // (undocumented) + promotions?: Promotion[]; + // (undocumented) + screen_name?: string; + // (undocumented) + search_term?: string; + // (undocumented) + shipping?: Currency; + // (undocumented) + shipping_tier?: string; + // (undocumented) + tax?: Currency; + // (undocumented) + transaction_id?: string; + // (undocumented) + value?: number; +} + +// @public +export function getAnalytics(app?: FirebaseApp): Analytics; + +// @public +export interface GtagConfigParams { + 'allow_google_signals?': boolean; + // (undocumented) + [key: string]: unknown; + 'allow_ad_personalization_signals'?: boolean; + 'anonymize_ip'?: boolean; + 'cookie_domain'?: string; + 'cookie_expires'?: number; + 'cookie_flags'?: string; + 'cookie_prefix'?: string; + 'cookie_update'?: boolean; + 'custom_map'?: { + [key: string]: unknown; + }; + 'link_attribution'?: boolean; + 'page_location'?: string; + 'page_path'?: string; + 'page_title'?: string; + 'send_page_view'?: boolean; +} + +// @public +export function initializeAnalytics(app: FirebaseApp, options?: AnalyticsOptions): Analytics; + +// @public +export function isSupported(): Promise; + +// @public +export interface Item { + // (undocumented) + affiliation?: string; + // @deprecated (undocumented) + brand?: string; + // @deprecated (undocumented) + category?: string; + // (undocumented) + coupon?: string; + // (undocumented) + creative_name?: string; + // (undocumented) + creative_slot?: string; + // (undocumented) + discount?: Currency; + // @deprecated (undocumented) + id?: string; + // (undocumented) + index?: number; + // (undocumented) + item_brand?: string; + // (undocumented) + item_category?: string; + // (undocumented) + item_category2?: string; + // (undocumented) + item_category3?: string; + // (undocumented) + item_category4?: string; + // (undocumented) + item_category5?: string; + // (undocumented) + item_id?: string; + // (undocumented) + item_list_id?: string; + // (undocumented) + item_list_name?: string; + // (undocumented) + item_name?: string; + // (undocumented) + item_variant?: string; + // (undocumented) + location_id?: string; + // @deprecated (undocumented) + name?: string; + // (undocumented) + price?: Currency; + // (undocumented) + promotion_id?: string; + // (undocumented) + promotion_name?: string; + // (undocumented) + quantity?: number; +} + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'add_payment_info', eventParams?: { + coupon?: EventParams['coupon']; + currency?: EventParams['currency']; + items?: EventParams['items']; + payment_type?: EventParams['payment_type']; + value?: EventParams['value']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'add_shipping_info', eventParams?: { + coupon?: EventParams['coupon']; + currency?: EventParams['currency']; + items?: EventParams['items']; + shipping_tier?: EventParams['shipping_tier']; + value?: EventParams['value']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'add_to_cart' | 'add_to_wishlist' | 'remove_from_cart', eventParams?: { + currency?: EventParams['currency']; + value?: EventParams['value']; + items?: EventParams['items']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'begin_checkout', eventParams?: { + currency?: EventParams['currency']; + coupon?: EventParams['coupon']; + value?: EventParams['value']; + items?: EventParams['items']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'checkout_progress', eventParams?: { + currency?: EventParams['currency']; + coupon?: EventParams['coupon']; + value?: EventParams['value']; + items?: EventParams['items']; + checkout_step?: EventParams['checkout_step']; + checkout_option?: EventParams['checkout_option']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'exception', eventParams?: { + description?: EventParams['description']; + fatal?: EventParams['fatal']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'generate_lead', eventParams?: { + value?: EventParams['value']; + currency?: EventParams['currency']; + transaction_id?: EventParams['transaction_id']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'login', eventParams?: { + method?: EventParams['method']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'page_view', eventParams?: { + page_title?: string; + page_location?: string; + page_path?: string; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'purchase' | 'refund', eventParams?: { + value?: EventParams['value']; + currency?: EventParams['currency']; + transaction_id: EventParams['transaction_id']; + tax?: EventParams['tax']; + shipping?: EventParams['shipping']; + items?: EventParams['items']; + coupon?: EventParams['coupon']; + affiliation?: EventParams['affiliation']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'screen_view', eventParams?: { + app_name: string; + screen_name: EventParams['screen_name']; + app_id?: string; + app_version?: string; + app_installer_id?: string; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'search' | 'view_search_results', eventParams?: { + search_term?: EventParams['search_term']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'select_content', eventParams?: { + items?: EventParams['items']; + promotions?: EventParams['promotions']; + content_type?: EventParams['content_type']; + content_id?: EventParams['content_id']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'select_item', eventParams?: { + items?: EventParams['items']; + item_list_name?: EventParams['item_list_name']; + item_list_id?: EventParams['item_list_id']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'select_promotion' | 'view_promotion', eventParams?: { + items?: EventParams['items']; + promotion_id?: EventParams['promotion_id']; + promotion_name?: EventParams['promotion_name']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'set_checkout_option', eventParams?: { + checkout_step?: EventParams['checkout_step']; + checkout_option?: EventParams['checkout_option']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'share', eventParams?: { + method?: EventParams['method']; + content_type?: EventParams['content_type']; + content_id?: EventParams['content_id']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'sign_up', eventParams?: { + method?: EventParams['method']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'timing_complete', eventParams?: { + name: string; + value: number; + event_category?: string; + event_label?: string; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'view_cart' | 'view_item', eventParams?: { + currency?: EventParams['currency']; + items?: EventParams['items']; + value?: EventParams['value']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: 'view_item_list', eventParams?: { + items?: EventParams['items']; + item_list_name?: EventParams['item_list_name']; + item_list_id?: EventParams['item_list_id']; + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public +export function logEvent(analyticsInstance: Analytics, eventName: CustomEventName, eventParams?: { + [key: string]: any; +}, options?: AnalyticsCallOptions): void; + +// @public @deprecated +export interface Promotion { + // (undocumented) + creative_name?: string; + // (undocumented) + creative_slot?: string; + // (undocumented) + id?: string; + // (undocumented) + name?: string; +} + +// @public +export function setAnalyticsCollectionEnabled(analyticsInstance: Analytics, enabled: boolean): void; + +// @public +export function setCurrentScreen(analyticsInstance: Analytics, screenName: string, options?: AnalyticsCallOptions): void; + +// @public +export function settings(options: SettingsOptions): void; + +// @public +export interface SettingsOptions { + dataLayerName?: string; + gtagName?: string; +} + +// @public +export function setUserId(analyticsInstance: Analytics, id: string, options?: AnalyticsCallOptions): void; + +// @public +export function setUserProperties(analyticsInstance: Analytics, properties: CustomParams, options?: AnalyticsCallOptions): void; + + +``` diff --git a/common/api-review/app-check-exp.api.md b/common/api-review/app-check-exp.api.md new file mode 100644 index 00000000000..8e48cdd795a --- /dev/null +++ b/common/api-review/app-check-exp.api.md @@ -0,0 +1,92 @@ +## API Report File for "@firebase/app-check-exp" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { FirebaseApp } from '@firebase/app-exp'; +import { PartialObserver } from '@firebase/util'; +import { Unsubscribe } from '@firebase/util'; + +// @public +export interface AppCheck { + app: FirebaseApp; +} + +// @internal (undocumented) +export type _AppCheckComponentName = 'app-check-exp'; + +// @internal (undocumented) +export type _AppCheckInternalComponentName = 'app-check-internal'; + +// @public +export interface AppCheckOptions { + isTokenAutoRefreshEnabled?: boolean; + provider: CustomProvider | ReCaptchaV3Provider; +} + +// @public +export interface AppCheckToken { + readonly expireTimeMillis: number; + // (undocumented) + readonly token: string; +} + +// @public +export type AppCheckTokenListener = (token: AppCheckTokenResult) => void; + +// @public +export interface AppCheckTokenResult { + readonly token: string; +} + +// Warning: (ae-forgotten-export) The symbol "AppCheckProvider" needs to be exported by the entry point index.d.ts +// +// @public +export class CustomProvider implements AppCheckProvider { + constructor(_customProviderOptions: CustomProviderOptions); + // Warning: (ae-forgotten-export) The symbol "AppCheckTokenInternal" needs to be exported by the entry point index.d.ts + // + // @internal (undocumented) + getToken(): Promise; + // @internal (undocumented) + initialize(app: FirebaseApp): void; +} + +// @public +export interface CustomProviderOptions { + getToken: () => Promise; +} + +// @public +export function getToken(appCheckInstance: AppCheck, forceRefresh?: boolean): Promise; + +// @public +export function initializeAppCheck(app: FirebaseApp | undefined, options: AppCheckOptions): AppCheck; + +// @public +export function onTokenChanged(appCheckInstance: AppCheck, observer: PartialObserver): Unsubscribe; + +// @public +export function onTokenChanged(appCheckInstance: AppCheck, onNext: (tokenResult: AppCheckTokenResult) => void, onError?: (error: Error) => void, onCompletion?: () => void): Unsubscribe; + +export { PartialObserver } + +// @public +export class ReCaptchaV3Provider implements AppCheckProvider { + constructor(_siteKey: string); + // @internal + getToken(): Promise; + // @internal (undocumented) + initialize(app: FirebaseApp): void; + } + +// @public +export function setTokenAutoRefreshEnabled(appCheckInstance: AppCheck, isTokenAutoRefreshEnabled: boolean): void; + +export { Unsubscribe } + + +// (No @packageDocumentation comment for this package) + +``` diff --git a/common/api-review/app-exp.api.md b/common/api-review/app-exp.api.md index bf230efd778..983711e383d 100644 --- a/common/api-review/app-exp.api.md +++ b/common/api-review/app-exp.api.md @@ -5,9 +5,7 @@ ```ts import { Component } from '@firebase/component'; -import { FirebaseApp } from '@firebase/app-types-exp'; -import { FirebaseAppConfig } from '@firebase/app-types-exp'; -import { FirebaseOptions } from '@firebase/app-types-exp'; +import { ComponentContainer } from '@firebase/component'; import { LogCallback } from '@firebase/logger'; import { LogLevelString } from '@firebase/logger'; import { LogOptions } from '@firebase/logger'; @@ -15,7 +13,7 @@ import { Name } from '@firebase/component'; import { Provider } from '@firebase/component'; // @internal (undocumented) -export function _addComponent(app: FirebaseApp, component: Component): void; +export function _addComponent(app: FirebaseApp, component: Component): void; // @internal (undocumented) export function _addOrOverwriteComponent(app: FirebaseApp, component: Component): void; @@ -35,6 +33,48 @@ export const _DEFAULT_ENTRY_NAME = "[DEFAULT]"; // @public export function deleteApp(app: FirebaseApp): Promise; +// @public +export interface FirebaseApp { + automaticDataCollectionEnabled: boolean; + readonly name: string; + readonly options: FirebaseOptions; +} + +// @public +export interface FirebaseAppConfig { + automaticDataCollectionEnabled?: boolean; + name?: string; +} + +// @internal (undocumented) +export interface _FirebaseAppInternal extends FirebaseApp { + // (undocumented) + checkDestroyed(): void; + // (undocumented) + container: ComponentContainer; + // (undocumented) + isDeleted: boolean; +} + +// @public +export interface FirebaseOptions { + apiKey?: string; + appId?: string; + authDomain?: string; + databaseURL?: string; + measurementId?: string; + messagingSenderId?: string; + projectId?: string; + storageBucket?: string; +} + +// @internal (undocumented) +export interface _FirebaseService { + // (undocumented) + app: FirebaseApp; + _delete(): Promise; +} + // @public export function getApp(name?: string): FirebaseApp; @@ -54,7 +94,7 @@ export function initializeApp(options: FirebaseOptions, config?: FirebaseAppConf export function onLog(logCallback: LogCallback | null, options?: LogOptions): void; // @internal (undocumented) -export function _registerComponent(component: Component): boolean; +export function _registerComponent(component: Component): boolean; // @public export function registerVersion(libraryKeyOrName: string, version: string, variant?: string): void; diff --git a/common/api-review/app-types-exp.api.md b/common/api-review/app-types-exp.api.md deleted file mode 100644 index d915bc160e0..00000000000 --- a/common/api-review/app-types-exp.api.md +++ /dev/null @@ -1,80 +0,0 @@ -## API Report File for "@firebase/app-types-exp" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { ComponentContainer } from '@firebase/component'; - -// @public -export interface FirebaseApp { - automaticDataCollectionEnabled: boolean; - - readonly name: string; - - readonly options: FirebaseOptions; -} - -// @public (undocumented) -export interface FirebaseAppConfig { - // (undocumented) - automaticDataCollectionEnabled?: boolean; - // (undocumented) - name?: string; -} - -// @internal (undocumented) -export interface _FirebaseAppInternal extends FirebaseApp { - // (undocumented) - checkDestroyed(): void; - // (undocumented) - container: ComponentContainer; - // (undocumented) - isDeleted: boolean; -} - -// @public (undocumented) -export interface FirebaseOptions { - // (undocumented) - apiKey?: string; - // (undocumented) - appId?: string; - // (undocumented) - authDomain?: string; - // (undocumented) - databaseURL?: string; - // (undocumented) - measurementId?: string; - // (undocumented) - messagingSenderId?: string; - // (undocumented) - projectId?: string; - // (undocumented) - storageBucket?: string; -} - -// @internal (undocumented) -export interface _FirebaseService { - // (undocumented) - app: FirebaseApp; - _delete(): Promise; -} - -// @public (undocumented) -export interface PlatformLoggerService { - // (undocumented) - getPlatformInfoString(): string; -} - -// @public (undocumented) -export interface VersionService { - // (undocumented) - library: string; - // (undocumented) - version: string; -} - - -// (No @packageDocumentation comment for this package) - -``` diff --git a/common/api-review/auth-exp.api.md b/common/api-review/auth-exp.api.md index c96d59265f1..a009e94a326 100644 --- a/common/api-review/auth-exp.api.md +++ b/common/api-review/auth-exp.api.md @@ -4,450 +4,700 @@ ```ts -import { Auth } from '@firebase/auth-types-exp'; import { CompleteFn } from '@firebase/util'; +import { ErrorFactory } from '@firebase/util'; import { ErrorFn } from '@firebase/util'; -import * as externs from '@firebase/auth-types-exp'; -import { FirebaseApp } from '@firebase/app-types-exp'; +import { FirebaseApp } from '@firebase/app-exp'; import { FirebaseError } from '@firebase/util'; import { NextFn } from '@firebase/util'; -import { ProviderId } from '@firebase/auth-types-exp'; +import { Observer } from '@firebase/util'; import { Unsubscribe } from '@firebase/util'; -import { UserCredential } from '@firebase/auth-types-exp'; -// @public (undocumented) -export class ActionCodeURL implements externs.ActionCodeURL { +// @public +export interface ActionCodeInfo { + data: { + email?: string | null; + multiFactorInfo?: MultiFactorInfo | null; + previousEmail?: string | null; + }; + operation: typeof ActionCodeOperation[keyof typeof ActionCodeOperation]; +} + +// @public +export const ActionCodeOperation: { + readonly EMAIL_SIGNIN: "EMAIL_SIGNIN"; + readonly PASSWORD_RESET: "PASSWORD_RESET"; + readonly RECOVER_EMAIL: "RECOVER_EMAIL"; + readonly REVERT_SECOND_FACTOR_ADDITION: "REVERT_SECOND_FACTOR_ADDITION"; + readonly VERIFY_AND_CHANGE_EMAIL: "VERIFY_AND_CHANGE_EMAIL"; + readonly VERIFY_EMAIL: "VERIFY_EMAIL"; +}; + +// @public +export interface ActionCodeSettings { + android?: { + installApp?: boolean; + minimumVersion?: string; + packageName: string; + }; + dynamicLinkDomain?: string; + handleCodeInApp?: boolean; + iOS?: { + bundleId: string; + }; + url: string; +} + +// @public +export class ActionCodeURL { // @internal constructor(actionLink: string); - // (undocumented) readonly apiKey: string; - // (undocumented) readonly code: string; - // (undocumented) readonly continueUrl: string | null; - // (undocumented) readonly languageCode: string | null; - // (undocumented) - readonly operation: externs.Operation; - // (undocumented) - static parseLink(link: string): externs.ActionCodeURL | null; - // (undocumented) + readonly operation: string; + static parseLink(link: string): ActionCodeURL | null; readonly tenantId: string | null; } -// @public (undocumented) -export function applyActionCode(auth: externs.Auth, oobCode: string): Promise; +// @public +export interface AdditionalUserInfo { + readonly isNewUser: boolean; + readonly profile: Record | null; + readonly providerId: string | null; + readonly username?: string | null; +} + +// @public +export interface ApplicationVerifier { + readonly type: string; + verify(): Promise; +} -// @public (undocumented) +// @public +export function applyActionCode(auth: Auth, oobCode: string): Promise; + +// @public +export interface Auth { + readonly config: Config; + readonly currentUser: User | null; + // Warning: (ae-forgotten-export) The symbol "EmulatorConfig" needs to be exported by the entry point index.d.ts + readonly emulatorConfig: EmulatorConfig | null; + languageCode: string | null; + readonly name: string; + onAuthStateChanged(nextOrObserver: NextOrObserver, error?: ErrorFn, completed?: CompleteFn): Unsubscribe; + onIdTokenChanged(nextOrObserver: NextOrObserver, error?: ErrorFn, completed?: CompleteFn): Unsubscribe; + setPersistence(persistence: Persistence): Promise; + readonly settings: AuthSettings; + signOut(): Promise; + tenantId: string | null; + updateCurrentUser(user: User | null): Promise; + useDeviceLanguage(): void; +} + +// @public export class AuthCredential { - protected constructor(providerId: string, signInMethod: string); - // Warning: (ae-forgotten-export) The symbol "Auth" needs to be exported by the entry point index.d.ts + // @internal + protected constructor( + providerId: string, + signInMethod: string); + // Warning: (ae-forgotten-export) The symbol "AuthInternal" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "PhoneOrOauthTokenResponse" needs to be exported by the entry point index.d.ts // - // (undocumented) - _getIdTokenResponse(_auth: Auth_2): Promise; - // (undocumented) - _getReauthenticationResolver(_auth: Auth_2): Promise; + // @internal (undocumented) + _getIdTokenResponse(_auth: AuthInternal): Promise; + // @internal (undocumented) + _getReauthenticationResolver(_auth: AuthInternal): Promise; // Warning: (ae-forgotten-export) The symbol "IdTokenResponse" needs to be exported by the entry point index.d.ts // - // (undocumented) - _linkToIdToken(_auth: Auth_2, _idToken: string): Promise; - // (undocumented) + // @internal (undocumented) + _linkToIdToken(_auth: AuthInternal, _idToken: string): Promise; readonly providerId: string; - // (undocumented) readonly signInMethod: string; - // (undocumented) toJSON(): object; } -// @public (undocumented) -export const browserLocalPersistence: externs.Persistence; +// @public +export interface AuthError extends FirebaseError { + readonly appName: string; + readonly email?: string; + readonly phoneNumber?: string; + readonly tenantid?: string; +} -// @public (undocumented) -export const browserPopupRedirectResolver: externs.PopupRedirectResolver; +// @public +export interface AuthErrorMap { +} -// @public (undocumented) -export const browserSessionPersistence: externs.Persistence; +// @public +export interface AuthProvider { + readonly providerId: string; +} -// @public (undocumented) -export function checkActionCode(auth: externs.Auth, oobCode: string): Promise; +// @public +export interface AuthSettings { + appVerificationDisabledForTesting: boolean; +} + +// @public +export const browserLocalPersistence: Persistence; + +// @public +export const browserPopupRedirectResolver: PopupRedirectResolver; + +// @public +export const browserSessionPersistence: Persistence; + +// @public +export function checkActionCode(auth: Auth, oobCode: string): Promise; + +export { CompleteFn } + +// @public +export interface Config { + apiHost: string; + apiKey: string; + apiScheme: string; + authDomain?: string; + sdkClientVersion: string; + tokenApiHost: string; +} -// @public (undocumented) -export function confirmPasswordReset(auth: externs.Auth, oobCode: string, newPassword: string): Promise; +// @public +export interface ConfirmationResult { + confirm(verificationCode: string): Promise; + readonly verificationId: string; +} + +// @public +export function confirmPasswordReset(auth: Auth, oobCode: string, newPassword: string): Promise; + +// @public +export function createUserWithEmailAndPassword(auth: Auth, email: string, password: string): Promise; + +// @public +export type CustomParameters = Record; -// @public (undocumented) -export function createUserWithEmailAndPassword(auth: externs.Auth, email: string, password: string): Promise; +// @public +export const debugErrorMap: AuthErrorMap; -// @public (undocumented) -export function deleteUser(user: externs.User): Promise; +// @public +export function deleteUser(user: User): Promise; -// @public (undocumented) -export class EmailAuthCredential extends AuthCredential implements externs.AuthCredential { - // (undocumented) - readonly email: string; - // (undocumented) +// @public +export interface Dependencies { + errorMap?: AuthErrorMap; + persistence?: Persistence | Persistence[]; + popupRedirectResolver?: PopupRedirectResolver; +} + +// @public +export class EmailAuthCredential extends AuthCredential { + // @internal (undocumented) + readonly _email: string; + // @internal (undocumented) static _fromEmailAndCode(email: string, oobCode: string, tenantId?: string | null): EmailAuthCredential; - // (undocumented) + // @internal (undocumented) static _fromEmailAndPassword(email: string, password: string): EmailAuthCredential; - // (undocumented) static fromJSON(json: object | string): EmailAuthCredential | null; - // (undocumented) - _getIdTokenResponse(auth: Auth_2): Promise; - // (undocumented) - _getReauthenticationResolver(auth: Auth_2): Promise; - // (undocumented) - _linkToIdToken(auth: Auth_2, idToken: string): Promise; - // (undocumented) - readonly password: string; - // (undocumented) - readonly tenantId: string | null; - // (undocumented) + // @internal (undocumented) + _getIdTokenResponse(auth: AuthInternal): Promise; + // @internal (undocumented) + _getReauthenticationResolver(auth: AuthInternal): Promise; + // @internal (undocumented) + _linkToIdToken(auth: AuthInternal, idToken: string): Promise; + // @internal (undocumented) + readonly _password: string; + // @internal (undocumented) + readonly _tenantId: string | null; toJSON(): object; } -// @public (undocumented) -export class EmailAuthProvider implements externs.EmailAuthProvider { - // (undocumented) +// @public +export class EmailAuthProvider implements AuthProvider { static credential(email: string, password: string): EmailAuthCredential; - // (undocumented) static credentialWithLink(email: string, emailLink: string): EmailAuthCredential; - // (undocumented) - static readonly EMAIL_LINK_SIGN_IN_METHOD = externs.SignInMethod.EMAIL_LINK; - // (undocumented) - static readonly EMAIL_PASSWORD_SIGN_IN_METHOD = externs.SignInMethod.EMAIL_PASSWORD; - // (undocumented) - static readonly PROVIDER_ID = externs.ProviderId.PASSWORD; - // (undocumented) - readonly providerId = externs.ProviderId.PASSWORD; -} - -// @public (undocumented) -export class FacebookAuthProvider extends OAuthProvider { - // (undocumented) - static credential(accessToken: string): externs.OAuthCredential; - // (undocumented) - static credentialFromError(error: FirebaseError): externs.OAuthCredential | null; - // (undocumented) - static credentialFromResult(userCredential: externs.UserCredential): externs.OAuthCredential | null; - // (undocumented) - static readonly FACEBOOK_SIGN_IN_METHOD = externs.SignInMethod.FACEBOOK; - // (undocumented) - static readonly PROVIDER_ID = externs.ProviderId.FACEBOOK; - // (undocumented) - readonly providerId = externs.ProviderId.FACEBOOK; -} - -// @public (undocumented) -export function fetchSignInMethodsForEmail(auth: externs.Auth, email: string): Promise; - -// @public (undocumented) -export function getAdditionalUserInfo(userCredential: externs.UserCredential): externs.AdditionalUserInfo | null; - -// @public (undocumented) -export function getAuth(app?: FirebaseApp): Auth; + static readonly EMAIL_LINK_SIGN_IN_METHOD = SignInMethod.EMAIL_LINK; + static readonly EMAIL_PASSWORD_SIGN_IN_METHOD = SignInMethod.EMAIL_PASSWORD; + static readonly PROVIDER_ID = ProviderId.PASSWORD; + readonly providerId = ProviderId.PASSWORD; +} -// @public (undocumented) -export function getIdToken(user: externs.User, forceRefresh?: boolean): Promise; - -// @public (undocumented) -export function getIdTokenResult(externUser: externs.User, forceRefresh?: boolean): Promise; - -// @public (undocumented) -export function getMultiFactorResolver(auth: externs.Auth, errorExtern: externs.MultiFactorError): externs.MultiFactorResolver; - -// @public (undocumented) -export function getRedirectResult(authExtern: externs.Auth, resolverExtern?: externs.PopupRedirectResolver): Promise; - -// @public (undocumented) -export class GithubAuthProvider extends OAuthProvider { - // (undocumented) - static credential(accessToken: string): externs.OAuthCredential; - // (undocumented) - static credentialFromError(error: FirebaseError): externs.OAuthCredential | null; - // (undocumented) - static credentialFromResult(userCredential: externs.UserCredential): externs.OAuthCredential | null; - // (undocumented) - static readonly GITHUB_SIGN_IN_METHOD = externs.SignInMethod.GITHUB; - // (undocumented) - static readonly PROVIDER_ID = externs.ProviderId.GITHUB; - // (undocumented) - readonly providerId = externs.ProviderId.GITHUB; -} - -// @public (undocumented) -export class GoogleAuthProvider extends OAuthProvider { - // (undocumented) - static credential(idToken?: string | null, accessToken?: string | null): externs.OAuthCredential; - // (undocumented) - static credentialFromError(error: FirebaseError): externs.OAuthCredential | null; - // (undocumented) - static credentialFromResult(userCredential: externs.UserCredential): externs.OAuthCredential | null; - // (undocumented) - static readonly GOOGLE_SIGN_IN_METHOD = externs.SignInMethod.GOOGLE; - // (undocumented) - static readonly PROVIDER_ID = externs.ProviderId.GOOGLE; - // (undocumented) - readonly providerId = externs.ProviderId.GOOGLE; -} - -// @public (undocumented) -export const indexedDBLocalPersistence: externs.Persistence; - -// Warning: (ae-forgotten-export) The symbol "Dependencies" needs to be exported by the entry point index.d.ts +export { ErrorFn } + +// Warning: (ae-forgotten-export) The symbol "BaseOAuthProvider" needs to be exported by the entry point index.d.ts // -// @public (undocumented) -export function initializeAuth(app?: FirebaseApp, deps?: Dependencies): externs.Auth; +// @public +export class FacebookAuthProvider extends BaseOAuthProvider { + constructor(); + static credential(accessToken: string): OAuthCredential; + static credentialFromError(error: FirebaseError): OAuthCredential | null; + static credentialFromResult(userCredential: UserCredential): OAuthCredential | null; + static readonly FACEBOOK_SIGN_IN_METHOD = SignInMethod.FACEBOOK; + static readonly PROVIDER_ID = ProviderId.FACEBOOK; +} + +// @public +export const FactorId: { + readonly PHONE: "phone"; +}; + +// @public +export function fetchSignInMethodsForEmail(auth: Auth, email: string): Promise; + +// @public +export function getAdditionalUserInfo(userCredential: UserCredential): AdditionalUserInfo | null; + +// @public +export function getAuth(app?: FirebaseApp): Auth; + +// @public +export function getIdToken(user: User, forceRefresh?: boolean): Promise; + +// @public +export function getIdTokenResult(user: User, forceRefresh?: boolean): Promise; + +// @public +export function getMultiFactorResolver(auth: Auth, error: MultiFactorError): MultiFactorResolver; + +// @public +export function getRedirectResult(auth: Auth, resolver?: PopupRedirectResolver): Promise; + +// @public +export class GithubAuthProvider extends BaseOAuthProvider { + constructor(); + static credential(accessToken: string): OAuthCredential; + static credentialFromError(error: FirebaseError): OAuthCredential | null; + static credentialFromResult(userCredential: UserCredential): OAuthCredential | null; + static readonly GITHUB_SIGN_IN_METHOD = SignInMethod.GITHUB; + static readonly PROVIDER_ID = ProviderId.GITHUB; +} + +// @public +export class GoogleAuthProvider extends BaseOAuthProvider { + constructor(); + static credential(idToken?: string | null, accessToken?: string | null): OAuthCredential; + static credentialFromError(error: FirebaseError): OAuthCredential | null; + static credentialFromResult(userCredential: UserCredential): OAuthCredential | null; + static readonly GOOGLE_SIGN_IN_METHOD = SignInMethod.GOOGLE; + static readonly PROVIDER_ID = ProviderId.GOOGLE; +} + +// @public +export interface IdTokenResult { + authTime: string; + claims: ParsedToken; + expirationTime: string; + issuedAtTime: string; + signInProvider: string | null; + signInSecondFactor: string | null; + token: string; +} + +// @public +export const indexedDBLocalPersistence: Persistence; + +// @public +export function initializeAuth(app: FirebaseApp, deps?: Dependencies): Auth; + +// @public +export const inMemoryPersistence: Persistence; + +// @public +export function isSignInWithEmailLink(auth: Auth, emailLink: string): boolean; + +// @public +export function linkWithCredential(user: User, credential: AuthCredential): Promise; -// @public (undocumented) -export const inMemoryPersistence: externs.Persistence; +// @public +export function linkWithPhoneNumber(user: User, phoneNumber: string, appVerifier: ApplicationVerifier): Promise; -// @public (undocumented) -export function isSignInWithEmailLink(auth: externs.Auth, emailLink: string): boolean; +// @public +export function linkWithPopup(user: User, provider: AuthProvider, resolver?: PopupRedirectResolver): Promise; -// @public (undocumented) -export function linkWithCredential(userExtern: externs.User, credentialExtern: externs.AuthCredential): Promise; +// @public +export function linkWithRedirect(user: User, provider: AuthProvider, resolver?: PopupRedirectResolver): Promise; -// @public (undocumented) -export function linkWithPhoneNumber(userExtern: externs.User, phoneNumber: string, appVerifier: externs.ApplicationVerifier): Promise; +// @public +export function multiFactor(user: User): MultiFactorUser; + +// @public +export interface MultiFactorAssertion { + readonly factorId: typeof FactorId[keyof typeof FactorId]; +} -// @public (undocumented) -export function linkWithPopup(userExtern: externs.User, provider: externs.AuthProvider, resolverExtern?: externs.PopupRedirectResolver): Promise; +// @public +export interface MultiFactorError extends AuthError { + readonly operationType: typeof OperationType[keyof typeof OperationType]; +} -// @public (undocumented) -export function linkWithRedirect(userExtern: externs.User, provider: externs.AuthProvider, resolverExtern?: externs.PopupRedirectResolver): Promise; +// @public +export interface MultiFactorInfo { + readonly displayName?: string | null; + readonly enrollmentTime: string; + readonly factorId: typeof FactorId[keyof typeof FactorId]; + readonly uid: string; +} -// @public (undocumented) -export function multiFactor(user: externs.User): externs.MultiFactorUser; +// @public +export interface MultiFactorResolver { + readonly hints: MultiFactorInfo[]; + resolveSignIn(assertion: MultiFactorAssertion): Promise; + readonly session: MultiFactorSession; +} -// @public (undocumented) -export class OAuthCredential extends AuthCredential implements externs.OAuthCredential { - // (undocumented) +// @public +export interface MultiFactorSession { +} + +// @public +export interface MultiFactorUser { + enroll(assertion: MultiFactorAssertion, displayName?: string | null): Promise; + readonly enrolledFactors: MultiFactorInfo[]; + getSession(): Promise; + unenroll(option: MultiFactorInfo | string): Promise; +} + +// @public +export type NextOrObserver = NextFn | Observer; + +// @public +export class OAuthCredential extends AuthCredential { accessToken?: string; - // (undocumented) static fromJSON(json: string | object): OAuthCredential | null; // Warning: (ae-forgotten-export) The symbol "OAuthCredentialParams" needs to be exported by the entry point index.d.ts // - // (undocumented) + // @internal (undocumented) static _fromParams(params: OAuthCredentialParams): OAuthCredential; - // (undocumented) - _getIdTokenResponse(auth: Auth_2): Promise; - // (undocumented) - _getReauthenticationResolver(auth: Auth_2): Promise; - // (undocumented) + // @internal (undocumented) + _getIdTokenResponse(auth: AuthInternal): Promise; + // @internal (undocumented) + _getReauthenticationResolver(auth: AuthInternal): Promise; idToken?: string; - // (undocumented) - _linkToIdToken(auth: Auth_2, idToken: string): Promise; - // (undocumented) + // @internal (undocumented) + _linkToIdToken(auth: AuthInternal, idToken: string): Promise; + // @internal (undocumented) nonce?: string; - // (undocumented) secret?: string; - // (undocumented) toJSON(): object; } -// @public (undocumented) -export class OAuthProvider implements externs.AuthProvider { - constructor(providerId: string); - // (undocumented) - addScope(scope: string): externs.AuthProvider; - // Warning: (ae-forgotten-export) The symbol "CredentialParameters" needs to be exported by the entry point index.d.ts - // - // (undocumented) - credential(params: CredentialParameters): externs.OAuthCredential; - // (undocumented) - static credentialFromJSON(json: object | string): externs.OAuthCredential; - // (undocumented) - defaultLanguageCode: string | null; - // (undocumented) - getCustomParameters(): CustomParameters; - // (undocumented) - getScopes(): string[]; - // (undocumented) - readonly providerId: string; - // Warning: (ae-forgotten-export) The symbol "CustomParameters" needs to be exported by the entry point index.d.ts - // - // (undocumented) - setCustomParameters(customOAuthParameters: CustomParameters): externs.AuthProvider; - // (undocumented) - setDefaultLanguage(languageCode: string | null): void; +// @public +export interface OAuthCredentialOptions { + accessToken?: string; + idToken?: string; + rawNonce?: string; } -// @public (undocumented) -export function onAuthStateChanged(auth: externs.Auth, nextOrObserver: externs.NextOrObserver, error?: ErrorFn, completed?: CompleteFn): Unsubscribe; +// @public +export class OAuthProvider extends BaseOAuthProvider { + credential(params: OAuthCredentialOptions): OAuthCredential; + static credentialFromError(error: FirebaseError): OAuthCredential | null; + static credentialFromJSON(json: object | string): OAuthCredential; + static credentialFromResult(userCredential: UserCredential): OAuthCredential | null; + } + +// @public +export function onAuthStateChanged(auth: Auth, nextOrObserver: NextOrObserver, error?: ErrorFn, completed?: CompleteFn): Unsubscribe; + +// @public +export function onIdTokenChanged(auth: Auth, nextOrObserver: NextOrObserver, error?: ErrorFn, completed?: CompleteFn): Unsubscribe; + +// @public +export const OperationType: { + readonly LINK: "link"; + readonly REAUTHENTICATE: "reauthenticate"; + readonly SIGN_IN: "signIn"; +}; -// @public (undocumented) -export function onIdTokenChanged(auth: externs.Auth, nextOrObserver: externs.NextOrObserver, error?: ErrorFn, completed?: CompleteFn): Unsubscribe; +// @public +export function parseActionCodeURL(link: string): ActionCodeURL | null; + +// @public +export interface ParsedToken { + [key: string]: string | object | undefined; + 'auth_time'?: string; + 'exp'?: string; + 'firebase'?: { + 'sign_in_provider'?: string; + 'sign_in_second_factor'?: string; + }; + 'iat'?: string; + 'sub'?: string; +} -// @public (undocumented) -export function parseActionCodeURL(link: string): externs.ActionCodeURL | null; +// @public +export interface Persistence { + readonly type: 'SESSION' | 'LOCAL' | 'NONE'; +} -// @public (undocumented) -export class PhoneAuthCredential extends AuthCredential implements externs.PhoneAuthCredential { - // (undocumented) +// @public +export class PhoneAuthCredential extends AuthCredential { static fromJSON(json: object | string): PhoneAuthCredential | null; - // (undocumented) + // @internal (undocumented) static _fromTokenResponse(phoneNumber: string, temporaryProof: string): PhoneAuthCredential; - // (undocumented) + // @internal (undocumented) static _fromVerification(verificationId: string, verificationCode: string): PhoneAuthCredential; - // (undocumented) - _getIdTokenResponse(auth: Auth_2): Promise; - // (undocumented) - _getReauthenticationResolver(auth: Auth_2): Promise; - // (undocumented) - _linkToIdToken(auth: Auth_2, idToken: string): Promise; + // @internal (undocumented) + _getIdTokenResponse(auth: AuthInternal): Promise; + // @internal (undocumented) + _getReauthenticationResolver(auth: AuthInternal): Promise; + // @internal (undocumented) + _linkToIdToken(auth: AuthInternal, idToken: string): Promise; // Warning: (ae-forgotten-export) The symbol "SignInWithPhoneNumberRequest" needs to be exported by the entry point index.d.ts // - // (undocumented) + // @internal (undocumented) _makeVerificationRequest(): SignInWithPhoneNumberRequest; - // (undocumented) toJSON(): object; } -// @public (undocumented) -export class PhoneAuthProvider implements externs.PhoneAuthProvider { - constructor(auth: externs.Auth); - // (undocumented) +// @public +export class PhoneAuthProvider { + constructor(auth: Auth); static credential(verificationId: string, verificationCode: string): PhoneAuthCredential; - // (undocumented) - static credentialFromResult(userCredential: externs.UserCredential): externs.AuthCredential | null; - // (undocumented) - static readonly PHONE_SIGN_IN_METHOD = externs.SignInMethod.PHONE; - // (undocumented) - static readonly PROVIDER_ID = externs.ProviderId.PHONE; - // (undocumented) - readonly providerId = externs.ProviderId.PHONE; - // (undocumented) - verifyPhoneNumber(phoneOptions: externs.PhoneInfoOptions | string, applicationVerifier: externs.ApplicationVerifier): Promise; + static credentialFromError(error: FirebaseError): AuthCredential | null; + static credentialFromResult(userCredential: UserCredential): AuthCredential | null; + static readonly PHONE_SIGN_IN_METHOD = SignInMethod.PHONE; + static readonly PROVIDER_ID = ProviderId.PHONE; + readonly providerId = ProviderId.PHONE; + verifyPhoneNumber(phoneOptions: PhoneInfoOptions | string, applicationVerifier: ApplicationVerifier): Promise; +} + +// @public +export type PhoneInfoOptions = PhoneSingleFactorInfoOptions | PhoneMultiFactorEnrollInfoOptions | PhoneMultiFactorSignInInfoOptions; + +// @public +export interface PhoneMultiFactorAssertion extends MultiFactorAssertion { +} + +// @public +export interface PhoneMultiFactorEnrollInfoOptions { + phoneNumber: string; + session: MultiFactorSession; +} + +// @public +export class PhoneMultiFactorGenerator { + static assertion(credential: PhoneAuthCredential): PhoneMultiFactorAssertion; +} + +// @public +export interface PhoneMultiFactorSignInInfoOptions { + multiFactorHint?: MultiFactorInfo; + multiFactorUid?: string; + session: MultiFactorSession; +} + +// @public +export interface PhoneSingleFactorInfoOptions { + phoneNumber: string; } -// @public (undocumented) -export class PhoneMultiFactorGenerator implements externs.PhoneMultiFactorGenerator { - // (undocumented) - static assertion(credential: externs.PhoneAuthCredential): externs.PhoneMultiFactorAssertion; +// @public +export interface PopupRedirectResolver { +} + +// @public +export const prodErrorMap: AuthErrorMap; + +// @public +export const ProviderId: { + readonly FACEBOOK: "facebook.com"; + readonly GITHUB: "github.com"; + readonly GOOGLE: "google.com"; + readonly PASSWORD: "password"; + readonly PHONE: "phone"; + readonly TWITTER: "twitter.com"; +}; + +// @public +export interface ReactNativeAsyncStorage { + getItem(key: string): Promise; + removeItem(key: string): Promise; + setItem(key: string, value: string): Promise; } -// @public (undocumented) -export function reauthenticateWithCredential(userExtern: externs.User, credentialExtern: externs.AuthCredential): Promise; +// @public +export function reauthenticateWithCredential(user: User, credential: AuthCredential): Promise; -// @public (undocumented) -export function reauthenticateWithPhoneNumber(userExtern: externs.User, phoneNumber: string, appVerifier: externs.ApplicationVerifier): Promise; +// @public +export function reauthenticateWithPhoneNumber(user: User, phoneNumber: string, appVerifier: ApplicationVerifier): Promise; -// @public (undocumented) -export function reauthenticateWithPopup(userExtern: externs.User, provider: externs.AuthProvider, resolverExtern?: externs.PopupRedirectResolver): Promise; +// @public +export function reauthenticateWithPopup(user: User, provider: AuthProvider, resolver?: PopupRedirectResolver): Promise; -// @public (undocumented) -export function reauthenticateWithRedirect(userExtern: externs.User, provider: externs.AuthProvider, resolverExtern?: externs.PopupRedirectResolver): Promise; +// @public +export function reauthenticateWithRedirect(user: User, provider: AuthProvider, resolver?: PopupRedirectResolver): Promise; -// Warning: (ae-forgotten-export) The symbol "ApplicationVerifier" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "ApplicationVerifierInternal" needs to be exported by the entry point index.d.ts // -// @public (undocumented) -export class RecaptchaVerifier implements externs.RecaptchaVerifier, ApplicationVerifier { +// @public +export class RecaptchaVerifier implements ApplicationVerifierInternal { // Warning: (ae-forgotten-export) The symbol "Parameters" needs to be exported by the entry point index.d.ts - constructor(containerOrId: HTMLElement | string, parameters: Parameters_2, authExtern: externs.Auth); - // (undocumented) + constructor(containerOrId: HTMLElement | string, parameters: Parameters_2, authExtern: Auth); clear(): void; // Warning: (ae-forgotten-export) The symbol "ReCaptchaLoader" needs to be exported by the entry point index.d.ts // - // (undocumented) + // @internal (undocumented) readonly _recaptchaLoader: ReCaptchaLoader; - // (undocumented) render(): Promise; - // (undocumented) + // @internal (undocumented) _reset(): void; - // (undocumented) readonly type = "recaptcha"; - // (undocumented) verify(): Promise; } -// @public (undocumented) -export function reload(externUser: externs.User): Promise; +// @public +export function reload(user: User): Promise; + +// Warning: (ae-forgotten-export) The symbol "FederatedAuthProvider" needs to be exported by the entry point index.d.ts +// +// @public +export class SAMLAuthProvider extends FederatedAuthProvider { + constructor(providerId: string); + static credentialFromError(error: FirebaseError): AuthCredential | null; + static credentialFromJSON(json: string | object): AuthCredential; + static credentialFromResult(userCredential: UserCredential): AuthCredential | null; + } + +// @public +export function sendEmailVerification(user: User, actionCodeSettings?: ActionCodeSettings | null): Promise; -// @public (undocumented) -export function sendEmailVerification(userExtern: externs.User, actionCodeSettings?: externs.ActionCodeSettings | null): Promise; +// @public +export function sendPasswordResetEmail(auth: Auth, email: string, actionCodeSettings?: ActionCodeSettings): Promise; -// @public (undocumented) -export function sendPasswordResetEmail(auth: externs.Auth, email: string, actionCodeSettings?: externs.ActionCodeSettings): Promise; +// @public +export function sendSignInLinkToEmail(auth: Auth, email: string, actionCodeSettings: ActionCodeSettings): Promise; -// @public (undocumented) -export function sendSignInLinkToEmail(auth: externs.Auth, email: string, actionCodeSettings?: externs.ActionCodeSettings): Promise; +// @public +export function setPersistence(auth: Auth, persistence: Persistence): Promise; -// @public (undocumented) -export function setPersistence(auth: externs.Auth, persistence: externs.Persistence): void; +// @public +export function signInAnonymously(auth: Auth): Promise; -// @public (undocumented) -export function signInAnonymously(auth: externs.Auth): Promise; +// @public +export const SignInMethod: { + readonly EMAIL_LINK: "emailLink"; + readonly EMAIL_PASSWORD: "password"; + readonly FACEBOOK: "facebook.com"; + readonly GITHUB: "github.com"; + readonly GOOGLE: "google.com"; + readonly PHONE: "phone"; + readonly TWITTER: "twitter.com"; +}; -// @public (undocumented) -export function signInWithCredential(auth: externs.Auth, credential: externs.AuthCredential): Promise; +// @public +export function signInWithCredential(auth: Auth, credential: AuthCredential): Promise; -// @public (undocumented) -export function signInWithCustomToken(authExtern: externs.Auth, customToken: string): Promise; +// @public +export function signInWithCustomToken(auth: Auth, customToken: string): Promise; -// @public (undocumented) -export function signInWithEmailAndPassword(auth: externs.Auth, email: string, password: string): Promise; +// @public +export function signInWithEmailAndPassword(auth: Auth, email: string, password: string): Promise; -// @public (undocumented) -export function signInWithEmailLink(auth: externs.Auth, email: string, emailLink?: string): Promise; +// @public +export function signInWithEmailLink(auth: Auth, email: string, emailLink?: string): Promise; -// @public (undocumented) -export function signInWithPhoneNumber(auth: externs.Auth, phoneNumber: string, appVerifier: externs.ApplicationVerifier): Promise; +// @public +export function signInWithPhoneNumber(auth: Auth, phoneNumber: string, appVerifier: ApplicationVerifier): Promise; -// @public (undocumented) -export function signInWithPopup(authExtern: externs.Auth, provider: externs.AuthProvider, resolverExtern?: externs.PopupRedirectResolver): Promise; +// @public +export function signInWithPopup(auth: Auth, provider: AuthProvider, resolver?: PopupRedirectResolver): Promise; -// @public (undocumented) -export function signInWithRedirect(authExtern: externs.Auth, provider: externs.AuthProvider, resolverExtern?: externs.PopupRedirectResolver): Promise; +// @public +export function signInWithRedirect(auth: Auth, provider: AuthProvider, resolver?: PopupRedirectResolver): Promise; -// @public (undocumented) -export function signOut(auth: externs.Auth): Promise; +// @public +export function signOut(auth: Auth): Promise; -// @public (undocumented) -export class TwitterAuthProvider extends OAuthProvider { - // (undocumented) - static credential(token: string, secret: string): externs.OAuthCredential; - // (undocumented) - static credentialFromError(error: FirebaseError): externs.OAuthCredential | null; - // (undocumented) - static credentialFromResult(userCredential: externs.UserCredential): externs.OAuthCredential | null; - // (undocumented) - static readonly PROVIDER_ID = externs.ProviderId.TWITTER; - // (undocumented) - readonly providerId = externs.ProviderId.TWITTER; - // (undocumented) - static readonly TWITTER_SIGN_IN_METHOD = externs.SignInMethod.TWITTER; +// @public +export class TwitterAuthProvider extends BaseOAuthProvider { + constructor(); + static credential(token: string, secret: string): OAuthCredential; + static credentialFromError(error: FirebaseError): OAuthCredential | null; + static credentialFromResult(userCredential: UserCredential): OAuthCredential | null; + static readonly PROVIDER_ID = ProviderId.TWITTER; + static readonly TWITTER_SIGN_IN_METHOD = SignInMethod.TWITTER; } // @public -export function unlink(userExtern: externs.User, providerId: externs.ProviderId): Promise; +export function unlink(user: User, providerId: string): Promise; -// @public (undocumented) -export function updateCurrentUser(auth: externs.Auth, user: externs.User | null): Promise; +export { Unsubscribe } -// @public (undocumented) -export function updateEmail(externUser: externs.User, newEmail: string): Promise; +// @public +export function updateCurrentUser(auth: Auth, user: User | null): Promise; -// @public (undocumented) -export function updatePassword(externUser: externs.User, newPassword: string): Promise; +// @public +export function updateEmail(user: User, newEmail: string): Promise; -// @public (undocumented) -export function updatePhoneNumber(user: externs.User, credential: externs.PhoneAuthCredential): Promise; +// @public +export function updatePassword(user: User, newPassword: string): Promise; -// Warning: (ae-forgotten-export) The symbol "Profile" needs to be exported by the entry point index.d.ts -// -// @public (undocumented) -export function updateProfile(externUser: externs.User, { displayName, photoURL: photoUrl }: Profile): Promise; +// @public +export function updatePhoneNumber(user: User, credential: PhoneAuthCredential): Promise; + +// @public +export function updateProfile(user: User, { displayName, photoURL: photoUrl }: { + displayName?: string | null; + photoURL?: string | null; +}): Promise; + +// @public +export function useAuthEmulator(auth: Auth, url: string, options?: { + disableWarnings: boolean; +}): void; + +// @public +export function useDeviceLanguage(auth: Auth): void; + +// @public +export interface User extends UserInfo { + delete(): Promise; + readonly emailVerified: boolean; + getIdToken(forceRefresh?: boolean): Promise; + getIdTokenResult(forceRefresh?: boolean): Promise; + readonly isAnonymous: boolean; + readonly metadata: UserMetadata; + readonly providerData: UserInfo[]; + readonly refreshToken: string; + reload(): Promise; + readonly tenantId: string | null; + toJSON(): object; +} + +// @public +export interface UserCredential { + operationType: typeof OperationType[keyof typeof OperationType]; + providerId: string | null; + user: User; +} + +// @public +export interface UserInfo { + readonly displayName: string | null; + readonly email: string | null; + readonly phoneNumber: string | null; + readonly photoURL: string | null; + readonly providerId: string; + readonly uid: string; +} -// @public (undocumented) -export function useDeviceLanguage(auth: externs.Auth): void; +// @public +export interface UserMetadata { + readonly creationTime?: string; + readonly lastSignInTime?: string; +} -// @public (undocumented) -export function verifyBeforeUpdateEmail(userExtern: externs.User, newEmail: string, actionCodeSettings?: externs.ActionCodeSettings | null): Promise; +// @public +export type UserProfile = Record; -// @public (undocumented) -export function verifyPasswordResetCode(auth: externs.Auth, code: string): Promise; +// @public +export function verifyBeforeUpdateEmail(user: User, newEmail: string, actionCodeSettings?: ActionCodeSettings | null): Promise; +// @public +export function verifyPasswordResetCode(auth: Auth, code: string): Promise; -// (No @packageDocumentation comment for this package) ``` diff --git a/common/api-review/auth-types-exp.api.md b/common/api-review/auth-types-exp.api.md deleted file mode 100644 index 8b8efb4585a..00000000000 --- a/common/api-review/auth-types-exp.api.md +++ /dev/null @@ -1,550 +0,0 @@ -## API Report File for "@firebase/auth-types-exp" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { CompleteFn } from '@firebase/util'; -import { ErrorFn } from '@firebase/util'; -import { FirebaseError } from '@firebase/util'; -import { NextFn } from '@firebase/util'; -import { Observer } from '@firebase/util'; -import { Unsubscribe } from '@firebase/util'; - -// @public -export interface ActionCodeInfo { - // (undocumented) - data: { - email?: string | null; - multiFactorInfo?: MultiFactorInfo | null; - previousEmail?: string | null; - }; - // (undocumented) - operation: Operation; -} - -// @public -export interface ActionCodeSettings { - // (undocumented) - android?: { - installApp?: boolean; - minimumVersion?: string; - packageName: string; - }; - // (undocumented) - dynamicLinkDomain?: string; - // (undocumented) - handleCodeInApp?: boolean; - // (undocumented) - iOS?: { - bundleId: string; - }; - // (undocumented) - url: string; -} - -// @public -export abstract class ActionCodeURL { - readonly apiKey: string; - readonly code: string; - readonly continueUrl: string | null; - readonly languageCode: string | null; - readonly operation: Operation; - static parseLink(link: string): ActionCodeURL | null; - readonly tenantId: string | null; -} - -// @public -export interface AdditionalUserInfo { - // (undocumented) - readonly isNewUser: boolean; - // (undocumented) - readonly profile: UserProfile | null; - // (undocumented) - readonly providerId: ProviderId | null; - // (undocumented) - readonly username?: string | null; -} - -// @public -export interface ApplicationVerifier { - // (undocumented) - readonly type: string; - // (undocumented) - verify(): Promise; -} - -// @public -export interface Auth { - // (undocumented) - readonly config: Config; - // (undocumented) - readonly currentUser: User | null; - // (undocumented) - languageCode: string | null; - // (undocumented) - readonly name: string; - // (undocumented) - onAuthStateChanged( - nextOrObserver: NextOrObserver, - error?: ErrorFn, - completed?: CompleteFn - ): Unsubscribe; - // (undocumented) - onIdTokenChanged( - nextOrObserver: NextOrObserver, - error?: ErrorFn, - completed?: CompleteFn - ): Unsubscribe; - // (undocumented) - setPersistence(persistence: Persistence): void; - // (undocumented) - readonly settings: AuthSettings; - // (undocumented) - signOut(): Promise; - // (undocumented) - tenantId: string | null; - // (undocumented) - updateCurrentUser(user: User | null): Promise; - // (undocumented) - useDeviceLanguage(): void; - // (undocumented) - useEmulator(url: string): void; -} - -// @public -export abstract class AuthCredential { - // (undocumented) - static fromJSON(json: object | string): AuthCredential | null; - - // (undocumented) - readonly providerId: string; - - // (undocumented) - readonly signInMethod: string; - - // (undocumented) - toJSON(): object; -} - -// @public -export interface AuthError extends FirebaseError { - // (undocumented) - readonly appName: string; - - // (undocumented) - readonly email?: string; - - // (undocumented) - readonly phoneNumber?: string; - - // (undocumented) - readonly tenantid?: string; -} - -// @public -export interface AuthProvider { - // (undocumented) - readonly providerId: string; -} - -// @public -export interface AuthSettings { - // (undocumented) - appVerificationDisabledForTesting: boolean; -} - -// @public -export interface Config { - // (undocumented) - apiHost: string; - // (undocumented) - apiKey: string; - // (undocumented) - apiScheme: string; - // (undocumented) - authDomain?: string; - // (undocumented) - sdkClientVersion: string; - // (undocumented) - tokenApiHost: string; -} - -// @public -export interface ConfirmationResult { - // (undocumented) - confirm(verificationCode: string): Promise; - // (undocumented) - readonly verificationId: string; -} - -// @public -export abstract class EmailAuthProvider implements AuthProvider { - // (undocumented) - static credential(email: string, password: string): AuthCredential; - // (undocumented) - static credentialWithLink( - auth: Auth, - email: string, - emailLink: string - ): AuthCredential; - // (undocumented) - static readonly EMAIL_LINK_SIGN_IN_METHOD: SignInMethod; - // (undocumented) - static readonly EMAIL_PASSWORD_SIGN_IN_METHOD: SignInMethod; - // (undocumented) - static readonly PROVIDER_ID: ProviderId; - // (undocumented) - readonly providerId: ProviderId; -} - -// @public -export interface IdTokenResult { - // (undocumented) - authTime: string; - // (undocumented) - claims: ParsedToken; - // (undocumented) - expirationTime: string; - // (undocumented) - issuedAtTime: string; - // (undocumented) - signInProvider: string | null; - // (undocumented) - signInSecondFactor: string | null; - // (undocumented) - token: string; -} - -// @public -export interface MultiFactorAssertion { - // (undocumented) - readonly factorId: string; -} - -// @public -export interface MultiFactorError extends AuthError { - // (undocumented) - readonly credential: AuthCredential; - // (undocumented) - readonly operationType: OperationType; -} - -// @public -export interface MultiFactorInfo { - // (undocumented) - readonly displayName?: string | null; - // (undocumented) - readonly enrollmentTime: string; - // (undocumented) - readonly factorId: ProviderId; - // (undocumented) - readonly uid: string; -} - -// @public -export abstract class MultiFactorResolver { - // (undocumented) - hints: MultiFactorInfo[]; - // (undocumented) - resolveSignIn(assertion: MultiFactorAssertion): Promise; - // (undocumented) - session: MultiFactorSession; -} - -// @public -export interface MultiFactorSession {} - -// @public -export interface MultiFactorUser { - // (undocumented) - enroll( - assertion: MultiFactorAssertion, - displayName?: string | null - ): Promise; - // (undocumented) - readonly enrolledFactors: MultiFactorInfo[]; - // (undocumented) - getSession(): Promise; - // (undocumented) - unenroll(option: MultiFactorInfo | string): Promise; -} - -// @public -export type NextOrObserver = NextFn | Observer; - -// @public -export abstract class OAuthCredential extends AuthCredential { - // (undocumented) - readonly accessToken?: string; - - // (undocumented) - static fromJSON(json: object | string): OAuthCredential | null; - - // (undocumented) - readonly idToken?: string; - - // (undocumented) - readonly secret?: string; -} - -// @public -export const enum Operation { - // (undocumented) - EMAIL_SIGNIN = 'EMAIL_SIGNIN', - // (undocumented) - PASSWORD_RESET = 'PASSWORD_RESET', - // (undocumented) - RECOVER_EMAIL = 'RECOVER_EMAIL', - // (undocumented) - REVERT_SECOND_FACTOR_ADDITION = 'REVERT_SECOND_FACTOR_ADDITION', - // (undocumented) - VERIFY_AND_CHANGE_EMAIL = 'VERIFY_AND_CHANGE_EMAIL', - // (undocumented) - VERIFY_EMAIL = 'VERIFY_EMAIL' -} - -// @public -export const enum OperationType { - // (undocumented) - LINK = 'link', - // (undocumented) - REAUTHENTICATE = 'reauthenticate', - // (undocumented) - SIGN_IN = 'signIn' -} - -// @public -export interface ParsedToken { - // (undocumented) - [key: string]: string | object | undefined; - // (undocumented) - 'auth_time'?: string; - // (undocumented) - 'exp'?: string; - // (undocumented) - 'firebase'?: { - 'sign_in_provider'?: string; - 'sign_in_second_factor'?: string; - }; - // (undocumented) - 'iat'?: string; - // (undocumented) - 'sub'?: string; -} - -// @public -export interface Persistence { - // (undocumented) - readonly type: 'SESSION' | 'LOCAL' | 'NONE'; -} - -// @public -export abstract class PhoneAuthCredential extends AuthCredential { - // (undocumented) - static fromJSON(json: object | string): PhoneAuthCredential | null; -} - -// @public -export class PhoneAuthProvider implements AuthProvider { - constructor(auth?: Auth | null); - // (undocumented) - static credential( - verificationId: string, - verificationCode: string - ): AuthCredential; - // (undocumented) - static readonly PHONE_SIGN_IN_METHOD: SignInMethod; - // (undocumented) - static readonly PROVIDER_ID: ProviderId; - // (undocumented) - readonly providerId: ProviderId; - // (undocumented) - verifyPhoneNumber( - phoneInfoOptions: PhoneInfoOptions | string, - applicationVerifier: ApplicationVerifier - ): Promise; -} - -// @public -export type PhoneInfoOptions = - | PhoneSingleFactorInfoOptions - | PhoneMultiFactorEnrollInfoOptions - | PhoneMultiFactorSignInInfoOptions; - -// @public -export interface PhoneMultiFactorAssertion extends MultiFactorAssertion {} - -// @public (undocumented) -export interface PhoneMultiFactorEnrollInfoOptions { - // (undocumented) - phoneNumber: string; - // (undocumented) - session: MultiFactorSession; -} - -// @public -export abstract class PhoneMultiFactorGenerator { - // (undocumented) - static assertion( - phoneAuthCredential: PhoneAuthCredential - ): PhoneMultiFactorAssertion; - // (undocumented) - static FACTOR_ID: ProviderId; -} - -// @public (undocumented) -export interface PhoneMultiFactorSignInInfoOptions { - // (undocumented) - multiFactorHint?: MultiFactorInfo; - // (undocumented) - multiFactorUid?: string; - // (undocumented) - session: MultiFactorSession; -} - -// @public (undocumented) -export interface PhoneSingleFactorInfoOptions { - // (undocumented) - phoneNumber: string; -} - -// @public -export interface PopupRedirectResolver {} - -// @public -export const enum ProviderId { - // (undocumented) - ANONYMOUS = 'anonymous', - // (undocumented) - CUSTOM = 'custom', - // (undocumented) - FACEBOOK = 'facebook.com', - // (undocumented) - FIREBASE = 'firebase', - // (undocumented) - GITHUB = 'github.com', - // (undocumented) - GOOGLE = 'google.com', - // (undocumented) - PASSWORD = 'password', - // (undocumented) - PHONE = 'phone', - // (undocumented) - TWITTER = 'twitter.com' -} - -// @public (undocumented) -export interface ReactNativeAsyncStorage { - // (undocumented) - getItem(key: string): Promise; - // (undocumented) - removeItem(key: string): Promise; - // (undocumented) - setItem(key: string, value: string): Promise; -} - -// @public -export abstract class RecaptchaVerifier implements ApplicationVerifier { - constructor( - container: any | string, - parameters?: Object | null, - auth?: Auth | null - ); - // (undocumented) - clear(): void; - // (undocumented) - render(): Promise; - // (undocumented) - readonly type: string; - // (undocumented) - verify(): Promise; -} - -// @public -export const enum SignInMethod { - // (undocumented) - ANONYMOUS = 'anonymous', - // (undocumented) - EMAIL_LINK = 'emailLink', - // (undocumented) - EMAIL_PASSWORD = 'password', - // (undocumented) - FACEBOOK = 'facebook.com', - // (undocumented) - GITHUB = 'github.com', - // (undocumented) - GOOGLE = 'google.com', - // (undocumented) - PHONE = 'phone', - // (undocumented) - TWITTER = 'twitter.com' -} - -// @public -export interface User extends UserInfo { - // (undocumented) - delete(): Promise; - // (undocumented) - readonly emailVerified: boolean; - // (undocumented) - getIdToken(forceRefresh?: boolean): Promise; - // (undocumented) - getIdTokenResult(forceRefresh?: boolean): Promise; - // (undocumented) - readonly isAnonymous: boolean; - // (undocumented) - readonly metadata: UserMetadata; - // (undocumented) - readonly providerData: UserInfo[]; - // (undocumented) - readonly refreshToken: string; - // (undocumented) - reload(): Promise; - // (undocumented) - readonly tenantId: string | null; - // (undocumented) - toJSON(): object; -} - -// @public -export interface UserCredential { - // (undocumented) - operationType: OperationType; - // (undocumented) - providerId: ProviderId | null; - // (undocumented) - user: User; -} - -// @public -export interface UserInfo { - // (undocumented) - readonly displayName: string | null; - // (undocumented) - readonly email: string | null; - // (undocumented) - readonly phoneNumber: string | null; - // (undocumented) - readonly photoURL: string | null; - // (undocumented) - readonly providerId: string; - // (undocumented) - readonly uid: string; -} - -// @public -export interface UserMetadata { - // (undocumented) - readonly creationTime?: string; - // (undocumented) - readonly lastSignInTime?: string; -} - -// @public -export type UserProfile = Record; - - -// (No @packageDocumentation comment for this package) - -``` diff --git a/common/api-review/database-exp.api.md b/common/api-review/database-exp.api.md new file mode 100644 index 00000000000..120262fdba8 --- /dev/null +++ b/common/api-review/database-exp.api.md @@ -0,0 +1,235 @@ +## API Report File for "@firebase/database-exp" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { FirebaseApp } from '@firebase/app'; + +// @public (undocumented) +export function child(parent: Reference, path: string): Reference; + +// @public +export class DataSnapshot { + child(path: string): DataSnapshot; + exists(): boolean; + exportVal(): any; + forEach(action: (child: DataSnapshot) => boolean | void): boolean; + hasChild(path: string): boolean; + hasChildren(): boolean; + get key(): string | null; + get priority(): string | number | null; + readonly ref: Reference; + get size(): number; + toJSON(): object | null; + val(): any; +} + +// @public +export function enableLogging(enabled: boolean, persistent?: boolean): any; + +// @public +export function enableLogging(logger: (message: string) => unknown): any; + +// @public +export function endAt(value: number | string | boolean | null, key?: string): QueryConstraint; + +// @public +export function endBefore(value: number | string | boolean | null, key?: string): QueryConstraint; + +// @public +export function equalTo(value: number | string | boolean | null, key?: string): QueryConstraint; + +// @public +export type EventType = 'value' | 'child_added' | 'child_changed' | 'child_moved' | 'child_removed'; + +// @public +export class FirebaseDatabase { + readonly app: FirebaseApp; + readonly 'type' = "database"; +} + +// @public +export function get(query: Query): Promise; + +// @public +export function getDatabase(app?: FirebaseApp, url?: string): FirebaseDatabase; + +// @public +export function goOffline(db: FirebaseDatabase): void; + +// @public +export function goOnline(db: FirebaseDatabase): void; + +// @public +export function increment(delta: number): object; + +// @public +export function limitToFirst(limit: number): QueryConstraint; + +// @public +export function limitToLast(limit: number): QueryConstraint; + +// @public +export interface ListenOptions { + readonly onlyOnce?: boolean; +} + +// @public +export function off(query: Query, eventType?: EventType, callback?: (snapshot: DataSnapshot, previousChildName?: string | null) => unknown): void; + +// @public +export function onChildAdded(query: Query, callback: (snapshot: DataSnapshot, previousChildName?: string | null) => unknown, cancelCallback?: (error: Error) => unknown): Unsubscribe; + +// @public +export function onChildAdded(query: Query, callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, options: ListenOptions): Unsubscribe; + +// @public +export function onChildAdded(query: Query, callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, cancelCallback: (error: Error) => unknown, options: ListenOptions): Unsubscribe; + +// @public +export function onChildChanged(query: Query, callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, cancelCallback?: (error: Error) => unknown): Unsubscribe; + +// @public +export function onChildChanged(query: Query, callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, options: ListenOptions): Unsubscribe; + +// @public +export function onChildChanged(query: Query, callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, cancelCallback: (error: Error) => unknown, options: ListenOptions): Unsubscribe; + +// @public +export function onChildMoved(query: Query, callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, cancelCallback?: (error: Error) => unknown): Unsubscribe; + +// @public +export function onChildMoved(query: Query, callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, options: ListenOptions): Unsubscribe; + +// @public +export function onChildMoved(query: Query, callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, cancelCallback: (error: Error) => unknown, options: ListenOptions): Unsubscribe; + +// @public +export function onChildRemoved(query: Query, callback: (snapshot: DataSnapshot) => unknown, cancelCallback?: (error: Error) => unknown): Unsubscribe; + +// @public +export function onChildRemoved(query: Query, callback: (snapshot: DataSnapshot) => unknown, options: ListenOptions): Unsubscribe; + +// @public +export function onChildRemoved(query: Query, callback: (snapshot: DataSnapshot) => unknown, cancelCallback: (error: Error) => unknown, options: ListenOptions): Unsubscribe; + +// @public +export class OnDisconnect { + cancel(): Promise; + remove(): Promise; + set(value: unknown): Promise; + setWithPriority(value: unknown, priority: number | string | null): Promise; + update(values: object): Promise; +} + +// @public +export function onDisconnect(ref: Reference): OnDisconnect; + +// @public +export function onValue(query: Query, callback: (snapshot: DataSnapshot) => unknown, cancelCallback?: (error: Error) => unknown): Unsubscribe; + +// @public +export function onValue(query: Query, callback: (snapshot: DataSnapshot) => unknown, options: ListenOptions): Unsubscribe; + +// @public +export function onValue(query: Query, callback: (snapshot: DataSnapshot) => unknown, cancelCallback: (error: Error) => unknown, options: ListenOptions): Unsubscribe; + +// @public +export function orderByChild(path: string): QueryConstraint; + +// @public +export function orderByKey(): QueryConstraint; + +// @public +export function orderByPriority(): QueryConstraint; + +// @public +export function orderByValue(): QueryConstraint; + +// @public +export function push(parent: Reference, value?: unknown): ThenableReference; + +// @public +export interface Query { + isEqual(other: Query | null): boolean; + readonly ref: Reference; + toJSON(): string; + toString(): string; +} + +// @public +export function query(query: Query, ...queryConstraints: QueryConstraint[]): Query; + +// @public +export abstract class QueryConstraint { + abstract readonly type: QueryConstraintType; +} + +// @public +export type QueryConstraintType = 'endAt' | 'endBefore' | 'startAt' | 'startAfter' | 'limitToFirst' | 'limitToLast' | 'orderByChild' | 'orderByKey' | 'orderByPriority' | 'orderByValue' | 'equalTo'; + +// @public +export function ref(db: FirebaseDatabase, path?: string): Reference; + +// @public +export interface Reference extends Query { + readonly key: string | null; + readonly parent: Reference | null; + readonly root: Reference; +} + +// @public +export function refFromURL(db: FirebaseDatabase, url: string): Reference; + +// @public +export function remove(ref: Reference): Promise; + +// @public +export function runTransaction(ref: Reference, transactionUpdate: (currentData: any) => unknown, options?: TransactionOptions): Promise; + +// @public +export function serverTimestamp(): object; + +// @public +export function set(ref: Reference, value: unknown): Promise; + +// @public +export function setPriority(ref: Reference, priority: string | number | null): Promise; + +// @public +export function setWithPriority(ref: Reference, value: unknown, priority: string | number | null): Promise; + +// @public +export function startAfter(value: number | string | boolean | null, key?: string): QueryConstraint; + +// @public +export function startAt(value?: number | string | boolean | null, key?: string): QueryConstraint; + +// @public +export interface ThenableReference extends Reference, Pick, 'then' | 'catch'> { +} + +// @public +export interface TransactionOptions { + readonly applyLocally?: boolean; +} + +// @public +export class TransactionResult { + readonly committed: boolean; + readonly snapshot: DataSnapshot; + toJSON(): object; +} + +// @public +export type Unsubscribe = () => void; + +// @public +export function update(ref: Reference, values: object): Promise; + +// @public +export function useDatabaseEmulator(db: FirebaseDatabase, host: string, port: number): void; + + +``` diff --git a/common/api-review/database.api.md b/common/api-review/database.api.md new file mode 100644 index 00000000000..ad8490ac286 --- /dev/null +++ b/common/api-review/database.api.md @@ -0,0 +1,238 @@ +## API Report File for "@firebase/database" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { EmulatorMockTokenOptions } from '@firebase/util'; +import { FirebaseApp } from '@firebase/app'; + +// @public +export function child(parent: Reference, path: string): Reference; + +// @public +export class DataSnapshot { + child(path: string): DataSnapshot; + exists(): boolean; + exportVal(): any; + forEach(action: (child: DataSnapshot) => boolean | void): boolean; + hasChild(path: string): boolean; + hasChildren(): boolean; + get key(): string | null; + get priority(): string | number | null; + readonly ref: Reference; + get size(): number; + toJSON(): object | null; + val(): any; +} + +// @public +export function enableLogging(enabled: boolean, persistent?: boolean): any; + +// @public +export function enableLogging(logger: (message: string) => unknown): any; + +// @public +export function endAt(value: number | string | boolean | null, key?: string): QueryConstraint; + +// @public +export function endBefore(value: number | string | boolean | null, key?: string): QueryConstraint; + +// @public +export function equalTo(value: number | string | boolean | null, key?: string): QueryConstraint; + +// @public +export type EventType = 'value' | 'child_added' | 'child_changed' | 'child_moved' | 'child_removed'; + +// @public +export class FirebaseDatabase { + readonly app: FirebaseApp; + readonly 'type' = "database"; +} + +// @public +export function get(query: Query): Promise; + +// @public +export function getDatabase(app?: FirebaseApp, url?: string): FirebaseDatabase; + +// @public +export function goOffline(db: FirebaseDatabase): void; + +// @public +export function goOnline(db: FirebaseDatabase): void; + +// @public +export function increment(delta: number): object; + +// @public +export function limitToFirst(limit: number): QueryConstraint; + +// @public +export function limitToLast(limit: number): QueryConstraint; + +// @public +export interface ListenOptions { + readonly onlyOnce?: boolean; +} + +// @public +export function off(query: Query, eventType?: EventType, callback?: (snapshot: DataSnapshot, previousChildName?: string | null) => unknown): void; + +// @public +export function onChildAdded(query: Query, callback: (snapshot: DataSnapshot, previousChildName?: string | null) => unknown, cancelCallback?: (error: Error) => unknown): Unsubscribe; + +// @public +export function onChildAdded(query: Query, callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, options: ListenOptions): Unsubscribe; + +// @public +export function onChildAdded(query: Query, callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, cancelCallback: (error: Error) => unknown, options: ListenOptions): Unsubscribe; + +// @public +export function onChildChanged(query: Query, callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, cancelCallback?: (error: Error) => unknown): Unsubscribe; + +// @public +export function onChildChanged(query: Query, callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, options: ListenOptions): Unsubscribe; + +// @public +export function onChildChanged(query: Query, callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, cancelCallback: (error: Error) => unknown, options: ListenOptions): Unsubscribe; + +// @public +export function onChildMoved(query: Query, callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, cancelCallback?: (error: Error) => unknown): Unsubscribe; + +// @public +export function onChildMoved(query: Query, callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, options: ListenOptions): Unsubscribe; + +// @public +export function onChildMoved(query: Query, callback: (snapshot: DataSnapshot, previousChildName: string | null) => unknown, cancelCallback: (error: Error) => unknown, options: ListenOptions): Unsubscribe; + +// @public +export function onChildRemoved(query: Query, callback: (snapshot: DataSnapshot) => unknown, cancelCallback?: (error: Error) => unknown): Unsubscribe; + +// @public +export function onChildRemoved(query: Query, callback: (snapshot: DataSnapshot) => unknown, options: ListenOptions): Unsubscribe; + +// @public +export function onChildRemoved(query: Query, callback: (snapshot: DataSnapshot) => unknown, cancelCallback: (error: Error) => unknown, options: ListenOptions): Unsubscribe; + +// @public +export class OnDisconnect { + cancel(): Promise; + remove(): Promise; + set(value: unknown): Promise; + setWithPriority(value: unknown, priority: number | string | null): Promise; + update(values: object): Promise; +} + +// @public +export function onDisconnect(ref: Reference): OnDisconnect; + +// @public +export function onValue(query: Query, callback: (snapshot: DataSnapshot) => unknown, cancelCallback?: (error: Error) => unknown): Unsubscribe; + +// @public +export function onValue(query: Query, callback: (snapshot: DataSnapshot) => unknown, options: ListenOptions): Unsubscribe; + +// @public +export function onValue(query: Query, callback: (snapshot: DataSnapshot) => unknown, cancelCallback: (error: Error) => unknown, options: ListenOptions): Unsubscribe; + +// @public +export function orderByChild(path: string): QueryConstraint; + +// @public +export function orderByKey(): QueryConstraint; + +// @public +export function orderByPriority(): QueryConstraint; + +// @public +export function orderByValue(): QueryConstraint; + +// @public +export function push(parent: Reference, value?: unknown): ThenableReference; + +// @public +export interface Query { + isEqual(other: Query | null): boolean; + readonly ref: Reference; + toJSON(): string; + toString(): string; +} + +// @public +export function query(query: Query, ...queryConstraints: QueryConstraint[]): Query; + +// @public +export abstract class QueryConstraint { + abstract readonly type: QueryConstraintType; +} + +// @public +export type QueryConstraintType = 'endAt' | 'endBefore' | 'startAt' | 'startAfter' | 'limitToFirst' | 'limitToLast' | 'orderByChild' | 'orderByKey' | 'orderByPriority' | 'orderByValue' | 'equalTo'; + +// @public +export function ref(db: FirebaseDatabase, path?: string): Reference; + +// @public +export interface Reference extends Query { + readonly key: string | null; + readonly parent: Reference | null; + readonly root: Reference; +} + +// @public +export function refFromURL(db: FirebaseDatabase, url: string): Reference; + +// @public +export function remove(ref: Reference): Promise; + +// @public +export function runTransaction(ref: Reference, transactionUpdate: (currentData: any) => unknown, options?: TransactionOptions): Promise; + +// @public +export function serverTimestamp(): object; + +// @public +export function set(ref: Reference, value: unknown): Promise; + +// @public +export function setPriority(ref: Reference, priority: string | number | null): Promise; + +// @public +export function setWithPriority(ref: Reference, value: unknown, priority: string | number | null): Promise; + +// @public +export function startAfter(value: number | string | boolean | null, key?: string): QueryConstraint; + +// @public +export function startAt(value?: number | string | boolean | null, key?: string): QueryConstraint; + +// @public +export interface ThenableReference extends Reference, Pick, 'then' | 'catch'> { +} + +// @public +export interface TransactionOptions { + readonly applyLocally?: boolean; +} + +// @public +export class TransactionResult { + readonly committed: boolean; + readonly snapshot: DataSnapshot; + toJSON(): object; +} + +// @public +export type Unsubscribe = () => void; + +// @public +export function update(ref: Reference, values: object): Promise; + +// @public +export function useDatabaseEmulator(db: FirebaseDatabase, host: string, port: number, options?: { + mockUserToken?: EmulatorMockTokenOptions; +}): void; + + +``` diff --git a/common/api-review/firestore-exp.api.md b/common/api-review/firestore-exp.api.md new file mode 100644 index 00000000000..171d45054cb --- /dev/null +++ b/common/api-review/firestore-exp.api.md @@ -0,0 +1,488 @@ +## API Report File for "@firebase/firestore-exp" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { FirebaseApp } from '@firebase/app-exp'; +import { LogLevelString as LogLevel } from '@firebase/logger'; + +// @public +export function addDoc(reference: CollectionReference, data: T): Promise>; + +// @public +export function arrayRemove(...elements: unknown[]): FieldValue; + +// @public +export function arrayUnion(...elements: unknown[]): FieldValue; + +// @public +export class Bytes { + static fromBase64String(base64: string): Bytes; + static fromUint8Array(array: Uint8Array): Bytes; + isEqual(other: Bytes): boolean; + toBase64(): string; + toString(): string; + toUint8Array(): Uint8Array; +} + +// @public +export const CACHE_SIZE_UNLIMITED = -1; + +// @public +export function clearIndexedDbPersistence(firestore: FirebaseFirestore): Promise; + +// @public +export function collection(firestore: FirebaseFirestore, path: string, ...pathSegments: string[]): CollectionReference; + +// @public +export function collection(reference: CollectionReference, path: string, ...pathSegments: string[]): CollectionReference; + +// @public +export function collection(reference: DocumentReference, path: string, ...pathSegments: string[]): CollectionReference; + +// @public +export function collectionGroup(firestore: FirebaseFirestore, collectionId: string): Query; + +// @public +export class CollectionReference extends Query { + get id(): string; + get parent(): DocumentReference | null; + get path(): string; + readonly type = "collection"; + withConverter(converter: FirestoreDataConverter): CollectionReference; + withConverter(converter: null): CollectionReference; +} + +// @public +export function deleteDoc(reference: DocumentReference): Promise; + +// @public +export function deleteField(): FieldValue; + +// @public +export function disableNetwork(firestore: FirebaseFirestore): Promise; + +// @public +export function doc(firestore: FirebaseFirestore, path: string, ...pathSegments: string[]): DocumentReference; + +// @public +export function doc(reference: CollectionReference, path?: string, ...pathSegments: string[]): DocumentReference; + +// @public +export function doc(reference: DocumentReference, path: string, ...pathSegments: string[]): DocumentReference; + +// @public +export interface DocumentChange { + readonly doc: QueryDocumentSnapshot; + readonly newIndex: number; + readonly oldIndex: number; + readonly type: DocumentChangeType; +} + +// @public +export type DocumentChangeType = 'added' | 'removed' | 'modified'; + +// @public +export interface DocumentData { + [field: string]: any; +} + +// @public +export function documentId(): FieldPath; + +// @public +export class DocumentReference { + readonly firestore: FirebaseFirestore; + get id(): string; + get parent(): CollectionReference; + get path(): string; + readonly type = "document"; + withConverter(converter: FirestoreDataConverter): DocumentReference; + withConverter(converter: null): DocumentReference; +} + +// @public +export class DocumentSnapshot { + protected constructor(); + data(options?: SnapshotOptions): T | undefined; + exists(): this is QueryDocumentSnapshot; + get(fieldPath: string | FieldPath, options?: SnapshotOptions): any; + get id(): string; + readonly metadata: SnapshotMetadata; + get ref(): DocumentReference; +} + +// @public +export function enableIndexedDbPersistence(firestore: FirebaseFirestore, persistenceSettings?: PersistenceSettings): Promise; + +// @public +export function enableMultiTabIndexedDbPersistence(firestore: FirebaseFirestore): Promise; + +// @public +export function enableNetwork(firestore: FirebaseFirestore): Promise; + +// @public +export function endAt(snapshot: DocumentSnapshot): QueryConstraint; + +// @public +export function endAt(...fieldValues: unknown[]): QueryConstraint; + +// @public +export function endBefore(snapshot: DocumentSnapshot): QueryConstraint; + +// @public +export function endBefore(...fieldValues: unknown[]): QueryConstraint; + +// @public +export class FieldPath { + constructor(...fieldNames: string[]); + isEqual(other: FieldPath): boolean; +} + +// @public +export abstract class FieldValue { + abstract isEqual(other: FieldValue): boolean; +} + +// @public +export class FirebaseFirestore { + get app(): FirebaseApp; + toJSON(): object; +} + +// @public +export interface FirestoreDataConverter { + fromFirestore(snapshot: QueryDocumentSnapshot, options?: SnapshotOptions): T; + toFirestore(modelObject: T): DocumentData; + toFirestore(modelObject: Partial, options: SetOptions): DocumentData; +} + +// @public +export class FirestoreError extends Error { + readonly code: FirestoreErrorCode; + readonly message: string; + readonly name: string; + readonly stack?: string; +} + +// @public +export type FirestoreErrorCode = 'cancelled' | 'unknown' | 'invalid-argument' | 'deadline-exceeded' | 'not-found' | 'already-exists' | 'permission-denied' | 'resource-exhausted' | 'failed-precondition' | 'aborted' | 'out-of-range' | 'unimplemented' | 'internal' | 'unavailable' | 'data-loss' | 'unauthenticated'; + +// @public +export class GeoPoint { + constructor(latitude: number, longitude: number); + isEqual(other: GeoPoint): boolean; + get latitude(): number; + get longitude(): number; + toJSON(): { + latitude: number; + longitude: number; + }; +} + +// @public +export function getDoc(reference: DocumentReference): Promise>; + +// @public +export function getDocFromCache(reference: DocumentReference): Promise>; + +// @public +export function getDocFromServer(reference: DocumentReference): Promise>; + +// @public +export function getDocs(query: Query): Promise>; + +// @public +export function getDocsFromCache(query: Query): Promise>; + +// @public +export function getDocsFromServer(query: Query): Promise>; + +// @public +export function getFirestore(app?: FirebaseApp): FirebaseFirestore; + +// @public +export function increment(n: number): FieldValue; + +// @public +export function initializeFirestore(app: FirebaseApp, settings: Settings): FirebaseFirestore; + +// @public +export function limit(limit: number): QueryConstraint; + +// @public +export function limitToLast(limit: number): QueryConstraint; + +// @public +export function loadBundle(firestore: FirebaseFirestore, bundleData: ReadableStream | ArrayBuffer | string): LoadBundleTask; + +// @public +export class LoadBundleTask implements PromiseLike { + catch(onRejected: (a: Error) => R | PromiseLike): Promise; + onProgress(next?: (progress: LoadBundleTaskProgress) => unknown, error?: (err: Error) => unknown, complete?: () => void): void; + then(onFulfilled?: (a: LoadBundleTaskProgress) => T | PromiseLike, onRejected?: (a: Error) => R | PromiseLike): Promise; +} + +// @public +export interface LoadBundleTaskProgress { + bytesLoaded: number; + documentsLoaded: number; + taskState: TaskState; + totalBytes: number; + totalDocuments: number; +} + +export { LogLevel } + +// @public +export function namedQuery(firestore: FirebaseFirestore, name: string): Promise; + +// @public +export function onSnapshot(reference: DocumentReference, observer: { + next?: (snapshot: DocumentSnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; +}): Unsubscribe; + +// @public +export function onSnapshot(reference: DocumentReference, options: SnapshotListenOptions, observer: { + next?: (snapshot: DocumentSnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; +}): Unsubscribe; + +// @public +export function onSnapshot(reference: DocumentReference, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; + +// @public +export function onSnapshot(reference: DocumentReference, options: SnapshotListenOptions, onNext: (snapshot: DocumentSnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; + +// @public +export function onSnapshot(query: Query, observer: { + next?: (snapshot: QuerySnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; +}): Unsubscribe; + +// @public +export function onSnapshot(query: Query, options: SnapshotListenOptions, observer: { + next?: (snapshot: QuerySnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; +}): Unsubscribe; + +// @public +export function onSnapshot(query: Query, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; + +// @public +export function onSnapshot(query: Query, options: SnapshotListenOptions, onNext: (snapshot: QuerySnapshot) => void, onError?: (error: FirestoreError) => void, onCompletion?: () => void): Unsubscribe; + +// @public +export function onSnapshotsInSync(firestore: FirebaseFirestore, observer: { + next?: (value: void) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; +}): Unsubscribe; + +// @public +export function onSnapshotsInSync(firestore: FirebaseFirestore, onSync: () => void): Unsubscribe; + +// @public +export function orderBy(fieldPath: string | FieldPath, directionStr?: OrderByDirection): QueryConstraint; + +// @public +export type OrderByDirection = 'desc' | 'asc'; + +// @public +export interface PersistenceSettings { + forceOwnership?: boolean; +} + +// @public +export class Query { + protected constructor(); + readonly firestore: FirebaseFirestore; + readonly type: 'query' | 'collection'; + withConverter(converter: null): Query; + withConverter(converter: FirestoreDataConverter): Query; +} + +// @public +export function query(query: Query, ...queryConstraints: QueryConstraint[]): Query; + +// @public +export abstract class QueryConstraint { + abstract readonly type: QueryConstraintType; +} + +// @public +export type QueryConstraintType = 'where' | 'orderBy' | 'limit' | 'limitToLast' | 'startAt' | 'startAfter' | 'endAt' | 'endBefore'; + +// @public +export class QueryDocumentSnapshot extends DocumentSnapshot { + // @override + data(options?: SnapshotOptions): T; +} + +// @public +export function queryEqual(left: Query, right: Query): boolean; + +// @public +export class QuerySnapshot { + docChanges(options?: SnapshotListenOptions): Array>; + get docs(): Array>; + get empty(): boolean; + forEach(callback: (result: QueryDocumentSnapshot) => void, thisArg?: unknown): void; + readonly metadata: SnapshotMetadata; + readonly query: Query; + get size(): number; +} + +// @public +export function refEqual(left: DocumentReference | CollectionReference, right: DocumentReference | CollectionReference): boolean; + +// @public +export function runTransaction(firestore: FirebaseFirestore, updateFunction: (transaction: Transaction) => Promise): Promise; + +// @public +export function serverTimestamp(): FieldValue; + +// @public +export function setDoc(reference: DocumentReference, data: T): Promise; + +// @public +export function setDoc(reference: DocumentReference, data: Partial, options: SetOptions): Promise; + +// @public +export function setLogLevel(logLevel: LogLevel): void; + +// @public +export type SetOptions = { + readonly merge?: boolean; +} | { + readonly mergeFields?: Array; +}; + +// @public +export interface Settings { + cacheSizeBytes?: number; + experimentalAutoDetectLongPolling?: boolean; + experimentalForceLongPolling?: boolean; + host?: string; + ignoreUndefinedProperties?: boolean; + ssl?: boolean; +} + +// @public +export function snapshotEqual(left: DocumentSnapshot | QuerySnapshot, right: DocumentSnapshot | QuerySnapshot): boolean; + +// @public +export interface SnapshotListenOptions { + readonly includeMetadataChanges?: boolean; +} + +// @public +export class SnapshotMetadata { + readonly fromCache: boolean; + readonly hasPendingWrites: boolean; + isEqual(other: SnapshotMetadata): boolean; +} + +// @public +export interface SnapshotOptions { + readonly serverTimestamps?: 'estimate' | 'previous' | 'none'; +} + +// @public +export function startAfter(snapshot: DocumentSnapshot): QueryConstraint; + +// @public +export function startAfter(...fieldValues: unknown[]): QueryConstraint; + +// @public +export function startAt(snapshot: DocumentSnapshot): QueryConstraint; + +// @public +export function startAt(...fieldValues: unknown[]): QueryConstraint; + +// @public +export type TaskState = 'Error' | 'Running' | 'Success'; + +// @public +export function terminate(firestore: FirebaseFirestore): Promise; + +// @public +export class Timestamp { + constructor( + seconds: number, + nanoseconds: number); + static fromDate(date: Date): Timestamp; + static fromMillis(milliseconds: number): Timestamp; + isEqual(other: Timestamp): boolean; + readonly nanoseconds: number; + static now(): Timestamp; + readonly seconds: number; + toDate(): Date; + toJSON(): { + seconds: number; + nanoseconds: number; + }; + toMillis(): number; + toString(): string; + valueOf(): string; +} + +// @public +export class Transaction { + delete(documentRef: DocumentReference): this; + get(documentRef: DocumentReference): Promise>; + set(documentRef: DocumentReference, data: T): this; + set(documentRef: DocumentReference, data: Partial, options: SetOptions): this; + update(documentRef: DocumentReference, data: UpdateData): this; + update(documentRef: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): this; +} + +// @public +export interface Unsubscribe { + (): void; +} + +// @public +export interface UpdateData { + [fieldPath: string]: any; +} + +// @public +export function updateDoc(reference: DocumentReference, data: UpdateData): Promise; + +// @public +export function updateDoc(reference: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): Promise; + +// @public +export function useFirestoreEmulator(firestore: FirebaseFirestore, host: string, port: number): void; + +// @public +export function waitForPendingWrites(firestore: FirebaseFirestore): Promise; + +// @public +export function where(fieldPath: string | FieldPath, opStr: WhereFilterOp, value: unknown): QueryConstraint; + +// @public +export type WhereFilterOp = '<' | '<=' | '==' | '!=' | '>=' | '>' | 'array-contains' | 'in' | 'array-contains-any' | 'not-in'; + +// @public +export class WriteBatch { + commit(): Promise; + delete(documentRef: DocumentReference): WriteBatch; + set(documentRef: DocumentReference, data: T): WriteBatch; + set(documentRef: DocumentReference, data: Partial, options: SetOptions): WriteBatch; + update(documentRef: DocumentReference, data: UpdateData): WriteBatch; + update(documentRef: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): WriteBatch; +} + +// @public +export function writeBatch(firestore: FirebaseFirestore): WriteBatch; + + +``` diff --git a/common/api-review/firestore-lite.api.md b/common/api-review/firestore-lite.api.md new file mode 100644 index 00000000000..5aceb99e451 --- /dev/null +++ b/common/api-review/firestore-lite.api.md @@ -0,0 +1,338 @@ +## API Report File for "@firebase/firestore-lite" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { FirebaseApp } from '@firebase/app-exp'; +import { LogLevelString as LogLevel } from '@firebase/logger'; + +// @public +export function addDoc(reference: CollectionReference, data: T): Promise>; + +// @public +export function arrayRemove(...elements: unknown[]): FieldValue; + +// @public +export function arrayUnion(...elements: unknown[]): FieldValue; + +// @public +export class Bytes { + static fromBase64String(base64: string): Bytes; + static fromUint8Array(array: Uint8Array): Bytes; + isEqual(other: Bytes): boolean; + toBase64(): string; + toString(): string; + toUint8Array(): Uint8Array; +} + +// @public +export function collection(firestore: FirebaseFirestore, path: string, ...pathSegments: string[]): CollectionReference; + +// @public +export function collection(reference: CollectionReference, path: string, ...pathSegments: string[]): CollectionReference; + +// @public +export function collection(reference: DocumentReference, path: string, ...pathSegments: string[]): CollectionReference; + +// @public +export function collectionGroup(firestore: FirebaseFirestore, collectionId: string): Query; + +// @public +export class CollectionReference extends Query { + get id(): string; + get parent(): DocumentReference | null; + get path(): string; + readonly type = "collection"; + withConverter(converter: FirestoreDataConverter): CollectionReference; + withConverter(converter: null): CollectionReference; +} + +// @public +export function deleteDoc(reference: DocumentReference): Promise; + +// @public +export function deleteField(): FieldValue; + +// @public +export function doc(firestore: FirebaseFirestore, path: string, ...pathSegments: string[]): DocumentReference; + +// @public +export function doc(reference: CollectionReference, path?: string, ...pathSegments: string[]): DocumentReference; + +// @public +export function doc(reference: DocumentReference, path: string, ...pathSegments: string[]): DocumentReference; + +// @public +export interface DocumentData { + [field: string]: any; +} + +// @public +export function documentId(): FieldPath; + +// @public +export class DocumentReference { + readonly firestore: FirebaseFirestore; + get id(): string; + get parent(): CollectionReference; + get path(): string; + readonly type = "document"; + withConverter(converter: FirestoreDataConverter): DocumentReference; + withConverter(converter: null): DocumentReference; +} + +// @public +export class DocumentSnapshot { + protected constructor(); + data(): T | undefined; + exists(): this is QueryDocumentSnapshot; + get(fieldPath: string | FieldPath): any; + get id(): string; + get ref(): DocumentReference; +} + +// @public +export function endAt(snapshot: DocumentSnapshot): QueryConstraint; + +// @public +export function endAt(...fieldValues: unknown[]): QueryConstraint; + +// @public +export function endBefore(snapshot: DocumentSnapshot): QueryConstraint; + +// @public +export function endBefore(...fieldValues: unknown[]): QueryConstraint; + +// @public +export class FieldPath { + constructor(...fieldNames: string[]); + isEqual(other: FieldPath): boolean; +} + +// @public +export abstract class FieldValue { + abstract isEqual(other: FieldValue): boolean; +} + +// @public +export class FirebaseFirestore { + get app(): FirebaseApp; + toJSON(): object; +} + +// @public +export interface FirestoreDataConverter { + fromFirestore(snapshot: QueryDocumentSnapshot): T; + toFirestore(modelObject: T): DocumentData; + toFirestore(modelObject: Partial, options: SetOptions): DocumentData; +} + +// @public +export class FirestoreError extends Error { + readonly code: FirestoreErrorCode; + readonly message: string; + readonly name: string; + readonly stack?: string; +} + +// @public +export type FirestoreErrorCode = 'cancelled' | 'unknown' | 'invalid-argument' | 'deadline-exceeded' | 'not-found' | 'already-exists' | 'permission-denied' | 'resource-exhausted' | 'failed-precondition' | 'aborted' | 'out-of-range' | 'unimplemented' | 'internal' | 'unavailable' | 'data-loss' | 'unauthenticated'; + +// @public +export class GeoPoint { + constructor(latitude: number, longitude: number); + isEqual(other: GeoPoint): boolean; + get latitude(): number; + get longitude(): number; + toJSON(): { + latitude: number; + longitude: number; + }; +} + +// @public +export function getDoc(reference: DocumentReference): Promise>; + +// @public +export function getDocs(query: Query): Promise>; + +// @public +export function getFirestore(app?: FirebaseApp): FirebaseFirestore; + +// @public +export function increment(n: number): FieldValue; + +// @public +export function initializeFirestore(app: FirebaseApp, settings: Settings): FirebaseFirestore; + +// @public +export function limit(limit: number): QueryConstraint; + +// @public +export function limitToLast(limit: number): QueryConstraint; + +export { LogLevel } + +// @public +export function orderBy(fieldPath: string | FieldPath, directionStr?: OrderByDirection): QueryConstraint; + +// @public +export type OrderByDirection = 'desc' | 'asc'; + +// @public +export class Query { + protected constructor(); + readonly firestore: FirebaseFirestore; + readonly type: 'query' | 'collection'; + withConverter(converter: null): Query; + withConverter(converter: FirestoreDataConverter): Query; +} + +// @public +export function query(query: Query, ...queryConstraints: QueryConstraint[]): Query; + +// @public +export abstract class QueryConstraint { + abstract readonly type: QueryConstraintType; +} + +// @public +export type QueryConstraintType = 'where' | 'orderBy' | 'limit' | 'limitToLast' | 'startAt' | 'startAfter' | 'endAt' | 'endBefore'; + +// @public +export class QueryDocumentSnapshot extends DocumentSnapshot { + // @override + data(): T; +} + +// @public +export function queryEqual(left: Query, right: Query): boolean; + +// @public +export class QuerySnapshot { + get docs(): Array>; + get empty(): boolean; + forEach(callback: (result: QueryDocumentSnapshot) => void, thisArg?: unknown): void; + readonly query: Query; + get size(): number; +} + +// @public +export function refEqual(left: DocumentReference | CollectionReference, right: DocumentReference | CollectionReference): boolean; + +// @public +export function runTransaction(firestore: FirebaseFirestore, updateFunction: (transaction: Transaction) => Promise): Promise; + +// @public +export function serverTimestamp(): FieldValue; + +// @public +export function setDoc(reference: DocumentReference, data: T): Promise; + +// @public +export function setDoc(reference: DocumentReference, data: Partial, options: SetOptions): Promise; + +// @public +export function setLogLevel(logLevel: LogLevel): void; + +// @public +export type SetOptions = { + readonly merge?: boolean; +} | { + readonly mergeFields?: Array; +}; + +// @public +export interface Settings { + host?: string; + ignoreUndefinedProperties?: boolean; + ssl?: boolean; +} + +// @public +export function snapshotEqual(left: DocumentSnapshot | QuerySnapshot, right: DocumentSnapshot | QuerySnapshot): boolean; + +// @public +export function startAfter(snapshot: DocumentSnapshot): QueryConstraint; + +// @public +export function startAfter(...fieldValues: unknown[]): QueryConstraint; + +// @public +export function startAt(snapshot: DocumentSnapshot): QueryConstraint; + +// @public +export function startAt(...fieldValues: unknown[]): QueryConstraint; + +// @public +export function terminate(firestore: FirebaseFirestore): Promise; + +// @public +export class Timestamp { + constructor( + seconds: number, + nanoseconds: number); + static fromDate(date: Date): Timestamp; + static fromMillis(milliseconds: number): Timestamp; + isEqual(other: Timestamp): boolean; + readonly nanoseconds: number; + static now(): Timestamp; + readonly seconds: number; + toDate(): Date; + toJSON(): { + seconds: number; + nanoseconds: number; + }; + toMillis(): number; + toString(): string; + valueOf(): string; +} + +// @public +export class Transaction { + delete(documentRef: DocumentReference): this; + get(documentRef: DocumentReference): Promise>; + set(documentRef: DocumentReference, data: T): this; + set(documentRef: DocumentReference, data: Partial, options: SetOptions): this; + update(documentRef: DocumentReference, data: UpdateData): this; + update(documentRef: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): this; +} + +// @public +export interface UpdateData { + [fieldPath: string]: any; +} + +// @public +export function updateDoc(reference: DocumentReference, data: UpdateData): Promise; + +// @public +export function updateDoc(reference: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): Promise; + +// @public +export function useFirestoreEmulator(firestore: FirebaseFirestore, host: string, port: number): void; + +// @public +export function where(fieldPath: string | FieldPath, opStr: WhereFilterOp, value: unknown): QueryConstraint; + +// @public +export type WhereFilterOp = '<' | '<=' | '==' | '!=' | '>=' | '>' | 'array-contains' | 'in' | 'array-contains-any' | 'not-in'; + +// @public +export class WriteBatch { + commit(): Promise; + delete(documentRef: DocumentReference): WriteBatch; + set(documentRef: DocumentReference, data: T): WriteBatch; + set(documentRef: DocumentReference, data: Partial, options: SetOptions): WriteBatch; + update(documentRef: DocumentReference, data: UpdateData): WriteBatch; + update(documentRef: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): WriteBatch; +} + +// @public +export function writeBatch(firestore: FirebaseFirestore): WriteBatch; + + +// (No @packageDocumentation comment for this package) + +``` diff --git a/common/api-review/functions-exp.api.md b/common/api-review/functions-exp.api.md index 947534f6548..340f5cdbdad 100644 --- a/common/api-review/functions-exp.api.md +++ b/common/api-review/functions-exp.api.md @@ -4,21 +4,46 @@ ```ts -import { FirebaseApp } from '@firebase/app-types-exp'; -import { Functions } from '@firebase/functions-types-exp'; -import { HttpsCallable } from '@firebase/functions-types-exp'; -import { HttpsCallableOptions } from '@firebase/functions-types-exp'; +import { FirebaseApp } from '@firebase/app-exp'; +import { FirebaseError } from '@firebase/util'; // @public -export function getFunctions(app: FirebaseApp, regionOrCustomDomain?: string): Functions; +export interface Functions { + app: FirebaseApp; + customDomain: string | null; + region: string; +} // @public -export function httpsCallable(functionsInstance: Functions, name: string, options?: HttpsCallableOptions): HttpsCallable; +export interface FunctionsError extends FirebaseError { + readonly code: FunctionsErrorCode; + readonly details?: unknown; +} // @public -export function useFunctionsEmulator(functionsInstance: Functions, origin: string): void; +export type FunctionsErrorCode = 'ok' | 'cancelled' | 'unknown' | 'invalid-argument' | 'deadline-exceeded' | 'not-found' | 'already-exists' | 'permission-denied' | 'resource-exhausted' | 'failed-precondition' | 'aborted' | 'out-of-range' | 'unimplemented' | 'internal' | 'unavailable' | 'data-loss' | 'unauthenticated'; +// @public +export function getFunctions(app?: FirebaseApp, regionOrCustomDomain?: string): Functions; + +// @public +export type HttpsCallable = (data?: RequestData | null) => Promise>; + +// @public +export function httpsCallable(functionsInstance: Functions, name: string, options?: HttpsCallableOptions): HttpsCallable; + +// @public +export interface HttpsCallableOptions { + timeout?: number; +} + +// @public +export interface HttpsCallableResult { + readonly data: ResponseData; +} + +// @public +export function useFunctionsEmulator(functionsInstance: Functions, host: string, port: number): void; -// (No @packageDocumentation comment for this package) ``` diff --git a/common/api-review/functions-types-exp.api.md b/common/api-review/functions-types-exp.api.md deleted file mode 100644 index 466816b7e1e..00000000000 --- a/common/api-review/functions-types-exp.api.md +++ /dev/null @@ -1,67 +0,0 @@ -## API Report File for "@firebase/functions-types-exp" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { FirebaseApp } from '@firebase/app-types-exp'; -import { FirebaseError } from '@firebase/util'; - -// @public -export interface Functions { - app: FirebaseApp; - - customDomain: string | null; - - region: string; -} - -// @public (undocumented) -export interface FunctionsError extends FirebaseError { - readonly code: FunctionsErrorCode; - - readonly details?: any; -} - -// @public -export type FunctionsErrorCode = - | 'ok' - | 'cancelled' - | 'unknown' - | 'invalid-argument' - | 'deadline-exceeded' - | 'not-found' - | 'already-exists' - | 'permission-denied' - | 'resource-exhausted' - | 'failed-precondition' - | 'aborted' - | 'out-of-range' - | 'unimplemented' - | 'internal' - | 'unavailable' - | 'data-loss' - | 'unauthenticated'; - -// @public -export interface HttpsCallable { - // (undocumented) - (data?: {} | null): Promise; -} - -// @public -export interface HttpsCallableOptions { - // (undocumented) - timeout?: number; // in millis -} - -// @public -export interface HttpsCallableResult { - // (undocumented) - readonly data: any; -} - - -// (No @packageDocumentation comment for this package) - -``` diff --git a/common/api-review/installations-exp.api.md b/common/api-review/installations-exp.api.md index 4759f5dceaf..438ae37d994 100644 --- a/common/api-review/installations-exp.api.md +++ b/common/api-review/installations-exp.api.md @@ -4,17 +4,26 @@ ```ts -import { FirebaseApp } from '@firebase/app-types-exp'; -import { FirebaseInstallations } from '@firebase/installations-types-exp'; +import { FirebaseApp } from '@firebase/app-exp'; // @public export function deleteInstallations(installations: FirebaseInstallations): Promise; +// @public +export interface FirebaseInstallations { +} + +// @internal +export interface _FirebaseInstallationsInternal { + getId(): Promise; + getToken(forceRefresh?: boolean): Promise; +} + // @public export function getId(installations: FirebaseInstallations): Promise; // @public -export function getInstallations(app: FirebaseApp): FirebaseInstallations; +export function getInstallations(app?: FirebaseApp): FirebaseInstallations; // @public export function getToken(installations: FirebaseInstallations, forceRefresh?: boolean): Promise; @@ -29,6 +38,4 @@ export type IdChangeUnsubscribeFn = () => void; export function onIdChange(installations: FirebaseInstallations, callback: IdChangeCallbackFn): IdChangeUnsubscribeFn; -// (No @packageDocumentation comment for this package) - ``` diff --git a/common/api-review/installations-types-exp.api.md b/common/api-review/installations-types-exp.api.md deleted file mode 100644 index 88344888c2a..00000000000 --- a/common/api-review/installations-types-exp.api.md +++ /dev/null @@ -1,20 +0,0 @@ -## API Report File for "@firebase/installations-types-exp" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -// @public -export interface FirebaseInstallations {} - -// @internal -export interface _FirebaseInstallationsInternal { - getId(): Promise; - - getToken(forceRefresh?: boolean): Promise; -} - - -// (No @packageDocumentation comment for this package) - -``` diff --git a/common/api-review/messaging-exp.api.md b/common/api-review/messaging-exp.api.md new file mode 100644 index 00000000000..635be9b5bff --- /dev/null +++ b/common/api-review/messaging-exp.api.md @@ -0,0 +1,71 @@ +## API Report File for "@firebase/messaging-exp" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { FirebaseApp } from '@firebase/app-exp'; +import { NextFn } from '@firebase/util'; +import { Observer } from '@firebase/util'; +import { Unsubscribe } from '@firebase/util'; + +// @public +export function deleteToken(messaging: FirebaseMessaging): Promise; + +// @public +export interface FcmOptions { + analyticsLabel?: string; + link?: string; +} + +// @public +export interface FirebaseMessaging { +} + +// @internal (undocumented) +export type _FirebaseMessagingName = 'messaging'; + +// @public +export function getMessaging(app?: FirebaseApp): FirebaseMessaging; + +// @public +export function getToken(messaging: FirebaseMessaging, options?: GetTokenOptions): Promise; + +// @public +export interface GetTokenOptions { + swReg?: ServiceWorkerRegistration; + vapidKey?: string; +} + +// @public +export function isSupported(): Promise; + +// @public +export interface MessagePayload { + collapseKey: string; + data?: { + [key: string]: string; + }; + fcmOptions?: FcmOptions; + from: string; + notification?: NotificationPayload; +} + +export { NextFn } + +// @public +export interface NotificationPayload { + body?: string; + image?: string; + title?: string; +} + +export { Observer } + +// @public +export function onMessage(messaging: FirebaseMessaging, nextOrObserver: NextFn | Observer): Unsubscribe; + +export { Unsubscribe } + + +``` diff --git a/common/api-review/messaging-exp.sw.api.md b/common/api-review/messaging-exp.sw.api.md new file mode 100644 index 00000000000..a0e2f9e8a8b --- /dev/null +++ b/common/api-review/messaging-exp.sw.api.md @@ -0,0 +1,56 @@ +## API Report File for "@firebase/messaging-exp/sw" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { FirebaseApp } from '@firebase/app-exp'; +import { NextFn } from '@firebase/util'; +import { Observer } from '@firebase/util'; +import { Unsubscribe } from '@firebase/util'; + +// @public +export interface FcmOptions { + analyticsLabel?: string; + link?: string; +} + +// @public +export interface FirebaseMessaging { +} + +// @public +export function getMessaging(app?: FirebaseApp): FirebaseMessaging; + +// @public +export function isSupported(): Promise; + +// @public +export interface MessagePayload { + collapseKey: string; + data?: { + [key: string]: string; + }; + fcmOptions?: FcmOptions; + from: string; + notification?: NotificationPayload; +} + +export { NextFn } + +// @public +export interface NotificationPayload { + body?: string; + image?: string; + title?: string; +} + +export { Observer } + +// @public +export function onBackgroundMessage(messaging: FirebaseMessaging, nextOrObserver: NextFn | Observer): Unsubscribe; + +export { Unsubscribe } + + +``` diff --git a/common/api-review/performance-exp.api.md b/common/api-review/performance-exp.api.md index 8907820c39a..bfc50e9be06 100644 --- a/common/api-review/performance-exp.api.md +++ b/common/api-review/performance-exp.api.md @@ -4,18 +4,51 @@ ```ts -import { FirebaseApp } from '@firebase/app-types-exp'; -import { FirebasePerformance } from '@firebase/performance-types-exp'; -import { PerformanceSettings } from '@firebase/performance-types-exp'; -import { PerformanceTrace } from '@firebase/performance-types-exp'; +import { FirebaseApp } from '@firebase/app-exp'; // @public -export function getPerformance(app: FirebaseApp, settings?: PerformanceSettings): FirebasePerformance; +export interface FirebasePerformance { + dataCollectionEnabled: boolean; + instrumentationEnabled: boolean; +} // @public -export function trace(performance: FirebasePerformance, name: string): PerformanceTrace; +export function getPerformance(app?: FirebaseApp): FirebasePerformance; + +// @public +export function initializePerformance(app: FirebaseApp, settings?: PerformanceSettings): FirebasePerformance; + +// @public +export interface PerformanceSettings { + dataCollectionEnabled?: boolean; + instrumentationEnabled?: boolean; +} +// @public +export interface PerformanceTrace { + getAttribute(attr: string): string | undefined; + getAttributes(): { + [key: string]: string; + }; + getMetric(metricName: string): number; + incrementMetric(metricName: string, num?: number): void; + putAttribute(attr: string, value: string): void; + putMetric(metricName: string, num: number): void; + record(startTime: number, duration: number, options?: { + metrics?: { + [key: string]: number; + }; + attributes?: { + [key: string]: string; + }; + }): void; + removeAttribute(attr: string): void; + start(): void; + stop(): void; +} + +// @public +export function trace(performance: FirebasePerformance, name: string): PerformanceTrace; -// (No @packageDocumentation comment for this package) ``` diff --git a/common/api-review/performance-types-exp.api.md b/common/api-review/performance-types-exp.api.md deleted file mode 100644 index 2b1532658ca..00000000000 --- a/common/api-review/performance-types-exp.api.md +++ /dev/null @@ -1,45 +0,0 @@ -## API Report File for "@firebase/performance-types-exp" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -// @public (undocumented) -export interface FirebasePerformance { - dataCollectionEnabled: boolean; - - instrumentationEnabled: boolean; -} - -// @public (undocumented) -export interface PerformanceSettings { - dataCollectionEnabled?: boolean; - - instrumentationEnabled?: boolean; -} - -// @public (undocumented) -export interface PerformanceTrace { - getAttribute(attr: string): string | undefined; - getAttributes(): { [key: string]: string }; - getMetric(metricName: string): number; - incrementMetric(metricName: string, num?: number): void; - putAttribute(attr: string, value: string): void; - putMetric(metricName: string, num: number): void; - record( - startTime: number, - duration: number, - options?: { - metrics?: { [key: string]: number }; - attributes?: { [key: string]: string }; - } - ): void; - removeAttribute(attr: string): void; - start(): void; - stop(): void; -} - - -// (No @packageDocumentation comment for this package) - -``` diff --git a/common/api-review/remote-config-exp.api.md b/common/api-review/remote-config-exp.api.md new file mode 100644 index 00000000000..5ea5c1d2240 --- /dev/null +++ b/common/api-review/remote-config-exp.api.md @@ -0,0 +1,76 @@ +## API Report File for "@firebase/remote-config-exp" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { FirebaseApp } from '@firebase/app-exp'; + +// @public +export function activate(remoteConfig: RemoteConfig): Promise; + +// @public +export function ensureInitialized(remoteConfig: RemoteConfig): Promise; + +// @public +export function fetchAndActivate(remoteConfig: RemoteConfig): Promise; + +// @public +export function fetchConfig(remoteConfig: RemoteConfig): Promise; + +// @public +export type FetchStatus = 'no-fetch-yet' | 'success' | 'failure' | 'throttle'; + +// @public +export function getAll(remoteConfig: RemoteConfig): Record; + +// @public +export function getBoolean(remoteConfig: RemoteConfig, key: string): boolean; + +// @public +export function getNumber(remoteConfig: RemoteConfig, key: string): number; + +// @public (undocumented) +export function getRemoteConfig(app?: FirebaseApp): RemoteConfig; + +// @public +export function getString(remoteConfig: RemoteConfig, key: string): string; + +// @public +export function getValue(remoteConfig: RemoteConfig, key: string): Value; + +// @public +export type LogLevel = 'debug' | 'error' | 'silent'; + +// @public +export interface RemoteConfig { + defaultConfig: { + [key: string]: string | number | boolean; + }; + fetchTimeMillis: number; + lastFetchStatus: FetchStatus; + settings: Settings; +} + +// @public +export function setLogLevel(remoteConfig: RemoteConfig, logLevel: LogLevel): void; + +// @public +export interface Settings { + fetchTimeoutMillis: number; + minimumFetchIntervalMillis: number; +} + +// @public +export interface Value { + asBoolean(): boolean; + asNumber(): number; + asString(): string; + getSource(): ValueSource; +} + +// @public +export type ValueSource = 'static' | 'default' | 'remote'; + + +``` diff --git a/common/api-review/storage.api.md b/common/api-review/storage.api.md new file mode 100644 index 00000000000..2847b663aac --- /dev/null +++ b/common/api-review/storage.api.md @@ -0,0 +1,261 @@ +## API Report File for "@firebase/storage" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types'; +import { CompleteFn } from '@firebase/util'; +import { FirebaseApp } from '@firebase/app'; +import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; +import { FirebaseError } from '@firebase/util'; +import { _FirebaseService } from '@firebase/app'; +import { NextFn } from '@firebase/util'; +import { Provider } from '@firebase/component'; +import { Subscribe } from '@firebase/util'; +import { Unsubscribe } from '@firebase/util'; + +// @public +export function deleteObject(ref: StorageReference): Promise; + +// @internal (undocumented) +export class _FbsBlob { + constructor(data: Blob | Uint8Array | ArrayBuffer, elideCopy?: boolean); + // (undocumented) + static getBlob(...args: Array): _FbsBlob | null; + // (undocumented) + size(): number; + // (undocumented) + slice(startByte: number, endByte: number): _FbsBlob | null; + // (undocumented) + type(): string; + // (undocumented) + uploadData(): Blob | Uint8Array; +} + +// @public +export interface FirebaseStorageError extends FirebaseError { + serverResponse: string | null; +} + +// @public +export interface FullMetadata extends UploadMetadata { + bucket: string; + downloadTokens: string[] | undefined; + fullPath: string; + generation: string; + metageneration: string; + name: string; + ref?: StorageReference | undefined; + size: number; + timeCreated: string; + updated: string; +} + +// @internal (undocumented) +export function _getChild(ref: StorageReference, childPath: string): _Reference; + +// @public +export function getDownloadURL(ref: StorageReference): Promise; + +// @public +export function getMetadata(ref: StorageReference): Promise; + +// @public +export function getStorage(app?: FirebaseApp, bucketUrl?: string): StorageService; + +// @public +export function list(ref: StorageReference, options?: ListOptions): Promise; + +// @public +export function listAll(ref: StorageReference): Promise; + +// @public +export interface ListOptions { + maxResults?: number | null; + pageToken?: string | null; +} + +// @public +export interface ListResult { + items: StorageReference[]; + nextPageToken?: string; + prefixes: StorageReference[]; +} + +// @internal +export class _Location { + constructor(bucket: string, path: string); + // (undocumented) + readonly bucket: string; + // (undocumented) + bucketOnlyServerUrl(): string; + // (undocumented) + fullServerUrl(): string; + // (undocumented) + get isRoot(): boolean; + // (undocumented) + static makeFromBucketSpec(bucketString: string, host: string): _Location; + // (undocumented) + static makeFromUrl(url: string, host: string): _Location; + // (undocumented) + get path(): string; + } + +// @public +export function ref(storage: StorageService, url?: string): StorageReference; + +// @public +export function ref(storageOrRef: StorageService | StorageReference, path?: string): StorageReference; + +// @internal +export class _Reference { + // Warning: (ae-forgotten-export) The symbol "StorageService" needs to be exported by the entry point index.d.ts + constructor(_service: StorageService_2, location: string | _Location); + get bucket(): string; + get fullPath(): string; + // (undocumented) + _location: _Location; + get name(): string; + // (undocumented) + protected _newRef(service: StorageService_2, location: _Location): _Reference; + get parent(): _Reference | null; + get root(): _Reference; + get storage(): StorageService_2; + _throwIfRoot(name: string): void; + // @override + toString(): string; +} + +// @public +export interface SettableMetadata { + cacheControl?: string | undefined; + contentDisposition?: string | undefined; + contentEncoding?: string | undefined; + contentLanguage?: string | undefined; + contentType?: string | undefined; + customMetadata?: { + [key: string]: string; + } | undefined; +} + +// @public +export interface StorageObserver { + // (undocumented) + complete?: CompleteFn | null; + // (undocumented) + error?: (error: FirebaseStorageError) => void | null; + // (undocumented) + next?: NextFn | null; +} + +// @public +export interface StorageReference { + bucket: string; + fullPath: string; + name: string; + parent: StorageReference | null; + root: StorageReference; + storage: StorageService; + toString(): string; +} + +// @public +export interface StorageService extends _FirebaseService { + readonly app: FirebaseApp; + maxOperationRetryTime: number; + maxUploadRetryTime: number; +} + +// @public +export type StringFormat = string; + +// @public +export const StringFormat: { + RAW: string; + BASE64: string; + BASE64URL: string; + DATA_URL: string; +}; + +// @public +export type TaskEvent = 'state_changed'; + +// @public +export type TaskState = 'running' | 'paused' | 'success' | 'canceled' | 'error'; + +// @public +export function updateMetadata(ref: StorageReference, metadata: SettableMetadata): Promise; + +// @public +export function uploadBytes(ref: StorageReference, data: Blob | Uint8Array | ArrayBuffer, metadata?: UploadMetadata): Promise; + +// @public +export function uploadBytesResumable(ref: StorageReference, data: Blob | Uint8Array | ArrayBuffer, metadata?: UploadMetadata): UploadTask; + +// @public +export interface UploadMetadata extends SettableMetadata { + md5Hash?: string | undefined; +} + +// @public +export interface UploadResult { + readonly metadata: FullMetadata; + readonly ref: StorageReference; +} + +// @public +export function uploadString(ref: StorageReference, value: string, format?: string, metadata?: UploadMetadata): Promise; + +// @public +export interface UploadTask { + cancel(): boolean; + catch(onRejected: (error: FirebaseStorageError) => unknown): Promise; + on(event: TaskEvent, nextOrObserver?: StorageObserver | null | ((snapshot: UploadTaskSnapshot) => unknown), error?: ((a: FirebaseStorageError) => unknown) | null, complete?: Unsubscribe | null): Unsubscribe | Subscribe; + pause(): boolean; + resume(): boolean; + snapshot: UploadTaskSnapshot; + then(onFulfilled?: ((snapshot: UploadTaskSnapshot) => unknown) | null, onRejected?: ((error: FirebaseStorageError) => unknown) | null): Promise; +} + +// @internal +export class _UploadTask { + constructor(ref: _Reference, blob: _FbsBlob, metadata?: Metadata | null); + _blob: _FbsBlob; + cancel(): boolean; + catch(onRejected: (p1: FirebaseStorageError_2) => T | Promise): Promise; + // Warning: (ae-forgotten-export) The symbol "Metadata" needs to be exported by the entry point index.d.ts + _metadata: Metadata | null; + // Warning: (ae-forgotten-export) The symbol "TaskEvent" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "StorageObserver" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "ErrorFn" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "CompleteFn" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "Unsubscribe" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "Subscribe" needs to be exported by the entry point index.d.ts + on(type: TaskEvent_2, nextOrObserver?: StorageObserver_2 | ((a: UploadTaskSnapshot_2) => unknown), error?: ErrorFn, completed?: CompleteFn_2): Unsubscribe_2 | Subscribe_2; + pause(): boolean; + resume(): boolean; + // Warning: (ae-forgotten-export) The symbol "UploadTaskSnapshot" needs to be exported by the entry point index.d.ts + get snapshot(): UploadTaskSnapshot_2; + // Warning: (ae-forgotten-export) The symbol "InternalTaskState" needs to be exported by the entry point index.d.ts + _state: InternalTaskState; + // Warning: (ae-forgotten-export) The symbol "FirebaseStorageError" needs to be exported by the entry point index.d.ts + then(onFulfilled?: ((value: UploadTaskSnapshot_2) => U | Promise) | null, onRejected?: ((error: FirebaseStorageError_2) => U | Promise) | null): Promise; + _transferred: number; + } + +// @public +export interface UploadTaskSnapshot { + bytesTransferred: number; + metadata: FullMetadata; + ref: StorageReference; + state: TaskState; + task: UploadTask; + totalBytes: number; +} + +// @public +export function useStorageEmulator(storage: StorageService, host: string, port: number): void; + + +``` diff --git a/config/.eslintrc.js b/config/.eslintrc.js index a7c60820a24..04de0f9d10c 100644 --- a/config/.eslintrc.js +++ b/config/.eslintrc.js @@ -24,7 +24,12 @@ module.exports = { 'node': true }, 'parser': '@typescript-eslint/parser', - 'plugins': ['@typescript-eslint', '@typescript-eslint/tslint', 'import'], + 'plugins': [ + '@typescript-eslint', + '@typescript-eslint/tslint', + 'import', + 'unused-imports' + ], 'parserOptions': { 'ecmaVersion': 2015, 'sourceType': 'module' @@ -72,6 +77,7 @@ module.exports = { } ], 'radix': 'error', + 'unused-imports/no-unused-imports-ts': 'error', 'default-case': 'error', 'eqeqeq': [ 'error', @@ -184,7 +190,7 @@ module.exports = { 'assertionStyle': 'as' } ], - '@typescript-eslint/no-explicit-any': 'error', + '@typescript-eslint/no-explicit-any': ['error', { 'ignoreRestArgs': true }], '@typescript-eslint/no-namespace': [ 'error', { diff --git a/config/functions/package.json b/config/functions/package.json index af8ce89993e..b9a25856576 100644 --- a/config/functions/package.json +++ b/config/functions/package.json @@ -3,8 +3,8 @@ "description": "Cloud Functions for Firebase", "dependencies": { "cors": "2.8.5", - "firebase-admin": "9.2.0", - "firebase-functions": "3.11.0" + "firebase-admin": "9.9.0", + "firebase-functions": "3.14.1" }, "private": true, "engines": { diff --git a/config/karma.base.js b/config/karma.base.js index 40f56fc3f17..83a1031c010 100644 --- a/config/karma.base.js +++ b/config/karma.base.js @@ -20,33 +20,6 @@ const path = require('path'); const webpackTestConfig = require('./webpack.test'); const { argv } = require('yargs'); -/** - * Custom SauceLabs Launchers - */ -const sauceLabsBrowsers = { - desktop_Safari: { - base: 'SauceLabs', - browserName: 'safari', - platform: 'OS X 10.11', - version: '9.0' - }, - iOS_Safari: { - appiumVersion: '1.6.5', - base: 'SauceLabs', - browserName: 'Safari', - deviceName: 'iPhone Simulator', - deviceOrientation: 'portrait', - platformName: 'iOS', - platformVersion: '9.0' - }, - IE_11: { - base: 'SauceLabs', - browserName: 'internet explorer', - platform: 'Windows 8.1', - version: '11' - } -}; - const config = { // disable watcher autoWatch: false, @@ -67,7 +40,7 @@ const config = { // test results reporter to use // possible values: 'dots', 'progress' // available reporters: https://npmjs.org/browse/keyword/karma-reporter - reporters: ['mocha', 'coverage-istanbul' /*, 'saucelabs' */], + reporters: ['mocha', 'coverage-istanbul'], // web server port port: 8089, @@ -84,8 +57,6 @@ const config = { // changes autoWatch: false, - customLaunchers: sauceLabsBrowsers, - // start these browsers // available browser launchers: // https://npmjs.org/browse/keyword/karma-launcher @@ -95,13 +66,6 @@ const config = { webpackMiddleware: { quiet: true, stats: { colors: true } }, - sauceLabs: { - tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER, - username: process.env.SAUCE_USERNAME, - accessKey: process.env.SAUCE_ACCESS_KEY, - startConnect: false - }, - singleRun: false, client: { @@ -120,10 +84,6 @@ const config = { } }; -// In CI environment, use saucelabs to test -if (false /* process.env.TRAVIS */) { - config.browsers = [...config.browsers, ...Object.keys(sauceLabsBrowsers)]; -} config.mochaReporter = { showDiff: true }; diff --git a/config/webpack.test.js b/config/webpack.test.js index 5da3c423b29..3612cc3db21 100644 --- a/config/webpack.test.js +++ b/config/webpack.test.js @@ -19,10 +19,11 @@ const path = require('path'); const webpack = require('webpack'); /** - * A regular expression used to replace Firestore's platform specific modules, - * which are located under 'packages/firestore/src/platform/'. + * A regular expression used to replace Firestore's and Storage's platform- + * specific modules, which are located under + * 'packages/(component)/src/platform/'. */ -const FIRESTORE_PLATFORM_RE = /^(.*)\/platform\/([^.\/]*)(\.ts)?$/; +const PLATFORM_RE = /^(.*)\/platform\/([^.\/]*)(\.ts)?$/; module.exports = { mode: 'development', @@ -74,25 +75,39 @@ module.exports = { ] } } + }, + /** + * Transform firebase packages to cjs, so they can be stubbed in tests + */ + { + test: /\.js$/, + include: function (modulePath) { + const match = /node_modules\/@firebase.*/.test(modulePath); + return match; + }, + use: { + loader: 'babel-loader', + options: { + plugins: ['@babel/plugin-transform-modules-commonjs'] + } + } } ] }, resolve: { modules: ['node_modules', path.resolve(__dirname, '../../node_modules')], - mainFields: ['browser', 'main', 'module'], - extensions: ['.js', '.ts'] + mainFields: ['browser', 'module', 'main'], + extensions: ['.js', '.ts'], + symlinks: false }, plugins: [ - new webpack.NormalModuleReplacementPlugin( - FIRESTORE_PLATFORM_RE, - resource => { - const targetPlatform = process.env.TEST_PLATFORM || 'browser'; - resource.request = resource.request.replace( - FIRESTORE_PLATFORM_RE, - `$1/platform/${targetPlatform}/$2.ts` - ); - } - ), + new webpack.NormalModuleReplacementPlugin(PLATFORM_RE, resource => { + const targetPlatform = process.env.TEST_PLATFORM || 'browser'; + resource.request = resource.request.replace( + PLATFORM_RE, + `$1/platform/${targetPlatform}/$2.ts` + ); + }), new webpack.EnvironmentPlugin([ 'RTDB_EMULATOR_PORT', 'RTDB_EMULATOR_NAMESPACE' diff --git a/docs-exp/app-types.firebaseapp.automaticdatacollectionenabled.md b/docs-exp/app-types.firebaseapp.automaticdatacollectionenabled.md deleted file mode 100644 index 6cca94e7bbc..00000000000 --- a/docs-exp/app-types.firebaseapp.automaticdatacollectionenabled.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/app-types](./app-types.md) > [FirebaseApp](./app-types.firebaseapp.md) > [automaticDataCollectionEnabled](./app-types.firebaseapp.automaticdatacollectionenabled.md) - -## FirebaseApp.automaticDataCollectionEnabled property - -The settable config flag for GDPR opt-in/opt-out - -Signature: - -```typescript -automaticDataCollectionEnabled: boolean; -``` diff --git a/docs-exp/app-types.firebaseapp.md b/docs-exp/app-types.firebaseapp.md deleted file mode 100644 index ad464fd9f01..00000000000 --- a/docs-exp/app-types.firebaseapp.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/app-types](./app-types.md) > [FirebaseApp](./app-types.firebaseapp.md) - -## FirebaseApp interface - -A FirebaseApp holds the initialization information for a collection of services. - -Do not call this constructor directly. Instead, use [initializeApp()](./app.initializeapp.md) to create an app. - -Signature: - -```typescript -export interface FirebaseApp -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [automaticDataCollectionEnabled](./app-types.firebaseapp.automaticdatacollectionenabled.md) | boolean | The settable config flag for GDPR opt-in/opt-out | -| [name](./app-types.firebaseapp.name.md) | string | The (read-only) name for this app.The default app's name is "[DEFAULT]". | -| [options](./app-types.firebaseapp.options.md) | [FirebaseOptions](./app-types.firebaseoptions.md) | The (read-only) configuration options for this app. These are the original parameters given in [initializeApp()](./app.initializeapp.md). | - diff --git a/docs-exp/app-types.firebaseapp.name.md b/docs-exp/app-types.firebaseapp.name.md deleted file mode 100644 index d2b830453a2..00000000000 --- a/docs-exp/app-types.firebaseapp.name.md +++ /dev/null @@ -1,36 +0,0 @@ - - -[Home](./index.md) > [@firebase/app-types](./app-types.md) > [FirebaseApp](./app-types.firebaseapp.md) > [name](./app-types.firebaseapp.name.md) - -## FirebaseApp.name property - -The (read-only) name for this app. - -The default app's name is `"[DEFAULT]"`. - -Signature: - -```typescript -readonly name: string; -``` - -## Example 1 - - -```javascript -// The default app's name is "[DEFAULT]" -const app = initializeApp(defaultAppConfig); -console.log(app.name); // "[DEFAULT]" - -``` - -## Example 2 - - -```javascript -// A named app's name is what you provide to initializeApp() -const otherApp = initializeApp(otherAppConfig, "other"); -console.log(otherApp.name); // "other" - -``` - diff --git a/docs-exp/app-types.firebaseapp.options.md b/docs-exp/app-types.firebaseapp.options.md deleted file mode 100644 index 56b8b4685c0..00000000000 --- a/docs-exp/app-types.firebaseapp.options.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/app-types](./app-types.md) > [FirebaseApp](./app-types.firebaseapp.md) > [options](./app-types.firebaseapp.options.md) - -## FirebaseApp.options property - -The (read-only) configuration options for this app. These are the original parameters given in [initializeApp()](./app.initializeapp.md). - -Signature: - -```typescript -readonly options: FirebaseOptions; -``` - -## Example - - -```javascript -const app = initializeApp(config); -console.log(app.options.databaseURL === config.databaseURL); // true - -``` - diff --git a/docs-exp/app-types.firebaseappconfig.automaticdatacollectionenabled.md b/docs-exp/app-types.firebaseappconfig.automaticdatacollectionenabled.md deleted file mode 100644 index 047db9a21fc..00000000000 --- a/docs-exp/app-types.firebaseappconfig.automaticdatacollectionenabled.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/app-types](./app-types.md) > [FirebaseAppConfig](./app-types.firebaseappconfig.md) > [automaticDataCollectionEnabled](./app-types.firebaseappconfig.automaticdatacollectionenabled.md) - -## FirebaseAppConfig.automaticDataCollectionEnabled property - -Signature: - -```typescript -automaticDataCollectionEnabled?: boolean; -``` diff --git a/docs-exp/app-types.firebaseappconfig.md b/docs-exp/app-types.firebaseappconfig.md deleted file mode 100644 index 2335ff6b387..00000000000 --- a/docs-exp/app-types.firebaseappconfig.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [@firebase/app-types](./app-types.md) > [FirebaseAppConfig](./app-types.firebaseappconfig.md) - -## FirebaseAppConfig interface - - -Signature: - -```typescript -export interface FirebaseAppConfig -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [automaticDataCollectionEnabled](./app-types.firebaseappconfig.automaticdatacollectionenabled.md) | boolean | | -| [name](./app-types.firebaseappconfig.name.md) | string | | - diff --git a/docs-exp/app-types.firebaseappconfig.name.md b/docs-exp/app-types.firebaseappconfig.name.md deleted file mode 100644 index be405e49548..00000000000 --- a/docs-exp/app-types.firebaseappconfig.name.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/app-types](./app-types.md) > [FirebaseAppConfig](./app-types.firebaseappconfig.md) > [name](./app-types.firebaseappconfig.name.md) - -## FirebaseAppConfig.name property - -Signature: - -```typescript -name?: string; -``` diff --git a/docs-exp/app-types.firebaseoptions.apikey.md b/docs-exp/app-types.firebaseoptions.apikey.md deleted file mode 100644 index e82438689f4..00000000000 --- a/docs-exp/app-types.firebaseoptions.apikey.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/app-types](./app-types.md) > [FirebaseOptions](./app-types.firebaseoptions.md) > [apiKey](./app-types.firebaseoptions.apikey.md) - -## FirebaseOptions.apiKey property - -Signature: - -```typescript -apiKey?: string; -``` diff --git a/docs-exp/app-types.firebaseoptions.appid.md b/docs-exp/app-types.firebaseoptions.appid.md deleted file mode 100644 index 2903b82adc8..00000000000 --- a/docs-exp/app-types.firebaseoptions.appid.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/app-types](./app-types.md) > [FirebaseOptions](./app-types.firebaseoptions.md) > [appId](./app-types.firebaseoptions.appid.md) - -## FirebaseOptions.appId property - -Signature: - -```typescript -appId?: string; -``` diff --git a/docs-exp/app-types.firebaseoptions.authdomain.md b/docs-exp/app-types.firebaseoptions.authdomain.md deleted file mode 100644 index 99dc66c8755..00000000000 --- a/docs-exp/app-types.firebaseoptions.authdomain.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/app-types](./app-types.md) > [FirebaseOptions](./app-types.firebaseoptions.md) > [authDomain](./app-types.firebaseoptions.authdomain.md) - -## FirebaseOptions.authDomain property - -Signature: - -```typescript -authDomain?: string; -``` diff --git a/docs-exp/app-types.firebaseoptions.databaseurl.md b/docs-exp/app-types.firebaseoptions.databaseurl.md deleted file mode 100644 index 025827f4d75..00000000000 --- a/docs-exp/app-types.firebaseoptions.databaseurl.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/app-types](./app-types.md) > [FirebaseOptions](./app-types.firebaseoptions.md) > [databaseURL](./app-types.firebaseoptions.databaseurl.md) - -## FirebaseOptions.databaseURL property - -Signature: - -```typescript -databaseURL?: string; -``` diff --git a/docs-exp/app-types.firebaseoptions.md b/docs-exp/app-types.firebaseoptions.md deleted file mode 100644 index 8d14523f967..00000000000 --- a/docs-exp/app-types.firebaseoptions.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/app-types](./app-types.md) > [FirebaseOptions](./app-types.firebaseoptions.md) - -## FirebaseOptions interface - - -Signature: - -```typescript -export interface FirebaseOptions -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [apiKey](./app-types.firebaseoptions.apikey.md) | string | | -| [appId](./app-types.firebaseoptions.appid.md) | string | | -| [authDomain](./app-types.firebaseoptions.authdomain.md) | string | | -| [databaseURL](./app-types.firebaseoptions.databaseurl.md) | string | | -| [measurementId](./app-types.firebaseoptions.measurementid.md) | string | | -| [messagingSenderId](./app-types.firebaseoptions.messagingsenderid.md) | string | | -| [projectId](./app-types.firebaseoptions.projectid.md) | string | | -| [storageBucket](./app-types.firebaseoptions.storagebucket.md) | string | | - diff --git a/docs-exp/app-types.firebaseoptions.measurementid.md b/docs-exp/app-types.firebaseoptions.measurementid.md deleted file mode 100644 index 84ffb13d480..00000000000 --- a/docs-exp/app-types.firebaseoptions.measurementid.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/app-types](./app-types.md) > [FirebaseOptions](./app-types.firebaseoptions.md) > [measurementId](./app-types.firebaseoptions.measurementid.md) - -## FirebaseOptions.measurementId property - -Signature: - -```typescript -measurementId?: string; -``` diff --git a/docs-exp/app-types.firebaseoptions.messagingsenderid.md b/docs-exp/app-types.firebaseoptions.messagingsenderid.md deleted file mode 100644 index c70c539e8b1..00000000000 --- a/docs-exp/app-types.firebaseoptions.messagingsenderid.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/app-types](./app-types.md) > [FirebaseOptions](./app-types.firebaseoptions.md) > [messagingSenderId](./app-types.firebaseoptions.messagingsenderid.md) - -## FirebaseOptions.messagingSenderId property - -Signature: - -```typescript -messagingSenderId?: string; -``` diff --git a/docs-exp/app-types.firebaseoptions.projectid.md b/docs-exp/app-types.firebaseoptions.projectid.md deleted file mode 100644 index fb4449ea500..00000000000 --- a/docs-exp/app-types.firebaseoptions.projectid.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/app-types](./app-types.md) > [FirebaseOptions](./app-types.firebaseoptions.md) > [projectId](./app-types.firebaseoptions.projectid.md) - -## FirebaseOptions.projectId property - -Signature: - -```typescript -projectId?: string; -``` diff --git a/docs-exp/app-types.firebaseoptions.storagebucket.md b/docs-exp/app-types.firebaseoptions.storagebucket.md deleted file mode 100644 index 771767cf357..00000000000 --- a/docs-exp/app-types.firebaseoptions.storagebucket.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/app-types](./app-types.md) > [FirebaseOptions](./app-types.firebaseoptions.md) > [storageBucket](./app-types.firebaseoptions.storagebucket.md) - -## FirebaseOptions.storageBucket property - -Signature: - -```typescript -storageBucket?: string; -``` diff --git a/docs-exp/app-types.md b/docs-exp/app-types.md deleted file mode 100644 index 5efb08e8551..00000000000 --- a/docs-exp/app-types.md +++ /dev/null @@ -1,16 +0,0 @@ - - -[Home](./index.md) > [@firebase/app-types](./app-types.md) - -## app-types package - -## Interfaces - -| Interface | Description | -| --- | --- | -| [FirebaseApp](./app-types.firebaseapp.md) | A FirebaseApp holds the initialization information for a collection of services.Do not call this constructor directly. Instead, use [initializeApp()](./app.initializeapp.md) to create an app. | -| [FirebaseAppConfig](./app-types.firebaseappconfig.md) | | -| [FirebaseOptions](./app-types.firebaseoptions.md) | | -| [PlatformLoggerService](./app-types.platformloggerservice.md) | | -| [VersionService](./app-types.versionservice.md) | | - diff --git a/docs-exp/app-types.platformloggerservice.getplatforminfostring.md b/docs-exp/app-types.platformloggerservice.getplatforminfostring.md deleted file mode 100644 index 6c8fb6444b4..00000000000 --- a/docs-exp/app-types.platformloggerservice.getplatforminfostring.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/app-types](./app-types.md) > [PlatformLoggerService](./app-types.platformloggerservice.md) > [getPlatformInfoString](./app-types.platformloggerservice.getplatforminfostring.md) - -## PlatformLoggerService.getPlatformInfoString() method - -Signature: - -```typescript -getPlatformInfoString(): string; -``` -Returns: - -string - diff --git a/docs-exp/app-types.platformloggerservice.md b/docs-exp/app-types.platformloggerservice.md deleted file mode 100644 index ace19895f69..00000000000 --- a/docs-exp/app-types.platformloggerservice.md +++ /dev/null @@ -1,19 +0,0 @@ - - -[Home](./index.md) > [@firebase/app-types](./app-types.md) > [PlatformLoggerService](./app-types.platformloggerservice.md) - -## PlatformLoggerService interface - - -Signature: - -```typescript -export interface PlatformLoggerService -``` - -## Methods - -| Method | Description | -| --- | --- | -| [getPlatformInfoString()](./app-types.platformloggerservice.getplatforminfostring.md) | | - diff --git a/docs-exp/app-types.versionservice.library.md b/docs-exp/app-types.versionservice.library.md deleted file mode 100644 index d93eb0bead3..00000000000 --- a/docs-exp/app-types.versionservice.library.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/app-types](./app-types.md) > [VersionService](./app-types.versionservice.md) > [library](./app-types.versionservice.library.md) - -## VersionService.library property - -Signature: - -```typescript -library: string; -``` diff --git a/docs-exp/app-types.versionservice.md b/docs-exp/app-types.versionservice.md deleted file mode 100644 index 337d8ace618..00000000000 --- a/docs-exp/app-types.versionservice.md +++ /dev/null @@ -1,19 +0,0 @@ - - -[Home](./index.md) > [@firebase/app-types](./app-types.md) > [VersionService](./app-types.versionservice.md) - -## VersionService interface - -Signature: - -```typescript -export interface VersionService -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [library](./app-types.versionservice.library.md) | string | | -| [version](./app-types.versionservice.version.md) | string | | - diff --git a/docs-exp/app-types.versionservice.version.md b/docs-exp/app-types.versionservice.version.md deleted file mode 100644 index e9700f9fabd..00000000000 --- a/docs-exp/app-types.versionservice.version.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/app-types](./app-types.md) > [VersionService](./app-types.versionservice.md) > [version](./app-types.versionservice.version.md) - -## VersionService.version property - -Signature: - -```typescript -version: string; -``` diff --git a/docs-exp/app.deleteapp.md b/docs-exp/app.deleteapp.md deleted file mode 100644 index 6f11a7cd6a9..00000000000 --- a/docs-exp/app.deleteapp.md +++ /dev/null @@ -1,38 +0,0 @@ - - -[Home](./index.md) > [@firebase/app](./app.md) > [deleteApp](./app.deleteapp.md) - -## deleteApp() function - -Renders this app unusable and frees the resources of all associated services. - -Signature: - -```typescript -export declare function deleteApp(app: FirebaseApp): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| app | [FirebaseApp](./app-types.firebaseapp.md) | | - -Returns: - -Promise<void> - -## Example - - -```javascript -deleteApp(app) - .then(function() { - console.log("App deleted successfully"); - }) - .catch(function(error) { - console.log("Error deleting app:", error); - }); - -``` - diff --git a/docs-exp/app.getapp.md b/docs-exp/app.getapp.md deleted file mode 100644 index 1edc929c09f..00000000000 --- a/docs-exp/app.getapp.md +++ /dev/null @@ -1,48 +0,0 @@ - - -[Home](./index.md) > [@firebase/app](./app.md) > [getApp](./app.getapp.md) - -## getApp() function - -Retrieves a FirebaseApp instance. - -When called with no arguments, the default app is returned. When an app name is provided, the app corresponding to that name is returned. - -An exception is thrown if the app being retrieved has not yet been initialized. - -Signature: - -```typescript -export declare function getApp(name?: string): FirebaseApp; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| name | string | Optional name of the app to return. If no name is provided, the default is "[DEFAULT]". | - -Returns: - -[FirebaseApp](./app-types.firebaseapp.md) - -The app corresponding to the provided app name. If no app name is provided, the default app is returned. - -## Example 1 - - -```javascript -// Return the default app -const app = getApp(); - -``` - -## Example 2 - - -```javascript -// Return a named app -const otherApp = getApp("otherApp"); - -``` - diff --git a/docs-exp/app.getapps.md b/docs-exp/app.getapps.md deleted file mode 100644 index 37efe6c254b..00000000000 --- a/docs-exp/app.getapps.md +++ /dev/null @@ -1,17 +0,0 @@ - - -[Home](./index.md) > [@firebase/app](./app.md) > [getApps](./app.getapps.md) - -## getApps() function - -A (read-only) array of all initialized apps. - -Signature: - -```typescript -export declare function getApps(): FirebaseApp[]; -``` -Returns: - -[FirebaseApp](./app-types.firebaseapp.md)\[\] - diff --git a/docs-exp/app.initializeapp.md b/docs-exp/app.initializeapp.md deleted file mode 100644 index bc0019a0859..00000000000 --- a/docs-exp/app.initializeapp.md +++ /dev/null @@ -1,60 +0,0 @@ - - -[Home](./index.md) > [@firebase/app](./app.md) > [initializeApp](./app.initializeapp.md) - -## initializeApp() function - -Creates and initializes a FirebaseApp instance. - -See [Add Firebase to your app](https://firebase.google.com/docs/web/setup#add_firebase_to_your_app) and [Initialize multiple projects](https://firebase.google.com/docs/web/setup#multiple-projects) for detailed documentation. - -Signature: - -```typescript -export declare function initializeApp(options: FirebaseOptions, name?: string): FirebaseApp; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| options | [FirebaseOptions](./app-types.firebaseoptions.md) | Options to configure the app's services. | -| name | string | Optional name of the app to initialize. If no name is provided, the default is "[DEFAULT]". | - -Returns: - -[FirebaseApp](./app-types.firebaseapp.md) - -The initialized app. - -## Example 1 - - -```javascript - -// Initialize default app -// Retrieve your own options values by adding a web app on -// https://console.firebase.google.com -initializeApp({ - apiKey: "AIza....", // Auth / General Use - authDomain: "YOUR_APP.firebaseapp.com", // Auth with popup/redirect - databaseURL: "https://YOUR_APP.firebaseio.com", // Realtime Database - storageBucket: "YOUR_APP.appspot.com", // Storage - messagingSenderId: "123456789" // Cloud Messaging -}); - -``` - -## Example 2 - - -```javascript - -// Initialize another app -const otherApp = initializeApp({ - databaseURL: "https://.firebaseio.com", - storageBucket: ".appspot.com" -}, "otherApp"); - -``` - diff --git a/docs-exp/app.initializeapp_1.md b/docs-exp/app.initializeapp_1.md deleted file mode 100644 index 68744e8c9a6..00000000000 --- a/docs-exp/app.initializeapp_1.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [@firebase/app](./app.md) > [initializeApp](./app.initializeapp_1.md) - -## initializeApp() function - -Creates and initializes a FirebaseApp instance. - -Signature: - -```typescript -export declare function initializeApp(options: FirebaseOptions, config?: FirebaseAppConfig): FirebaseApp; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| options | [FirebaseOptions](./app-types.firebaseoptions.md) | Options to configure the app's services. | -| config | [FirebaseAppConfig](./app-types.firebaseappconfig.md) | FirebaseApp Configuration | - -Returns: - -[FirebaseApp](./app-types.firebaseapp.md) - diff --git a/docs-exp/app.md b/docs-exp/app.md deleted file mode 100644 index d9ec021d77c..00000000000 --- a/docs-exp/app.md +++ /dev/null @@ -1,31 +0,0 @@ - - -[Home](./index.md) > [@firebase/app](./app.md) - -## app package - -Firebase App - -## Remarks - -This package coordinates the communication between the different Firebase components - -## Functions - -| Function | Description | -| --- | --- | -| [deleteApp(app)](./app.deleteapp.md) | Renders this app unusable and frees the resources of all associated services. | -| [getApp(name)](./app.getapp.md) | Retrieves a FirebaseApp instance.When called with no arguments, the default app is returned. When an app name is provided, the app corresponding to that name is returned.An exception is thrown if the app being retrieved has not yet been initialized. | -| [getApps()](./app.getapps.md) | A (read-only) array of all initialized apps. | -| [initializeApp(options, name)](./app.initializeapp.md) | Creates and initializes a FirebaseApp instance.See [Add Firebase to your app](https://firebase.google.com/docs/web/setup#add_firebase_to_your_app) and [Initialize multiple projects](https://firebase.google.com/docs/web/setup#multiple-projects) for detailed documentation. | -| [initializeApp(options, config)](./app.initializeapp_1.md) | Creates and initializes a FirebaseApp instance. | -| [onLog(logCallback, options)](./app.onlog.md) | Sets log handler for all Firebase SDKs. | -| [registerVersion(libraryKeyOrName, version, variant)](./app.registerversion.md) | Registers a library's name and version for platform logging purposes. | -| [setLogLevel(logLevel)](./app.setloglevel.md) | Sets log level for all Firebase SDKs.All of the log types above the current log level are captured (i.e. if you set the log level to info, errors are logged, but debug and verbose logs are not). | - -## Variables - -| Variable | Description | -| --- | --- | -| [SDK\_VERSION](./app.sdk_version.md) | The current SDK version. | - diff --git a/docs-exp/app.onlog.md b/docs-exp/app.onlog.md deleted file mode 100644 index 845f3985003..00000000000 --- a/docs-exp/app.onlog.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [@firebase/app](./app.md) > [onLog](./app.onlog.md) - -## onLog() function - -Sets log handler for all Firebase SDKs. - -Signature: - -```typescript -export declare function onLog(logCallback: LogCallback | null, options?: LogOptions): void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| logCallback | LogCallback \| null | An optional custom log handler that executes user code whenever the Firebase SDK makes a logging call. | -| options | LogOptions | | - -Returns: - -void - diff --git a/docs-exp/app.registerversion.md b/docs-exp/app.registerversion.md deleted file mode 100644 index 7c339dd4b8e..00000000000 --- a/docs-exp/app.registerversion.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/app](./app.md) > [registerVersion](./app.registerversion.md) - -## registerVersion() function - -Registers a library's name and version for platform logging purposes. - -Signature: - -```typescript -export declare function registerVersion(libraryKeyOrName: string, version: string, variant?: string): void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| libraryKeyOrName | string | | -| version | string | Current version of that library. | -| variant | string | Bundle variant, e.g., node, rn, etc. | - -Returns: - -void - diff --git a/docs-exp/app.sdk_version.md b/docs-exp/app.sdk_version.md deleted file mode 100644 index 7d186fc2041..00000000000 --- a/docs-exp/app.sdk_version.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/app](./app.md) > [SDK\_VERSION](./app.sdk_version.md) - -## SDK\_VERSION variable - -The current SDK version. - -Signature: - -```typescript -SDK_VERSION: string -``` diff --git a/docs-exp/app.setloglevel.md b/docs-exp/app.setloglevel.md deleted file mode 100644 index 7043d6e2503..00000000000 --- a/docs-exp/app.setloglevel.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/app](./app.md) > [setLogLevel](./app.setloglevel.md) - -## setLogLevel() function - -Sets log level for all Firebase SDKs. - -All of the log types above the current log level are captured (i.e. if you set the log level to `info`, errors are logged, but `debug` and `verbose` logs are not). - -Signature: - -```typescript -export declare function setLogLevel(logLevel: LogLevelString): void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| logLevel | LogLevelString | | - -Returns: - -void - diff --git a/docs-exp/auth-types.actioncodeinfo.data.md b/docs-exp/auth-types.actioncodeinfo.data.md deleted file mode 100644 index fcb46b989bb..00000000000 --- a/docs-exp/auth-types.actioncodeinfo.data.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ActionCodeInfo](./auth-types.actioncodeinfo.md) > [data](./auth-types.actioncodeinfo.data.md) - -## ActionCodeInfo.data property - -Signature: - -```typescript -data: { - email?: string | null; - multiFactorInfo?: MultiFactorInfo | null; - previousEmail?: string | null; - }; -``` diff --git a/docs-exp/auth-types.actioncodeinfo.md b/docs-exp/auth-types.actioncodeinfo.md deleted file mode 100644 index 2c94067f213..00000000000 --- a/docs-exp/auth-types.actioncodeinfo.md +++ /dev/null @@ -1,21 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ActionCodeInfo](./auth-types.actioncodeinfo.md) - -## ActionCodeInfo interface - -https://firebase.google.com/docs/reference/js/firebase.auth.ActionCodeInfo - -Signature: - -```typescript -export interface ActionCodeInfo -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [data](./auth-types.actioncodeinfo.data.md) | { email?: string \| null; multiFactorInfo?: [MultiFactorInfo](./auth-types.multifactorinfo.md) \| null; previousEmail?: string \| null; } | | -| [operation](./auth-types.actioncodeinfo.operation.md) | [Operation](./auth-types.operation.md) | | - diff --git a/docs-exp/auth-types.actioncodeinfo.operation.md b/docs-exp/auth-types.actioncodeinfo.operation.md deleted file mode 100644 index 107a974d87a..00000000000 --- a/docs-exp/auth-types.actioncodeinfo.operation.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ActionCodeInfo](./auth-types.actioncodeinfo.md) > [operation](./auth-types.actioncodeinfo.operation.md) - -## ActionCodeInfo.operation property - -Signature: - -```typescript -operation: Operation; -``` diff --git a/docs-exp/auth-types.actioncodesettings.android.md b/docs-exp/auth-types.actioncodesettings.android.md deleted file mode 100644 index 3f2fb765245..00000000000 --- a/docs-exp/auth-types.actioncodesettings.android.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ActionCodeSettings](./auth-types.actioncodesettings.md) > [android](./auth-types.actioncodesettings.android.md) - -## ActionCodeSettings.android property - -Signature: - -```typescript -android?: { - installApp?: boolean; - minimumVersion?: string; - packageName: string; - }; -``` diff --git a/docs-exp/auth-types.actioncodesettings.dynamiclinkdomain.md b/docs-exp/auth-types.actioncodesettings.dynamiclinkdomain.md deleted file mode 100644 index b99eda2a742..00000000000 --- a/docs-exp/auth-types.actioncodesettings.dynamiclinkdomain.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ActionCodeSettings](./auth-types.actioncodesettings.md) > [dynamicLinkDomain](./auth-types.actioncodesettings.dynamiclinkdomain.md) - -## ActionCodeSettings.dynamicLinkDomain property - -Signature: - -```typescript -dynamicLinkDomain?: string; -``` diff --git a/docs-exp/auth-types.actioncodesettings.handlecodeinapp.md b/docs-exp/auth-types.actioncodesettings.handlecodeinapp.md deleted file mode 100644 index 44f86269269..00000000000 --- a/docs-exp/auth-types.actioncodesettings.handlecodeinapp.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ActionCodeSettings](./auth-types.actioncodesettings.md) > [handleCodeInApp](./auth-types.actioncodesettings.handlecodeinapp.md) - -## ActionCodeSettings.handleCodeInApp property - -Signature: - -```typescript -handleCodeInApp?: boolean; -``` diff --git a/docs-exp/auth-types.actioncodesettings.ios.md b/docs-exp/auth-types.actioncodesettings.ios.md deleted file mode 100644 index fd66f5e5485..00000000000 --- a/docs-exp/auth-types.actioncodesettings.ios.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ActionCodeSettings](./auth-types.actioncodesettings.md) > [iOS](./auth-types.actioncodesettings.ios.md) - -## ActionCodeSettings.iOS property - -Signature: - -```typescript -iOS?: { - bundleId: string; - }; -``` diff --git a/docs-exp/auth-types.actioncodesettings.md b/docs-exp/auth-types.actioncodesettings.md deleted file mode 100644 index 3a3c1455397..00000000000 --- a/docs-exp/auth-types.actioncodesettings.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ActionCodeSettings](./auth-types.actioncodesettings.md) - -## ActionCodeSettings interface - -https://firebase.google.com/docs/reference/js/firebase.auth\#actioncodesettings - -Signature: - -```typescript -export interface ActionCodeSettings -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [android](./auth-types.actioncodesettings.android.md) | { installApp?: boolean; minimumVersion?: string; packageName: string; } | | -| [dynamicLinkDomain](./auth-types.actioncodesettings.dynamiclinkdomain.md) | string | | -| [handleCodeInApp](./auth-types.actioncodesettings.handlecodeinapp.md) | boolean | | -| [iOS](./auth-types.actioncodesettings.ios.md) | { bundleId: string; } | | -| [url](./auth-types.actioncodesettings.url.md) | string | | - diff --git a/docs-exp/auth-types.actioncodesettings.url.md b/docs-exp/auth-types.actioncodesettings.url.md deleted file mode 100644 index ab2ef041a8d..00000000000 --- a/docs-exp/auth-types.actioncodesettings.url.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ActionCodeSettings](./auth-types.actioncodesettings.md) > [url](./auth-types.actioncodesettings.url.md) - -## ActionCodeSettings.url property - -Signature: - -```typescript -url: string; -``` diff --git a/docs-exp/auth-types.actioncodeurl.apikey.md b/docs-exp/auth-types.actioncodeurl.apikey.md deleted file mode 100644 index 6984aa7faef..00000000000 --- a/docs-exp/auth-types.actioncodeurl.apikey.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ActionCodeURL](./auth-types.actioncodeurl.md) > [apiKey](./auth-types.actioncodeurl.apikey.md) - -## ActionCodeURL.apiKey property - -The API key of the email action link. - -Signature: - -```typescript -readonly apiKey: string; -``` diff --git a/docs-exp/auth-types.actioncodeurl.code.md b/docs-exp/auth-types.actioncodeurl.code.md deleted file mode 100644 index df7dcf0d7e7..00000000000 --- a/docs-exp/auth-types.actioncodeurl.code.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ActionCodeURL](./auth-types.actioncodeurl.md) > [code](./auth-types.actioncodeurl.code.md) - -## ActionCodeURL.code property - -The action code of the email action link. - -Signature: - -```typescript -readonly code: string; -``` diff --git a/docs-exp/auth-types.actioncodeurl.continueurl.md b/docs-exp/auth-types.actioncodeurl.continueurl.md deleted file mode 100644 index d25785dbad3..00000000000 --- a/docs-exp/auth-types.actioncodeurl.continueurl.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ActionCodeURL](./auth-types.actioncodeurl.md) > [continueUrl](./auth-types.actioncodeurl.continueurl.md) - -## ActionCodeURL.continueUrl property - -The continue URL of the email action link. Null if not provided. - -Signature: - -```typescript -readonly continueUrl: string | null; -``` diff --git a/docs-exp/auth-types.actioncodeurl.languagecode.md b/docs-exp/auth-types.actioncodeurl.languagecode.md deleted file mode 100644 index 6ad17299693..00000000000 --- a/docs-exp/auth-types.actioncodeurl.languagecode.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ActionCodeURL](./auth-types.actioncodeurl.md) > [languageCode](./auth-types.actioncodeurl.languagecode.md) - -## ActionCodeURL.languageCode property - -The language code of the email action link. Null if not provided. - -Signature: - -```typescript -readonly languageCode: string | null; -``` diff --git a/docs-exp/auth-types.actioncodeurl.md b/docs-exp/auth-types.actioncodeurl.md deleted file mode 100644 index f3de723cf1c..00000000000 --- a/docs-exp/auth-types.actioncodeurl.md +++ /dev/null @@ -1,31 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ActionCodeURL](./auth-types.actioncodeurl.md) - -## ActionCodeURL class - -A utility class to parse email action URLs such as password reset, email verification, email link sign in, etc. - -Signature: - -```typescript -export abstract class ActionCodeURL -``` - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [apiKey](./auth-types.actioncodeurl.apikey.md) | | string | The API key of the email action link. | -| [code](./auth-types.actioncodeurl.code.md) | | string | The action code of the email action link. | -| [continueUrl](./auth-types.actioncodeurl.continueurl.md) | | string \| null | The continue URL of the email action link. Null if not provided. | -| [languageCode](./auth-types.actioncodeurl.languagecode.md) | | string \| null | The language code of the email action link. Null if not provided. | -| [operation](./auth-types.actioncodeurl.operation.md) | | [Operation](./auth-types.operation.md) | The action performed by the email action link. It returns from one of the types from [ActionCodeInfo](./auth-types.actioncodeinfo.md) | -| [tenantId](./auth-types.actioncodeurl.tenantid.md) | | string \| null | The tenant ID of the email action link. Null if the email action is from the parent project. | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [parseLink(link)](./auth-types.actioncodeurl.parselink.md) | static | Parses the email action link string and returns an ActionCodeURL object if the link is valid, otherwise returns null. | - diff --git a/docs-exp/auth-types.actioncodeurl.operation.md b/docs-exp/auth-types.actioncodeurl.operation.md deleted file mode 100644 index 234fca8c658..00000000000 --- a/docs-exp/auth-types.actioncodeurl.operation.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ActionCodeURL](./auth-types.actioncodeurl.md) > [operation](./auth-types.actioncodeurl.operation.md) - -## ActionCodeURL.operation property - -The action performed by the email action link. It returns from one of the types from [ActionCodeInfo](./auth-types.actioncodeinfo.md) - -Signature: - -```typescript -readonly operation: Operation; -``` diff --git a/docs-exp/auth-types.actioncodeurl.parselink.md b/docs-exp/auth-types.actioncodeurl.parselink.md deleted file mode 100644 index 05d933b4ef5..00000000000 --- a/docs-exp/auth-types.actioncodeurl.parselink.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ActionCodeURL](./auth-types.actioncodeurl.md) > [parseLink](./auth-types.actioncodeurl.parselink.md) - -## ActionCodeURL.parseLink() method - -Parses the email action link string and returns an ActionCodeURL object if the link is valid, otherwise returns null. - -Signature: - -```typescript -static parseLink(link: string): ActionCodeURL | null; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| link | string | The email action link string. | - -Returns: - -[ActionCodeURL](./auth-types.actioncodeurl.md) \| null - -The ActionCodeURL object, or null if the link is invalid. - diff --git a/docs-exp/auth-types.actioncodeurl.tenantid.md b/docs-exp/auth-types.actioncodeurl.tenantid.md deleted file mode 100644 index 06002ff16a4..00000000000 --- a/docs-exp/auth-types.actioncodeurl.tenantid.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ActionCodeURL](./auth-types.actioncodeurl.md) > [tenantId](./auth-types.actioncodeurl.tenantid.md) - -## ActionCodeURL.tenantId property - -The tenant ID of the email action link. Null if the email action is from the parent project. - -Signature: - -```typescript -readonly tenantId: string | null; -``` diff --git a/docs-exp/auth-types.additionaluserinfo.isnewuser.md b/docs-exp/auth-types.additionaluserinfo.isnewuser.md deleted file mode 100644 index 7a35baf9a6b..00000000000 --- a/docs-exp/auth-types.additionaluserinfo.isnewuser.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [AdditionalUserInfo](./auth-types.additionaluserinfo.md) > [isNewUser](./auth-types.additionaluserinfo.isnewuser.md) - -## AdditionalUserInfo.isNewUser property - -Signature: - -```typescript -readonly isNewUser: boolean; -``` diff --git a/docs-exp/auth-types.additionaluserinfo.md b/docs-exp/auth-types.additionaluserinfo.md deleted file mode 100644 index dcda2f0fcc4..00000000000 --- a/docs-exp/auth-types.additionaluserinfo.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [AdditionalUserInfo](./auth-types.additionaluserinfo.md) - -## AdditionalUserInfo interface - -Additional user information. - -Signature: - -```typescript -export interface AdditionalUserInfo -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [isNewUser](./auth-types.additionaluserinfo.isnewuser.md) | boolean | | -| [profile](./auth-types.additionaluserinfo.profile.md) | [UserProfile](./auth-types.userprofile.md) \| null | | -| [providerId](./auth-types.additionaluserinfo.providerid.md) | [ProviderId](./auth-types.providerid.md) \| null | | -| [username](./auth-types.additionaluserinfo.username.md) | string \| null | | - diff --git a/docs-exp/auth-types.additionaluserinfo.profile.md b/docs-exp/auth-types.additionaluserinfo.profile.md deleted file mode 100644 index 367352cc9f3..00000000000 --- a/docs-exp/auth-types.additionaluserinfo.profile.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [AdditionalUserInfo](./auth-types.additionaluserinfo.md) > [profile](./auth-types.additionaluserinfo.profile.md) - -## AdditionalUserInfo.profile property - -Signature: - -```typescript -readonly profile: UserProfile | null; -``` diff --git a/docs-exp/auth-types.additionaluserinfo.providerid.md b/docs-exp/auth-types.additionaluserinfo.providerid.md deleted file mode 100644 index 457c6c9da9e..00000000000 --- a/docs-exp/auth-types.additionaluserinfo.providerid.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [AdditionalUserInfo](./auth-types.additionaluserinfo.md) > [providerId](./auth-types.additionaluserinfo.providerid.md) - -## AdditionalUserInfo.providerId property - -Signature: - -```typescript -readonly providerId: ProviderId | null; -``` diff --git a/docs-exp/auth-types.additionaluserinfo.username.md b/docs-exp/auth-types.additionaluserinfo.username.md deleted file mode 100644 index 0690abf0c09..00000000000 --- a/docs-exp/auth-types.additionaluserinfo.username.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [AdditionalUserInfo](./auth-types.additionaluserinfo.md) > [username](./auth-types.additionaluserinfo.username.md) - -## AdditionalUserInfo.username property - -Signature: - -```typescript -readonly username?: string | null; -``` diff --git a/docs-exp/auth-types.applicationverifier.md b/docs-exp/auth-types.applicationverifier.md deleted file mode 100644 index b62ba6a19c8..00000000000 --- a/docs-exp/auth-types.applicationverifier.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ApplicationVerifier](./auth-types.applicationverifier.md) - -## ApplicationVerifier interface - -https://firebase.google.com/docs/reference/js/firebase.auth.ApplicationVerifier - -Signature: - -```typescript -export interface ApplicationVerifier -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [type](./auth-types.applicationverifier.type.md) | string | | - -## Methods - -| Method | Description | -| --- | --- | -| [verify()](./auth-types.applicationverifier.verify.md) | | - diff --git a/docs-exp/auth-types.applicationverifier.type.md b/docs-exp/auth-types.applicationverifier.type.md deleted file mode 100644 index 7be9a80fb70..00000000000 --- a/docs-exp/auth-types.applicationverifier.type.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ApplicationVerifier](./auth-types.applicationverifier.md) > [type](./auth-types.applicationverifier.type.md) - -## ApplicationVerifier.type property - -Signature: - -```typescript -readonly type: string; -``` diff --git a/docs-exp/auth-types.applicationverifier.verify.md b/docs-exp/auth-types.applicationverifier.verify.md deleted file mode 100644 index 088e9c8cb4d..00000000000 --- a/docs-exp/auth-types.applicationverifier.verify.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ApplicationVerifier](./auth-types.applicationverifier.md) > [verify](./auth-types.applicationverifier.verify.md) - -## ApplicationVerifier.verify() method - -Signature: - -```typescript -verify(): Promise; -``` -Returns: - -Promise<string> - diff --git a/docs-exp/auth-types.auth.config.md b/docs-exp/auth-types.auth.config.md deleted file mode 100644 index 707a520af87..00000000000 --- a/docs-exp/auth-types.auth.config.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [Auth](./auth-types.auth.md) > [config](./auth-types.auth.config.md) - -## Auth.config property - -Signature: - -```typescript -readonly config: Config; -``` diff --git a/docs-exp/auth-types.auth.currentuser.md b/docs-exp/auth-types.auth.currentuser.md deleted file mode 100644 index f25aed8a581..00000000000 --- a/docs-exp/auth-types.auth.currentuser.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [Auth](./auth-types.auth.md) > [currentUser](./auth-types.auth.currentuser.md) - -## Auth.currentUser property - -Signature: - -```typescript -readonly currentUser: User | null; -``` diff --git a/docs-exp/auth-types.auth.languagecode.md b/docs-exp/auth-types.auth.languagecode.md deleted file mode 100644 index f7ccb01afda..00000000000 --- a/docs-exp/auth-types.auth.languagecode.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [Auth](./auth-types.auth.md) > [languageCode](./auth-types.auth.languagecode.md) - -## Auth.languageCode property - -Signature: - -```typescript -languageCode: string | null; -``` diff --git a/docs-exp/auth-types.auth.md b/docs-exp/auth-types.auth.md deleted file mode 100644 index 40732f59ae2..00000000000 --- a/docs-exp/auth-types.auth.md +++ /dev/null @@ -1,37 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [Auth](./auth-types.auth.md) - -## Auth interface - -https://firebase.google.com/docs/reference/js/firebase.auth.Auth - -Signature: - -```typescript -export interface Auth -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [config](./auth-types.auth.config.md) | [Config](./auth-types.config.md) | | -| [currentUser](./auth-types.auth.currentuser.md) | [User](./auth-types.user.md) \| null | | -| [languageCode](./auth-types.auth.languagecode.md) | string \| null | | -| [name](./auth-types.auth.name.md) | string | | -| [settings](./auth-types.auth.settings.md) | [AuthSettings](./auth-types.authsettings.md) | | -| [tenantId](./auth-types.auth.tenantid.md) | string \| null | | - -## Methods - -| Method | Description | -| --- | --- | -| [onAuthStateChanged(nextOrObserver, error, completed)](./auth-types.auth.onauthstatechanged.md) | | -| [onIdTokenChanged(nextOrObserver, error, completed)](./auth-types.auth.onidtokenchanged.md) | | -| [setPersistence(persistence)](./auth-types.auth.setpersistence.md) | | -| [signOut()](./auth-types.auth.signout.md) | | -| [updateCurrentUser(user)](./auth-types.auth.updatecurrentuser.md) | | -| [useDeviceLanguage()](./auth-types.auth.usedevicelanguage.md) | | -| [useEmulator(url)](./auth-types.auth.useemulator.md) | | - diff --git a/docs-exp/auth-types.auth.name.md b/docs-exp/auth-types.auth.name.md deleted file mode 100644 index 3efeb1ac5b2..00000000000 --- a/docs-exp/auth-types.auth.name.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [Auth](./auth-types.auth.md) > [name](./auth-types.auth.name.md) - -## Auth.name property - -Signature: - -```typescript -readonly name: string; -``` diff --git a/docs-exp/auth-types.auth.onauthstatechanged.md b/docs-exp/auth-types.auth.onauthstatechanged.md deleted file mode 100644 index a3b1905f277..00000000000 --- a/docs-exp/auth-types.auth.onauthstatechanged.md +++ /dev/null @@ -1,28 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [Auth](./auth-types.auth.md) > [onAuthStateChanged](./auth-types.auth.onauthstatechanged.md) - -## Auth.onAuthStateChanged() method - -Signature: - -```typescript -onAuthStateChanged( - nextOrObserver: NextOrObserver, - error?: ErrorFn, - completed?: CompleteFn - ): Unsubscribe; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| nextOrObserver | [NextOrObserver](./auth-types.nextorobserver.md)<[User](./auth-types.user.md)> | | -| error | ErrorFn | | -| completed | CompleteFn | | - -Returns: - -Unsubscribe - diff --git a/docs-exp/auth-types.auth.onidtokenchanged.md b/docs-exp/auth-types.auth.onidtokenchanged.md deleted file mode 100644 index 954413feb5f..00000000000 --- a/docs-exp/auth-types.auth.onidtokenchanged.md +++ /dev/null @@ -1,28 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [Auth](./auth-types.auth.md) > [onIdTokenChanged](./auth-types.auth.onidtokenchanged.md) - -## Auth.onIdTokenChanged() method - -Signature: - -```typescript -onIdTokenChanged( - nextOrObserver: NextOrObserver, - error?: ErrorFn, - completed?: CompleteFn - ): Unsubscribe; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| nextOrObserver | [NextOrObserver](./auth-types.nextorobserver.md)<[User](./auth-types.user.md)> | | -| error | ErrorFn | | -| completed | CompleteFn | | - -Returns: - -Unsubscribe - diff --git a/docs-exp/auth-types.auth.setpersistence.md b/docs-exp/auth-types.auth.setpersistence.md deleted file mode 100644 index c0e164d0b52..00000000000 --- a/docs-exp/auth-types.auth.setpersistence.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [Auth](./auth-types.auth.md) > [setPersistence](./auth-types.auth.setpersistence.md) - -## Auth.setPersistence() method - -Signature: - -```typescript -setPersistence(persistence: Persistence): void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| persistence | [Persistence](./auth-types.persistence.md) | | - -Returns: - -void - diff --git a/docs-exp/auth-types.auth.settings.md b/docs-exp/auth-types.auth.settings.md deleted file mode 100644 index 5ea4d5c65e1..00000000000 --- a/docs-exp/auth-types.auth.settings.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [Auth](./auth-types.auth.md) > [settings](./auth-types.auth.settings.md) - -## Auth.settings property - -Signature: - -```typescript -readonly settings: AuthSettings; -``` diff --git a/docs-exp/auth-types.auth.signout.md b/docs-exp/auth-types.auth.signout.md deleted file mode 100644 index e6a44b038bc..00000000000 --- a/docs-exp/auth-types.auth.signout.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [Auth](./auth-types.auth.md) > [signOut](./auth-types.auth.signout.md) - -## Auth.signOut() method - -Signature: - -```typescript -signOut(): Promise; -``` -Returns: - -Promise<void> - diff --git a/docs-exp/auth-types.auth.tenantid.md b/docs-exp/auth-types.auth.tenantid.md deleted file mode 100644 index 57cba12a545..00000000000 --- a/docs-exp/auth-types.auth.tenantid.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [Auth](./auth-types.auth.md) > [tenantId](./auth-types.auth.tenantid.md) - -## Auth.tenantId property - -Signature: - -```typescript -tenantId: string | null; -``` diff --git a/docs-exp/auth-types.auth.updatecurrentuser.md b/docs-exp/auth-types.auth.updatecurrentuser.md deleted file mode 100644 index 3bcd24c70e1..00000000000 --- a/docs-exp/auth-types.auth.updatecurrentuser.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [Auth](./auth-types.auth.md) > [updateCurrentUser](./auth-types.auth.updatecurrentuser.md) - -## Auth.updateCurrentUser() method - -Signature: - -```typescript -updateCurrentUser(user: User | null): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| user | [User](./auth-types.user.md) \| null | | - -Returns: - -Promise<void> - diff --git a/docs-exp/auth-types.auth.usedevicelanguage.md b/docs-exp/auth-types.auth.usedevicelanguage.md deleted file mode 100644 index 8a19378f071..00000000000 --- a/docs-exp/auth-types.auth.usedevicelanguage.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [Auth](./auth-types.auth.md) > [useDeviceLanguage](./auth-types.auth.usedevicelanguage.md) - -## Auth.useDeviceLanguage() method - -Signature: - -```typescript -useDeviceLanguage(): void; -``` -Returns: - -void - diff --git a/docs-exp/auth-types.auth.useemulator.md b/docs-exp/auth-types.auth.useemulator.md deleted file mode 100644 index 675456aa60b..00000000000 --- a/docs-exp/auth-types.auth.useemulator.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [Auth](./auth-types.auth.md) > [useEmulator](./auth-types.auth.useemulator.md) - -## Auth.useEmulator() method - -Signature: - -```typescript -useEmulator(url: string): void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| url | string | | - -Returns: - -void - diff --git a/docs-exp/auth-types.authcredential.fromjson.md b/docs-exp/auth-types.authcredential.fromjson.md deleted file mode 100644 index 47f8f53921d..00000000000 --- a/docs-exp/auth-types.authcredential.fromjson.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [AuthCredential](./auth-types.authcredential.md) > [fromJSON](./auth-types.authcredential.fromjson.md) - -## AuthCredential.fromJSON() method - -Signature: - -```typescript -static fromJSON(json: object | string): AuthCredential | null; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| json | object \| string | | - -Returns: - -[AuthCredential](./auth-types.authcredential.md) \| null - diff --git a/docs-exp/auth-types.authcredential.md b/docs-exp/auth-types.authcredential.md deleted file mode 100644 index 6796708cf90..00000000000 --- a/docs-exp/auth-types.authcredential.md +++ /dev/null @@ -1,28 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [AuthCredential](./auth-types.authcredential.md) - -## AuthCredential class - -https://firebase.google.com/docs/reference/js/firebase.auth.AuthCredential - -Signature: - -```typescript -export abstract class AuthCredential -``` - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [providerId](./auth-types.authcredential.providerid.md) | | string | | -| [signInMethod](./auth-types.authcredential.signinmethod.md) | | string | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [fromJSON(json)](./auth-types.authcredential.fromjson.md) | static | | -| [toJSON()](./auth-types.authcredential.tojson.md) | | | - diff --git a/docs-exp/auth-types.authcredential.providerid.md b/docs-exp/auth-types.authcredential.providerid.md deleted file mode 100644 index 260a7e8889b..00000000000 --- a/docs-exp/auth-types.authcredential.providerid.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [AuthCredential](./auth-types.authcredential.md) > [providerId](./auth-types.authcredential.providerid.md) - -## AuthCredential.providerId property - -Signature: - -```typescript -readonly providerId: string; -``` diff --git a/docs-exp/auth-types.authcredential.signinmethod.md b/docs-exp/auth-types.authcredential.signinmethod.md deleted file mode 100644 index 083a4421d8b..00000000000 --- a/docs-exp/auth-types.authcredential.signinmethod.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [AuthCredential](./auth-types.authcredential.md) > [signInMethod](./auth-types.authcredential.signinmethod.md) - -## AuthCredential.signInMethod property - -Signature: - -```typescript -readonly signInMethod: string; -``` diff --git a/docs-exp/auth-types.authcredential.tojson.md b/docs-exp/auth-types.authcredential.tojson.md deleted file mode 100644 index a60ba35bd59..00000000000 --- a/docs-exp/auth-types.authcredential.tojson.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [AuthCredential](./auth-types.authcredential.md) > [toJSON](./auth-types.authcredential.tojson.md) - -## AuthCredential.toJSON() method - -Signature: - -```typescript -toJSON(): object; -``` -Returns: - -object - diff --git a/docs-exp/auth-types.autherror.appname.md b/docs-exp/auth-types.autherror.appname.md deleted file mode 100644 index 5d214250458..00000000000 --- a/docs-exp/auth-types.autherror.appname.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [AuthError](./auth-types.autherror.md) > [appName](./auth-types.autherror.appname.md) - -## AuthError.appName property - -Signature: - -```typescript -readonly appName: string; -``` diff --git a/docs-exp/auth-types.autherror.email.md b/docs-exp/auth-types.autherror.email.md deleted file mode 100644 index 8e1fe38b659..00000000000 --- a/docs-exp/auth-types.autherror.email.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [AuthError](./auth-types.autherror.md) > [email](./auth-types.autherror.email.md) - -## AuthError.email property - -Signature: - -```typescript -readonly email?: string; -``` diff --git a/docs-exp/auth-types.autherror.md b/docs-exp/auth-types.autherror.md deleted file mode 100644 index 79b5615f737..00000000000 --- a/docs-exp/auth-types.autherror.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [AuthError](./auth-types.autherror.md) - -## AuthError interface - -https://firebase.google.com/docs/reference/js/firebase.auth.AuthError - -Signature: - -```typescript -export interface AuthError extends FirebaseError -``` -Extends: FirebaseError - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [appName](./auth-types.autherror.appname.md) | string | | -| [email](./auth-types.autherror.email.md) | string | | -| [phoneNumber](./auth-types.autherror.phonenumber.md) | string | | -| [tenantid](./auth-types.autherror.tenantid.md) | string | | - diff --git a/docs-exp/auth-types.autherror.phonenumber.md b/docs-exp/auth-types.autherror.phonenumber.md deleted file mode 100644 index ffce6d1fe59..00000000000 --- a/docs-exp/auth-types.autherror.phonenumber.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [AuthError](./auth-types.autherror.md) > [phoneNumber](./auth-types.autherror.phonenumber.md) - -## AuthError.phoneNumber property - -Signature: - -```typescript -readonly phoneNumber?: string; -``` diff --git a/docs-exp/auth-types.autherror.tenantid.md b/docs-exp/auth-types.autherror.tenantid.md deleted file mode 100644 index a3c396633d7..00000000000 --- a/docs-exp/auth-types.autherror.tenantid.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [AuthError](./auth-types.autherror.md) > [tenantid](./auth-types.autherror.tenantid.md) - -## AuthError.tenantid property - -Signature: - -```typescript -readonly tenantid?: string; -``` diff --git a/docs-exp/auth-types.authprovider.md b/docs-exp/auth-types.authprovider.md deleted file mode 100644 index c9a29ec6d6c..00000000000 --- a/docs-exp/auth-types.authprovider.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [AuthProvider](./auth-types.authprovider.md) - -## AuthProvider interface - -A provider for generating credentials - -https://firebase.google.com/docs/reference/js/firebase.auth.AuthProvider - -Signature: - -```typescript -export interface AuthProvider -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [providerId](./auth-types.authprovider.providerid.md) | string | | - diff --git a/docs-exp/auth-types.authprovider.providerid.md b/docs-exp/auth-types.authprovider.providerid.md deleted file mode 100644 index a8af80c7e7a..00000000000 --- a/docs-exp/auth-types.authprovider.providerid.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [AuthProvider](./auth-types.authprovider.md) > [providerId](./auth-types.authprovider.providerid.md) - -## AuthProvider.providerId property - -Signature: - -```typescript -readonly providerId: string; -``` diff --git a/docs-exp/auth-types.authsettings.appverificationdisabledfortesting.md b/docs-exp/auth-types.authsettings.appverificationdisabledfortesting.md deleted file mode 100644 index f2512d1887a..00000000000 --- a/docs-exp/auth-types.authsettings.appverificationdisabledfortesting.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [AuthSettings](./auth-types.authsettings.md) > [appVerificationDisabledForTesting](./auth-types.authsettings.appverificationdisabledfortesting.md) - -## AuthSettings.appVerificationDisabledForTesting property - -Signature: - -```typescript -appVerificationDisabledForTesting: boolean; -``` diff --git a/docs-exp/auth-types.authsettings.md b/docs-exp/auth-types.authsettings.md deleted file mode 100644 index 92ea98b6768..00000000000 --- a/docs-exp/auth-types.authsettings.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [AuthSettings](./auth-types.authsettings.md) - -## AuthSettings interface - -https://firebase.google.com/docs/reference/js/firebase.auth.AuthSettings - -Signature: - -```typescript -export interface AuthSettings -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [appVerificationDisabledForTesting](./auth-types.authsettings.appverificationdisabledfortesting.md) | boolean | | - diff --git a/docs-exp/auth-types.config.apihost.md b/docs-exp/auth-types.config.apihost.md deleted file mode 100644 index 0b2fa63a20e..00000000000 --- a/docs-exp/auth-types.config.apihost.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [Config](./auth-types.config.md) > [apiHost](./auth-types.config.apihost.md) - -## Config.apiHost property - -Signature: - -```typescript -apiHost: string; -``` diff --git a/docs-exp/auth-types.config.apikey.md b/docs-exp/auth-types.config.apikey.md deleted file mode 100644 index c7cc7fe1591..00000000000 --- a/docs-exp/auth-types.config.apikey.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [Config](./auth-types.config.md) > [apiKey](./auth-types.config.apikey.md) - -## Config.apiKey property - -Signature: - -```typescript -apiKey: string; -``` diff --git a/docs-exp/auth-types.config.apischeme.md b/docs-exp/auth-types.config.apischeme.md deleted file mode 100644 index a9af37a5dcd..00000000000 --- a/docs-exp/auth-types.config.apischeme.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [Config](./auth-types.config.md) > [apiScheme](./auth-types.config.apischeme.md) - -## Config.apiScheme property - -Signature: - -```typescript -apiScheme: string; -``` diff --git a/docs-exp/auth-types.config.authdomain.md b/docs-exp/auth-types.config.authdomain.md deleted file mode 100644 index ef2fa19916c..00000000000 --- a/docs-exp/auth-types.config.authdomain.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [Config](./auth-types.config.md) > [authDomain](./auth-types.config.authdomain.md) - -## Config.authDomain property - -Signature: - -```typescript -authDomain?: string; -``` diff --git a/docs-exp/auth-types.config.md b/docs-exp/auth-types.config.md deleted file mode 100644 index 099ba33075e..00000000000 --- a/docs-exp/auth-types.config.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [Config](./auth-types.config.md) - -## Config interface - -Auth config object - -Signature: - -```typescript -export interface Config -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [apiHost](./auth-types.config.apihost.md) | string | | -| [apiKey](./auth-types.config.apikey.md) | string | | -| [apiScheme](./auth-types.config.apischeme.md) | string | | -| [authDomain](./auth-types.config.authdomain.md) | string | | -| [sdkClientVersion](./auth-types.config.sdkclientversion.md) | string | | -| [tokenApiHost](./auth-types.config.tokenapihost.md) | string | | - diff --git a/docs-exp/auth-types.config.sdkclientversion.md b/docs-exp/auth-types.config.sdkclientversion.md deleted file mode 100644 index 0b9e404605a..00000000000 --- a/docs-exp/auth-types.config.sdkclientversion.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [Config](./auth-types.config.md) > [sdkClientVersion](./auth-types.config.sdkclientversion.md) - -## Config.sdkClientVersion property - -Signature: - -```typescript -sdkClientVersion: string; -``` diff --git a/docs-exp/auth-types.config.tokenapihost.md b/docs-exp/auth-types.config.tokenapihost.md deleted file mode 100644 index 2b2af334b34..00000000000 --- a/docs-exp/auth-types.config.tokenapihost.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [Config](./auth-types.config.md) > [tokenApiHost](./auth-types.config.tokenapihost.md) - -## Config.tokenApiHost property - -Signature: - -```typescript -tokenApiHost: string; -``` diff --git a/docs-exp/auth-types.confirmationresult.confirm.md b/docs-exp/auth-types.confirmationresult.confirm.md deleted file mode 100644 index 703f435a1be..00000000000 --- a/docs-exp/auth-types.confirmationresult.confirm.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ConfirmationResult](./auth-types.confirmationresult.md) > [confirm](./auth-types.confirmationresult.confirm.md) - -## ConfirmationResult.confirm() method - -Signature: - -```typescript -confirm(verificationCode: string): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| verificationCode | string | | - -Returns: - -Promise<[UserCredential](./auth-types.usercredential.md)> - diff --git a/docs-exp/auth-types.confirmationresult.md b/docs-exp/auth-types.confirmationresult.md deleted file mode 100644 index 305be1c045b..00000000000 --- a/docs-exp/auth-types.confirmationresult.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ConfirmationResult](./auth-types.confirmationresult.md) - -## ConfirmationResult interface - -https://firebase.google.com/docs/reference/js/firebase.auth.ConfirmationResult - -Signature: - -```typescript -export interface ConfirmationResult -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [verificationId](./auth-types.confirmationresult.verificationid.md) | string | | - -## Methods - -| Method | Description | -| --- | --- | -| [confirm(verificationCode)](./auth-types.confirmationresult.confirm.md) | | - diff --git a/docs-exp/auth-types.confirmationresult.verificationid.md b/docs-exp/auth-types.confirmationresult.verificationid.md deleted file mode 100644 index afc0eb72872..00000000000 --- a/docs-exp/auth-types.confirmationresult.verificationid.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ConfirmationResult](./auth-types.confirmationresult.md) > [verificationId](./auth-types.confirmationresult.verificationid.md) - -## ConfirmationResult.verificationId property - -Signature: - -```typescript -readonly verificationId: string; -``` diff --git a/docs-exp/auth-types.emailauthprovider.credential.md b/docs-exp/auth-types.emailauthprovider.credential.md deleted file mode 100644 index 1489fd2b067..00000000000 --- a/docs-exp/auth-types.emailauthprovider.credential.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [EmailAuthProvider](./auth-types.emailauthprovider.md) > [credential](./auth-types.emailauthprovider.credential.md) - -## EmailAuthProvider.credential() method - -Signature: - -```typescript -static credential(email: string, password: string): AuthCredential; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| email | string | | -| password | string | | - -Returns: - -[AuthCredential](./auth-types.authcredential.md) - diff --git a/docs-exp/auth-types.emailauthprovider.credentialwithlink.md b/docs-exp/auth-types.emailauthprovider.credentialwithlink.md deleted file mode 100644 index 2857ddd8cba..00000000000 --- a/docs-exp/auth-types.emailauthprovider.credentialwithlink.md +++ /dev/null @@ -1,28 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [EmailAuthProvider](./auth-types.emailauthprovider.md) > [credentialWithLink](./auth-types.emailauthprovider.credentialwithlink.md) - -## EmailAuthProvider.credentialWithLink() method - -Signature: - -```typescript -static credentialWithLink( - auth: Auth, - email: string, - emailLink: string - ): AuthCredential; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | [Auth](./auth-types.auth.md) | | -| email | string | | -| emailLink | string | | - -Returns: - -[AuthCredential](./auth-types.authcredential.md) - diff --git a/docs-exp/auth-types.emailauthprovider.email_link_sign_in_method.md b/docs-exp/auth-types.emailauthprovider.email_link_sign_in_method.md deleted file mode 100644 index 3118cbe2354..00000000000 --- a/docs-exp/auth-types.emailauthprovider.email_link_sign_in_method.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [EmailAuthProvider](./auth-types.emailauthprovider.md) > [EMAIL\_LINK\_SIGN\_IN\_METHOD](./auth-types.emailauthprovider.email_link_sign_in_method.md) - -## EmailAuthProvider.EMAIL\_LINK\_SIGN\_IN\_METHOD property - -Signature: - -```typescript -static readonly EMAIL_LINK_SIGN_IN_METHOD: SignInMethod; -``` diff --git a/docs-exp/auth-types.emailauthprovider.email_password_sign_in_method.md b/docs-exp/auth-types.emailauthprovider.email_password_sign_in_method.md deleted file mode 100644 index 8dabf3dfc9a..00000000000 --- a/docs-exp/auth-types.emailauthprovider.email_password_sign_in_method.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [EmailAuthProvider](./auth-types.emailauthprovider.md) > [EMAIL\_PASSWORD\_SIGN\_IN\_METHOD](./auth-types.emailauthprovider.email_password_sign_in_method.md) - -## EmailAuthProvider.EMAIL\_PASSWORD\_SIGN\_IN\_METHOD property - -Signature: - -```typescript -static readonly EMAIL_PASSWORD_SIGN_IN_METHOD: SignInMethod; -``` diff --git a/docs-exp/auth-types.emailauthprovider.md b/docs-exp/auth-types.emailauthprovider.md deleted file mode 100644 index 18019b248e3..00000000000 --- a/docs-exp/auth-types.emailauthprovider.md +++ /dev/null @@ -1,33 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [EmailAuthProvider](./auth-types.emailauthprovider.md) - -## EmailAuthProvider class - -A provider for generating email & password and email link credentials - -https://firebase.google.com/docs/reference/js/firebase.auth.EmailAuthProvider - -Signature: - -```typescript -export abstract class EmailAuthProvider implements AuthProvider -``` -Implements: [AuthProvider](./auth-types.authprovider.md) - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [EMAIL\_LINK\_SIGN\_IN\_METHOD](./auth-types.emailauthprovider.email_link_sign_in_method.md) | static | [SignInMethod](./auth-types.signinmethod.md) | | -| [EMAIL\_PASSWORD\_SIGN\_IN\_METHOD](./auth-types.emailauthprovider.email_password_sign_in_method.md) | static | [SignInMethod](./auth-types.signinmethod.md) | | -| [PROVIDER\_ID](./auth-types.emailauthprovider.provider_id.md) | static | [ProviderId](./auth-types.providerid.md) | | -| [providerId](./auth-types.emailauthprovider.providerid.md) | | [ProviderId](./auth-types.providerid.md) | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [credential(email, password)](./auth-types.emailauthprovider.credential.md) | static | | -| [credentialWithLink(auth, email, emailLink)](./auth-types.emailauthprovider.credentialwithlink.md) | static | | - diff --git a/docs-exp/auth-types.emailauthprovider.provider_id.md b/docs-exp/auth-types.emailauthprovider.provider_id.md deleted file mode 100644 index f6f44c17f10..00000000000 --- a/docs-exp/auth-types.emailauthprovider.provider_id.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [EmailAuthProvider](./auth-types.emailauthprovider.md) > [PROVIDER\_ID](./auth-types.emailauthprovider.provider_id.md) - -## EmailAuthProvider.PROVIDER\_ID property - -Signature: - -```typescript -static readonly PROVIDER_ID: ProviderId; -``` diff --git a/docs-exp/auth-types.emailauthprovider.providerid.md b/docs-exp/auth-types.emailauthprovider.providerid.md deleted file mode 100644 index 235ffe71a2d..00000000000 --- a/docs-exp/auth-types.emailauthprovider.providerid.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [EmailAuthProvider](./auth-types.emailauthprovider.md) > [providerId](./auth-types.emailauthprovider.providerid.md) - -## EmailAuthProvider.providerId property - -Signature: - -```typescript -readonly providerId: ProviderId; -``` diff --git a/docs-exp/auth-types.idtokenresult.authtime.md b/docs-exp/auth-types.idtokenresult.authtime.md deleted file mode 100644 index 86cdd6c275c..00000000000 --- a/docs-exp/auth-types.idtokenresult.authtime.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [IdTokenResult](./auth-types.idtokenresult.md) > [authTime](./auth-types.idtokenresult.authtime.md) - -## IdTokenResult.authTime property - -Signature: - -```typescript -authTime: string; -``` diff --git a/docs-exp/auth-types.idtokenresult.claims.md b/docs-exp/auth-types.idtokenresult.claims.md deleted file mode 100644 index 8947f1a3e1c..00000000000 --- a/docs-exp/auth-types.idtokenresult.claims.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [IdTokenResult](./auth-types.idtokenresult.md) > [claims](./auth-types.idtokenresult.claims.md) - -## IdTokenResult.claims property - -Signature: - -```typescript -claims: ParsedToken; -``` diff --git a/docs-exp/auth-types.idtokenresult.expirationtime.md b/docs-exp/auth-types.idtokenresult.expirationtime.md deleted file mode 100644 index 73aacfd4c98..00000000000 --- a/docs-exp/auth-types.idtokenresult.expirationtime.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [IdTokenResult](./auth-types.idtokenresult.md) > [expirationTime](./auth-types.idtokenresult.expirationtime.md) - -## IdTokenResult.expirationTime property - -Signature: - -```typescript -expirationTime: string; -``` diff --git a/docs-exp/auth-types.idtokenresult.issuedattime.md b/docs-exp/auth-types.idtokenresult.issuedattime.md deleted file mode 100644 index 931e4797660..00000000000 --- a/docs-exp/auth-types.idtokenresult.issuedattime.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [IdTokenResult](./auth-types.idtokenresult.md) > [issuedAtTime](./auth-types.idtokenresult.issuedattime.md) - -## IdTokenResult.issuedAtTime property - -Signature: - -```typescript -issuedAtTime: string; -``` diff --git a/docs-exp/auth-types.idtokenresult.md b/docs-exp/auth-types.idtokenresult.md deleted file mode 100644 index b7c7dc66b6f..00000000000 --- a/docs-exp/auth-types.idtokenresult.md +++ /dev/null @@ -1,28 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [IdTokenResult](./auth-types.idtokenresult.md) - -## IdTokenResult interface - -Parsed IdToken for use in public API - -https://firebase.google.com/docs/reference/js/firebase.auth.IDTokenResult - -Signature: - -```typescript -export interface IdTokenResult -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [authTime](./auth-types.idtokenresult.authtime.md) | string | | -| [claims](./auth-types.idtokenresult.claims.md) | [ParsedToken](./auth-types.parsedtoken.md) | | -| [expirationTime](./auth-types.idtokenresult.expirationtime.md) | string | | -| [issuedAtTime](./auth-types.idtokenresult.issuedattime.md) | string | | -| [signInProvider](./auth-types.idtokenresult.signinprovider.md) | string \| null | | -| [signInSecondFactor](./auth-types.idtokenresult.signinsecondfactor.md) | string \| null | | -| [token](./auth-types.idtokenresult.token.md) | string | | - diff --git a/docs-exp/auth-types.idtokenresult.signinprovider.md b/docs-exp/auth-types.idtokenresult.signinprovider.md deleted file mode 100644 index 62819045f53..00000000000 --- a/docs-exp/auth-types.idtokenresult.signinprovider.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [IdTokenResult](./auth-types.idtokenresult.md) > [signInProvider](./auth-types.idtokenresult.signinprovider.md) - -## IdTokenResult.signInProvider property - -Signature: - -```typescript -signInProvider: string | null; -``` diff --git a/docs-exp/auth-types.idtokenresult.signinsecondfactor.md b/docs-exp/auth-types.idtokenresult.signinsecondfactor.md deleted file mode 100644 index c0228609989..00000000000 --- a/docs-exp/auth-types.idtokenresult.signinsecondfactor.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [IdTokenResult](./auth-types.idtokenresult.md) > [signInSecondFactor](./auth-types.idtokenresult.signinsecondfactor.md) - -## IdTokenResult.signInSecondFactor property - -Signature: - -```typescript -signInSecondFactor: string | null; -``` diff --git a/docs-exp/auth-types.idtokenresult.token.md b/docs-exp/auth-types.idtokenresult.token.md deleted file mode 100644 index 222cf970c2e..00000000000 --- a/docs-exp/auth-types.idtokenresult.token.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [IdTokenResult](./auth-types.idtokenresult.md) > [token](./auth-types.idtokenresult.token.md) - -## IdTokenResult.token property - -Signature: - -```typescript -token: string; -``` diff --git a/docs-exp/auth-types.md b/docs-exp/auth-types.md deleted file mode 100644 index 8fed5c4494f..00000000000 --- a/docs-exp/auth-types.md +++ /dev/null @@ -1,70 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) - -## auth-types package - -## Classes - -| Class | Description | -| --- | --- | -| [ActionCodeURL](./auth-types.actioncodeurl.md) | A utility class to parse email action URLs such as password reset, email verification, email link sign in, etc. | -| [AuthCredential](./auth-types.authcredential.md) | https://firebase.google.com/docs/reference/js/firebase.auth.AuthCredential | -| [EmailAuthProvider](./auth-types.emailauthprovider.md) | A provider for generating email & password and email link credentialshttps://firebase.google.com/docs/reference/js/firebase.auth.EmailAuthProvider | -| [MultiFactorResolver](./auth-types.multifactorresolver.md) | https://firebase.google.com/docs/reference/js/firebase.auth.multifactorresolver | -| [OAuthCredential](./auth-types.oauthcredential.md) | https://firebase.google.com/docs/reference/js/firebase.auth.OAuthCredential | -| [PhoneAuthCredential](./auth-types.phoneauthcredential.md) | https://firebase.google.com/docs/reference/js/firebase.auth.phoneauthcredential | -| [PhoneAuthProvider](./auth-types.phoneauthprovider.md) | A provider for generating phone credentialshttps://firebase.google.com/docs/reference/js/firebase.auth.PhoneAuthProvider | -| [PhoneMultiFactorGenerator](./auth-types.phonemultifactorgenerator.md) | https://firebase.google.com/docs/reference/js/firebase.auth.phonemultifactorgenerator | -| [RecaptchaVerifier](./auth-types.recaptchaverifier.md) | https://firebase.google.com/docs/reference/js/firebase.auth.RecaptchaVerifier | - -## Enumerations - -| Enumeration | Description | -| --- | --- | -| [Operation](./auth-types.operation.md) | https://firebase.google.com/docs/reference/js/firebase.auth.ActionCodeInfo\#operation\_2 | -| [OperationType](./auth-types.operationtype.md) | Supported operation types | -| [ProviderId](./auth-types.providerid.md) | Supported providers | -| [SignInMethod](./auth-types.signinmethod.md) | Supported sign in methods | - -## Interfaces - -| Interface | Description | -| --- | --- | -| [ActionCodeInfo](./auth-types.actioncodeinfo.md) | https://firebase.google.com/docs/reference/js/firebase.auth.ActionCodeInfo | -| [ActionCodeSettings](./auth-types.actioncodesettings.md) | https://firebase.google.com/docs/reference/js/firebase.auth\#actioncodesettings | -| [AdditionalUserInfo](./auth-types.additionaluserinfo.md) | Additional user information. | -| [ApplicationVerifier](./auth-types.applicationverifier.md) | https://firebase.google.com/docs/reference/js/firebase.auth.ApplicationVerifier | -| [Auth](./auth-types.auth.md) | https://firebase.google.com/docs/reference/js/firebase.auth.Auth | -| [AuthError](./auth-types.autherror.md) | https://firebase.google.com/docs/reference/js/firebase.auth.AuthError | -| [AuthProvider](./auth-types.authprovider.md) | A provider for generating credentialshttps://firebase.google.com/docs/reference/js/firebase.auth.AuthProvider | -| [AuthSettings](./auth-types.authsettings.md) | https://firebase.google.com/docs/reference/js/firebase.auth.AuthSettings | -| [Config](./auth-types.config.md) | Auth config object | -| [ConfirmationResult](./auth-types.confirmationresult.md) | https://firebase.google.com/docs/reference/js/firebase.auth.ConfirmationResult | -| [IdTokenResult](./auth-types.idtokenresult.md) | Parsed IdToken for use in public APIhttps://firebase.google.com/docs/reference/js/firebase.auth.IDTokenResult | -| [MultiFactorAssertion](./auth-types.multifactorassertion.md) | https://firebase.google.com/docs/reference/js/firebase.auth.multifactorassertion | -| [MultiFactorError](./auth-types.multifactorerror.md) | https://firebase.google.com/docs/reference/js/firebase.auth.multifactorerror | -| [MultiFactorInfo](./auth-types.multifactorinfo.md) | https://firebase.google.com/docs/reference/js/firebase.auth.multifactorinfo | -| [MultiFactorSession](./auth-types.multifactorsession.md) | https://firebase.google.com/docs/reference/js/firebase.auth.multifactorsession | -| [MultiFactorUser](./auth-types.multifactoruser.md) | https://firebase.google.com/docs/reference/js/firebase.user.multifactoruser | -| [ParsedToken](./auth-types.parsedtoken.md) | Parsed Id TokenTODO(avolkovi): consolidate with parsed\_token in implementation | -| [Persistence](./auth-types.persistence.md) | https://firebase.google.com/docs/reference/js/firebase.auth.Auth\#persistence | -| [PhoneMultiFactorAssertion](./auth-types.phonemultifactorassertion.md) | https://firebase.google.com/docs/reference/js/firebase.auth.phonemultifactorassertion | -| [PhoneMultiFactorEnrollInfoOptions](./auth-types.phonemultifactorenrollinfooptions.md) | | -| [PhoneMultiFactorSignInInfoOptions](./auth-types.phonemultifactorsignininfooptions.md) | | -| [PhoneSingleFactorInfoOptions](./auth-types.phonesinglefactorinfooptions.md) | | -| [PopupRedirectResolver](./auth-types.popupredirectresolver.md) | No documentation for this yet | -| [ReactNativeAsyncStorage](./auth-types.reactnativeasyncstorage.md) | | -| [User](./auth-types.user.md) | https://firebase.google.com/docs/reference/js/firebase.User | -| [UserCredential](./auth-types.usercredential.md) | https://firebase.google.com/docs/reference/js/firebase.auth\#usercredential | -| [UserInfo](./auth-types.userinfo.md) | https://firebase.google.com/docs/reference/js/firebase.UserInfo | -| [UserMetadata](./auth-types.usermetadata.md) | https://firebase.google.com/docs/reference/js/firebase.auth.UserMetadata | - -## Type Aliases - -| Type Alias | Description | -| --- | --- | -| [NextOrObserver](./auth-types.nextorobserver.md) | TODO(avolkovi): should we consolidate with Subscribe since we're changing the API anyway? | -| [PhoneInfoOptions](./auth-types.phoneinfooptions.md) | https://firebase.google.com/docs/reference/js/firebase.auth\#phoneinfooptions | -| [UserProfile](./auth-types.userprofile.md) | User profile used in AdditionalUserInfo | - diff --git a/docs-exp/auth-types.multifactorassertion.factorid.md b/docs-exp/auth-types.multifactorassertion.factorid.md deleted file mode 100644 index 75339bf067f..00000000000 --- a/docs-exp/auth-types.multifactorassertion.factorid.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [MultiFactorAssertion](./auth-types.multifactorassertion.md) > [factorId](./auth-types.multifactorassertion.factorid.md) - -## MultiFactorAssertion.factorId property - -Signature: - -```typescript -readonly factorId: string; -``` diff --git a/docs-exp/auth-types.multifactorassertion.md b/docs-exp/auth-types.multifactorassertion.md deleted file mode 100644 index 15569fc4db3..00000000000 --- a/docs-exp/auth-types.multifactorassertion.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [MultiFactorAssertion](./auth-types.multifactorassertion.md) - -## MultiFactorAssertion interface - -https://firebase.google.com/docs/reference/js/firebase.auth.multifactorassertion - -Signature: - -```typescript -export interface MultiFactorAssertion -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [factorId](./auth-types.multifactorassertion.factorid.md) | string | | - diff --git a/docs-exp/auth-types.multifactorerror.credential.md b/docs-exp/auth-types.multifactorerror.credential.md deleted file mode 100644 index 96bf195d2a7..00000000000 --- a/docs-exp/auth-types.multifactorerror.credential.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [MultiFactorError](./auth-types.multifactorerror.md) > [credential](./auth-types.multifactorerror.credential.md) - -## MultiFactorError.credential property - -Signature: - -```typescript -readonly credential: AuthCredential; -``` diff --git a/docs-exp/auth-types.multifactorerror.md b/docs-exp/auth-types.multifactorerror.md deleted file mode 100644 index b3de26523cf..00000000000 --- a/docs-exp/auth-types.multifactorerror.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [MultiFactorError](./auth-types.multifactorerror.md) - -## MultiFactorError interface - -https://firebase.google.com/docs/reference/js/firebase.auth.multifactorerror - -Signature: - -```typescript -export interface MultiFactorError extends AuthError -``` -Extends: [AuthError](./auth-types.autherror.md) - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [credential](./auth-types.multifactorerror.credential.md) | [AuthCredential](./auth-types.authcredential.md) | | -| [operationType](./auth-types.multifactorerror.operationtype.md) | [OperationType](./auth-types.operationtype.md) | | - diff --git a/docs-exp/auth-types.multifactorerror.operationtype.md b/docs-exp/auth-types.multifactorerror.operationtype.md deleted file mode 100644 index 9720ba8e154..00000000000 --- a/docs-exp/auth-types.multifactorerror.operationtype.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [MultiFactorError](./auth-types.multifactorerror.md) > [operationType](./auth-types.multifactorerror.operationtype.md) - -## MultiFactorError.operationType property - -Signature: - -```typescript -readonly operationType: OperationType; -``` diff --git a/docs-exp/auth-types.multifactorinfo.displayname.md b/docs-exp/auth-types.multifactorinfo.displayname.md deleted file mode 100644 index 5d52c8f06db..00000000000 --- a/docs-exp/auth-types.multifactorinfo.displayname.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [MultiFactorInfo](./auth-types.multifactorinfo.md) > [displayName](./auth-types.multifactorinfo.displayname.md) - -## MultiFactorInfo.displayName property - -Signature: - -```typescript -readonly displayName?: string | null; -``` diff --git a/docs-exp/auth-types.multifactorinfo.enrollmenttime.md b/docs-exp/auth-types.multifactorinfo.enrollmenttime.md deleted file mode 100644 index a205bd20065..00000000000 --- a/docs-exp/auth-types.multifactorinfo.enrollmenttime.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [MultiFactorInfo](./auth-types.multifactorinfo.md) > [enrollmentTime](./auth-types.multifactorinfo.enrollmenttime.md) - -## MultiFactorInfo.enrollmentTime property - -Signature: - -```typescript -readonly enrollmentTime: string; -``` diff --git a/docs-exp/auth-types.multifactorinfo.factorid.md b/docs-exp/auth-types.multifactorinfo.factorid.md deleted file mode 100644 index 9b6413c3c36..00000000000 --- a/docs-exp/auth-types.multifactorinfo.factorid.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [MultiFactorInfo](./auth-types.multifactorinfo.md) > [factorId](./auth-types.multifactorinfo.factorid.md) - -## MultiFactorInfo.factorId property - -Signature: - -```typescript -readonly factorId: ProviderId; -``` diff --git a/docs-exp/auth-types.multifactorinfo.md b/docs-exp/auth-types.multifactorinfo.md deleted file mode 100644 index fc8f15d3944..00000000000 --- a/docs-exp/auth-types.multifactorinfo.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [MultiFactorInfo](./auth-types.multifactorinfo.md) - -## MultiFactorInfo interface - -https://firebase.google.com/docs/reference/js/firebase.auth.multifactorinfo - -Signature: - -```typescript -export interface MultiFactorInfo -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [displayName](./auth-types.multifactorinfo.displayname.md) | string \| null | | -| [enrollmentTime](./auth-types.multifactorinfo.enrollmenttime.md) | string | | -| [factorId](./auth-types.multifactorinfo.factorid.md) | [ProviderId](./auth-types.providerid.md) | | -| [uid](./auth-types.multifactorinfo.uid.md) | string | | - diff --git a/docs-exp/auth-types.multifactorinfo.uid.md b/docs-exp/auth-types.multifactorinfo.uid.md deleted file mode 100644 index 7c2dad6df7a..00000000000 --- a/docs-exp/auth-types.multifactorinfo.uid.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [MultiFactorInfo](./auth-types.multifactorinfo.md) > [uid](./auth-types.multifactorinfo.uid.md) - -## MultiFactorInfo.uid property - -Signature: - -```typescript -readonly uid: string; -``` diff --git a/docs-exp/auth-types.multifactorresolver.hints.md b/docs-exp/auth-types.multifactorresolver.hints.md deleted file mode 100644 index 1fe9ff6c0ff..00000000000 --- a/docs-exp/auth-types.multifactorresolver.hints.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [MultiFactorResolver](./auth-types.multifactorresolver.md) > [hints](./auth-types.multifactorresolver.hints.md) - -## MultiFactorResolver.hints property - -Signature: - -```typescript -hints: MultiFactorInfo[]; -``` diff --git a/docs-exp/auth-types.multifactorresolver.md b/docs-exp/auth-types.multifactorresolver.md deleted file mode 100644 index 46316dac83d..00000000000 --- a/docs-exp/auth-types.multifactorresolver.md +++ /dev/null @@ -1,27 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [MultiFactorResolver](./auth-types.multifactorresolver.md) - -## MultiFactorResolver class - -https://firebase.google.com/docs/reference/js/firebase.auth.multifactorresolver - -Signature: - -```typescript -export abstract class MultiFactorResolver -``` - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [hints](./auth-types.multifactorresolver.hints.md) | | [MultiFactorInfo](./auth-types.multifactorinfo.md)\[\] | | -| [session](./auth-types.multifactorresolver.session.md) | | [MultiFactorSession](./auth-types.multifactorsession.md) | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [resolveSignIn(assertion)](./auth-types.multifactorresolver.resolvesignin.md) | | | - diff --git a/docs-exp/auth-types.multifactorresolver.resolvesignin.md b/docs-exp/auth-types.multifactorresolver.resolvesignin.md deleted file mode 100644 index 245110ec88c..00000000000 --- a/docs-exp/auth-types.multifactorresolver.resolvesignin.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [MultiFactorResolver](./auth-types.multifactorresolver.md) > [resolveSignIn](./auth-types.multifactorresolver.resolvesignin.md) - -## MultiFactorResolver.resolveSignIn() method - -Signature: - -```typescript -resolveSignIn(assertion: MultiFactorAssertion): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| assertion | [MultiFactorAssertion](./auth-types.multifactorassertion.md) | | - -Returns: - -Promise<[UserCredential](./auth-types.usercredential.md)> - diff --git a/docs-exp/auth-types.multifactorresolver.session.md b/docs-exp/auth-types.multifactorresolver.session.md deleted file mode 100644 index ab51c8b820d..00000000000 --- a/docs-exp/auth-types.multifactorresolver.session.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [MultiFactorResolver](./auth-types.multifactorresolver.md) > [session](./auth-types.multifactorresolver.session.md) - -## MultiFactorResolver.session property - -Signature: - -```typescript -session: MultiFactorSession; -``` diff --git a/docs-exp/auth-types.multifactorsession.md b/docs-exp/auth-types.multifactorsession.md deleted file mode 100644 index fa4c4349441..00000000000 --- a/docs-exp/auth-types.multifactorsession.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [MultiFactorSession](./auth-types.multifactorsession.md) - -## MultiFactorSession interface - -https://firebase.google.com/docs/reference/js/firebase.auth.multifactorsession - -Signature: - -```typescript -export interface MultiFactorSession -``` diff --git a/docs-exp/auth-types.multifactoruser.enroll.md b/docs-exp/auth-types.multifactoruser.enroll.md deleted file mode 100644 index e31fe0fc83c..00000000000 --- a/docs-exp/auth-types.multifactoruser.enroll.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [MultiFactorUser](./auth-types.multifactoruser.md) > [enroll](./auth-types.multifactoruser.enroll.md) - -## MultiFactorUser.enroll() method - -Signature: - -```typescript -enroll( - assertion: MultiFactorAssertion, - displayName?: string | null - ): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| assertion | [MultiFactorAssertion](./auth-types.multifactorassertion.md) | | -| displayName | string \| null | | - -Returns: - -Promise<void> - diff --git a/docs-exp/auth-types.multifactoruser.enrolledfactors.md b/docs-exp/auth-types.multifactoruser.enrolledfactors.md deleted file mode 100644 index 2f04912ecb1..00000000000 --- a/docs-exp/auth-types.multifactoruser.enrolledfactors.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [MultiFactorUser](./auth-types.multifactoruser.md) > [enrolledFactors](./auth-types.multifactoruser.enrolledfactors.md) - -## MultiFactorUser.enrolledFactors property - -Signature: - -```typescript -readonly enrolledFactors: MultiFactorInfo[]; -``` diff --git a/docs-exp/auth-types.multifactoruser.getsession.md b/docs-exp/auth-types.multifactoruser.getsession.md deleted file mode 100644 index 70d23240cea..00000000000 --- a/docs-exp/auth-types.multifactoruser.getsession.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [MultiFactorUser](./auth-types.multifactoruser.md) > [getSession](./auth-types.multifactoruser.getsession.md) - -## MultiFactorUser.getSession() method - -Signature: - -```typescript -getSession(): Promise; -``` -Returns: - -Promise<[MultiFactorSession](./auth-types.multifactorsession.md)> - diff --git a/docs-exp/auth-types.multifactoruser.md b/docs-exp/auth-types.multifactoruser.md deleted file mode 100644 index cb5ee260d3e..00000000000 --- a/docs-exp/auth-types.multifactoruser.md +++ /dev/null @@ -1,28 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [MultiFactorUser](./auth-types.multifactoruser.md) - -## MultiFactorUser interface - -https://firebase.google.com/docs/reference/js/firebase.user.multifactoruser - -Signature: - -```typescript -export interface MultiFactorUser -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [enrolledFactors](./auth-types.multifactoruser.enrolledfactors.md) | [MultiFactorInfo](./auth-types.multifactorinfo.md)\[\] | | - -## Methods - -| Method | Description | -| --- | --- | -| [enroll(assertion, displayName)](./auth-types.multifactoruser.enroll.md) | | -| [getSession()](./auth-types.multifactoruser.getsession.md) | | -| [unenroll(option)](./auth-types.multifactoruser.unenroll.md) | | - diff --git a/docs-exp/auth-types.multifactoruser.unenroll.md b/docs-exp/auth-types.multifactoruser.unenroll.md deleted file mode 100644 index efb4ac0cdfd..00000000000 --- a/docs-exp/auth-types.multifactoruser.unenroll.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [MultiFactorUser](./auth-types.multifactoruser.md) > [unenroll](./auth-types.multifactoruser.unenroll.md) - -## MultiFactorUser.unenroll() method - -Signature: - -```typescript -unenroll(option: MultiFactorInfo | string): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| option | [MultiFactorInfo](./auth-types.multifactorinfo.md) \| string | | - -Returns: - -Promise<void> - diff --git a/docs-exp/auth-types.nextorobserver.md b/docs-exp/auth-types.nextorobserver.md deleted file mode 100644 index 9daf288571a..00000000000 --- a/docs-exp/auth-types.nextorobserver.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [NextOrObserver](./auth-types.nextorobserver.md) - -## NextOrObserver type - -TODO(avolkovi): should we consolidate with Subscribe since we're changing the API anyway? - -Signature: - -```typescript -export type NextOrObserver = NextFn | Observer; -``` diff --git a/docs-exp/auth-types.oauthcredential.accesstoken.md b/docs-exp/auth-types.oauthcredential.accesstoken.md deleted file mode 100644 index 276bacfae87..00000000000 --- a/docs-exp/auth-types.oauthcredential.accesstoken.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [OAuthCredential](./auth-types.oauthcredential.md) > [accessToken](./auth-types.oauthcredential.accesstoken.md) - -## OAuthCredential.accessToken property - -Signature: - -```typescript -readonly accessToken?: string; -``` diff --git a/docs-exp/auth-types.oauthcredential.fromjson.md b/docs-exp/auth-types.oauthcredential.fromjson.md deleted file mode 100644 index 483f8a78712..00000000000 --- a/docs-exp/auth-types.oauthcredential.fromjson.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [OAuthCredential](./auth-types.oauthcredential.md) > [fromJSON](./auth-types.oauthcredential.fromjson.md) - -## OAuthCredential.fromJSON() method - -Signature: - -```typescript -static fromJSON(json: object | string): OAuthCredential | null; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| json | object \| string | | - -Returns: - -[OAuthCredential](./auth-types.oauthcredential.md) \| null - diff --git a/docs-exp/auth-types.oauthcredential.idtoken.md b/docs-exp/auth-types.oauthcredential.idtoken.md deleted file mode 100644 index 833cf622122..00000000000 --- a/docs-exp/auth-types.oauthcredential.idtoken.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [OAuthCredential](./auth-types.oauthcredential.md) > [idToken](./auth-types.oauthcredential.idtoken.md) - -## OAuthCredential.idToken property - -Signature: - -```typescript -readonly idToken?: string; -``` diff --git a/docs-exp/auth-types.oauthcredential.md b/docs-exp/auth-types.oauthcredential.md deleted file mode 100644 index 526916240a9..00000000000 --- a/docs-exp/auth-types.oauthcredential.md +++ /dev/null @@ -1,29 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [OAuthCredential](./auth-types.oauthcredential.md) - -## OAuthCredential class - -https://firebase.google.com/docs/reference/js/firebase.auth.OAuthCredential - -Signature: - -```typescript -export abstract class OAuthCredential extends AuthCredential -``` -Extends: [AuthCredential](./auth-types.authcredential.md) - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [accessToken](./auth-types.oauthcredential.accesstoken.md) | | string | | -| [idToken](./auth-types.oauthcredential.idtoken.md) | | string | | -| [secret](./auth-types.oauthcredential.secret.md) | | string | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [fromJSON(json)](./auth-types.oauthcredential.fromjson.md) | static | | - diff --git a/docs-exp/auth-types.oauthcredential.secret.md b/docs-exp/auth-types.oauthcredential.secret.md deleted file mode 100644 index 21bbcee19c7..00000000000 --- a/docs-exp/auth-types.oauthcredential.secret.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [OAuthCredential](./auth-types.oauthcredential.md) > [secret](./auth-types.oauthcredential.secret.md) - -## OAuthCredential.secret property - -Signature: - -```typescript -readonly secret?: string; -``` diff --git a/docs-exp/auth-types.operation.md b/docs-exp/auth-types.operation.md deleted file mode 100644 index 46abf37184e..00000000000 --- a/docs-exp/auth-types.operation.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [Operation](./auth-types.operation.md) - -## Operation enum - -https://firebase.google.com/docs/reference/js/firebase.auth.ActionCodeInfo\#operation\_2 - -Signature: - -```typescript -export const enum Operation -``` - -## Enumeration Members - -| Member | Value | Description | -| --- | --- | --- | -| EMAIL\_SIGNIN | 'EMAIL_SIGNIN' | | -| PASSWORD\_RESET | 'PASSWORD_RESET' | | -| RECOVER\_EMAIL | 'RECOVER_EMAIL' | | -| REVERT\_SECOND\_FACTOR\_ADDITION | 'REVERT_SECOND_FACTOR_ADDITION' | | -| VERIFY\_AND\_CHANGE\_EMAIL | 'VERIFY_AND_CHANGE_EMAIL' | | -| VERIFY\_EMAIL | 'VERIFY_EMAIL' | | - diff --git a/docs-exp/auth-types.operationtype.md b/docs-exp/auth-types.operationtype.md deleted file mode 100644 index 8dd44a52b3c..00000000000 --- a/docs-exp/auth-types.operationtype.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [OperationType](./auth-types.operationtype.md) - -## OperationType enum - -Supported operation types - -Signature: - -```typescript -export const enum OperationType -``` - -## Enumeration Members - -| Member | Value | Description | -| --- | --- | --- | -| LINK | 'link' | | -| REAUTHENTICATE | 'reauthenticate' | | -| SIGN\_IN | 'signIn' | | - diff --git a/docs-exp/auth-types.parsedtoken.auth_time.md b/docs-exp/auth-types.parsedtoken.auth_time.md deleted file mode 100644 index ba89759d0a2..00000000000 --- a/docs-exp/auth-types.parsedtoken.auth_time.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ParsedToken](./auth-types.parsedtoken.md) > [auth\_time](./auth-types.parsedtoken.auth_time.md) - -## ParsedToken.auth\_time property - -Signature: - -```typescript -'auth_time'?: string; -``` diff --git a/docs-exp/auth-types.parsedtoken.exp.md b/docs-exp/auth-types.parsedtoken.exp.md deleted file mode 100644 index fa63501e70a..00000000000 --- a/docs-exp/auth-types.parsedtoken.exp.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ParsedToken](./auth-types.parsedtoken.md) > [exp](./auth-types.parsedtoken.exp.md) - -## ParsedToken.exp property - -Signature: - -```typescript -'exp'?: string; -``` diff --git a/docs-exp/auth-types.parsedtoken.firebase.md b/docs-exp/auth-types.parsedtoken.firebase.md deleted file mode 100644 index 946fa7eeb66..00000000000 --- a/docs-exp/auth-types.parsedtoken.firebase.md +++ /dev/null @@ -1,14 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ParsedToken](./auth-types.parsedtoken.md) > [firebase](./auth-types.parsedtoken.firebase.md) - -## ParsedToken.firebase property - -Signature: - -```typescript -'firebase'?: { - 'sign_in_provider'?: string; - 'sign_in_second_factor'?: string; - }; -``` diff --git a/docs-exp/auth-types.parsedtoken.iat.md b/docs-exp/auth-types.parsedtoken.iat.md deleted file mode 100644 index 1f233edcb1f..00000000000 --- a/docs-exp/auth-types.parsedtoken.iat.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ParsedToken](./auth-types.parsedtoken.md) > [iat](./auth-types.parsedtoken.iat.md) - -## ParsedToken.iat property - -Signature: - -```typescript -'iat'?: string; -``` diff --git a/docs-exp/auth-types.parsedtoken.md b/docs-exp/auth-types.parsedtoken.md deleted file mode 100644 index 30466793bd3..00000000000 --- a/docs-exp/auth-types.parsedtoken.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ParsedToken](./auth-types.parsedtoken.md) - -## ParsedToken interface - -Parsed Id Token - -TODO(avolkovi): consolidate with parsed\_token in implementation - -Signature: - -```typescript -export interface ParsedToken -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [auth\_time](./auth-types.parsedtoken.auth_time.md) | string | | -| [exp](./auth-types.parsedtoken.exp.md) | string | | -| [firebase](./auth-types.parsedtoken.firebase.md) | { 'sign\_in\_provider'?: string; 'sign\_in\_second\_factor'?: string; } | | -| [iat](./auth-types.parsedtoken.iat.md) | string | | -| [sub](./auth-types.parsedtoken.sub.md) | string | | - diff --git a/docs-exp/auth-types.parsedtoken.sub.md b/docs-exp/auth-types.parsedtoken.sub.md deleted file mode 100644 index 0307efde1f5..00000000000 --- a/docs-exp/auth-types.parsedtoken.sub.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ParsedToken](./auth-types.parsedtoken.md) > [sub](./auth-types.parsedtoken.sub.md) - -## ParsedToken.sub property - -Signature: - -```typescript -'sub'?: string; -``` diff --git a/docs-exp/auth-types.persistence.md b/docs-exp/auth-types.persistence.md deleted file mode 100644 index 5b785a55f33..00000000000 --- a/docs-exp/auth-types.persistence.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [Persistence](./auth-types.persistence.md) - -## Persistence interface - -https://firebase.google.com/docs/reference/js/firebase.auth.Auth\#persistence - -Signature: - -```typescript -export interface Persistence -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [type](./auth-types.persistence.type.md) | 'SESSION' \| 'LOCAL' \| 'NONE' | | - diff --git a/docs-exp/auth-types.persistence.type.md b/docs-exp/auth-types.persistence.type.md deleted file mode 100644 index 9de2d0c0bee..00000000000 --- a/docs-exp/auth-types.persistence.type.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [Persistence](./auth-types.persistence.md) > [type](./auth-types.persistence.type.md) - -## Persistence.type property - -Signature: - -```typescript -readonly type: 'SESSION' | 'LOCAL' | 'NONE'; -``` diff --git a/docs-exp/auth-types.phoneauthcredential.fromjson.md b/docs-exp/auth-types.phoneauthcredential.fromjson.md deleted file mode 100644 index fc2ba74883a..00000000000 --- a/docs-exp/auth-types.phoneauthcredential.fromjson.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [PhoneAuthCredential](./auth-types.phoneauthcredential.md) > [fromJSON](./auth-types.phoneauthcredential.fromjson.md) - -## PhoneAuthCredential.fromJSON() method - -Signature: - -```typescript -static fromJSON(json: object | string): PhoneAuthCredential | null; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| json | object \| string | | - -Returns: - -[PhoneAuthCredential](./auth-types.phoneauthcredential.md) \| null - diff --git a/docs-exp/auth-types.phoneauthcredential.md b/docs-exp/auth-types.phoneauthcredential.md deleted file mode 100644 index 8cb19042899..00000000000 --- a/docs-exp/auth-types.phoneauthcredential.md +++ /dev/null @@ -1,21 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [PhoneAuthCredential](./auth-types.phoneauthcredential.md) - -## PhoneAuthCredential class - -https://firebase.google.com/docs/reference/js/firebase.auth.phoneauthcredential - -Signature: - -```typescript -export abstract class PhoneAuthCredential extends AuthCredential -``` -Extends: [AuthCredential](./auth-types.authcredential.md) - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [fromJSON(json)](./auth-types.phoneauthcredential.fromjson.md) | static | | - diff --git a/docs-exp/auth-types.phoneauthprovider._constructor_.md b/docs-exp/auth-types.phoneauthprovider._constructor_.md deleted file mode 100644 index 27251f405c8..00000000000 --- a/docs-exp/auth-types.phoneauthprovider._constructor_.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [PhoneAuthProvider](./auth-types.phoneauthprovider.md) > [(constructor)](./auth-types.phoneauthprovider._constructor_.md) - -## PhoneAuthProvider.(constructor) - -Constructs a new instance of the `PhoneAuthProvider` class - -Signature: - -```typescript -constructor(auth?: Auth | null); -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | [Auth](./auth-types.auth.md) \| null | | - diff --git a/docs-exp/auth-types.phoneauthprovider.credential.md b/docs-exp/auth-types.phoneauthprovider.credential.md deleted file mode 100644 index 3e063737cf0..00000000000 --- a/docs-exp/auth-types.phoneauthprovider.credential.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [PhoneAuthProvider](./auth-types.phoneauthprovider.md) > [credential](./auth-types.phoneauthprovider.credential.md) - -## PhoneAuthProvider.credential() method - -Signature: - -```typescript -static credential( - verificationId: string, - verificationCode: string - ): AuthCredential; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| verificationId | string | | -| verificationCode | string | | - -Returns: - -[AuthCredential](./auth-types.authcredential.md) - diff --git a/docs-exp/auth-types.phoneauthprovider.md b/docs-exp/auth-types.phoneauthprovider.md deleted file mode 100644 index 2321bd78d72..00000000000 --- a/docs-exp/auth-types.phoneauthprovider.md +++ /dev/null @@ -1,38 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [PhoneAuthProvider](./auth-types.phoneauthprovider.md) - -## PhoneAuthProvider class - -A provider for generating phone credentials - -https://firebase.google.com/docs/reference/js/firebase.auth.PhoneAuthProvider - -Signature: - -```typescript -export class PhoneAuthProvider implements AuthProvider -``` -Implements: [AuthProvider](./auth-types.authprovider.md) - -## Constructors - -| Constructor | Modifiers | Description | -| --- | --- | --- | -| [(constructor)(auth)](./auth-types.phoneauthprovider._constructor_.md) | | Constructs a new instance of the PhoneAuthProvider class | - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [PHONE\_SIGN\_IN\_METHOD](./auth-types.phoneauthprovider.phone_sign_in_method.md) | static | [SignInMethod](./auth-types.signinmethod.md) | | -| [PROVIDER\_ID](./auth-types.phoneauthprovider.provider_id.md) | static | [ProviderId](./auth-types.providerid.md) | | -| [providerId](./auth-types.phoneauthprovider.providerid.md) | | [ProviderId](./auth-types.providerid.md) | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [credential(verificationId, verificationCode)](./auth-types.phoneauthprovider.credential.md) | static | | -| [verifyPhoneNumber(phoneInfoOptions, applicationVerifier)](./auth-types.phoneauthprovider.verifyphonenumber.md) | | | - diff --git a/docs-exp/auth-types.phoneauthprovider.phone_sign_in_method.md b/docs-exp/auth-types.phoneauthprovider.phone_sign_in_method.md deleted file mode 100644 index a138cf4f643..00000000000 --- a/docs-exp/auth-types.phoneauthprovider.phone_sign_in_method.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [PhoneAuthProvider](./auth-types.phoneauthprovider.md) > [PHONE\_SIGN\_IN\_METHOD](./auth-types.phoneauthprovider.phone_sign_in_method.md) - -## PhoneAuthProvider.PHONE\_SIGN\_IN\_METHOD property - -Signature: - -```typescript -static readonly PHONE_SIGN_IN_METHOD: SignInMethod; -``` diff --git a/docs-exp/auth-types.phoneauthprovider.provider_id.md b/docs-exp/auth-types.phoneauthprovider.provider_id.md deleted file mode 100644 index a2d422cc365..00000000000 --- a/docs-exp/auth-types.phoneauthprovider.provider_id.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [PhoneAuthProvider](./auth-types.phoneauthprovider.md) > [PROVIDER\_ID](./auth-types.phoneauthprovider.provider_id.md) - -## PhoneAuthProvider.PROVIDER\_ID property - -Signature: - -```typescript -static readonly PROVIDER_ID: ProviderId; -``` diff --git a/docs-exp/auth-types.phoneauthprovider.providerid.md b/docs-exp/auth-types.phoneauthprovider.providerid.md deleted file mode 100644 index d93c98f1f8c..00000000000 --- a/docs-exp/auth-types.phoneauthprovider.providerid.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [PhoneAuthProvider](./auth-types.phoneauthprovider.md) > [providerId](./auth-types.phoneauthprovider.providerid.md) - -## PhoneAuthProvider.providerId property - -Signature: - -```typescript -readonly providerId: ProviderId; -``` diff --git a/docs-exp/auth-types.phoneauthprovider.verifyphonenumber.md b/docs-exp/auth-types.phoneauthprovider.verifyphonenumber.md deleted file mode 100644 index 83f934ddb82..00000000000 --- a/docs-exp/auth-types.phoneauthprovider.verifyphonenumber.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [PhoneAuthProvider](./auth-types.phoneauthprovider.md) > [verifyPhoneNumber](./auth-types.phoneauthprovider.verifyphonenumber.md) - -## PhoneAuthProvider.verifyPhoneNumber() method - -Signature: - -```typescript -verifyPhoneNumber( - phoneInfoOptions: PhoneInfoOptions | string, - applicationVerifier: ApplicationVerifier - ): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| phoneInfoOptions | [PhoneInfoOptions](./auth-types.phoneinfooptions.md) \| string | | -| applicationVerifier | [ApplicationVerifier](./auth-types.applicationverifier.md) | | - -Returns: - -Promise<string> - diff --git a/docs-exp/auth-types.phoneinfooptions.md b/docs-exp/auth-types.phoneinfooptions.md deleted file mode 100644 index a46d881bfb5..00000000000 --- a/docs-exp/auth-types.phoneinfooptions.md +++ /dev/null @@ -1,16 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [PhoneInfoOptions](./auth-types.phoneinfooptions.md) - -## PhoneInfoOptions type - -https://firebase.google.com/docs/reference/js/firebase.auth\#phoneinfooptions - -Signature: - -```typescript -export type PhoneInfoOptions = - | PhoneSingleFactorInfoOptions - | PhoneMultiFactorEnrollInfoOptions - | PhoneMultiFactorSignInInfoOptions; -``` diff --git a/docs-exp/auth-types.phonemultifactorassertion.md b/docs-exp/auth-types.phonemultifactorassertion.md deleted file mode 100644 index d2e6a47de25..00000000000 --- a/docs-exp/auth-types.phonemultifactorassertion.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [PhoneMultiFactorAssertion](./auth-types.phonemultifactorassertion.md) - -## PhoneMultiFactorAssertion interface - -https://firebase.google.com/docs/reference/js/firebase.auth.phonemultifactorassertion - -Signature: - -```typescript -export interface PhoneMultiFactorAssertion extends MultiFactorAssertion -``` -Extends: [MultiFactorAssertion](./auth-types.multifactorassertion.md) - diff --git a/docs-exp/auth-types.phonemultifactorenrollinfooptions.md b/docs-exp/auth-types.phonemultifactorenrollinfooptions.md deleted file mode 100644 index 447376d2446..00000000000 --- a/docs-exp/auth-types.phonemultifactorenrollinfooptions.md +++ /dev/null @@ -1,19 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [PhoneMultiFactorEnrollInfoOptions](./auth-types.phonemultifactorenrollinfooptions.md) - -## PhoneMultiFactorEnrollInfoOptions interface - -Signature: - -```typescript -export interface PhoneMultiFactorEnrollInfoOptions -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [phoneNumber](./auth-types.phonemultifactorenrollinfooptions.phonenumber.md) | string | | -| [session](./auth-types.phonemultifactorenrollinfooptions.session.md) | [MultiFactorSession](./auth-types.multifactorsession.md) | | - diff --git a/docs-exp/auth-types.phonemultifactorenrollinfooptions.phonenumber.md b/docs-exp/auth-types.phonemultifactorenrollinfooptions.phonenumber.md deleted file mode 100644 index 8fe88cafa7c..00000000000 --- a/docs-exp/auth-types.phonemultifactorenrollinfooptions.phonenumber.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [PhoneMultiFactorEnrollInfoOptions](./auth-types.phonemultifactorenrollinfooptions.md) > [phoneNumber](./auth-types.phonemultifactorenrollinfooptions.phonenumber.md) - -## PhoneMultiFactorEnrollInfoOptions.phoneNumber property - -Signature: - -```typescript -phoneNumber: string; -``` diff --git a/docs-exp/auth-types.phonemultifactorenrollinfooptions.session.md b/docs-exp/auth-types.phonemultifactorenrollinfooptions.session.md deleted file mode 100644 index db214681f5b..00000000000 --- a/docs-exp/auth-types.phonemultifactorenrollinfooptions.session.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [PhoneMultiFactorEnrollInfoOptions](./auth-types.phonemultifactorenrollinfooptions.md) > [session](./auth-types.phonemultifactorenrollinfooptions.session.md) - -## PhoneMultiFactorEnrollInfoOptions.session property - -Signature: - -```typescript -session: MultiFactorSession; -``` diff --git a/docs-exp/auth-types.phonemultifactorgenerator.assertion.md b/docs-exp/auth-types.phonemultifactorgenerator.assertion.md deleted file mode 100644 index 7a4565c9a98..00000000000 --- a/docs-exp/auth-types.phonemultifactorgenerator.assertion.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [PhoneMultiFactorGenerator](./auth-types.phonemultifactorgenerator.md) > [assertion](./auth-types.phonemultifactorgenerator.assertion.md) - -## PhoneMultiFactorGenerator.assertion() method - -Signature: - -```typescript -static assertion( - phoneAuthCredential: PhoneAuthCredential - ): PhoneMultiFactorAssertion; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| phoneAuthCredential | [PhoneAuthCredential](./auth-types.phoneauthcredential.md) | | - -Returns: - -[PhoneMultiFactorAssertion](./auth-types.phonemultifactorassertion.md) - diff --git a/docs-exp/auth-types.phonemultifactorgenerator.factor_id.md b/docs-exp/auth-types.phonemultifactorgenerator.factor_id.md deleted file mode 100644 index 94552bd4c90..00000000000 --- a/docs-exp/auth-types.phonemultifactorgenerator.factor_id.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [PhoneMultiFactorGenerator](./auth-types.phonemultifactorgenerator.md) > [FACTOR\_ID](./auth-types.phonemultifactorgenerator.factor_id.md) - -## PhoneMultiFactorGenerator.FACTOR\_ID property - -Signature: - -```typescript -static FACTOR_ID: ProviderId; -``` diff --git a/docs-exp/auth-types.phonemultifactorgenerator.md b/docs-exp/auth-types.phonemultifactorgenerator.md deleted file mode 100644 index 140e132e5c7..00000000000 --- a/docs-exp/auth-types.phonemultifactorgenerator.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [PhoneMultiFactorGenerator](./auth-types.phonemultifactorgenerator.md) - -## PhoneMultiFactorGenerator class - -https://firebase.google.com/docs/reference/js/firebase.auth.phonemultifactorgenerator - -Signature: - -```typescript -export abstract class PhoneMultiFactorGenerator -``` - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [FACTOR\_ID](./auth-types.phonemultifactorgenerator.factor_id.md) | static | [ProviderId](./auth-types.providerid.md) | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [assertion(phoneAuthCredential)](./auth-types.phonemultifactorgenerator.assertion.md) | static | | - diff --git a/docs-exp/auth-types.phonemultifactorsignininfooptions.md b/docs-exp/auth-types.phonemultifactorsignininfooptions.md deleted file mode 100644 index dae9188a691..00000000000 --- a/docs-exp/auth-types.phonemultifactorsignininfooptions.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [PhoneMultiFactorSignInInfoOptions](./auth-types.phonemultifactorsignininfooptions.md) - -## PhoneMultiFactorSignInInfoOptions interface - -Signature: - -```typescript -export interface PhoneMultiFactorSignInInfoOptions -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [multiFactorHint](./auth-types.phonemultifactorsignininfooptions.multifactorhint.md) | [MultiFactorInfo](./auth-types.multifactorinfo.md) | | -| [multiFactorUid](./auth-types.phonemultifactorsignininfooptions.multifactoruid.md) | string | | -| [session](./auth-types.phonemultifactorsignininfooptions.session.md) | [MultiFactorSession](./auth-types.multifactorsession.md) | | - diff --git a/docs-exp/auth-types.phonemultifactorsignininfooptions.multifactorhint.md b/docs-exp/auth-types.phonemultifactorsignininfooptions.multifactorhint.md deleted file mode 100644 index add03e11348..00000000000 --- a/docs-exp/auth-types.phonemultifactorsignininfooptions.multifactorhint.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [PhoneMultiFactorSignInInfoOptions](./auth-types.phonemultifactorsignininfooptions.md) > [multiFactorHint](./auth-types.phonemultifactorsignininfooptions.multifactorhint.md) - -## PhoneMultiFactorSignInInfoOptions.multiFactorHint property - -Signature: - -```typescript -multiFactorHint?: MultiFactorInfo; -``` diff --git a/docs-exp/auth-types.phonemultifactorsignininfooptions.multifactoruid.md b/docs-exp/auth-types.phonemultifactorsignininfooptions.multifactoruid.md deleted file mode 100644 index 684026a83f0..00000000000 --- a/docs-exp/auth-types.phonemultifactorsignininfooptions.multifactoruid.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [PhoneMultiFactorSignInInfoOptions](./auth-types.phonemultifactorsignininfooptions.md) > [multiFactorUid](./auth-types.phonemultifactorsignininfooptions.multifactoruid.md) - -## PhoneMultiFactorSignInInfoOptions.multiFactorUid property - -Signature: - -```typescript -multiFactorUid?: string; -``` diff --git a/docs-exp/auth-types.phonemultifactorsignininfooptions.session.md b/docs-exp/auth-types.phonemultifactorsignininfooptions.session.md deleted file mode 100644 index 7a6f51c6d60..00000000000 --- a/docs-exp/auth-types.phonemultifactorsignininfooptions.session.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [PhoneMultiFactorSignInInfoOptions](./auth-types.phonemultifactorsignininfooptions.md) > [session](./auth-types.phonemultifactorsignininfooptions.session.md) - -## PhoneMultiFactorSignInInfoOptions.session property - -Signature: - -```typescript -session: MultiFactorSession; -``` diff --git a/docs-exp/auth-types.phonesinglefactorinfooptions.md b/docs-exp/auth-types.phonesinglefactorinfooptions.md deleted file mode 100644 index 69f54678555..00000000000 --- a/docs-exp/auth-types.phonesinglefactorinfooptions.md +++ /dev/null @@ -1,18 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [PhoneSingleFactorInfoOptions](./auth-types.phonesinglefactorinfooptions.md) - -## PhoneSingleFactorInfoOptions interface - -Signature: - -```typescript -export interface PhoneSingleFactorInfoOptions -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [phoneNumber](./auth-types.phonesinglefactorinfooptions.phonenumber.md) | string | | - diff --git a/docs-exp/auth-types.phonesinglefactorinfooptions.phonenumber.md b/docs-exp/auth-types.phonesinglefactorinfooptions.phonenumber.md deleted file mode 100644 index 97e1de367f5..00000000000 --- a/docs-exp/auth-types.phonesinglefactorinfooptions.phonenumber.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [PhoneSingleFactorInfoOptions](./auth-types.phonesinglefactorinfooptions.md) > [phoneNumber](./auth-types.phonesinglefactorinfooptions.phonenumber.md) - -## PhoneSingleFactorInfoOptions.phoneNumber property - -Signature: - -```typescript -phoneNumber: string; -``` diff --git a/docs-exp/auth-types.popupredirectresolver.md b/docs-exp/auth-types.popupredirectresolver.md deleted file mode 100644 index 5faf7698f91..00000000000 --- a/docs-exp/auth-types.popupredirectresolver.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [PopupRedirectResolver](./auth-types.popupredirectresolver.md) - -## PopupRedirectResolver interface - -No documentation for this yet - -Signature: - -```typescript -export interface PopupRedirectResolver -``` diff --git a/docs-exp/auth-types.providerid.md b/docs-exp/auth-types.providerid.md deleted file mode 100644 index aaf1e9a3a35..00000000000 --- a/docs-exp/auth-types.providerid.md +++ /dev/null @@ -1,28 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ProviderId](./auth-types.providerid.md) - -## ProviderId enum - -Supported providers - -Signature: - -```typescript -export const enum ProviderId -``` - -## Enumeration Members - -| Member | Value | Description | -| --- | --- | --- | -| ANONYMOUS | 'anonymous' | | -| CUSTOM | 'custom' | | -| FACEBOOK | 'facebook.com' | | -| FIREBASE | 'firebase' | | -| GITHUB | 'github.com' | | -| GOOGLE | 'google.com' | | -| PASSWORD | 'password' | | -| PHONE | 'phone' | | -| TWITTER | 'twitter.com' | | - diff --git a/docs-exp/auth-types.reactnativeasyncstorage.getitem.md b/docs-exp/auth-types.reactnativeasyncstorage.getitem.md deleted file mode 100644 index 94b149edb56..00000000000 --- a/docs-exp/auth-types.reactnativeasyncstorage.getitem.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ReactNativeAsyncStorage](./auth-types.reactnativeasyncstorage.md) > [getItem](./auth-types.reactnativeasyncstorage.getitem.md) - -## ReactNativeAsyncStorage.getItem() method - -Signature: - -```typescript -getItem(key: string): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| key | string | | - -Returns: - -Promise<string \| null> - diff --git a/docs-exp/auth-types.reactnativeasyncstorage.md b/docs-exp/auth-types.reactnativeasyncstorage.md deleted file mode 100644 index 3804977e864..00000000000 --- a/docs-exp/auth-types.reactnativeasyncstorage.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ReactNativeAsyncStorage](./auth-types.reactnativeasyncstorage.md) - -## ReactNativeAsyncStorage interface - -Signature: - -```typescript -export interface ReactNativeAsyncStorage -``` - -## Methods - -| Method | Description | -| --- | --- | -| [getItem(key)](./auth-types.reactnativeasyncstorage.getitem.md) | | -| [removeItem(key)](./auth-types.reactnativeasyncstorage.removeitem.md) | | -| [setItem(key, value)](./auth-types.reactnativeasyncstorage.setitem.md) | | - diff --git a/docs-exp/auth-types.reactnativeasyncstorage.removeitem.md b/docs-exp/auth-types.reactnativeasyncstorage.removeitem.md deleted file mode 100644 index 98e273c4d05..00000000000 --- a/docs-exp/auth-types.reactnativeasyncstorage.removeitem.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ReactNativeAsyncStorage](./auth-types.reactnativeasyncstorage.md) > [removeItem](./auth-types.reactnativeasyncstorage.removeitem.md) - -## ReactNativeAsyncStorage.removeItem() method - -Signature: - -```typescript -removeItem(key: string): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| key | string | | - -Returns: - -Promise<void> - diff --git a/docs-exp/auth-types.reactnativeasyncstorage.setitem.md b/docs-exp/auth-types.reactnativeasyncstorage.setitem.md deleted file mode 100644 index 17531386e46..00000000000 --- a/docs-exp/auth-types.reactnativeasyncstorage.setitem.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [ReactNativeAsyncStorage](./auth-types.reactnativeasyncstorage.md) > [setItem](./auth-types.reactnativeasyncstorage.setitem.md) - -## ReactNativeAsyncStorage.setItem() method - -Signature: - -```typescript -setItem(key: string, value: string): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| key | string | | -| value | string | | - -Returns: - -Promise<void> - diff --git a/docs-exp/auth-types.recaptchaverifier._constructor_.md b/docs-exp/auth-types.recaptchaverifier._constructor_.md deleted file mode 100644 index d58af83f20a..00000000000 --- a/docs-exp/auth-types.recaptchaverifier._constructor_.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [RecaptchaVerifier](./auth-types.recaptchaverifier.md) > [(constructor)](./auth-types.recaptchaverifier._constructor_.md) - -## RecaptchaVerifier.(constructor) - -Constructs a new instance of the `RecaptchaVerifier` class - -Signature: - -```typescript -constructor( - container: any | string, - parameters?: Object | null, - auth?: Auth | null - ); -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| container | any \| string | | -| parameters | Object \| null | | -| auth | [Auth](./auth-types.auth.md) \| null | | - diff --git a/docs-exp/auth-types.recaptchaverifier.clear.md b/docs-exp/auth-types.recaptchaverifier.clear.md deleted file mode 100644 index 55d3f1ca3c1..00000000000 --- a/docs-exp/auth-types.recaptchaverifier.clear.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [RecaptchaVerifier](./auth-types.recaptchaverifier.md) > [clear](./auth-types.recaptchaverifier.clear.md) - -## RecaptchaVerifier.clear() method - -Signature: - -```typescript -clear(): void; -``` -Returns: - -void - diff --git a/docs-exp/auth-types.recaptchaverifier.md b/docs-exp/auth-types.recaptchaverifier.md deleted file mode 100644 index 1c9bdf40a67..00000000000 --- a/docs-exp/auth-types.recaptchaverifier.md +++ /dev/null @@ -1,35 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [RecaptchaVerifier](./auth-types.recaptchaverifier.md) - -## RecaptchaVerifier class - -https://firebase.google.com/docs/reference/js/firebase.auth.RecaptchaVerifier - -Signature: - -```typescript -export abstract class RecaptchaVerifier implements ApplicationVerifier -``` -Implements: [ApplicationVerifier](./auth-types.applicationverifier.md) - -## Constructors - -| Constructor | Modifiers | Description | -| --- | --- | --- | -| [(constructor)(container, parameters, auth)](./auth-types.recaptchaverifier._constructor_.md) | | Constructs a new instance of the RecaptchaVerifier class | - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [type](./auth-types.recaptchaverifier.type.md) | | string | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [clear()](./auth-types.recaptchaverifier.clear.md) | | | -| [render()](./auth-types.recaptchaverifier.render.md) | | | -| [verify()](./auth-types.recaptchaverifier.verify.md) | | | - diff --git a/docs-exp/auth-types.recaptchaverifier.render.md b/docs-exp/auth-types.recaptchaverifier.render.md deleted file mode 100644 index 728caab96d0..00000000000 --- a/docs-exp/auth-types.recaptchaverifier.render.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [RecaptchaVerifier](./auth-types.recaptchaverifier.md) > [render](./auth-types.recaptchaverifier.render.md) - -## RecaptchaVerifier.render() method - -Signature: - -```typescript -render(): Promise; -``` -Returns: - -Promise<number> - diff --git a/docs-exp/auth-types.recaptchaverifier.type.md b/docs-exp/auth-types.recaptchaverifier.type.md deleted file mode 100644 index 4e000332c59..00000000000 --- a/docs-exp/auth-types.recaptchaverifier.type.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [RecaptchaVerifier](./auth-types.recaptchaverifier.md) > [type](./auth-types.recaptchaverifier.type.md) - -## RecaptchaVerifier.type property - -Signature: - -```typescript -readonly type: string; -``` diff --git a/docs-exp/auth-types.recaptchaverifier.verify.md b/docs-exp/auth-types.recaptchaverifier.verify.md deleted file mode 100644 index e0335cb7a98..00000000000 --- a/docs-exp/auth-types.recaptchaverifier.verify.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [RecaptchaVerifier](./auth-types.recaptchaverifier.md) > [verify](./auth-types.recaptchaverifier.verify.md) - -## RecaptchaVerifier.verify() method - -Signature: - -```typescript -verify(): Promise; -``` -Returns: - -Promise<string> - diff --git a/docs-exp/auth-types.signinmethod.md b/docs-exp/auth-types.signinmethod.md deleted file mode 100644 index dfd7ca0ceb0..00000000000 --- a/docs-exp/auth-types.signinmethod.md +++ /dev/null @@ -1,27 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [SignInMethod](./auth-types.signinmethod.md) - -## SignInMethod enum - -Supported sign in methods - -Signature: - -```typescript -export const enum SignInMethod -``` - -## Enumeration Members - -| Member | Value | Description | -| --- | --- | --- | -| ANONYMOUS | 'anonymous' | | -| EMAIL\_LINK | 'emailLink' | | -| EMAIL\_PASSWORD | 'password' | | -| FACEBOOK | 'facebook.com' | | -| GITHUB | 'github.com' | | -| GOOGLE | 'google.com' | | -| PHONE | 'phone' | | -| TWITTER | 'twitter.com' | | - diff --git a/docs-exp/auth-types.user.delete.md b/docs-exp/auth-types.user.delete.md deleted file mode 100644 index e8868741dc3..00000000000 --- a/docs-exp/auth-types.user.delete.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [User](./auth-types.user.md) > [delete](./auth-types.user.delete.md) - -## User.delete() method - -Signature: - -```typescript -delete(): Promise; -``` -Returns: - -Promise<void> - diff --git a/docs-exp/auth-types.user.emailverified.md b/docs-exp/auth-types.user.emailverified.md deleted file mode 100644 index 98b1999336f..00000000000 --- a/docs-exp/auth-types.user.emailverified.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [User](./auth-types.user.md) > [emailVerified](./auth-types.user.emailverified.md) - -## User.emailVerified property - -Signature: - -```typescript -readonly emailVerified: boolean; -``` diff --git a/docs-exp/auth-types.user.getidtoken.md b/docs-exp/auth-types.user.getidtoken.md deleted file mode 100644 index 83a13d93420..00000000000 --- a/docs-exp/auth-types.user.getidtoken.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [User](./auth-types.user.md) > [getIdToken](./auth-types.user.getidtoken.md) - -## User.getIdToken() method - -Signature: - -```typescript -getIdToken(forceRefresh?: boolean): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| forceRefresh | boolean | | - -Returns: - -Promise<string> - diff --git a/docs-exp/auth-types.user.getidtokenresult.md b/docs-exp/auth-types.user.getidtokenresult.md deleted file mode 100644 index 29fa689f867..00000000000 --- a/docs-exp/auth-types.user.getidtokenresult.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [User](./auth-types.user.md) > [getIdTokenResult](./auth-types.user.getidtokenresult.md) - -## User.getIdTokenResult() method - -Signature: - -```typescript -getIdTokenResult(forceRefresh?: boolean): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| forceRefresh | boolean | | - -Returns: - -Promise<[IdTokenResult](./auth-types.idtokenresult.md)> - diff --git a/docs-exp/auth-types.user.isanonymous.md b/docs-exp/auth-types.user.isanonymous.md deleted file mode 100644 index 9e679c81b09..00000000000 --- a/docs-exp/auth-types.user.isanonymous.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [User](./auth-types.user.md) > [isAnonymous](./auth-types.user.isanonymous.md) - -## User.isAnonymous property - -Signature: - -```typescript -readonly isAnonymous: boolean; -``` diff --git a/docs-exp/auth-types.user.md b/docs-exp/auth-types.user.md deleted file mode 100644 index 73a5dcbd3ae..00000000000 --- a/docs-exp/auth-types.user.md +++ /dev/null @@ -1,36 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [User](./auth-types.user.md) - -## User interface - -https://firebase.google.com/docs/reference/js/firebase.User - -Signature: - -```typescript -export interface User extends UserInfo -``` -Extends: [UserInfo](./auth-types.userinfo.md) - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [emailVerified](./auth-types.user.emailverified.md) | boolean | | -| [isAnonymous](./auth-types.user.isanonymous.md) | boolean | | -| [metadata](./auth-types.user.metadata.md) | [UserMetadata](./auth-types.usermetadata.md) | | -| [providerData](./auth-types.user.providerdata.md) | [UserInfo](./auth-types.userinfo.md)\[\] | | -| [refreshToken](./auth-types.user.refreshtoken.md) | string | | -| [tenantId](./auth-types.user.tenantid.md) | string \| null | | - -## Methods - -| Method | Description | -| --- | --- | -| [delete()](./auth-types.user.delete.md) | | -| [getIdToken(forceRefresh)](./auth-types.user.getidtoken.md) | | -| [getIdTokenResult(forceRefresh)](./auth-types.user.getidtokenresult.md) | | -| [reload()](./auth-types.user.reload.md) | | -| [toJSON()](./auth-types.user.tojson.md) | | - diff --git a/docs-exp/auth-types.user.metadata.md b/docs-exp/auth-types.user.metadata.md deleted file mode 100644 index 5ea9521b2cd..00000000000 --- a/docs-exp/auth-types.user.metadata.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [User](./auth-types.user.md) > [metadata](./auth-types.user.metadata.md) - -## User.metadata property - -Signature: - -```typescript -readonly metadata: UserMetadata; -``` diff --git a/docs-exp/auth-types.user.providerdata.md b/docs-exp/auth-types.user.providerdata.md deleted file mode 100644 index 6c7cf6def44..00000000000 --- a/docs-exp/auth-types.user.providerdata.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [User](./auth-types.user.md) > [providerData](./auth-types.user.providerdata.md) - -## User.providerData property - -Signature: - -```typescript -readonly providerData: UserInfo[]; -``` diff --git a/docs-exp/auth-types.user.refreshtoken.md b/docs-exp/auth-types.user.refreshtoken.md deleted file mode 100644 index 953ed1c4e54..00000000000 --- a/docs-exp/auth-types.user.refreshtoken.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [User](./auth-types.user.md) > [refreshToken](./auth-types.user.refreshtoken.md) - -## User.refreshToken property - -Signature: - -```typescript -readonly refreshToken: string; -``` diff --git a/docs-exp/auth-types.user.reload.md b/docs-exp/auth-types.user.reload.md deleted file mode 100644 index 3a9879344d0..00000000000 --- a/docs-exp/auth-types.user.reload.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [User](./auth-types.user.md) > [reload](./auth-types.user.reload.md) - -## User.reload() method - -Signature: - -```typescript -reload(): Promise; -``` -Returns: - -Promise<void> - diff --git a/docs-exp/auth-types.user.tenantid.md b/docs-exp/auth-types.user.tenantid.md deleted file mode 100644 index 5a4f180bd65..00000000000 --- a/docs-exp/auth-types.user.tenantid.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [User](./auth-types.user.md) > [tenantId](./auth-types.user.tenantid.md) - -## User.tenantId property - -Signature: - -```typescript -readonly tenantId: string | null; -``` diff --git a/docs-exp/auth-types.user.tojson.md b/docs-exp/auth-types.user.tojson.md deleted file mode 100644 index 31d1264ddae..00000000000 --- a/docs-exp/auth-types.user.tojson.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [User](./auth-types.user.md) > [toJSON](./auth-types.user.tojson.md) - -## User.toJSON() method - -Signature: - -```typescript -toJSON(): object; -``` -Returns: - -object - diff --git a/docs-exp/auth-types.usercredential.md b/docs-exp/auth-types.usercredential.md deleted file mode 100644 index a13cfd6eb6e..00000000000 --- a/docs-exp/auth-types.usercredential.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [UserCredential](./auth-types.usercredential.md) - -## UserCredential interface - -https://firebase.google.com/docs/reference/js/firebase.auth\#usercredential - -Signature: - -```typescript -export interface UserCredential -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [operationType](./auth-types.usercredential.operationtype.md) | [OperationType](./auth-types.operationtype.md) | | -| [providerId](./auth-types.usercredential.providerid.md) | [ProviderId](./auth-types.providerid.md) \| null | | -| [user](./auth-types.usercredential.user.md) | [User](./auth-types.user.md) | | - diff --git a/docs-exp/auth-types.usercredential.operationtype.md b/docs-exp/auth-types.usercredential.operationtype.md deleted file mode 100644 index a36c2ad6eb7..00000000000 --- a/docs-exp/auth-types.usercredential.operationtype.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [UserCredential](./auth-types.usercredential.md) > [operationType](./auth-types.usercredential.operationtype.md) - -## UserCredential.operationType property - -Signature: - -```typescript -operationType: OperationType; -``` diff --git a/docs-exp/auth-types.usercredential.providerid.md b/docs-exp/auth-types.usercredential.providerid.md deleted file mode 100644 index 425e9fc157a..00000000000 --- a/docs-exp/auth-types.usercredential.providerid.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [UserCredential](./auth-types.usercredential.md) > [providerId](./auth-types.usercredential.providerid.md) - -## UserCredential.providerId property - -Signature: - -```typescript -providerId: ProviderId | null; -``` diff --git a/docs-exp/auth-types.usercredential.user.md b/docs-exp/auth-types.usercredential.user.md deleted file mode 100644 index e89a6407fa9..00000000000 --- a/docs-exp/auth-types.usercredential.user.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [UserCredential](./auth-types.usercredential.md) > [user](./auth-types.usercredential.user.md) - -## UserCredential.user property - -Signature: - -```typescript -user: User; -``` diff --git a/docs-exp/auth-types.userinfo.displayname.md b/docs-exp/auth-types.userinfo.displayname.md deleted file mode 100644 index 87e72d23cc3..00000000000 --- a/docs-exp/auth-types.userinfo.displayname.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [UserInfo](./auth-types.userinfo.md) > [displayName](./auth-types.userinfo.displayname.md) - -## UserInfo.displayName property - -Signature: - -```typescript -readonly displayName: string | null; -``` diff --git a/docs-exp/auth-types.userinfo.email.md b/docs-exp/auth-types.userinfo.email.md deleted file mode 100644 index 32d0cb4813e..00000000000 --- a/docs-exp/auth-types.userinfo.email.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [UserInfo](./auth-types.userinfo.md) > [email](./auth-types.userinfo.email.md) - -## UserInfo.email property - -Signature: - -```typescript -readonly email: string | null; -``` diff --git a/docs-exp/auth-types.userinfo.md b/docs-exp/auth-types.userinfo.md deleted file mode 100644 index ff8607e0bf1..00000000000 --- a/docs-exp/auth-types.userinfo.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [UserInfo](./auth-types.userinfo.md) - -## UserInfo interface - -https://firebase.google.com/docs/reference/js/firebase.UserInfo - -Signature: - -```typescript -export interface UserInfo -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [displayName](./auth-types.userinfo.displayname.md) | string \| null | | -| [email](./auth-types.userinfo.email.md) | string \| null | | -| [phoneNumber](./auth-types.userinfo.phonenumber.md) | string \| null | | -| [photoURL](./auth-types.userinfo.photourl.md) | string \| null | | -| [providerId](./auth-types.userinfo.providerid.md) | string | | -| [uid](./auth-types.userinfo.uid.md) | string | | - diff --git a/docs-exp/auth-types.userinfo.phonenumber.md b/docs-exp/auth-types.userinfo.phonenumber.md deleted file mode 100644 index 38a13f54cec..00000000000 --- a/docs-exp/auth-types.userinfo.phonenumber.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [UserInfo](./auth-types.userinfo.md) > [phoneNumber](./auth-types.userinfo.phonenumber.md) - -## UserInfo.phoneNumber property - -Signature: - -```typescript -readonly phoneNumber: string | null; -``` diff --git a/docs-exp/auth-types.userinfo.photourl.md b/docs-exp/auth-types.userinfo.photourl.md deleted file mode 100644 index e096f2726b2..00000000000 --- a/docs-exp/auth-types.userinfo.photourl.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [UserInfo](./auth-types.userinfo.md) > [photoURL](./auth-types.userinfo.photourl.md) - -## UserInfo.photoURL property - -Signature: - -```typescript -readonly photoURL: string | null; -``` diff --git a/docs-exp/auth-types.userinfo.providerid.md b/docs-exp/auth-types.userinfo.providerid.md deleted file mode 100644 index 46602904366..00000000000 --- a/docs-exp/auth-types.userinfo.providerid.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [UserInfo](./auth-types.userinfo.md) > [providerId](./auth-types.userinfo.providerid.md) - -## UserInfo.providerId property - -Signature: - -```typescript -readonly providerId: string; -``` diff --git a/docs-exp/auth-types.userinfo.uid.md b/docs-exp/auth-types.userinfo.uid.md deleted file mode 100644 index 6a5a4b5ee33..00000000000 --- a/docs-exp/auth-types.userinfo.uid.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [UserInfo](./auth-types.userinfo.md) > [uid](./auth-types.userinfo.uid.md) - -## UserInfo.uid property - -Signature: - -```typescript -readonly uid: string; -``` diff --git a/docs-exp/auth-types.usermetadata.creationtime.md b/docs-exp/auth-types.usermetadata.creationtime.md deleted file mode 100644 index 8f878555a12..00000000000 --- a/docs-exp/auth-types.usermetadata.creationtime.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [UserMetadata](./auth-types.usermetadata.md) > [creationTime](./auth-types.usermetadata.creationtime.md) - -## UserMetadata.creationTime property - -Signature: - -```typescript -readonly creationTime?: string; -``` diff --git a/docs-exp/auth-types.usermetadata.lastsignintime.md b/docs-exp/auth-types.usermetadata.lastsignintime.md deleted file mode 100644 index a27139887e5..00000000000 --- a/docs-exp/auth-types.usermetadata.lastsignintime.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [UserMetadata](./auth-types.usermetadata.md) > [lastSignInTime](./auth-types.usermetadata.lastsignintime.md) - -## UserMetadata.lastSignInTime property - -Signature: - -```typescript -readonly lastSignInTime?: string; -``` diff --git a/docs-exp/auth-types.usermetadata.md b/docs-exp/auth-types.usermetadata.md deleted file mode 100644 index 7f7aa8397e4..00000000000 --- a/docs-exp/auth-types.usermetadata.md +++ /dev/null @@ -1,21 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [UserMetadata](./auth-types.usermetadata.md) - -## UserMetadata interface - -https://firebase.google.com/docs/reference/js/firebase.auth.UserMetadata - -Signature: - -```typescript -export interface UserMetadata -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [creationTime](./auth-types.usermetadata.creationtime.md) | string | | -| [lastSignInTime](./auth-types.usermetadata.lastsignintime.md) | string | | - diff --git a/docs-exp/auth-types.userprofile.md b/docs-exp/auth-types.userprofile.md deleted file mode 100644 index 4a099d88872..00000000000 --- a/docs-exp/auth-types.userprofile.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth-types](./auth-types.md) > [UserProfile](./auth-types.userprofile.md) - -## UserProfile type - -User profile used in `AdditionalUserInfo` - -Signature: - -```typescript -export type UserProfile = Record; -``` diff --git a/docs-exp/auth.actioncodeurl.apikey.md b/docs-exp/auth.actioncodeurl.apikey.md deleted file mode 100644 index adb1e936942..00000000000 --- a/docs-exp/auth.actioncodeurl.apikey.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [ActionCodeURL](./auth.actioncodeurl.md) > [apiKey](./auth.actioncodeurl.apikey.md) - -## ActionCodeURL.apiKey property - -The API key of the email action link. - -Signature: - -```typescript -readonly apiKey: string; -``` diff --git a/docs-exp/auth.actioncodeurl.code.md b/docs-exp/auth.actioncodeurl.code.md deleted file mode 100644 index 8cc8b393b82..00000000000 --- a/docs-exp/auth.actioncodeurl.code.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [ActionCodeURL](./auth.actioncodeurl.md) > [code](./auth.actioncodeurl.code.md) - -## ActionCodeURL.code property - -The action code of the email action link. - -Signature: - -```typescript -readonly code: string; -``` diff --git a/docs-exp/auth.actioncodeurl.continueurl.md b/docs-exp/auth.actioncodeurl.continueurl.md deleted file mode 100644 index b619a32feae..00000000000 --- a/docs-exp/auth.actioncodeurl.continueurl.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [ActionCodeURL](./auth.actioncodeurl.md) > [continueUrl](./auth.actioncodeurl.continueurl.md) - -## ActionCodeURL.continueUrl property - -The continue URL of the email action link. Null if not provided. - -Signature: - -```typescript -readonly continueUrl: string | null; -``` diff --git a/docs-exp/auth.actioncodeurl.languagecode.md b/docs-exp/auth.actioncodeurl.languagecode.md deleted file mode 100644 index 083c3a17906..00000000000 --- a/docs-exp/auth.actioncodeurl.languagecode.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [ActionCodeURL](./auth.actioncodeurl.md) > [languageCode](./auth.actioncodeurl.languagecode.md) - -## ActionCodeURL.languageCode property - -The language code of the email action link. Null if not provided. - -Signature: - -```typescript -readonly languageCode: string | null; -``` diff --git a/docs-exp/auth.actioncodeurl.md b/docs-exp/auth.actioncodeurl.md deleted file mode 100644 index 30d65d287c6..00000000000 --- a/docs-exp/auth.actioncodeurl.md +++ /dev/null @@ -1,32 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [ActionCodeURL](./auth.actioncodeurl.md) - -## ActionCodeURL class - -A utility class to parse email action URLs such as password reset, email verification, email link sign in, etc. - -Signature: - -```typescript -export declare class ActionCodeURL implements externs.ActionCodeURL -``` -Implements: externs.[ActionCodeURL](./auth-types.actioncodeurl.md) - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [apiKey](./auth.actioncodeurl.apikey.md) | | string | The API key of the email action link. | -| [code](./auth.actioncodeurl.code.md) | | string | The action code of the email action link. | -| [continueUrl](./auth.actioncodeurl.continueurl.md) | | string \| null | The continue URL of the email action link. Null if not provided. | -| [languageCode](./auth.actioncodeurl.languagecode.md) | | string \| null | The language code of the email action link. Null if not provided. | -| [operation](./auth.actioncodeurl.operation.md) | | externs.[Operation](./auth-types.operation.md) | The action performed by the email action link. It returns from one of the types from | -| [tenantId](./auth.actioncodeurl.tenantid.md) | | string \| null | The tenant ID of the email action link. Null if the email action is from the parent project. | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [parseLink(link)](./auth.actioncodeurl.parselink.md) | static | Parses the email action link string and returns an ActionCodeURL object if the link is valid, otherwise returns null. | - diff --git a/docs-exp/auth.actioncodeurl.operation.md b/docs-exp/auth.actioncodeurl.operation.md deleted file mode 100644 index 0c39873bc6b..00000000000 --- a/docs-exp/auth.actioncodeurl.operation.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [ActionCodeURL](./auth.actioncodeurl.md) > [operation](./auth.actioncodeurl.operation.md) - -## ActionCodeURL.operation property - -The action performed by the email action link. It returns from one of the types from - -Signature: - -```typescript -readonly operation: externs.Operation; -``` diff --git a/docs-exp/auth.actioncodeurl.parselink.md b/docs-exp/auth.actioncodeurl.parselink.md deleted file mode 100644 index 4e2861b1efa..00000000000 --- a/docs-exp/auth.actioncodeurl.parselink.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [ActionCodeURL](./auth.actioncodeurl.md) > [parseLink](./auth.actioncodeurl.parselink.md) - -## ActionCodeURL.parseLink() method - -Parses the email action link string and returns an ActionCodeURL object if the link is valid, otherwise returns null. - -Signature: - -```typescript -static parseLink(link: string): externs.ActionCodeURL | null; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| link | string | The email action link string. | - -Returns: - -externs.[ActionCodeURL](./auth-types.actioncodeurl.md) \| null - -The ActionCodeURL object, or null if the link is invalid. - diff --git a/docs-exp/auth.actioncodeurl.tenantid.md b/docs-exp/auth.actioncodeurl.tenantid.md deleted file mode 100644 index 7b43806b4c6..00000000000 --- a/docs-exp/auth.actioncodeurl.tenantid.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [ActionCodeURL](./auth.actioncodeurl.md) > [tenantId](./auth.actioncodeurl.tenantid.md) - -## ActionCodeURL.tenantId property - -The tenant ID of the email action link. Null if the email action is from the parent project. - -Signature: - -```typescript -readonly tenantId: string | null; -``` diff --git a/docs-exp/auth.applyactioncode.md b/docs-exp/auth.applyactioncode.md deleted file mode 100644 index c2bcb0f8416..00000000000 --- a/docs-exp/auth.applyactioncode.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [applyActionCode](./auth.applyactioncode.md) - -## applyActionCode() function - -Signature: - -```typescript -export declare function applyActionCode(auth: externs.Auth, oobCode: string): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | externs.[Auth](./auth-types.auth.md) | | -| oobCode | string | | - -Returns: - -Promise<void> - diff --git a/docs-exp/auth.authcredential._constructor_.md b/docs-exp/auth.authcredential._constructor_.md deleted file mode 100644 index b5c53f465e3..00000000000 --- a/docs-exp/auth.authcredential._constructor_.md +++ /dev/null @@ -1,21 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [AuthCredential](./auth.authcredential.md) > [(constructor)](./auth.authcredential._constructor_.md) - -## AuthCredential.(constructor) - -Constructs a new instance of the `AuthCredential` class - -Signature: - -```typescript -protected constructor(providerId: string, signInMethod: string); -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| providerId | string | | -| signInMethod | string | | - diff --git a/docs-exp/auth.authcredential._getidtokenresponse.md b/docs-exp/auth.authcredential._getidtokenresponse.md deleted file mode 100644 index 03c42c19047..00000000000 --- a/docs-exp/auth.authcredential._getidtokenresponse.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [AuthCredential](./auth.authcredential.md) > [\_getIdTokenResponse](./auth.authcredential._getidtokenresponse.md) - -## AuthCredential.\_getIdTokenResponse() method - -Signature: - -```typescript -_getIdTokenResponse(_auth: Auth): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| \_auth | Auth | | - -Returns: - -Promise<PhoneOrOauthTokenResponse> - diff --git a/docs-exp/auth.authcredential._getreauthenticationresolver.md b/docs-exp/auth.authcredential._getreauthenticationresolver.md deleted file mode 100644 index db7f3688f37..00000000000 --- a/docs-exp/auth.authcredential._getreauthenticationresolver.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [AuthCredential](./auth.authcredential.md) > [\_getReauthenticationResolver](./auth.authcredential._getreauthenticationresolver.md) - -## AuthCredential.\_getReauthenticationResolver() method - -Signature: - -```typescript -_getReauthenticationResolver(_auth: Auth): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| \_auth | Auth | | - -Returns: - -Promise<IdTokenResponse> - diff --git a/docs-exp/auth.authcredential._linktoidtoken.md b/docs-exp/auth.authcredential._linktoidtoken.md deleted file mode 100644 index 67bcf7c2ddc..00000000000 --- a/docs-exp/auth.authcredential._linktoidtoken.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [AuthCredential](./auth.authcredential.md) > [\_linkToIdToken](./auth.authcredential._linktoidtoken.md) - -## AuthCredential.\_linkToIdToken() method - -Signature: - -```typescript -_linkToIdToken(_auth: Auth, _idToken: string): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| \_auth | Auth | | -| \_idToken | string | | - -Returns: - -Promise<IdTokenResponse> - diff --git a/docs-exp/auth.authcredential.md b/docs-exp/auth.authcredential.md deleted file mode 100644 index 9afaf9f26e8..00000000000 --- a/docs-exp/auth.authcredential.md +++ /dev/null @@ -1,34 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [AuthCredential](./auth.authcredential.md) - -## AuthCredential class - -Signature: - -```typescript -export declare class AuthCredential -``` - -## Constructors - -| Constructor | Modifiers | Description | -| --- | --- | --- | -| [(constructor)(providerId, signInMethod)](./auth.authcredential._constructor_.md) | | Constructs a new instance of the AuthCredential class | - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [providerId](./auth.authcredential.providerid.md) | | string | | -| [signInMethod](./auth.authcredential.signinmethod.md) | | string | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [\_getIdTokenResponse(\_auth)](./auth.authcredential._getidtokenresponse.md) | | | -| [\_getReauthenticationResolver(\_auth)](./auth.authcredential._getreauthenticationresolver.md) | | | -| [\_linkToIdToken(\_auth, \_idToken)](./auth.authcredential._linktoidtoken.md) | | | -| [toJSON()](./auth.authcredential.tojson.md) | | | - diff --git a/docs-exp/auth.authcredential.providerid.md b/docs-exp/auth.authcredential.providerid.md deleted file mode 100644 index 181c671bdea..00000000000 --- a/docs-exp/auth.authcredential.providerid.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [AuthCredential](./auth.authcredential.md) > [providerId](./auth.authcredential.providerid.md) - -## AuthCredential.providerId property - -Signature: - -```typescript -readonly providerId: string; -``` diff --git a/docs-exp/auth.authcredential.signinmethod.md b/docs-exp/auth.authcredential.signinmethod.md deleted file mode 100644 index 6c9034f18ef..00000000000 --- a/docs-exp/auth.authcredential.signinmethod.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [AuthCredential](./auth.authcredential.md) > [signInMethod](./auth.authcredential.signinmethod.md) - -## AuthCredential.signInMethod property - -Signature: - -```typescript -readonly signInMethod: string; -``` diff --git a/docs-exp/auth.authcredential.tojson.md b/docs-exp/auth.authcredential.tojson.md deleted file mode 100644 index cf6beaa1d04..00000000000 --- a/docs-exp/auth.authcredential.tojson.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [AuthCredential](./auth.authcredential.md) > [toJSON](./auth.authcredential.tojson.md) - -## AuthCredential.toJSON() method - -Signature: - -```typescript -toJSON(): object; -``` -Returns: - -object - diff --git a/docs-exp/auth.browserlocalpersistence.md b/docs-exp/auth.browserlocalpersistence.md deleted file mode 100644 index fb783710869..00000000000 --- a/docs-exp/auth.browserlocalpersistence.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [browserLocalPersistence](./auth.browserlocalpersistence.md) - -## browserLocalPersistence variable - -Signature: - -```typescript -browserLocalPersistence: externs.Persistence -``` diff --git a/docs-exp/auth.browserpopupredirectresolver.md b/docs-exp/auth.browserpopupredirectresolver.md deleted file mode 100644 index fcf0fe7fd25..00000000000 --- a/docs-exp/auth.browserpopupredirectresolver.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [browserPopupRedirectResolver](./auth.browserpopupredirectresolver.md) - -## browserPopupRedirectResolver variable - -Signature: - -```typescript -browserPopupRedirectResolver: externs.PopupRedirectResolver -``` diff --git a/docs-exp/auth.browsersessionpersistence.md b/docs-exp/auth.browsersessionpersistence.md deleted file mode 100644 index 614260f5092..00000000000 --- a/docs-exp/auth.browsersessionpersistence.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [browserSessionPersistence](./auth.browsersessionpersistence.md) - -## browserSessionPersistence variable - -Signature: - -```typescript -browserSessionPersistence: externs.Persistence -``` diff --git a/docs-exp/auth.checkactioncode.md b/docs-exp/auth.checkactioncode.md deleted file mode 100644 index 349fd4daba4..00000000000 --- a/docs-exp/auth.checkactioncode.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [checkActionCode](./auth.checkactioncode.md) - -## checkActionCode() function - -Signature: - -```typescript -export declare function checkActionCode(auth: externs.Auth, oobCode: string): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | externs.[Auth](./auth-types.auth.md) | | -| oobCode | string | | - -Returns: - -Promise<externs.[ActionCodeInfo](./auth-types.actioncodeinfo.md)> - diff --git a/docs-exp/auth.confirmpasswordreset.md b/docs-exp/auth.confirmpasswordreset.md deleted file mode 100644 index 2006eaa961b..00000000000 --- a/docs-exp/auth.confirmpasswordreset.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [confirmPasswordReset](./auth.confirmpasswordreset.md) - -## confirmPasswordReset() function - -Signature: - -```typescript -export declare function confirmPasswordReset(auth: externs.Auth, oobCode: string, newPassword: string): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | externs.[Auth](./auth-types.auth.md) | | -| oobCode | string | | -| newPassword | string | | - -Returns: - -Promise<void> - diff --git a/docs-exp/auth.createuserwithemailandpassword.md b/docs-exp/auth.createuserwithemailandpassword.md deleted file mode 100644 index 9b7b66ab58f..00000000000 --- a/docs-exp/auth.createuserwithemailandpassword.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [createUserWithEmailAndPassword](./auth.createuserwithemailandpassword.md) - -## createUserWithEmailAndPassword() function - -Signature: - -```typescript -export declare function createUserWithEmailAndPassword(auth: externs.Auth, email: string, password: string): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | externs.[Auth](./auth-types.auth.md) | | -| email | string | | -| password | string | | - -Returns: - -Promise<externs.[UserCredential](./auth-types.usercredential.md)> - diff --git a/docs-exp/auth.deleteuser.md b/docs-exp/auth.deleteuser.md deleted file mode 100644 index 3cc1811f228..00000000000 --- a/docs-exp/auth.deleteuser.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [deleteUser](./auth.deleteuser.md) - -## deleteUser() function - -Signature: - -```typescript -export declare function deleteUser(user: externs.User): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| user | externs.[User](./auth-types.user.md) | | - -Returns: - -Promise<void> - diff --git a/docs-exp/auth.emailauthcredential._fromemailandcode.md b/docs-exp/auth.emailauthcredential._fromemailandcode.md deleted file mode 100644 index e446f40f791..00000000000 --- a/docs-exp/auth.emailauthcredential._fromemailandcode.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [EmailAuthCredential](./auth.emailauthcredential.md) > [\_fromEmailAndCode](./auth.emailauthcredential._fromemailandcode.md) - -## EmailAuthCredential.\_fromEmailAndCode() method - -Signature: - -```typescript -static _fromEmailAndCode(email: string, oobCode: string, tenantId?: string | null): EmailAuthCredential; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| email | string | | -| oobCode | string | | -| tenantId | string \| null | | - -Returns: - -[EmailAuthCredential](./auth.emailauthcredential.md) - diff --git a/docs-exp/auth.emailauthcredential._fromemailandpassword.md b/docs-exp/auth.emailauthcredential._fromemailandpassword.md deleted file mode 100644 index f362d10f488..00000000000 --- a/docs-exp/auth.emailauthcredential._fromemailandpassword.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [EmailAuthCredential](./auth.emailauthcredential.md) > [\_fromEmailAndPassword](./auth.emailauthcredential._fromemailandpassword.md) - -## EmailAuthCredential.\_fromEmailAndPassword() method - -Signature: - -```typescript -static _fromEmailAndPassword(email: string, password: string): EmailAuthCredential; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| email | string | | -| password | string | | - -Returns: - -[EmailAuthCredential](./auth.emailauthcredential.md) - diff --git a/docs-exp/auth.emailauthcredential._getidtokenresponse.md b/docs-exp/auth.emailauthcredential._getidtokenresponse.md deleted file mode 100644 index 15535401477..00000000000 --- a/docs-exp/auth.emailauthcredential._getidtokenresponse.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [EmailAuthCredential](./auth.emailauthcredential.md) > [\_getIdTokenResponse](./auth.emailauthcredential._getidtokenresponse.md) - -## EmailAuthCredential.\_getIdTokenResponse() method - -Signature: - -```typescript -_getIdTokenResponse(auth: Auth): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | Auth | | - -Returns: - -Promise<IdTokenResponse> - diff --git a/docs-exp/auth.emailauthcredential._getreauthenticationresolver.md b/docs-exp/auth.emailauthcredential._getreauthenticationresolver.md deleted file mode 100644 index 7b8eadfbff1..00000000000 --- a/docs-exp/auth.emailauthcredential._getreauthenticationresolver.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [EmailAuthCredential](./auth.emailauthcredential.md) > [\_getReauthenticationResolver](./auth.emailauthcredential._getreauthenticationresolver.md) - -## EmailAuthCredential.\_getReauthenticationResolver() method - -Signature: - -```typescript -_getReauthenticationResolver(auth: Auth): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | Auth | | - -Returns: - -Promise<IdTokenResponse> - diff --git a/docs-exp/auth.emailauthcredential._linktoidtoken.md b/docs-exp/auth.emailauthcredential._linktoidtoken.md deleted file mode 100644 index ea4f92b5264..00000000000 --- a/docs-exp/auth.emailauthcredential._linktoidtoken.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [EmailAuthCredential](./auth.emailauthcredential.md) > [\_linkToIdToken](./auth.emailauthcredential._linktoidtoken.md) - -## EmailAuthCredential.\_linkToIdToken() method - -Signature: - -```typescript -_linkToIdToken(auth: Auth, idToken: string): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | Auth | | -| idToken | string | | - -Returns: - -Promise<IdTokenResponse> - diff --git a/docs-exp/auth.emailauthcredential.email.md b/docs-exp/auth.emailauthcredential.email.md deleted file mode 100644 index 5ff3e67b9f0..00000000000 --- a/docs-exp/auth.emailauthcredential.email.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [EmailAuthCredential](./auth.emailauthcredential.md) > [email](./auth.emailauthcredential.email.md) - -## EmailAuthCredential.email property - -Signature: - -```typescript -readonly email: string; -``` diff --git a/docs-exp/auth.emailauthcredential.fromjson.md b/docs-exp/auth.emailauthcredential.fromjson.md deleted file mode 100644 index 9b59922d1d9..00000000000 --- a/docs-exp/auth.emailauthcredential.fromjson.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [EmailAuthCredential](./auth.emailauthcredential.md) > [fromJSON](./auth.emailauthcredential.fromjson.md) - -## EmailAuthCredential.fromJSON() method - -Signature: - -```typescript -static fromJSON(json: object | string): EmailAuthCredential | null; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| json | object \| string | | - -Returns: - -[EmailAuthCredential](./auth.emailauthcredential.md) \| null - diff --git a/docs-exp/auth.emailauthcredential.md b/docs-exp/auth.emailauthcredential.md deleted file mode 100644 index d4c4de7cb10..00000000000 --- a/docs-exp/auth.emailauthcredential.md +++ /dev/null @@ -1,35 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [EmailAuthCredential](./auth.emailauthcredential.md) - -## EmailAuthCredential class - -Signature: - -```typescript -export declare class EmailAuthCredential extends AuthCredential implements externs.AuthCredential -``` -Extends: [AuthCredential](./auth.authcredential.md) - -Implements: externs.[AuthCredential](./auth-types.authcredential.md) - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [email](./auth.emailauthcredential.email.md) | | string | | -| [password](./auth.emailauthcredential.password.md) | | string | | -| [tenantId](./auth.emailauthcredential.tenantid.md) | | string \| null | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [\_fromEmailAndCode(email, oobCode, tenantId)](./auth.emailauthcredential._fromemailandcode.md) | static | | -| [\_fromEmailAndPassword(email, password)](./auth.emailauthcredential._fromemailandpassword.md) | static | | -| [\_getIdTokenResponse(auth)](./auth.emailauthcredential._getidtokenresponse.md) | | | -| [\_getReauthenticationResolver(auth)](./auth.emailauthcredential._getreauthenticationresolver.md) | | | -| [\_linkToIdToken(auth, idToken)](./auth.emailauthcredential._linktoidtoken.md) | | | -| [fromJSON(json)](./auth.emailauthcredential.fromjson.md) | static | | -| [toJSON()](./auth.emailauthcredential.tojson.md) | | | - diff --git a/docs-exp/auth.emailauthcredential.password.md b/docs-exp/auth.emailauthcredential.password.md deleted file mode 100644 index 4e2cad762a7..00000000000 --- a/docs-exp/auth.emailauthcredential.password.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [EmailAuthCredential](./auth.emailauthcredential.md) > [password](./auth.emailauthcredential.password.md) - -## EmailAuthCredential.password property - -Signature: - -```typescript -readonly password: string; -``` diff --git a/docs-exp/auth.emailauthcredential.tenantid.md b/docs-exp/auth.emailauthcredential.tenantid.md deleted file mode 100644 index a1394f2c4b6..00000000000 --- a/docs-exp/auth.emailauthcredential.tenantid.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [EmailAuthCredential](./auth.emailauthcredential.md) > [tenantId](./auth.emailauthcredential.tenantid.md) - -## EmailAuthCredential.tenantId property - -Signature: - -```typescript -readonly tenantId: string | null; -``` diff --git a/docs-exp/auth.emailauthcredential.tojson.md b/docs-exp/auth.emailauthcredential.tojson.md deleted file mode 100644 index 2c361c80906..00000000000 --- a/docs-exp/auth.emailauthcredential.tojson.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [EmailAuthCredential](./auth.emailauthcredential.md) > [toJSON](./auth.emailauthcredential.tojson.md) - -## EmailAuthCredential.toJSON() method - -Signature: - -```typescript -toJSON(): object; -``` -Returns: - -object - diff --git a/docs-exp/auth.emailauthprovider.credential.md b/docs-exp/auth.emailauthprovider.credential.md deleted file mode 100644 index 259c895d748..00000000000 --- a/docs-exp/auth.emailauthprovider.credential.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [EmailAuthProvider](./auth.emailauthprovider.md) > [credential](./auth.emailauthprovider.credential.md) - -## EmailAuthProvider.credential() method - -Signature: - -```typescript -static credential(email: string, password: string): EmailAuthCredential; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| email | string | | -| password | string | | - -Returns: - -[EmailAuthCredential](./auth.emailauthcredential.md) - diff --git a/docs-exp/auth.emailauthprovider.credentialwithlink.md b/docs-exp/auth.emailauthprovider.credentialwithlink.md deleted file mode 100644 index 63f44a625f8..00000000000 --- a/docs-exp/auth.emailauthprovider.credentialwithlink.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [EmailAuthProvider](./auth.emailauthprovider.md) > [credentialWithLink](./auth.emailauthprovider.credentialwithlink.md) - -## EmailAuthProvider.credentialWithLink() method - -Signature: - -```typescript -static credentialWithLink(email: string, emailLink: string): EmailAuthCredential; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| email | string | | -| emailLink | string | | - -Returns: - -[EmailAuthCredential](./auth.emailauthcredential.md) - diff --git a/docs-exp/auth.emailauthprovider.email_link_sign_in_method.md b/docs-exp/auth.emailauthprovider.email_link_sign_in_method.md deleted file mode 100644 index 88cb9fa850d..00000000000 --- a/docs-exp/auth.emailauthprovider.email_link_sign_in_method.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [EmailAuthProvider](./auth.emailauthprovider.md) > [EMAIL\_LINK\_SIGN\_IN\_METHOD](./auth.emailauthprovider.email_link_sign_in_method.md) - -## EmailAuthProvider.EMAIL\_LINK\_SIGN\_IN\_METHOD property - -Signature: - -```typescript -static readonly EMAIL_LINK_SIGN_IN_METHOD = externs.SignInMethod.EMAIL_LINK; -``` diff --git a/docs-exp/auth.emailauthprovider.email_password_sign_in_method.md b/docs-exp/auth.emailauthprovider.email_password_sign_in_method.md deleted file mode 100644 index 35e1392dc4f..00000000000 --- a/docs-exp/auth.emailauthprovider.email_password_sign_in_method.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [EmailAuthProvider](./auth.emailauthprovider.md) > [EMAIL\_PASSWORD\_SIGN\_IN\_METHOD](./auth.emailauthprovider.email_password_sign_in_method.md) - -## EmailAuthProvider.EMAIL\_PASSWORD\_SIGN\_IN\_METHOD property - -Signature: - -```typescript -static readonly EMAIL_PASSWORD_SIGN_IN_METHOD = externs.SignInMethod.EMAIL_PASSWORD; -``` diff --git a/docs-exp/auth.emailauthprovider.md b/docs-exp/auth.emailauthprovider.md deleted file mode 100644 index 3908a331975..00000000000 --- a/docs-exp/auth.emailauthprovider.md +++ /dev/null @@ -1,29 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [EmailAuthProvider](./auth.emailauthprovider.md) - -## EmailAuthProvider class - -Signature: - -```typescript -export declare class EmailAuthProvider implements externs.EmailAuthProvider -``` -Implements: externs.[EmailAuthProvider](./auth-types.emailauthprovider.md) - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [EMAIL\_LINK\_SIGN\_IN\_METHOD](./auth.emailauthprovider.email_link_sign_in_method.md) | static | (not declared) | | -| [EMAIL\_PASSWORD\_SIGN\_IN\_METHOD](./auth.emailauthprovider.email_password_sign_in_method.md) | static | (not declared) | | -| [PROVIDER\_ID](./auth.emailauthprovider.provider_id.md) | static | (not declared) | | -| [providerId](./auth.emailauthprovider.providerid.md) | | (not declared) | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [credential(email, password)](./auth.emailauthprovider.credential.md) | static | | -| [credentialWithLink(email, emailLink)](./auth.emailauthprovider.credentialwithlink.md) | static | | - diff --git a/docs-exp/auth.emailauthprovider.provider_id.md b/docs-exp/auth.emailauthprovider.provider_id.md deleted file mode 100644 index f0ee1c8473f..00000000000 --- a/docs-exp/auth.emailauthprovider.provider_id.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [EmailAuthProvider](./auth.emailauthprovider.md) > [PROVIDER\_ID](./auth.emailauthprovider.provider_id.md) - -## EmailAuthProvider.PROVIDER\_ID property - -Signature: - -```typescript -static readonly PROVIDER_ID = externs.ProviderId.PASSWORD; -``` diff --git a/docs-exp/auth.emailauthprovider.providerid.md b/docs-exp/auth.emailauthprovider.providerid.md deleted file mode 100644 index 78f44363bf5..00000000000 --- a/docs-exp/auth.emailauthprovider.providerid.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [EmailAuthProvider](./auth.emailauthprovider.md) > [providerId](./auth.emailauthprovider.providerid.md) - -## EmailAuthProvider.providerId property - -Signature: - -```typescript -readonly providerId = externs.ProviderId.PASSWORD; -``` diff --git a/docs-exp/auth.facebookauthprovider.credential.md b/docs-exp/auth.facebookauthprovider.credential.md deleted file mode 100644 index afbb7920b65..00000000000 --- a/docs-exp/auth.facebookauthprovider.credential.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [FacebookAuthProvider](./auth.facebookauthprovider.md) > [credential](./auth.facebookauthprovider.credential.md) - -## FacebookAuthProvider.credential() method - -Signature: - -```typescript -static credential(accessToken: string): externs.OAuthCredential; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| accessToken | string | | - -Returns: - -externs.[OAuthCredential](./auth-types.oauthcredential.md) - diff --git a/docs-exp/auth.facebookauthprovider.credentialfromerror.md b/docs-exp/auth.facebookauthprovider.credentialfromerror.md deleted file mode 100644 index a0db89a729a..00000000000 --- a/docs-exp/auth.facebookauthprovider.credentialfromerror.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [FacebookAuthProvider](./auth.facebookauthprovider.md) > [credentialFromError](./auth.facebookauthprovider.credentialfromerror.md) - -## FacebookAuthProvider.credentialFromError() method - -Signature: - -```typescript -static credentialFromError(error: FirebaseError): externs.OAuthCredential | null; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| error | FirebaseError | | - -Returns: - -externs.[OAuthCredential](./auth-types.oauthcredential.md) \| null - diff --git a/docs-exp/auth.facebookauthprovider.credentialfromresult.md b/docs-exp/auth.facebookauthprovider.credentialfromresult.md deleted file mode 100644 index 30b05a61011..00000000000 --- a/docs-exp/auth.facebookauthprovider.credentialfromresult.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [FacebookAuthProvider](./auth.facebookauthprovider.md) > [credentialFromResult](./auth.facebookauthprovider.credentialfromresult.md) - -## FacebookAuthProvider.credentialFromResult() method - -Signature: - -```typescript -static credentialFromResult(userCredential: externs.UserCredential): externs.OAuthCredential | null; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| userCredential | externs.[UserCredential](./auth-types.usercredential.md) | | - -Returns: - -externs.[OAuthCredential](./auth-types.oauthcredential.md) \| null - diff --git a/docs-exp/auth.facebookauthprovider.facebook_sign_in_method.md b/docs-exp/auth.facebookauthprovider.facebook_sign_in_method.md deleted file mode 100644 index 80627f8c3f6..00000000000 --- a/docs-exp/auth.facebookauthprovider.facebook_sign_in_method.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [FacebookAuthProvider](./auth.facebookauthprovider.md) > [FACEBOOK\_SIGN\_IN\_METHOD](./auth.facebookauthprovider.facebook_sign_in_method.md) - -## FacebookAuthProvider.FACEBOOK\_SIGN\_IN\_METHOD property - -Signature: - -```typescript -static readonly FACEBOOK_SIGN_IN_METHOD = externs.SignInMethod.FACEBOOK; -``` diff --git a/docs-exp/auth.facebookauthprovider.md b/docs-exp/auth.facebookauthprovider.md deleted file mode 100644 index 79b717981d7..00000000000 --- a/docs-exp/auth.facebookauthprovider.md +++ /dev/null @@ -1,29 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [FacebookAuthProvider](./auth.facebookauthprovider.md) - -## FacebookAuthProvider class - -Signature: - -```typescript -export declare class FacebookAuthProvider extends OAuthProvider -``` -Extends: [OAuthProvider](./auth.oauthprovider.md) - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [FACEBOOK\_SIGN\_IN\_METHOD](./auth.facebookauthprovider.facebook_sign_in_method.md) | static | (not declared) | | -| [PROVIDER\_ID](./auth.facebookauthprovider.provider_id.md) | static | (not declared) | | -| [providerId](./auth.facebookauthprovider.providerid.md) | | (not declared) | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [credential(accessToken)](./auth.facebookauthprovider.credential.md) | static | | -| [credentialFromError(error)](./auth.facebookauthprovider.credentialfromerror.md) | static | | -| [credentialFromResult(userCredential)](./auth.facebookauthprovider.credentialfromresult.md) | static | | - diff --git a/docs-exp/auth.facebookauthprovider.provider_id.md b/docs-exp/auth.facebookauthprovider.provider_id.md deleted file mode 100644 index 22f95748f94..00000000000 --- a/docs-exp/auth.facebookauthprovider.provider_id.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [FacebookAuthProvider](./auth.facebookauthprovider.md) > [PROVIDER\_ID](./auth.facebookauthprovider.provider_id.md) - -## FacebookAuthProvider.PROVIDER\_ID property - -Signature: - -```typescript -static readonly PROVIDER_ID = externs.ProviderId.FACEBOOK; -``` diff --git a/docs-exp/auth.facebookauthprovider.providerid.md b/docs-exp/auth.facebookauthprovider.providerid.md deleted file mode 100644 index 7af0f4f7b13..00000000000 --- a/docs-exp/auth.facebookauthprovider.providerid.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [FacebookAuthProvider](./auth.facebookauthprovider.md) > [providerId](./auth.facebookauthprovider.providerid.md) - -## FacebookAuthProvider.providerId property - -Signature: - -```typescript -readonly providerId = externs.ProviderId.FACEBOOK; -``` diff --git a/docs-exp/auth.fetchsigninmethodsforemail.md b/docs-exp/auth.fetchsigninmethodsforemail.md deleted file mode 100644 index 4dc3362b6b1..00000000000 --- a/docs-exp/auth.fetchsigninmethodsforemail.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [fetchSignInMethodsForEmail](./auth.fetchsigninmethodsforemail.md) - -## fetchSignInMethodsForEmail() function - -Signature: - -```typescript -export declare function fetchSignInMethodsForEmail(auth: externs.Auth, email: string): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | externs.[Auth](./auth-types.auth.md) | | -| email | string | | - -Returns: - -Promise<string\[\]> - diff --git a/docs-exp/auth.getadditionaluserinfo.md b/docs-exp/auth.getadditionaluserinfo.md deleted file mode 100644 index 126ec9e41ed..00000000000 --- a/docs-exp/auth.getadditionaluserinfo.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [getAdditionalUserInfo](./auth.getadditionaluserinfo.md) - -## getAdditionalUserInfo() function - -Signature: - -```typescript -export declare function getAdditionalUserInfo(userCredential: externs.UserCredential): externs.AdditionalUserInfo | null; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| userCredential | externs.[UserCredential](./auth-types.usercredential.md) | | - -Returns: - -externs.[AdditionalUserInfo](./auth-types.additionaluserinfo.md) \| null - diff --git a/docs-exp/auth.getauth.md b/docs-exp/auth.getauth.md deleted file mode 100644 index 0dab35d0fff..00000000000 --- a/docs-exp/auth.getauth.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [getAuth](./auth.getauth.md) - -## getAuth() function - -Signature: - -```typescript -export declare function getAuth(app?: FirebaseApp): Auth; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| app | [FirebaseApp](./app-types.firebaseapp.md) | | - -Returns: - -[Auth](./auth-types.auth.md) - diff --git a/docs-exp/auth.getidtoken.md b/docs-exp/auth.getidtoken.md deleted file mode 100644 index 3824d79ed81..00000000000 --- a/docs-exp/auth.getidtoken.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [getIdToken](./auth.getidtoken.md) - -## getIdToken() function - -Signature: - -```typescript -export declare function getIdToken(user: externs.User, forceRefresh?: boolean): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| user | externs.[User](./auth-types.user.md) | | -| forceRefresh | boolean | | - -Returns: - -Promise<string> - diff --git a/docs-exp/auth.getidtokenresult.md b/docs-exp/auth.getidtokenresult.md deleted file mode 100644 index ff8e33424ee..00000000000 --- a/docs-exp/auth.getidtokenresult.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [getIdTokenResult](./auth.getidtokenresult.md) - -## getIdTokenResult() function - -Signature: - -```typescript -export declare function getIdTokenResult(externUser: externs.User, forceRefresh?: boolean): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| externUser | externs.[User](./auth-types.user.md) | | -| forceRefresh | boolean | | - -Returns: - -Promise<externs.[IdTokenResult](./auth-types.idtokenresult.md)> - diff --git a/docs-exp/auth.getmultifactorresolver.md b/docs-exp/auth.getmultifactorresolver.md deleted file mode 100644 index eace552c52b..00000000000 --- a/docs-exp/auth.getmultifactorresolver.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [getMultiFactorResolver](./auth.getmultifactorresolver.md) - -## getMultiFactorResolver() function - -Signature: - -```typescript -export declare function getMultiFactorResolver(auth: externs.Auth, errorExtern: externs.MultiFactorError): externs.MultiFactorResolver; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | externs.[Auth](./auth-types.auth.md) | | -| errorExtern | externs.[MultiFactorError](./auth-types.multifactorerror.md) | | - -Returns: - -externs.[MultiFactorResolver](./auth-types.multifactorresolver.md) - diff --git a/docs-exp/auth.getredirectresult.md b/docs-exp/auth.getredirectresult.md deleted file mode 100644 index 1a793eb5f1e..00000000000 --- a/docs-exp/auth.getredirectresult.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [getRedirectResult](./auth.getredirectresult.md) - -## getRedirectResult() function - -Signature: - -```typescript -export declare function getRedirectResult(authExtern: externs.Auth, resolverExtern?: externs.PopupRedirectResolver): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| authExtern | externs.[Auth](./auth-types.auth.md) | | -| resolverExtern | externs.[PopupRedirectResolver](./auth-types.popupredirectresolver.md) | | - -Returns: - -Promise<externs.[UserCredential](./auth-types.usercredential.md) \| null> - diff --git a/docs-exp/auth.githubauthprovider.credential.md b/docs-exp/auth.githubauthprovider.credential.md deleted file mode 100644 index 19ea0dd2857..00000000000 --- a/docs-exp/auth.githubauthprovider.credential.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [GithubAuthProvider](./auth.githubauthprovider.md) > [credential](./auth.githubauthprovider.credential.md) - -## GithubAuthProvider.credential() method - -Signature: - -```typescript -static credential(accessToken: string): externs.OAuthCredential; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| accessToken | string | | - -Returns: - -externs.[OAuthCredential](./auth-types.oauthcredential.md) - diff --git a/docs-exp/auth.githubauthprovider.credentialfromerror.md b/docs-exp/auth.githubauthprovider.credentialfromerror.md deleted file mode 100644 index 1735880b10d..00000000000 --- a/docs-exp/auth.githubauthprovider.credentialfromerror.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [GithubAuthProvider](./auth.githubauthprovider.md) > [credentialFromError](./auth.githubauthprovider.credentialfromerror.md) - -## GithubAuthProvider.credentialFromError() method - -Signature: - -```typescript -static credentialFromError(error: FirebaseError): externs.OAuthCredential | null; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| error | FirebaseError | | - -Returns: - -externs.[OAuthCredential](./auth-types.oauthcredential.md) \| null - diff --git a/docs-exp/auth.githubauthprovider.credentialfromresult.md b/docs-exp/auth.githubauthprovider.credentialfromresult.md deleted file mode 100644 index 0705a1dd58a..00000000000 --- a/docs-exp/auth.githubauthprovider.credentialfromresult.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [GithubAuthProvider](./auth.githubauthprovider.md) > [credentialFromResult](./auth.githubauthprovider.credentialfromresult.md) - -## GithubAuthProvider.credentialFromResult() method - -Signature: - -```typescript -static credentialFromResult(userCredential: externs.UserCredential): externs.OAuthCredential | null; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| userCredential | externs.[UserCredential](./auth-types.usercredential.md) | | - -Returns: - -externs.[OAuthCredential](./auth-types.oauthcredential.md) \| null - diff --git a/docs-exp/auth.githubauthprovider.github_sign_in_method.md b/docs-exp/auth.githubauthprovider.github_sign_in_method.md deleted file mode 100644 index 0ceb5771f6f..00000000000 --- a/docs-exp/auth.githubauthprovider.github_sign_in_method.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [GithubAuthProvider](./auth.githubauthprovider.md) > [GITHUB\_SIGN\_IN\_METHOD](./auth.githubauthprovider.github_sign_in_method.md) - -## GithubAuthProvider.GITHUB\_SIGN\_IN\_METHOD property - -Signature: - -```typescript -static readonly GITHUB_SIGN_IN_METHOD = externs.SignInMethod.GITHUB; -``` diff --git a/docs-exp/auth.githubauthprovider.md b/docs-exp/auth.githubauthprovider.md deleted file mode 100644 index 137d327bfef..00000000000 --- a/docs-exp/auth.githubauthprovider.md +++ /dev/null @@ -1,29 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [GithubAuthProvider](./auth.githubauthprovider.md) - -## GithubAuthProvider class - -Signature: - -```typescript -export declare class GithubAuthProvider extends OAuthProvider -``` -Extends: [OAuthProvider](./auth.oauthprovider.md) - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [GITHUB\_SIGN\_IN\_METHOD](./auth.githubauthprovider.github_sign_in_method.md) | static | (not declared) | | -| [PROVIDER\_ID](./auth.githubauthprovider.provider_id.md) | static | (not declared) | | -| [providerId](./auth.githubauthprovider.providerid.md) | | (not declared) | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [credential(accessToken)](./auth.githubauthprovider.credential.md) | static | | -| [credentialFromError(error)](./auth.githubauthprovider.credentialfromerror.md) | static | | -| [credentialFromResult(userCredential)](./auth.githubauthprovider.credentialfromresult.md) | static | | - diff --git a/docs-exp/auth.githubauthprovider.provider_id.md b/docs-exp/auth.githubauthprovider.provider_id.md deleted file mode 100644 index 702ae3425ac..00000000000 --- a/docs-exp/auth.githubauthprovider.provider_id.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [GithubAuthProvider](./auth.githubauthprovider.md) > [PROVIDER\_ID](./auth.githubauthprovider.provider_id.md) - -## GithubAuthProvider.PROVIDER\_ID property - -Signature: - -```typescript -static readonly PROVIDER_ID = externs.ProviderId.GITHUB; -``` diff --git a/docs-exp/auth.githubauthprovider.providerid.md b/docs-exp/auth.githubauthprovider.providerid.md deleted file mode 100644 index 7aaaf5165a5..00000000000 --- a/docs-exp/auth.githubauthprovider.providerid.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [GithubAuthProvider](./auth.githubauthprovider.md) > [providerId](./auth.githubauthprovider.providerid.md) - -## GithubAuthProvider.providerId property - -Signature: - -```typescript -readonly providerId = externs.ProviderId.GITHUB; -``` diff --git a/docs-exp/auth.googleauthprovider.credential.md b/docs-exp/auth.googleauthprovider.credential.md deleted file mode 100644 index 5d115aa03df..00000000000 --- a/docs-exp/auth.googleauthprovider.credential.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [GoogleAuthProvider](./auth.googleauthprovider.md) > [credential](./auth.googleauthprovider.credential.md) - -## GoogleAuthProvider.credential() method - -Signature: - -```typescript -static credential(idToken?: string | null, accessToken?: string | null): externs.OAuthCredential; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| idToken | string \| null | | -| accessToken | string \| null | | - -Returns: - -externs.[OAuthCredential](./auth-types.oauthcredential.md) - diff --git a/docs-exp/auth.googleauthprovider.credentialfromerror.md b/docs-exp/auth.googleauthprovider.credentialfromerror.md deleted file mode 100644 index e793b3ba237..00000000000 --- a/docs-exp/auth.googleauthprovider.credentialfromerror.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [GoogleAuthProvider](./auth.googleauthprovider.md) > [credentialFromError](./auth.googleauthprovider.credentialfromerror.md) - -## GoogleAuthProvider.credentialFromError() method - -Signature: - -```typescript -static credentialFromError(error: FirebaseError): externs.OAuthCredential | null; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| error | FirebaseError | | - -Returns: - -externs.[OAuthCredential](./auth-types.oauthcredential.md) \| null - diff --git a/docs-exp/auth.googleauthprovider.credentialfromresult.md b/docs-exp/auth.googleauthprovider.credentialfromresult.md deleted file mode 100644 index 50e009cf5a6..00000000000 --- a/docs-exp/auth.googleauthprovider.credentialfromresult.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [GoogleAuthProvider](./auth.googleauthprovider.md) > [credentialFromResult](./auth.googleauthprovider.credentialfromresult.md) - -## GoogleAuthProvider.credentialFromResult() method - -Signature: - -```typescript -static credentialFromResult(userCredential: externs.UserCredential): externs.OAuthCredential | null; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| userCredential | externs.[UserCredential](./auth-types.usercredential.md) | | - -Returns: - -externs.[OAuthCredential](./auth-types.oauthcredential.md) \| null - diff --git a/docs-exp/auth.googleauthprovider.google_sign_in_method.md b/docs-exp/auth.googleauthprovider.google_sign_in_method.md deleted file mode 100644 index 1a9fc3251a4..00000000000 --- a/docs-exp/auth.googleauthprovider.google_sign_in_method.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [GoogleAuthProvider](./auth.googleauthprovider.md) > [GOOGLE\_SIGN\_IN\_METHOD](./auth.googleauthprovider.google_sign_in_method.md) - -## GoogleAuthProvider.GOOGLE\_SIGN\_IN\_METHOD property - -Signature: - -```typescript -static readonly GOOGLE_SIGN_IN_METHOD = externs.SignInMethod.GOOGLE; -``` diff --git a/docs-exp/auth.googleauthprovider.md b/docs-exp/auth.googleauthprovider.md deleted file mode 100644 index 008400579d8..00000000000 --- a/docs-exp/auth.googleauthprovider.md +++ /dev/null @@ -1,29 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [GoogleAuthProvider](./auth.googleauthprovider.md) - -## GoogleAuthProvider class - -Signature: - -```typescript -export declare class GoogleAuthProvider extends OAuthProvider -``` -Extends: [OAuthProvider](./auth.oauthprovider.md) - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [GOOGLE\_SIGN\_IN\_METHOD](./auth.googleauthprovider.google_sign_in_method.md) | static | (not declared) | | -| [PROVIDER\_ID](./auth.googleauthprovider.provider_id.md) | static | (not declared) | | -| [providerId](./auth.googleauthprovider.providerid.md) | | (not declared) | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [credential(idToken, accessToken)](./auth.googleauthprovider.credential.md) | static | | -| [credentialFromError(error)](./auth.googleauthprovider.credentialfromerror.md) | static | | -| [credentialFromResult(userCredential)](./auth.googleauthprovider.credentialfromresult.md) | static | | - diff --git a/docs-exp/auth.googleauthprovider.provider_id.md b/docs-exp/auth.googleauthprovider.provider_id.md deleted file mode 100644 index 912a8dab3da..00000000000 --- a/docs-exp/auth.googleauthprovider.provider_id.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [GoogleAuthProvider](./auth.googleauthprovider.md) > [PROVIDER\_ID](./auth.googleauthprovider.provider_id.md) - -## GoogleAuthProvider.PROVIDER\_ID property - -Signature: - -```typescript -static readonly PROVIDER_ID = externs.ProviderId.GOOGLE; -``` diff --git a/docs-exp/auth.googleauthprovider.providerid.md b/docs-exp/auth.googleauthprovider.providerid.md deleted file mode 100644 index 6b80693aecc..00000000000 --- a/docs-exp/auth.googleauthprovider.providerid.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [GoogleAuthProvider](./auth.googleauthprovider.md) > [providerId](./auth.googleauthprovider.providerid.md) - -## GoogleAuthProvider.providerId property - -Signature: - -```typescript -readonly providerId = externs.ProviderId.GOOGLE; -``` diff --git a/docs-exp/auth.indexeddblocalpersistence.md b/docs-exp/auth.indexeddblocalpersistence.md deleted file mode 100644 index 9373896af2f..00000000000 --- a/docs-exp/auth.indexeddblocalpersistence.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [indexedDBLocalPersistence](./auth.indexeddblocalpersistence.md) - -## indexedDBLocalPersistence variable - -Signature: - -```typescript -indexedDBLocalPersistence: externs.Persistence -``` diff --git a/docs-exp/auth.initializeauth.md b/docs-exp/auth.initializeauth.md deleted file mode 100644 index eb5e84a4f4b..00000000000 --- a/docs-exp/auth.initializeauth.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [initializeAuth](./auth.initializeauth.md) - -## initializeAuth() function - -Signature: - -```typescript -export declare function initializeAuth(app?: FirebaseApp, deps?: Dependencies): externs.Auth; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| app | [FirebaseApp](./app-types.firebaseapp.md) | | -| deps | Dependencies | | - -Returns: - -externs.[Auth](./auth-types.auth.md) - diff --git a/docs-exp/auth.inmemorypersistence.md b/docs-exp/auth.inmemorypersistence.md deleted file mode 100644 index 6f139c69668..00000000000 --- a/docs-exp/auth.inmemorypersistence.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [inMemoryPersistence](./auth.inmemorypersistence.md) - -## inMemoryPersistence variable - -Signature: - -```typescript -inMemoryPersistence: externs.Persistence -``` diff --git a/docs-exp/auth.issigninwithemaillink.md b/docs-exp/auth.issigninwithemaillink.md deleted file mode 100644 index 5bd6fc0e6d6..00000000000 --- a/docs-exp/auth.issigninwithemaillink.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [isSignInWithEmailLink](./auth.issigninwithemaillink.md) - -## isSignInWithEmailLink() function - -Signature: - -```typescript -export declare function isSignInWithEmailLink(auth: externs.Auth, emailLink: string): boolean; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | externs.[Auth](./auth-types.auth.md) | | -| emailLink | string | | - -Returns: - -boolean - diff --git a/docs-exp/auth.linkwithcredential.md b/docs-exp/auth.linkwithcredential.md deleted file mode 100644 index 0a55932ece5..00000000000 --- a/docs-exp/auth.linkwithcredential.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [linkWithCredential](./auth.linkwithcredential.md) - -## linkWithCredential() function - -Signature: - -```typescript -export declare function linkWithCredential(userExtern: externs.User, credentialExtern: externs.AuthCredential): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| userExtern | externs.[User](./auth-types.user.md) | | -| credentialExtern | externs.[AuthCredential](./auth-types.authcredential.md) | | - -Returns: - -Promise<[UserCredential](./auth-types.usercredential.md)> - diff --git a/docs-exp/auth.linkwithphonenumber.md b/docs-exp/auth.linkwithphonenumber.md deleted file mode 100644 index d2b406e922a..00000000000 --- a/docs-exp/auth.linkwithphonenumber.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [linkWithPhoneNumber](./auth.linkwithphonenumber.md) - -## linkWithPhoneNumber() function - -Signature: - -```typescript -export declare function linkWithPhoneNumber(userExtern: externs.User, phoneNumber: string, appVerifier: externs.ApplicationVerifier): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| userExtern | externs.[User](./auth-types.user.md) | | -| phoneNumber | string | | -| appVerifier | externs.[ApplicationVerifier](./auth-types.applicationverifier.md) | | - -Returns: - -Promise<externs.[ConfirmationResult](./auth-types.confirmationresult.md)> - diff --git a/docs-exp/auth.linkwithpopup.md b/docs-exp/auth.linkwithpopup.md deleted file mode 100644 index 76c36378633..00000000000 --- a/docs-exp/auth.linkwithpopup.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [linkWithPopup](./auth.linkwithpopup.md) - -## linkWithPopup() function - -Signature: - -```typescript -export declare function linkWithPopup(userExtern: externs.User, provider: externs.AuthProvider, resolverExtern?: externs.PopupRedirectResolver): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| userExtern | externs.[User](./auth-types.user.md) | | -| provider | externs.[AuthProvider](./auth-types.authprovider.md) | | -| resolverExtern | externs.[PopupRedirectResolver](./auth-types.popupredirectresolver.md) | | - -Returns: - -Promise<externs.[UserCredential](./auth-types.usercredential.md)> - diff --git a/docs-exp/auth.linkwithredirect.md b/docs-exp/auth.linkwithredirect.md deleted file mode 100644 index dc31203de75..00000000000 --- a/docs-exp/auth.linkwithredirect.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [linkWithRedirect](./auth.linkwithredirect.md) - -## linkWithRedirect() function - -Signature: - -```typescript -export declare function linkWithRedirect(userExtern: externs.User, provider: externs.AuthProvider, resolverExtern?: externs.PopupRedirectResolver): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| userExtern | externs.[User](./auth-types.user.md) | | -| provider | externs.[AuthProvider](./auth-types.authprovider.md) | | -| resolverExtern | externs.[PopupRedirectResolver](./auth-types.popupredirectresolver.md) | | - -Returns: - -Promise<never> - diff --git a/docs-exp/auth.md b/docs-exp/auth.md deleted file mode 100644 index e36e91f8cf3..00000000000 --- a/docs-exp/auth.md +++ /dev/null @@ -1,89 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) - -## auth package - -## Classes - -| Class | Description | -| --- | --- | -| [ActionCodeURL](./auth.actioncodeurl.md) | A utility class to parse email action URLs such as password reset, email verification, email link sign in, etc. | -| [AuthCredential](./auth.authcredential.md) | | -| [EmailAuthCredential](./auth.emailauthcredential.md) | | -| [EmailAuthProvider](./auth.emailauthprovider.md) | | -| [FacebookAuthProvider](./auth.facebookauthprovider.md) | | -| [GithubAuthProvider](./auth.githubauthprovider.md) | | -| [GoogleAuthProvider](./auth.googleauthprovider.md) | | -| [OAuthCredential](./auth.oauthcredential.md) | | -| [OAuthProvider](./auth.oauthprovider.md) | | -| [PhoneAuthCredential](./auth.phoneauthcredential.md) | | -| [PhoneAuthProvider](./auth.phoneauthprovider.md) | | -| [PhoneMultiFactorGenerator](./auth.phonemultifactorgenerator.md) | | -| [RecaptchaVerifier](./auth.recaptchaverifier.md) | | -| [TwitterAuthProvider](./auth.twitterauthprovider.md) | | - -## Functions - -| Function | Description | -| --- | --- | -| [applyActionCode(auth, oobCode)](./auth.applyactioncode.md) | | -| [checkActionCode(auth, oobCode)](./auth.checkactioncode.md) | | -| [confirmPasswordReset(auth, oobCode, newPassword)](./auth.confirmpasswordreset.md) | | -| [createUserWithEmailAndPassword(auth, email, password)](./auth.createuserwithemailandpassword.md) | | -| [deleteUser(user)](./auth.deleteuser.md) | | -| [fetchSignInMethodsForEmail(auth, email)](./auth.fetchsigninmethodsforemail.md) | | -| [getAdditionalUserInfo(userCredential)](./auth.getadditionaluserinfo.md) | | -| [getAuth(app)](./auth.getauth.md) | | -| [getIdToken(user, forceRefresh)](./auth.getidtoken.md) | | -| [getIdTokenResult(externUser, forceRefresh)](./auth.getidtokenresult.md) | | -| [getMultiFactorResolver(auth, errorExtern)](./auth.getmultifactorresolver.md) | | -| [getRedirectResult(authExtern, resolverExtern)](./auth.getredirectresult.md) | | -| [initializeAuth(app, deps)](./auth.initializeauth.md) | | -| [isSignInWithEmailLink(auth, emailLink)](./auth.issigninwithemaillink.md) | | -| [linkWithCredential(userExtern, credentialExtern)](./auth.linkwithcredential.md) | | -| [linkWithPhoneNumber(userExtern, phoneNumber, appVerifier)](./auth.linkwithphonenumber.md) | | -| [linkWithPopup(userExtern, provider, resolverExtern)](./auth.linkwithpopup.md) | | -| [linkWithRedirect(userExtern, provider, resolverExtern)](./auth.linkwithredirect.md) | | -| [multiFactor(user)](./auth.multifactor.md) | | -| [onAuthStateChanged(auth, nextOrObserver, error, completed)](./auth.onauthstatechanged.md) | | -| [onIdTokenChanged(auth, nextOrObserver, error, completed)](./auth.onidtokenchanged.md) | | -| [parseActionCodeURL(link)](./auth.parseactioncodeurl.md) | Parses the email action link string and returns an ActionCodeURL object if the link is valid, otherwise returns null. | -| [reauthenticateWithCredential(userExtern, credentialExtern)](./auth.reauthenticatewithcredential.md) | | -| [reauthenticateWithPhoneNumber(userExtern, phoneNumber, appVerifier)](./auth.reauthenticatewithphonenumber.md) | | -| [reauthenticateWithPopup(userExtern, provider, resolverExtern)](./auth.reauthenticatewithpopup.md) | | -| [reauthenticateWithRedirect(userExtern, provider, resolverExtern)](./auth.reauthenticatewithredirect.md) | | -| [reload(externUser)](./auth.reload.md) | | -| [sendEmailVerification(userExtern, actionCodeSettings)](./auth.sendemailverification.md) | | -| [sendPasswordResetEmail(auth, email, actionCodeSettings)](./auth.sendpasswordresetemail.md) | | -| [sendSignInLinkToEmail(auth, email, actionCodeSettings)](./auth.sendsigninlinktoemail.md) | | -| [setPersistence(auth, persistence)](./auth.setpersistence.md) | | -| [signInAnonymously(auth)](./auth.signinanonymously.md) | | -| [signInWithCredential(auth, credential)](./auth.signinwithcredential.md) | | -| [signInWithCustomToken(authExtern, customToken)](./auth.signinwithcustomtoken.md) | | -| [signInWithEmailAndPassword(auth, email, password)](./auth.signinwithemailandpassword.md) | | -| [signInWithEmailLink(auth, email, emailLink)](./auth.signinwithemaillink.md) | | -| [signInWithPhoneNumber(auth, phoneNumber, appVerifier)](./auth.signinwithphonenumber.md) | | -| [signInWithPopup(authExtern, provider, resolverExtern)](./auth.signinwithpopup.md) | | -| [signInWithRedirect(authExtern, provider, resolverExtern)](./auth.signinwithredirect.md) | | -| [signOut(auth)](./auth.signout.md) | | -| [unlink(userExtern, providerId)](./auth.unlink.md) | This is the externally visible unlink function | -| [updateCurrentUser(auth, user)](./auth.updatecurrentuser.md) | | -| [updateEmail(externUser, newEmail)](./auth.updateemail.md) | | -| [updatePassword(externUser, newPassword)](./auth.updatepassword.md) | | -| [updatePhoneNumber(user, credential)](./auth.updatephonenumber.md) | | -| [updateProfile(externUser, { displayName, photoURL: photoUrl })](./auth.updateprofile.md) | | -| [useDeviceLanguage(auth)](./auth.usedevicelanguage.md) | | -| [verifyBeforeUpdateEmail(userExtern, newEmail, actionCodeSettings)](./auth.verifybeforeupdateemail.md) | | -| [verifyPasswordResetCode(auth, code)](./auth.verifypasswordresetcode.md) | | - -## Variables - -| Variable | Description | -| --- | --- | -| [browserLocalPersistence](./auth.browserlocalpersistence.md) | | -| [browserPopupRedirectResolver](./auth.browserpopupredirectresolver.md) | | -| [browserSessionPersistence](./auth.browsersessionpersistence.md) | | -| [indexedDBLocalPersistence](./auth.indexeddblocalpersistence.md) | | -| [inMemoryPersistence](./auth.inmemorypersistence.md) | | - diff --git a/docs-exp/auth.multifactor.md b/docs-exp/auth.multifactor.md deleted file mode 100644 index 1e46c923e73..00000000000 --- a/docs-exp/auth.multifactor.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [multiFactor](./auth.multifactor.md) - -## multiFactor() function - -Signature: - -```typescript -export declare function multiFactor(user: externs.User): externs.MultiFactorUser; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| user | externs.[User](./auth-types.user.md) | | - -Returns: - -externs.[MultiFactorUser](./auth-types.multifactoruser.md) - diff --git a/docs-exp/auth.oauthcredential._fromparams.md b/docs-exp/auth.oauthcredential._fromparams.md deleted file mode 100644 index bc958994759..00000000000 --- a/docs-exp/auth.oauthcredential._fromparams.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [OAuthCredential](./auth.oauthcredential.md) > [\_fromParams](./auth.oauthcredential._fromparams.md) - -## OAuthCredential.\_fromParams() method - -Signature: - -```typescript -static _fromParams(params: OAuthCredentialParams): OAuthCredential; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| params | OAuthCredentialParams | | - -Returns: - -[OAuthCredential](./auth.oauthcredential.md) - diff --git a/docs-exp/auth.oauthcredential._getidtokenresponse.md b/docs-exp/auth.oauthcredential._getidtokenresponse.md deleted file mode 100644 index a1b21269649..00000000000 --- a/docs-exp/auth.oauthcredential._getidtokenresponse.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [OAuthCredential](./auth.oauthcredential.md) > [\_getIdTokenResponse](./auth.oauthcredential._getidtokenresponse.md) - -## OAuthCredential.\_getIdTokenResponse() method - -Signature: - -```typescript -_getIdTokenResponse(auth: Auth): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | Auth | | - -Returns: - -Promise<IdTokenResponse> - diff --git a/docs-exp/auth.oauthcredential._getreauthenticationresolver.md b/docs-exp/auth.oauthcredential._getreauthenticationresolver.md deleted file mode 100644 index 35a047fda48..00000000000 --- a/docs-exp/auth.oauthcredential._getreauthenticationresolver.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [OAuthCredential](./auth.oauthcredential.md) > [\_getReauthenticationResolver](./auth.oauthcredential._getreauthenticationresolver.md) - -## OAuthCredential.\_getReauthenticationResolver() method - -Signature: - -```typescript -_getReauthenticationResolver(auth: Auth): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | Auth | | - -Returns: - -Promise<IdTokenResponse> - diff --git a/docs-exp/auth.oauthcredential._linktoidtoken.md b/docs-exp/auth.oauthcredential._linktoidtoken.md deleted file mode 100644 index eb0a27249d3..00000000000 --- a/docs-exp/auth.oauthcredential._linktoidtoken.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [OAuthCredential](./auth.oauthcredential.md) > [\_linkToIdToken](./auth.oauthcredential._linktoidtoken.md) - -## OAuthCredential.\_linkToIdToken() method - -Signature: - -```typescript -_linkToIdToken(auth: Auth, idToken: string): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | Auth | | -| idToken | string | | - -Returns: - -Promise<IdTokenResponse> - diff --git a/docs-exp/auth.oauthcredential.accesstoken.md b/docs-exp/auth.oauthcredential.accesstoken.md deleted file mode 100644 index 2c7a53a9da3..00000000000 --- a/docs-exp/auth.oauthcredential.accesstoken.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [OAuthCredential](./auth.oauthcredential.md) > [accessToken](./auth.oauthcredential.accesstoken.md) - -## OAuthCredential.accessToken property - -Signature: - -```typescript -accessToken?: string; -``` diff --git a/docs-exp/auth.oauthcredential.fromjson.md b/docs-exp/auth.oauthcredential.fromjson.md deleted file mode 100644 index 5fe608786a4..00000000000 --- a/docs-exp/auth.oauthcredential.fromjson.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [OAuthCredential](./auth.oauthcredential.md) > [fromJSON](./auth.oauthcredential.fromjson.md) - -## OAuthCredential.fromJSON() method - -Signature: - -```typescript -static fromJSON(json: string | object): OAuthCredential | null; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| json | string \| object | | - -Returns: - -[OAuthCredential](./auth.oauthcredential.md) \| null - diff --git a/docs-exp/auth.oauthcredential.idtoken.md b/docs-exp/auth.oauthcredential.idtoken.md deleted file mode 100644 index 416e44ede0c..00000000000 --- a/docs-exp/auth.oauthcredential.idtoken.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [OAuthCredential](./auth.oauthcredential.md) > [idToken](./auth.oauthcredential.idtoken.md) - -## OAuthCredential.idToken property - -Signature: - -```typescript -idToken?: string; -``` diff --git a/docs-exp/auth.oauthcredential.md b/docs-exp/auth.oauthcredential.md deleted file mode 100644 index 8463d002dd3..00000000000 --- a/docs-exp/auth.oauthcredential.md +++ /dev/null @@ -1,35 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [OAuthCredential](./auth.oauthcredential.md) - -## OAuthCredential class - -Signature: - -```typescript -export declare class OAuthCredential extends AuthCredential implements externs.OAuthCredential -``` -Extends: [AuthCredential](./auth.authcredential.md) - -Implements: externs.[OAuthCredential](./auth-types.oauthcredential.md) - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [accessToken](./auth.oauthcredential.accesstoken.md) | | string | | -| [idToken](./auth.oauthcredential.idtoken.md) | | string | | -| [nonce](./auth.oauthcredential.nonce.md) | | string | | -| [secret](./auth.oauthcredential.secret.md) | | string | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [\_fromParams(params)](./auth.oauthcredential._fromparams.md) | static | | -| [\_getIdTokenResponse(auth)](./auth.oauthcredential._getidtokenresponse.md) | | | -| [\_getReauthenticationResolver(auth)](./auth.oauthcredential._getreauthenticationresolver.md) | | | -| [\_linkToIdToken(auth, idToken)](./auth.oauthcredential._linktoidtoken.md) | | | -| [fromJSON(json)](./auth.oauthcredential.fromjson.md) | static | | -| [toJSON()](./auth.oauthcredential.tojson.md) | | | - diff --git a/docs-exp/auth.oauthcredential.nonce.md b/docs-exp/auth.oauthcredential.nonce.md deleted file mode 100644 index a3486055025..00000000000 --- a/docs-exp/auth.oauthcredential.nonce.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [OAuthCredential](./auth.oauthcredential.md) > [nonce](./auth.oauthcredential.nonce.md) - -## OAuthCredential.nonce property - -Signature: - -```typescript -nonce?: string; -``` diff --git a/docs-exp/auth.oauthcredential.secret.md b/docs-exp/auth.oauthcredential.secret.md deleted file mode 100644 index a5fd3ab596d..00000000000 --- a/docs-exp/auth.oauthcredential.secret.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [OAuthCredential](./auth.oauthcredential.md) > [secret](./auth.oauthcredential.secret.md) - -## OAuthCredential.secret property - -Signature: - -```typescript -secret?: string; -``` diff --git a/docs-exp/auth.oauthcredential.tojson.md b/docs-exp/auth.oauthcredential.tojson.md deleted file mode 100644 index efa72b2674d..00000000000 --- a/docs-exp/auth.oauthcredential.tojson.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [OAuthCredential](./auth.oauthcredential.md) > [toJSON](./auth.oauthcredential.tojson.md) - -## OAuthCredential.toJSON() method - -Signature: - -```typescript -toJSON(): object; -``` -Returns: - -object - diff --git a/docs-exp/auth.oauthprovider._constructor_.md b/docs-exp/auth.oauthprovider._constructor_.md deleted file mode 100644 index ff89f537fc4..00000000000 --- a/docs-exp/auth.oauthprovider._constructor_.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [OAuthProvider](./auth.oauthprovider.md) > [(constructor)](./auth.oauthprovider._constructor_.md) - -## OAuthProvider.(constructor) - -Constructs a new instance of the `OAuthProvider` class - -Signature: - -```typescript -constructor(providerId: string); -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| providerId | string | | - diff --git a/docs-exp/auth.oauthprovider.addscope.md b/docs-exp/auth.oauthprovider.addscope.md deleted file mode 100644 index 8ed187e64e1..00000000000 --- a/docs-exp/auth.oauthprovider.addscope.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [OAuthProvider](./auth.oauthprovider.md) > [addScope](./auth.oauthprovider.addscope.md) - -## OAuthProvider.addScope() method - -Signature: - -```typescript -addScope(scope: string): externs.AuthProvider; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| scope | string | | - -Returns: - -externs.[AuthProvider](./auth-types.authprovider.md) - diff --git a/docs-exp/auth.oauthprovider.credential.md b/docs-exp/auth.oauthprovider.credential.md deleted file mode 100644 index a57400bc70d..00000000000 --- a/docs-exp/auth.oauthprovider.credential.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [OAuthProvider](./auth.oauthprovider.md) > [credential](./auth.oauthprovider.credential.md) - -## OAuthProvider.credential() method - -Signature: - -```typescript -credential(params: CredentialParameters): externs.OAuthCredential; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| params | CredentialParameters | | - -Returns: - -externs.[OAuthCredential](./auth-types.oauthcredential.md) - diff --git a/docs-exp/auth.oauthprovider.credentialfromjson.md b/docs-exp/auth.oauthprovider.credentialfromjson.md deleted file mode 100644 index e9e202558c2..00000000000 --- a/docs-exp/auth.oauthprovider.credentialfromjson.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [OAuthProvider](./auth.oauthprovider.md) > [credentialFromJSON](./auth.oauthprovider.credentialfromjson.md) - -## OAuthProvider.credentialFromJSON() method - -Signature: - -```typescript -static credentialFromJSON(json: object | string): externs.OAuthCredential; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| json | object \| string | | - -Returns: - -externs.[OAuthCredential](./auth-types.oauthcredential.md) - diff --git a/docs-exp/auth.oauthprovider.defaultlanguagecode.md b/docs-exp/auth.oauthprovider.defaultlanguagecode.md deleted file mode 100644 index 139635a85b4..00000000000 --- a/docs-exp/auth.oauthprovider.defaultlanguagecode.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [OAuthProvider](./auth.oauthprovider.md) > [defaultLanguageCode](./auth.oauthprovider.defaultlanguagecode.md) - -## OAuthProvider.defaultLanguageCode property - -Signature: - -```typescript -defaultLanguageCode: string | null; -``` diff --git a/docs-exp/auth.oauthprovider.getcustomparameters.md b/docs-exp/auth.oauthprovider.getcustomparameters.md deleted file mode 100644 index 541d5d21cec..00000000000 --- a/docs-exp/auth.oauthprovider.getcustomparameters.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [OAuthProvider](./auth.oauthprovider.md) > [getCustomParameters](./auth.oauthprovider.getcustomparameters.md) - -## OAuthProvider.getCustomParameters() method - -Signature: - -```typescript -getCustomParameters(): CustomParameters; -``` -Returns: - -CustomParameters - diff --git a/docs-exp/auth.oauthprovider.getscopes.md b/docs-exp/auth.oauthprovider.getscopes.md deleted file mode 100644 index f98f4644306..00000000000 --- a/docs-exp/auth.oauthprovider.getscopes.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [OAuthProvider](./auth.oauthprovider.md) > [getScopes](./auth.oauthprovider.getscopes.md) - -## OAuthProvider.getScopes() method - -Signature: - -```typescript -getScopes(): string[]; -``` -Returns: - -string\[\] - diff --git a/docs-exp/auth.oauthprovider.md b/docs-exp/auth.oauthprovider.md deleted file mode 100644 index 8976004def3..00000000000 --- a/docs-exp/auth.oauthprovider.md +++ /dev/null @@ -1,38 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [OAuthProvider](./auth.oauthprovider.md) - -## OAuthProvider class - -Signature: - -```typescript -export declare class OAuthProvider implements externs.AuthProvider -``` -Implements: externs.[AuthProvider](./auth-types.authprovider.md) - -## Constructors - -| Constructor | Modifiers | Description | -| --- | --- | --- | -| [(constructor)(providerId)](./auth.oauthprovider._constructor_.md) | | Constructs a new instance of the OAuthProvider class | - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [defaultLanguageCode](./auth.oauthprovider.defaultlanguagecode.md) | | string \| null | | -| [providerId](./auth.oauthprovider.providerid.md) | | string | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [addScope(scope)](./auth.oauthprovider.addscope.md) | | | -| [credential(params)](./auth.oauthprovider.credential.md) | | | -| [credentialFromJSON(json)](./auth.oauthprovider.credentialfromjson.md) | static | | -| [getCustomParameters()](./auth.oauthprovider.getcustomparameters.md) | | | -| [getScopes()](./auth.oauthprovider.getscopes.md) | | | -| [setCustomParameters(customOAuthParameters)](./auth.oauthprovider.setcustomparameters.md) | | | -| [setDefaultLanguage(languageCode)](./auth.oauthprovider.setdefaultlanguage.md) | | | - diff --git a/docs-exp/auth.oauthprovider.providerid.md b/docs-exp/auth.oauthprovider.providerid.md deleted file mode 100644 index 6ae20dc611f..00000000000 --- a/docs-exp/auth.oauthprovider.providerid.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [OAuthProvider](./auth.oauthprovider.md) > [providerId](./auth.oauthprovider.providerid.md) - -## OAuthProvider.providerId property - -Signature: - -```typescript -readonly providerId: string; -``` diff --git a/docs-exp/auth.oauthprovider.setcustomparameters.md b/docs-exp/auth.oauthprovider.setcustomparameters.md deleted file mode 100644 index f79b8e251db..00000000000 --- a/docs-exp/auth.oauthprovider.setcustomparameters.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [OAuthProvider](./auth.oauthprovider.md) > [setCustomParameters](./auth.oauthprovider.setcustomparameters.md) - -## OAuthProvider.setCustomParameters() method - -Signature: - -```typescript -setCustomParameters(customOAuthParameters: CustomParameters): externs.AuthProvider; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| customOAuthParameters | CustomParameters | | - -Returns: - -externs.[AuthProvider](./auth-types.authprovider.md) - diff --git a/docs-exp/auth.oauthprovider.setdefaultlanguage.md b/docs-exp/auth.oauthprovider.setdefaultlanguage.md deleted file mode 100644 index 471c56f8d03..00000000000 --- a/docs-exp/auth.oauthprovider.setdefaultlanguage.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [OAuthProvider](./auth.oauthprovider.md) > [setDefaultLanguage](./auth.oauthprovider.setdefaultlanguage.md) - -## OAuthProvider.setDefaultLanguage() method - -Signature: - -```typescript -setDefaultLanguage(languageCode: string | null): void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| languageCode | string \| null | | - -Returns: - -void - diff --git a/docs-exp/auth.onauthstatechanged.md b/docs-exp/auth.onauthstatechanged.md deleted file mode 100644 index 2085ae6d37f..00000000000 --- a/docs-exp/auth.onauthstatechanged.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [onAuthStateChanged](./auth.onauthstatechanged.md) - -## onAuthStateChanged() function - -Signature: - -```typescript -export declare function onAuthStateChanged(auth: externs.Auth, nextOrObserver: externs.NextOrObserver, error?: ErrorFn, completed?: CompleteFn): Unsubscribe; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | externs.[Auth](./auth-types.auth.md) | | -| nextOrObserver | externs.[NextOrObserver](./auth-types.nextorobserver.md)<externs.[User](./auth-types.user.md)> | | -| error | ErrorFn | | -| completed | CompleteFn | | - -Returns: - -Unsubscribe - diff --git a/docs-exp/auth.onidtokenchanged.md b/docs-exp/auth.onidtokenchanged.md deleted file mode 100644 index fc9ba84e3d3..00000000000 --- a/docs-exp/auth.onidtokenchanged.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [onIdTokenChanged](./auth.onidtokenchanged.md) - -## onIdTokenChanged() function - -Signature: - -```typescript -export declare function onIdTokenChanged(auth: externs.Auth, nextOrObserver: externs.NextOrObserver, error?: ErrorFn, completed?: CompleteFn): Unsubscribe; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | externs.[Auth](./auth-types.auth.md) | | -| nextOrObserver | externs.[NextOrObserver](./auth-types.nextorobserver.md)<externs.[User](./auth-types.user.md)> | | -| error | ErrorFn | | -| completed | CompleteFn | | - -Returns: - -Unsubscribe - diff --git a/docs-exp/auth.parseactioncodeurl.md b/docs-exp/auth.parseactioncodeurl.md deleted file mode 100644 index 9b50bf16f0e..00000000000 --- a/docs-exp/auth.parseactioncodeurl.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [parseActionCodeURL](./auth.parseactioncodeurl.md) - -## parseActionCodeURL() function - -Parses the email action link string and returns an ActionCodeURL object if the link is valid, otherwise returns null. - -Signature: - -```typescript -export declare function parseActionCodeURL(link: string): externs.ActionCodeURL | null; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| link | string | The email action link string. | - -Returns: - -externs.[ActionCodeURL](./auth-types.actioncodeurl.md) \| null - -The ActionCodeURL object, or null if the link is invalid. - diff --git a/docs-exp/auth.phoneauthcredential._fromtokenresponse.md b/docs-exp/auth.phoneauthcredential._fromtokenresponse.md deleted file mode 100644 index 72f3797ebaa..00000000000 --- a/docs-exp/auth.phoneauthcredential._fromtokenresponse.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [PhoneAuthCredential](./auth.phoneauthcredential.md) > [\_fromTokenResponse](./auth.phoneauthcredential._fromtokenresponse.md) - -## PhoneAuthCredential.\_fromTokenResponse() method - -Signature: - -```typescript -static _fromTokenResponse(phoneNumber: string, temporaryProof: string): PhoneAuthCredential; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| phoneNumber | string | | -| temporaryProof | string | | - -Returns: - -[PhoneAuthCredential](./auth.phoneauthcredential.md) - diff --git a/docs-exp/auth.phoneauthcredential._fromverification.md b/docs-exp/auth.phoneauthcredential._fromverification.md deleted file mode 100644 index e058d1b940a..00000000000 --- a/docs-exp/auth.phoneauthcredential._fromverification.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [PhoneAuthCredential](./auth.phoneauthcredential.md) > [\_fromVerification](./auth.phoneauthcredential._fromverification.md) - -## PhoneAuthCredential.\_fromVerification() method - -Signature: - -```typescript -static _fromVerification(verificationId: string, verificationCode: string): PhoneAuthCredential; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| verificationId | string | | -| verificationCode | string | | - -Returns: - -[PhoneAuthCredential](./auth.phoneauthcredential.md) - diff --git a/docs-exp/auth.phoneauthcredential._getidtokenresponse.md b/docs-exp/auth.phoneauthcredential._getidtokenresponse.md deleted file mode 100644 index 48f709938d9..00000000000 --- a/docs-exp/auth.phoneauthcredential._getidtokenresponse.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [PhoneAuthCredential](./auth.phoneauthcredential.md) > [\_getIdTokenResponse](./auth.phoneauthcredential._getidtokenresponse.md) - -## PhoneAuthCredential.\_getIdTokenResponse() method - -Signature: - -```typescript -_getIdTokenResponse(auth: Auth): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | Auth | | - -Returns: - -Promise<PhoneOrOauthTokenResponse> - diff --git a/docs-exp/auth.phoneauthcredential._getreauthenticationresolver.md b/docs-exp/auth.phoneauthcredential._getreauthenticationresolver.md deleted file mode 100644 index 468f2e72bf5..00000000000 --- a/docs-exp/auth.phoneauthcredential._getreauthenticationresolver.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [PhoneAuthCredential](./auth.phoneauthcredential.md) > [\_getReauthenticationResolver](./auth.phoneauthcredential._getreauthenticationresolver.md) - -## PhoneAuthCredential.\_getReauthenticationResolver() method - -Signature: - -```typescript -_getReauthenticationResolver(auth: Auth): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | Auth | | - -Returns: - -Promise<IdTokenResponse> - diff --git a/docs-exp/auth.phoneauthcredential._linktoidtoken.md b/docs-exp/auth.phoneauthcredential._linktoidtoken.md deleted file mode 100644 index 9152759d2f0..00000000000 --- a/docs-exp/auth.phoneauthcredential._linktoidtoken.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [PhoneAuthCredential](./auth.phoneauthcredential.md) > [\_linkToIdToken](./auth.phoneauthcredential._linktoidtoken.md) - -## PhoneAuthCredential.\_linkToIdToken() method - -Signature: - -```typescript -_linkToIdToken(auth: Auth, idToken: string): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | Auth | | -| idToken | string | | - -Returns: - -Promise<IdTokenResponse> - diff --git a/docs-exp/auth.phoneauthcredential._makeverificationrequest.md b/docs-exp/auth.phoneauthcredential._makeverificationrequest.md deleted file mode 100644 index 44de591a975..00000000000 --- a/docs-exp/auth.phoneauthcredential._makeverificationrequest.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [PhoneAuthCredential](./auth.phoneauthcredential.md) > [\_makeVerificationRequest](./auth.phoneauthcredential._makeverificationrequest.md) - -## PhoneAuthCredential.\_makeVerificationRequest() method - -Signature: - -```typescript -_makeVerificationRequest(): SignInWithPhoneNumberRequest; -``` -Returns: - -SignInWithPhoneNumberRequest - diff --git a/docs-exp/auth.phoneauthcredential.fromjson.md b/docs-exp/auth.phoneauthcredential.fromjson.md deleted file mode 100644 index 303befdae07..00000000000 --- a/docs-exp/auth.phoneauthcredential.fromjson.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [PhoneAuthCredential](./auth.phoneauthcredential.md) > [fromJSON](./auth.phoneauthcredential.fromjson.md) - -## PhoneAuthCredential.fromJSON() method - -Signature: - -```typescript -static fromJSON(json: object | string): PhoneAuthCredential | null; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| json | object \| string | | - -Returns: - -[PhoneAuthCredential](./auth.phoneauthcredential.md) \| null - diff --git a/docs-exp/auth.phoneauthcredential.md b/docs-exp/auth.phoneauthcredential.md deleted file mode 100644 index 4f090f61b3d..00000000000 --- a/docs-exp/auth.phoneauthcredential.md +++ /dev/null @@ -1,28 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [PhoneAuthCredential](./auth.phoneauthcredential.md) - -## PhoneAuthCredential class - -Signature: - -```typescript -export declare class PhoneAuthCredential extends AuthCredential implements externs.PhoneAuthCredential -``` -Extends: [AuthCredential](./auth.authcredential.md) - -Implements: externs.[PhoneAuthCredential](./auth-types.phoneauthcredential.md) - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [\_fromTokenResponse(phoneNumber, temporaryProof)](./auth.phoneauthcredential._fromtokenresponse.md) | static | | -| [\_fromVerification(verificationId, verificationCode)](./auth.phoneauthcredential._fromverification.md) | static | | -| [\_getIdTokenResponse(auth)](./auth.phoneauthcredential._getidtokenresponse.md) | | | -| [\_getReauthenticationResolver(auth)](./auth.phoneauthcredential._getreauthenticationresolver.md) | | | -| [\_linkToIdToken(auth, idToken)](./auth.phoneauthcredential._linktoidtoken.md) | | | -| [\_makeVerificationRequest()](./auth.phoneauthcredential._makeverificationrequest.md) | | | -| [fromJSON(json)](./auth.phoneauthcredential.fromjson.md) | static | | -| [toJSON()](./auth.phoneauthcredential.tojson.md) | | | - diff --git a/docs-exp/auth.phoneauthcredential.tojson.md b/docs-exp/auth.phoneauthcredential.tojson.md deleted file mode 100644 index aeb65eaf380..00000000000 --- a/docs-exp/auth.phoneauthcredential.tojson.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [PhoneAuthCredential](./auth.phoneauthcredential.md) > [toJSON](./auth.phoneauthcredential.tojson.md) - -## PhoneAuthCredential.toJSON() method - -Signature: - -```typescript -toJSON(): object; -``` -Returns: - -object - diff --git a/docs-exp/auth.phoneauthprovider._constructor_.md b/docs-exp/auth.phoneauthprovider._constructor_.md deleted file mode 100644 index 849cad76568..00000000000 --- a/docs-exp/auth.phoneauthprovider._constructor_.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [PhoneAuthProvider](./auth.phoneauthprovider.md) > [(constructor)](./auth.phoneauthprovider._constructor_.md) - -## PhoneAuthProvider.(constructor) - -Constructs a new instance of the `PhoneAuthProvider` class - -Signature: - -```typescript -constructor(auth: externs.Auth); -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | externs.[Auth](./auth-types.auth.md) | | - diff --git a/docs-exp/auth.phoneauthprovider.credential.md b/docs-exp/auth.phoneauthprovider.credential.md deleted file mode 100644 index 02d00cc9888..00000000000 --- a/docs-exp/auth.phoneauthprovider.credential.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [PhoneAuthProvider](./auth.phoneauthprovider.md) > [credential](./auth.phoneauthprovider.credential.md) - -## PhoneAuthProvider.credential() method - -Signature: - -```typescript -static credential(verificationId: string, verificationCode: string): PhoneAuthCredential; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| verificationId | string | | -| verificationCode | string | | - -Returns: - -[PhoneAuthCredential](./auth.phoneauthcredential.md) - diff --git a/docs-exp/auth.phoneauthprovider.credentialfromresult.md b/docs-exp/auth.phoneauthprovider.credentialfromresult.md deleted file mode 100644 index 84d50fd38ce..00000000000 --- a/docs-exp/auth.phoneauthprovider.credentialfromresult.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [PhoneAuthProvider](./auth.phoneauthprovider.md) > [credentialFromResult](./auth.phoneauthprovider.credentialfromresult.md) - -## PhoneAuthProvider.credentialFromResult() method - -Signature: - -```typescript -static credentialFromResult(userCredential: externs.UserCredential): externs.AuthCredential | null; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| userCredential | externs.[UserCredential](./auth-types.usercredential.md) | | - -Returns: - -externs.[AuthCredential](./auth-types.authcredential.md) \| null - diff --git a/docs-exp/auth.phoneauthprovider.md b/docs-exp/auth.phoneauthprovider.md deleted file mode 100644 index 0aba88cd843..00000000000 --- a/docs-exp/auth.phoneauthprovider.md +++ /dev/null @@ -1,35 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [PhoneAuthProvider](./auth.phoneauthprovider.md) - -## PhoneAuthProvider class - -Signature: - -```typescript -export declare class PhoneAuthProvider implements externs.PhoneAuthProvider -``` -Implements: externs.[PhoneAuthProvider](./auth-types.phoneauthprovider.md) - -## Constructors - -| Constructor | Modifiers | Description | -| --- | --- | --- | -| [(constructor)(auth)](./auth.phoneauthprovider._constructor_.md) | | Constructs a new instance of the PhoneAuthProvider class | - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [PHONE\_SIGN\_IN\_METHOD](./auth.phoneauthprovider.phone_sign_in_method.md) | static | (not declared) | | -| [PROVIDER\_ID](./auth.phoneauthprovider.provider_id.md) | static | (not declared) | | -| [providerId](./auth.phoneauthprovider.providerid.md) | | (not declared) | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [credential(verificationId, verificationCode)](./auth.phoneauthprovider.credential.md) | static | | -| [credentialFromResult(userCredential)](./auth.phoneauthprovider.credentialfromresult.md) | static | | -| [verifyPhoneNumber(phoneOptions, applicationVerifier)](./auth.phoneauthprovider.verifyphonenumber.md) | | | - diff --git a/docs-exp/auth.phoneauthprovider.phone_sign_in_method.md b/docs-exp/auth.phoneauthprovider.phone_sign_in_method.md deleted file mode 100644 index c6a47a86cef..00000000000 --- a/docs-exp/auth.phoneauthprovider.phone_sign_in_method.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [PhoneAuthProvider](./auth.phoneauthprovider.md) > [PHONE\_SIGN\_IN\_METHOD](./auth.phoneauthprovider.phone_sign_in_method.md) - -## PhoneAuthProvider.PHONE\_SIGN\_IN\_METHOD property - -Signature: - -```typescript -static readonly PHONE_SIGN_IN_METHOD = externs.SignInMethod.PHONE; -``` diff --git a/docs-exp/auth.phoneauthprovider.provider_id.md b/docs-exp/auth.phoneauthprovider.provider_id.md deleted file mode 100644 index 4aa2cfb3f95..00000000000 --- a/docs-exp/auth.phoneauthprovider.provider_id.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [PhoneAuthProvider](./auth.phoneauthprovider.md) > [PROVIDER\_ID](./auth.phoneauthprovider.provider_id.md) - -## PhoneAuthProvider.PROVIDER\_ID property - -Signature: - -```typescript -static readonly PROVIDER_ID = externs.ProviderId.PHONE; -``` diff --git a/docs-exp/auth.phoneauthprovider.providerid.md b/docs-exp/auth.phoneauthprovider.providerid.md deleted file mode 100644 index e7fd5e2385e..00000000000 --- a/docs-exp/auth.phoneauthprovider.providerid.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [PhoneAuthProvider](./auth.phoneauthprovider.md) > [providerId](./auth.phoneauthprovider.providerid.md) - -## PhoneAuthProvider.providerId property - -Signature: - -```typescript -readonly providerId = externs.ProviderId.PHONE; -``` diff --git a/docs-exp/auth.phoneauthprovider.verifyphonenumber.md b/docs-exp/auth.phoneauthprovider.verifyphonenumber.md deleted file mode 100644 index f0985746ac3..00000000000 --- a/docs-exp/auth.phoneauthprovider.verifyphonenumber.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [PhoneAuthProvider](./auth.phoneauthprovider.md) > [verifyPhoneNumber](./auth.phoneauthprovider.verifyphonenumber.md) - -## PhoneAuthProvider.verifyPhoneNumber() method - -Signature: - -```typescript -verifyPhoneNumber(phoneOptions: externs.PhoneInfoOptions | string, applicationVerifier: externs.ApplicationVerifier): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| phoneOptions | externs.[PhoneInfoOptions](./auth-types.phoneinfooptions.md) \| string | | -| applicationVerifier | externs.[ApplicationVerifier](./auth-types.applicationverifier.md) | | - -Returns: - -Promise<string> - diff --git a/docs-exp/auth.phonemultifactorgenerator.assertion.md b/docs-exp/auth.phonemultifactorgenerator.assertion.md deleted file mode 100644 index ec67f7dd535..00000000000 --- a/docs-exp/auth.phonemultifactorgenerator.assertion.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [PhoneMultiFactorGenerator](./auth.phonemultifactorgenerator.md) > [assertion](./auth.phonemultifactorgenerator.assertion.md) - -## PhoneMultiFactorGenerator.assertion() method - -Signature: - -```typescript -static assertion(credential: externs.PhoneAuthCredential): externs.PhoneMultiFactorAssertion; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| credential | externs.[PhoneAuthCredential](./auth-types.phoneauthcredential.md) | | - -Returns: - -externs.[PhoneMultiFactorAssertion](./auth-types.phonemultifactorassertion.md) - diff --git a/docs-exp/auth.phonemultifactorgenerator.md b/docs-exp/auth.phonemultifactorgenerator.md deleted file mode 100644 index 3b554942994..00000000000 --- a/docs-exp/auth.phonemultifactorgenerator.md +++ /dev/null @@ -1,19 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [PhoneMultiFactorGenerator](./auth.phonemultifactorgenerator.md) - -## PhoneMultiFactorGenerator class - -Signature: - -```typescript -export declare class PhoneMultiFactorGenerator implements externs.PhoneMultiFactorGenerator -``` -Implements: externs.[PhoneMultiFactorGenerator](./auth-types.phonemultifactorgenerator.md) - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [assertion(credential)](./auth.phonemultifactorgenerator.assertion.md) | static | | - diff --git a/docs-exp/auth.reauthenticatewithcredential.md b/docs-exp/auth.reauthenticatewithcredential.md deleted file mode 100644 index 76550ed0700..00000000000 --- a/docs-exp/auth.reauthenticatewithcredential.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [reauthenticateWithCredential](./auth.reauthenticatewithcredential.md) - -## reauthenticateWithCredential() function - -Signature: - -```typescript -export declare function reauthenticateWithCredential(userExtern: externs.User, credentialExtern: externs.AuthCredential): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| userExtern | externs.[User](./auth-types.user.md) | | -| credentialExtern | externs.[AuthCredential](./auth-types.authcredential.md) | | - -Returns: - -Promise<externs.[UserCredential](./auth-types.usercredential.md)> - diff --git a/docs-exp/auth.reauthenticatewithphonenumber.md b/docs-exp/auth.reauthenticatewithphonenumber.md deleted file mode 100644 index 47c25c6ecba..00000000000 --- a/docs-exp/auth.reauthenticatewithphonenumber.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [reauthenticateWithPhoneNumber](./auth.reauthenticatewithphonenumber.md) - -## reauthenticateWithPhoneNumber() function - -Signature: - -```typescript -export declare function reauthenticateWithPhoneNumber(userExtern: externs.User, phoneNumber: string, appVerifier: externs.ApplicationVerifier): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| userExtern | externs.[User](./auth-types.user.md) | | -| phoneNumber | string | | -| appVerifier | externs.[ApplicationVerifier](./auth-types.applicationverifier.md) | | - -Returns: - -Promise<externs.[ConfirmationResult](./auth-types.confirmationresult.md)> - diff --git a/docs-exp/auth.reauthenticatewithpopup.md b/docs-exp/auth.reauthenticatewithpopup.md deleted file mode 100644 index f759246a63a..00000000000 --- a/docs-exp/auth.reauthenticatewithpopup.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [reauthenticateWithPopup](./auth.reauthenticatewithpopup.md) - -## reauthenticateWithPopup() function - -Signature: - -```typescript -export declare function reauthenticateWithPopup(userExtern: externs.User, provider: externs.AuthProvider, resolverExtern?: externs.PopupRedirectResolver): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| userExtern | externs.[User](./auth-types.user.md) | | -| provider | externs.[AuthProvider](./auth-types.authprovider.md) | | -| resolverExtern | externs.[PopupRedirectResolver](./auth-types.popupredirectresolver.md) | | - -Returns: - -Promise<externs.[UserCredential](./auth-types.usercredential.md)> - diff --git a/docs-exp/auth.reauthenticatewithredirect.md b/docs-exp/auth.reauthenticatewithredirect.md deleted file mode 100644 index 537f4810542..00000000000 --- a/docs-exp/auth.reauthenticatewithredirect.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [reauthenticateWithRedirect](./auth.reauthenticatewithredirect.md) - -## reauthenticateWithRedirect() function - -Signature: - -```typescript -export declare function reauthenticateWithRedirect(userExtern: externs.User, provider: externs.AuthProvider, resolverExtern?: externs.PopupRedirectResolver): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| userExtern | externs.[User](./auth-types.user.md) | | -| provider | externs.[AuthProvider](./auth-types.authprovider.md) | | -| resolverExtern | externs.[PopupRedirectResolver](./auth-types.popupredirectresolver.md) | | - -Returns: - -Promise<never> - diff --git a/docs-exp/auth.recaptchaverifier._constructor_.md b/docs-exp/auth.recaptchaverifier._constructor_.md deleted file mode 100644 index b30e021e866..00000000000 --- a/docs-exp/auth.recaptchaverifier._constructor_.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [RecaptchaVerifier](./auth.recaptchaverifier.md) > [(constructor)](./auth.recaptchaverifier._constructor_.md) - -## RecaptchaVerifier.(constructor) - -Constructs a new instance of the `RecaptchaVerifier` class - -Signature: - -```typescript -constructor(containerOrId: HTMLElement | string, parameters: Parameters, authExtern: externs.Auth); -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| containerOrId | HTMLElement \| string | | -| parameters | Parameters | | -| authExtern | externs.[Auth](./auth-types.auth.md) | | - diff --git a/docs-exp/auth.recaptchaverifier._recaptchaloader.md b/docs-exp/auth.recaptchaverifier._recaptchaloader.md deleted file mode 100644 index d007fadcd77..00000000000 --- a/docs-exp/auth.recaptchaverifier._recaptchaloader.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [RecaptchaVerifier](./auth.recaptchaverifier.md) > [\_recaptchaLoader](./auth.recaptchaverifier._recaptchaloader.md) - -## RecaptchaVerifier.\_recaptchaLoader property - -Signature: - -```typescript -readonly _recaptchaLoader: ReCaptchaLoader; -``` diff --git a/docs-exp/auth.recaptchaverifier._reset.md b/docs-exp/auth.recaptchaverifier._reset.md deleted file mode 100644 index 9eac4ac21fd..00000000000 --- a/docs-exp/auth.recaptchaverifier._reset.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [RecaptchaVerifier](./auth.recaptchaverifier.md) > [\_reset](./auth.recaptchaverifier._reset.md) - -## RecaptchaVerifier.\_reset() method - -Signature: - -```typescript -_reset(): void; -``` -Returns: - -void - diff --git a/docs-exp/auth.recaptchaverifier.clear.md b/docs-exp/auth.recaptchaverifier.clear.md deleted file mode 100644 index ba123cbca89..00000000000 --- a/docs-exp/auth.recaptchaverifier.clear.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [RecaptchaVerifier](./auth.recaptchaverifier.md) > [clear](./auth.recaptchaverifier.clear.md) - -## RecaptchaVerifier.clear() method - -Signature: - -```typescript -clear(): void; -``` -Returns: - -void - diff --git a/docs-exp/auth.recaptchaverifier.md b/docs-exp/auth.recaptchaverifier.md deleted file mode 100644 index ecbf1958428..00000000000 --- a/docs-exp/auth.recaptchaverifier.md +++ /dev/null @@ -1,35 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [RecaptchaVerifier](./auth.recaptchaverifier.md) - -## RecaptchaVerifier class - -Signature: - -```typescript -export declare class RecaptchaVerifier implements externs.RecaptchaVerifier, ApplicationVerifier -``` -Implements: externs.[RecaptchaVerifier](./auth-types.recaptchaverifier.md), ApplicationVerifier - -## Constructors - -| Constructor | Modifiers | Description | -| --- | --- | --- | -| [(constructor)(containerOrId, parameters, authExtern)](./auth.recaptchaverifier._constructor_.md) | | Constructs a new instance of the RecaptchaVerifier class | - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [\_recaptchaLoader](./auth.recaptchaverifier._recaptchaloader.md) | | ReCaptchaLoader | | -| [type](./auth.recaptchaverifier.type.md) | | (not declared) | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [\_reset()](./auth.recaptchaverifier._reset.md) | | | -| [clear()](./auth.recaptchaverifier.clear.md) | | | -| [render()](./auth.recaptchaverifier.render.md) | | | -| [verify()](./auth.recaptchaverifier.verify.md) | | | - diff --git a/docs-exp/auth.recaptchaverifier.render.md b/docs-exp/auth.recaptchaverifier.render.md deleted file mode 100644 index 8906d27b543..00000000000 --- a/docs-exp/auth.recaptchaverifier.render.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [RecaptchaVerifier](./auth.recaptchaverifier.md) > [render](./auth.recaptchaverifier.render.md) - -## RecaptchaVerifier.render() method - -Signature: - -```typescript -render(): Promise; -``` -Returns: - -Promise<number> - diff --git a/docs-exp/auth.recaptchaverifier.type.md b/docs-exp/auth.recaptchaverifier.type.md deleted file mode 100644 index bbfe066e173..00000000000 --- a/docs-exp/auth.recaptchaverifier.type.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [RecaptchaVerifier](./auth.recaptchaverifier.md) > [type](./auth.recaptchaverifier.type.md) - -## RecaptchaVerifier.type property - -Signature: - -```typescript -readonly type = "recaptcha"; -``` diff --git a/docs-exp/auth.recaptchaverifier.verify.md b/docs-exp/auth.recaptchaverifier.verify.md deleted file mode 100644 index b43a620c077..00000000000 --- a/docs-exp/auth.recaptchaverifier.verify.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [RecaptchaVerifier](./auth.recaptchaverifier.md) > [verify](./auth.recaptchaverifier.verify.md) - -## RecaptchaVerifier.verify() method - -Signature: - -```typescript -verify(): Promise; -``` -Returns: - -Promise<string> - diff --git a/docs-exp/auth.reload.md b/docs-exp/auth.reload.md deleted file mode 100644 index 80b815e6fba..00000000000 --- a/docs-exp/auth.reload.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [reload](./auth.reload.md) - -## reload() function - -Signature: - -```typescript -export declare function reload(externUser: externs.User): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| externUser | externs.[User](./auth-types.user.md) | | - -Returns: - -Promise<void> - diff --git a/docs-exp/auth.sendemailverification.md b/docs-exp/auth.sendemailverification.md deleted file mode 100644 index 48a278b86bf..00000000000 --- a/docs-exp/auth.sendemailverification.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [sendEmailVerification](./auth.sendemailverification.md) - -## sendEmailVerification() function - -Signature: - -```typescript -export declare function sendEmailVerification(userExtern: externs.User, actionCodeSettings?: externs.ActionCodeSettings | null): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| userExtern | externs.[User](./auth-types.user.md) | | -| actionCodeSettings | externs.[ActionCodeSettings](./auth-types.actioncodesettings.md) \| null | | - -Returns: - -Promise<void> - diff --git a/docs-exp/auth.sendpasswordresetemail.md b/docs-exp/auth.sendpasswordresetemail.md deleted file mode 100644 index 43dfca24398..00000000000 --- a/docs-exp/auth.sendpasswordresetemail.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [sendPasswordResetEmail](./auth.sendpasswordresetemail.md) - -## sendPasswordResetEmail() function - -Signature: - -```typescript -export declare function sendPasswordResetEmail(auth: externs.Auth, email: string, actionCodeSettings?: externs.ActionCodeSettings): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | externs.[Auth](./auth-types.auth.md) | | -| email | string | | -| actionCodeSettings | externs.[ActionCodeSettings](./auth-types.actioncodesettings.md) | | - -Returns: - -Promise<void> - diff --git a/docs-exp/auth.sendsigninlinktoemail.md b/docs-exp/auth.sendsigninlinktoemail.md deleted file mode 100644 index 22df05cb5cf..00000000000 --- a/docs-exp/auth.sendsigninlinktoemail.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [sendSignInLinkToEmail](./auth.sendsigninlinktoemail.md) - -## sendSignInLinkToEmail() function - -Signature: - -```typescript -export declare function sendSignInLinkToEmail(auth: externs.Auth, email: string, actionCodeSettings?: externs.ActionCodeSettings): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | externs.[Auth](./auth-types.auth.md) | | -| email | string | | -| actionCodeSettings | externs.[ActionCodeSettings](./auth-types.actioncodesettings.md) | | - -Returns: - -Promise<void> - diff --git a/docs-exp/auth.setpersistence.md b/docs-exp/auth.setpersistence.md deleted file mode 100644 index 36a154e6e26..00000000000 --- a/docs-exp/auth.setpersistence.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [setPersistence](./auth.setpersistence.md) - -## setPersistence() function - -Signature: - -```typescript -export declare function setPersistence(auth: externs.Auth, persistence: externs.Persistence): void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | externs.[Auth](./auth-types.auth.md) | | -| persistence | externs.[Persistence](./auth-types.persistence.md) | | - -Returns: - -void - diff --git a/docs-exp/auth.signinanonymously.md b/docs-exp/auth.signinanonymously.md deleted file mode 100644 index a3c8d7be30e..00000000000 --- a/docs-exp/auth.signinanonymously.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [signInAnonymously](./auth.signinanonymously.md) - -## signInAnonymously() function - -Signature: - -```typescript -export declare function signInAnonymously(auth: externs.Auth): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | externs.[Auth](./auth-types.auth.md) | | - -Returns: - -Promise<externs.[UserCredential](./auth-types.usercredential.md)> - diff --git a/docs-exp/auth.signinwithcredential.md b/docs-exp/auth.signinwithcredential.md deleted file mode 100644 index bc33d4a27d9..00000000000 --- a/docs-exp/auth.signinwithcredential.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [signInWithCredential](./auth.signinwithcredential.md) - -## signInWithCredential() function - -Signature: - -```typescript -export declare function signInWithCredential(auth: externs.Auth, credential: externs.AuthCredential): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | externs.[Auth](./auth-types.auth.md) | | -| credential | externs.[AuthCredential](./auth-types.authcredential.md) | | - -Returns: - -Promise<externs.[UserCredential](./auth-types.usercredential.md)> - diff --git a/docs-exp/auth.signinwithcustomtoken.md b/docs-exp/auth.signinwithcustomtoken.md deleted file mode 100644 index 236c14cd1c4..00000000000 --- a/docs-exp/auth.signinwithcustomtoken.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [signInWithCustomToken](./auth.signinwithcustomtoken.md) - -## signInWithCustomToken() function - -Signature: - -```typescript -export declare function signInWithCustomToken(authExtern: externs.Auth, customToken: string): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| authExtern | externs.[Auth](./auth-types.auth.md) | | -| customToken | string | | - -Returns: - -Promise<externs.[UserCredential](./auth-types.usercredential.md)> - diff --git a/docs-exp/auth.signinwithemailandpassword.md b/docs-exp/auth.signinwithemailandpassword.md deleted file mode 100644 index 38cc2a1832f..00000000000 --- a/docs-exp/auth.signinwithemailandpassword.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [signInWithEmailAndPassword](./auth.signinwithemailandpassword.md) - -## signInWithEmailAndPassword() function - -Signature: - -```typescript -export declare function signInWithEmailAndPassword(auth: externs.Auth, email: string, password: string): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | externs.[Auth](./auth-types.auth.md) | | -| email | string | | -| password | string | | - -Returns: - -Promise<externs.[UserCredential](./auth-types.usercredential.md)> - diff --git a/docs-exp/auth.signinwithemaillink.md b/docs-exp/auth.signinwithemaillink.md deleted file mode 100644 index e99930a1828..00000000000 --- a/docs-exp/auth.signinwithemaillink.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [signInWithEmailLink](./auth.signinwithemaillink.md) - -## signInWithEmailLink() function - -Signature: - -```typescript -export declare function signInWithEmailLink(auth: externs.Auth, email: string, emailLink?: string): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | externs.[Auth](./auth-types.auth.md) | | -| email | string | | -| emailLink | string | | - -Returns: - -Promise<externs.[UserCredential](./auth-types.usercredential.md)> - diff --git a/docs-exp/auth.signinwithphonenumber.md b/docs-exp/auth.signinwithphonenumber.md deleted file mode 100644 index 82768c1b0ee..00000000000 --- a/docs-exp/auth.signinwithphonenumber.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [signInWithPhoneNumber](./auth.signinwithphonenumber.md) - -## signInWithPhoneNumber() function - -Signature: - -```typescript -export declare function signInWithPhoneNumber(auth: externs.Auth, phoneNumber: string, appVerifier: externs.ApplicationVerifier): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | externs.[Auth](./auth-types.auth.md) | | -| phoneNumber | string | | -| appVerifier | externs.[ApplicationVerifier](./auth-types.applicationverifier.md) | | - -Returns: - -Promise<externs.[ConfirmationResult](./auth-types.confirmationresult.md)> - diff --git a/docs-exp/auth.signinwithpopup.md b/docs-exp/auth.signinwithpopup.md deleted file mode 100644 index 4be7d318680..00000000000 --- a/docs-exp/auth.signinwithpopup.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [signInWithPopup](./auth.signinwithpopup.md) - -## signInWithPopup() function - -Signature: - -```typescript -export declare function signInWithPopup(authExtern: externs.Auth, provider: externs.AuthProvider, resolverExtern?: externs.PopupRedirectResolver): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| authExtern | externs.[Auth](./auth-types.auth.md) | | -| provider | externs.[AuthProvider](./auth-types.authprovider.md) | | -| resolverExtern | externs.[PopupRedirectResolver](./auth-types.popupredirectresolver.md) | | - -Returns: - -Promise<externs.[UserCredential](./auth-types.usercredential.md)> - diff --git a/docs-exp/auth.signinwithredirect.md b/docs-exp/auth.signinwithredirect.md deleted file mode 100644 index 86336c4a436..00000000000 --- a/docs-exp/auth.signinwithredirect.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [signInWithRedirect](./auth.signinwithredirect.md) - -## signInWithRedirect() function - -Signature: - -```typescript -export declare function signInWithRedirect(authExtern: externs.Auth, provider: externs.AuthProvider, resolverExtern?: externs.PopupRedirectResolver): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| authExtern | externs.[Auth](./auth-types.auth.md) | | -| provider | externs.[AuthProvider](./auth-types.authprovider.md) | | -| resolverExtern | externs.[PopupRedirectResolver](./auth-types.popupredirectresolver.md) | | - -Returns: - -Promise<never> - diff --git a/docs-exp/auth.signout.md b/docs-exp/auth.signout.md deleted file mode 100644 index 81de60f223d..00000000000 --- a/docs-exp/auth.signout.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [signOut](./auth.signout.md) - -## signOut() function - -Signature: - -```typescript -export declare function signOut(auth: externs.Auth): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | externs.[Auth](./auth-types.auth.md) | | - -Returns: - -Promise<void> - diff --git a/docs-exp/auth.twitterauthprovider.credential.md b/docs-exp/auth.twitterauthprovider.credential.md deleted file mode 100644 index 6d69b167d4f..00000000000 --- a/docs-exp/auth.twitterauthprovider.credential.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [TwitterAuthProvider](./auth.twitterauthprovider.md) > [credential](./auth.twitterauthprovider.credential.md) - -## TwitterAuthProvider.credential() method - -Signature: - -```typescript -static credential(token: string, secret: string): externs.OAuthCredential; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| token | string | | -| secret | string | | - -Returns: - -externs.[OAuthCredential](./auth-types.oauthcredential.md) - diff --git a/docs-exp/auth.twitterauthprovider.credentialfromerror.md b/docs-exp/auth.twitterauthprovider.credentialfromerror.md deleted file mode 100644 index 65415df82a8..00000000000 --- a/docs-exp/auth.twitterauthprovider.credentialfromerror.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [TwitterAuthProvider](./auth.twitterauthprovider.md) > [credentialFromError](./auth.twitterauthprovider.credentialfromerror.md) - -## TwitterAuthProvider.credentialFromError() method - -Signature: - -```typescript -static credentialFromError(error: FirebaseError): externs.OAuthCredential | null; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| error | FirebaseError | | - -Returns: - -externs.[OAuthCredential](./auth-types.oauthcredential.md) \| null - diff --git a/docs-exp/auth.twitterauthprovider.credentialfromresult.md b/docs-exp/auth.twitterauthprovider.credentialfromresult.md deleted file mode 100644 index 1f48a64aa6a..00000000000 --- a/docs-exp/auth.twitterauthprovider.credentialfromresult.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [TwitterAuthProvider](./auth.twitterauthprovider.md) > [credentialFromResult](./auth.twitterauthprovider.credentialfromresult.md) - -## TwitterAuthProvider.credentialFromResult() method - -Signature: - -```typescript -static credentialFromResult(userCredential: externs.UserCredential): externs.OAuthCredential | null; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| userCredential | externs.[UserCredential](./auth-types.usercredential.md) | | - -Returns: - -externs.[OAuthCredential](./auth-types.oauthcredential.md) \| null - diff --git a/docs-exp/auth.twitterauthprovider.md b/docs-exp/auth.twitterauthprovider.md deleted file mode 100644 index 82878bcb9e3..00000000000 --- a/docs-exp/auth.twitterauthprovider.md +++ /dev/null @@ -1,29 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [TwitterAuthProvider](./auth.twitterauthprovider.md) - -## TwitterAuthProvider class - -Signature: - -```typescript -export declare class TwitterAuthProvider extends OAuthProvider -``` -Extends: [OAuthProvider](./auth.oauthprovider.md) - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [PROVIDER\_ID](./auth.twitterauthprovider.provider_id.md) | static | (not declared) | | -| [providerId](./auth.twitterauthprovider.providerid.md) | | (not declared) | | -| [TWITTER\_SIGN\_IN\_METHOD](./auth.twitterauthprovider.twitter_sign_in_method.md) | static | (not declared) | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [credential(token, secret)](./auth.twitterauthprovider.credential.md) | static | | -| [credentialFromError(error)](./auth.twitterauthprovider.credentialfromerror.md) | static | | -| [credentialFromResult(userCredential)](./auth.twitterauthprovider.credentialfromresult.md) | static | | - diff --git a/docs-exp/auth.twitterauthprovider.provider_id.md b/docs-exp/auth.twitterauthprovider.provider_id.md deleted file mode 100644 index 420e0760780..00000000000 --- a/docs-exp/auth.twitterauthprovider.provider_id.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [TwitterAuthProvider](./auth.twitterauthprovider.md) > [PROVIDER\_ID](./auth.twitterauthprovider.provider_id.md) - -## TwitterAuthProvider.PROVIDER\_ID property - -Signature: - -```typescript -static readonly PROVIDER_ID = externs.ProviderId.TWITTER; -``` diff --git a/docs-exp/auth.twitterauthprovider.providerid.md b/docs-exp/auth.twitterauthprovider.providerid.md deleted file mode 100644 index 511fd75a0d7..00000000000 --- a/docs-exp/auth.twitterauthprovider.providerid.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [TwitterAuthProvider](./auth.twitterauthprovider.md) > [providerId](./auth.twitterauthprovider.providerid.md) - -## TwitterAuthProvider.providerId property - -Signature: - -```typescript -readonly providerId = externs.ProviderId.TWITTER; -``` diff --git a/docs-exp/auth.twitterauthprovider.twitter_sign_in_method.md b/docs-exp/auth.twitterauthprovider.twitter_sign_in_method.md deleted file mode 100644 index 49c6e2064b3..00000000000 --- a/docs-exp/auth.twitterauthprovider.twitter_sign_in_method.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [TwitterAuthProvider](./auth.twitterauthprovider.md) > [TWITTER\_SIGN\_IN\_METHOD](./auth.twitterauthprovider.twitter_sign_in_method.md) - -## TwitterAuthProvider.TWITTER\_SIGN\_IN\_METHOD property - -Signature: - -```typescript -static readonly TWITTER_SIGN_IN_METHOD = externs.SignInMethod.TWITTER; -``` diff --git a/docs-exp/auth.unlink.md b/docs-exp/auth.unlink.md deleted file mode 100644 index 9ccb9bec4ca..00000000000 --- a/docs-exp/auth.unlink.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [unlink](./auth.unlink.md) - -## unlink() function - -This is the externally visible unlink function - -Signature: - -```typescript -export declare function unlink(userExtern: externs.User, providerId: externs.ProviderId): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| userExtern | externs.[User](./auth-types.user.md) | | -| providerId | externs.[ProviderId](./auth-types.providerid.md) | | - -Returns: - -Promise<externs.[User](./auth-types.user.md)> - diff --git a/docs-exp/auth.updatecurrentuser.md b/docs-exp/auth.updatecurrentuser.md deleted file mode 100644 index c80b7fcadc2..00000000000 --- a/docs-exp/auth.updatecurrentuser.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [updateCurrentUser](./auth.updatecurrentuser.md) - -## updateCurrentUser() function - -Signature: - -```typescript -export declare function updateCurrentUser(auth: externs.Auth, user: externs.User | null): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | externs.[Auth](./auth-types.auth.md) | | -| user | externs.[User](./auth-types.user.md) \| null | | - -Returns: - -Promise<void> - diff --git a/docs-exp/auth.updateemail.md b/docs-exp/auth.updateemail.md deleted file mode 100644 index 23b46b2f4d8..00000000000 --- a/docs-exp/auth.updateemail.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [updateEmail](./auth.updateemail.md) - -## updateEmail() function - -Signature: - -```typescript -export declare function updateEmail(externUser: externs.User, newEmail: string): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| externUser | externs.[User](./auth-types.user.md) | | -| newEmail | string | | - -Returns: - -Promise<void> - diff --git a/docs-exp/auth.updatepassword.md b/docs-exp/auth.updatepassword.md deleted file mode 100644 index 2ab59fd0837..00000000000 --- a/docs-exp/auth.updatepassword.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [updatePassword](./auth.updatepassword.md) - -## updatePassword() function - -Signature: - -```typescript -export declare function updatePassword(externUser: externs.User, newPassword: string): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| externUser | externs.[User](./auth-types.user.md) | | -| newPassword | string | | - -Returns: - -Promise<void> - diff --git a/docs-exp/auth.updatephonenumber.md b/docs-exp/auth.updatephonenumber.md deleted file mode 100644 index b744161c6e8..00000000000 --- a/docs-exp/auth.updatephonenumber.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [updatePhoneNumber](./auth.updatephonenumber.md) - -## updatePhoneNumber() function - -Signature: - -```typescript -export declare function updatePhoneNumber(user: externs.User, credential: externs.PhoneAuthCredential): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| user | externs.[User](./auth-types.user.md) | | -| credential | externs.[PhoneAuthCredential](./auth-types.phoneauthcredential.md) | | - -Returns: - -Promise<void> - diff --git a/docs-exp/auth.updateprofile.md b/docs-exp/auth.updateprofile.md deleted file mode 100644 index 4e6185bc6ec..00000000000 --- a/docs-exp/auth.updateprofile.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [updateProfile](./auth.updateprofile.md) - -## updateProfile() function - -Signature: - -```typescript -export declare function updateProfile(externUser: externs.User, { displayName, photoURL: photoUrl }: Profile): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| externUser | externs.[User](./auth-types.user.md) | | -| { displayName, photoURL: photoUrl } | Profile | | - -Returns: - -Promise<void> - diff --git a/docs-exp/auth.usedevicelanguage.md b/docs-exp/auth.usedevicelanguage.md deleted file mode 100644 index 66d97759634..00000000000 --- a/docs-exp/auth.usedevicelanguage.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [useDeviceLanguage](./auth.usedevicelanguage.md) - -## useDeviceLanguage() function - -Signature: - -```typescript -export declare function useDeviceLanguage(auth: externs.Auth): void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | externs.[Auth](./auth-types.auth.md) | | - -Returns: - -void - diff --git a/docs-exp/auth.verifybeforeupdateemail.md b/docs-exp/auth.verifybeforeupdateemail.md deleted file mode 100644 index c2e1bc53ebc..00000000000 --- a/docs-exp/auth.verifybeforeupdateemail.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [verifyBeforeUpdateEmail](./auth.verifybeforeupdateemail.md) - -## verifyBeforeUpdateEmail() function - -Signature: - -```typescript -export declare function verifyBeforeUpdateEmail(userExtern: externs.User, newEmail: string, actionCodeSettings?: externs.ActionCodeSettings | null): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| userExtern | externs.[User](./auth-types.user.md) | | -| newEmail | string | | -| actionCodeSettings | externs.[ActionCodeSettings](./auth-types.actioncodesettings.md) \| null | | - -Returns: - -Promise<void> - diff --git a/docs-exp/auth.verifypasswordresetcode.md b/docs-exp/auth.verifypasswordresetcode.md deleted file mode 100644 index 9639e5f5e02..00000000000 --- a/docs-exp/auth.verifypasswordresetcode.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/auth](./auth.md) > [verifyPasswordResetCode](./auth.verifypasswordresetcode.md) - -## verifyPasswordResetCode() function - -Signature: - -```typescript -export declare function verifyPasswordResetCode(auth: externs.Auth, code: string): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| auth | externs.[Auth](./auth-types.auth.md) | | -| code | string | | - -Returns: - -Promise<string> - diff --git a/docs-exp/firestore.md b/docs-exp/firestore.md deleted file mode 100644 index 43b3e8c657c..00000000000 --- a/docs-exp/firestore.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) - -## firestore package - -| Entry Point | Description | -| --- | --- | -| [/](./firestore_.md) | | -| [/lite](./firestore_lite.md) | | - diff --git a/docs-exp/firestore_.adddoc.md b/docs-exp/firestore_.adddoc.md deleted file mode 100644 index 016fd2836bc..00000000000 --- a/docs-exp/firestore_.adddoc.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [addDoc](./firestore_.adddoc.md) - -## addDoc() function - -Signature: - -```typescript -export function addDoc( - reference: CollectionReference, - data: T -): Promise>; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| reference | [CollectionReference](./firestore_.collectionreference.md)<T> | | -| data | T | | - -Returns: - -Promise<[DocumentReference](./firestore_.documentreference.md)<T>> - diff --git a/docs-exp/firestore_.arrayremove.md b/docs-exp/firestore_.arrayremove.md deleted file mode 100644 index 916e727e356..00000000000 --- a/docs-exp/firestore_.arrayremove.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [arrayRemove](./firestore_.arrayremove.md) - -## arrayRemove() function - -Signature: - -```typescript -export function arrayRemove(...elements: any[]): FieldValue; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| elements | any\[\] | | - -Returns: - -[FieldValue](./firestore_.fieldvalue.md) - diff --git a/docs-exp/firestore_.arrayunion.md b/docs-exp/firestore_.arrayunion.md deleted file mode 100644 index c6f8ea4dfc8..00000000000 --- a/docs-exp/firestore_.arrayunion.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [arrayUnion](./firestore_.arrayunion.md) - -## arrayUnion() function - -Signature: - -```typescript -export function arrayUnion(...elements: any[]): FieldValue; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| elements | any\[\] | | - -Returns: - -[FieldValue](./firestore_.fieldvalue.md) - diff --git a/docs-exp/firestore_.bytes.frombase64string.md b/docs-exp/firestore_.bytes.frombase64string.md deleted file mode 100644 index 60940c4e7c0..00000000000 --- a/docs-exp/firestore_.bytes.frombase64string.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Bytes](./firestore_.bytes.md) > [fromBase64String](./firestore_.bytes.frombase64string.md) - -## Bytes.fromBase64String() method - -Signature: - -```typescript -static fromBase64String(base64: string): Blob; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| base64 | string | | - -Returns: - -Blob - diff --git a/docs-exp/firestore_.bytes.fromuint8array.md b/docs-exp/firestore_.bytes.fromuint8array.md deleted file mode 100644 index 0ddcf272731..00000000000 --- a/docs-exp/firestore_.bytes.fromuint8array.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Bytes](./firestore_.bytes.md) > [fromUint8Array](./firestore_.bytes.fromuint8array.md) - -## Bytes.fromUint8Array() method - -Signature: - -```typescript -static fromUint8Array(array: Uint8Array): Blob; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| array | Uint8Array | | - -Returns: - -Blob - diff --git a/docs-exp/firestore_.bytes.isequal.md b/docs-exp/firestore_.bytes.isequal.md deleted file mode 100644 index 33f03a32245..00000000000 --- a/docs-exp/firestore_.bytes.isequal.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Bytes](./firestore_.bytes.md) > [isEqual](./firestore_.bytes.isequal.md) - -## Bytes.isEqual() method - -Signature: - -```typescript -isEqual(other: Blob): boolean; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| other | Blob | | - -Returns: - -boolean - diff --git a/docs-exp/firestore_.bytes.md b/docs-exp/firestore_.bytes.md deleted file mode 100644 index 6e531118131..00000000000 --- a/docs-exp/firestore_.bytes.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Bytes](./firestore_.bytes.md) - -## Bytes class - -Signature: - -```typescript -export class Bytes -``` - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [fromBase64String(base64)](./firestore_.bytes.frombase64string.md) | static | | -| [fromUint8Array(array)](./firestore_.bytes.fromuint8array.md) | static | | -| [isEqual(other)](./firestore_.bytes.isequal.md) | | | -| [toBase64()](./firestore_.bytes.tobase64.md) | | | -| [toUint8Array()](./firestore_.bytes.touint8array.md) | | | - diff --git a/docs-exp/firestore_.bytes.tobase64.md b/docs-exp/firestore_.bytes.tobase64.md deleted file mode 100644 index d014597113b..00000000000 --- a/docs-exp/firestore_.bytes.tobase64.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Bytes](./firestore_.bytes.md) > [toBase64](./firestore_.bytes.tobase64.md) - -## Bytes.toBase64() method - -Signature: - -```typescript -toBase64(): string; -``` -Returns: - -string - diff --git a/docs-exp/firestore_.bytes.touint8array.md b/docs-exp/firestore_.bytes.touint8array.md deleted file mode 100644 index 0864fd7108b..00000000000 --- a/docs-exp/firestore_.bytes.touint8array.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Bytes](./firestore_.bytes.md) > [toUint8Array](./firestore_.bytes.touint8array.md) - -## Bytes.toUint8Array() method - -Signature: - -```typescript -toUint8Array(): Uint8Array; -``` -Returns: - -Uint8Array - diff --git a/docs-exp/firestore_.cache_size_unlimited.md b/docs-exp/firestore_.cache_size_unlimited.md deleted file mode 100644 index deb1bde75c2..00000000000 --- a/docs-exp/firestore_.cache_size_unlimited.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [CACHE\_SIZE\_UNLIMITED](./firestore_.cache_size_unlimited.md) - -## CACHE\_SIZE\_UNLIMITED variable - -Signature: - -```typescript -CACHE_SIZE_UNLIMITED: number -``` diff --git a/docs-exp/firestore_.clearindexeddbpersistence.md b/docs-exp/firestore_.clearindexeddbpersistence.md deleted file mode 100644 index 290a9f68c2b..00000000000 --- a/docs-exp/firestore_.clearindexeddbpersistence.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [clearIndexedDbPersistence](./firestore_.clearindexeddbpersistence.md) - -## clearIndexedDbPersistence() function - -Signature: - -```typescript -export function clearIndexedDbPersistence( - firestore: FirebaseFirestore -): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| firestore | [FirebaseFirestore](./firestore_.firebasefirestore.md) | | - -Returns: - -Promise<void> - diff --git a/docs-exp/firestore_.collection.md b/docs-exp/firestore_.collection.md deleted file mode 100644 index bff81779aa8..00000000000 --- a/docs-exp/firestore_.collection.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [collection](./firestore_.collection.md) - -## collection() function - -Signature: - -```typescript -export function collection( - firestore: FirebaseFirestore, - collectionPath: string -): CollectionReference; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| firestore | [FirebaseFirestore](./firestore_.firebasefirestore.md) | | -| collectionPath | string | | - -Returns: - -[CollectionReference](./firestore_.collectionreference.md)<[DocumentData](./firestore_.documentdata.md)> - diff --git a/docs-exp/firestore_.collection_1.md b/docs-exp/firestore_.collection_1.md deleted file mode 100644 index dc4f299ad12..00000000000 --- a/docs-exp/firestore_.collection_1.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [collection](./firestore_.collection_1.md) - -## collection() function - -Signature: - -```typescript -export function collection( - reference: CollectionReference, - collectionPath: string -): CollectionReference; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| reference | [CollectionReference](./firestore_.collectionreference.md)<unknown> | | -| collectionPath | string | | - -Returns: - -[CollectionReference](./firestore_.collectionreference.md)<[DocumentData](./firestore_.documentdata.md)> - diff --git a/docs-exp/firestore_.collection_2.md b/docs-exp/firestore_.collection_2.md deleted file mode 100644 index 880268da15f..00000000000 --- a/docs-exp/firestore_.collection_2.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [collection](./firestore_.collection_2.md) - -## collection() function - -Signature: - -```typescript -export function collection( - reference: DocumentReference, - collectionPath: string -): CollectionReference; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| reference | [DocumentReference](./firestore_.documentreference.md) | | -| collectionPath | string | | - -Returns: - -[CollectionReference](./firestore_.collectionreference.md)<[DocumentData](./firestore_.documentdata.md)> - diff --git a/docs-exp/firestore_.collectiongroup.md b/docs-exp/firestore_.collectiongroup.md deleted file mode 100644 index dd0db4cf766..00000000000 --- a/docs-exp/firestore_.collectiongroup.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [collectionGroup](./firestore_.collectiongroup.md) - -## collectionGroup() function - -Signature: - -```typescript -export function collectionGroup( - firestore: FirebaseFirestore, - collectionId: string -): Query; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| firestore | [FirebaseFirestore](./firestore_.firebasefirestore.md) | | -| collectionId | string | | - -Returns: - -[Query](./firestore_.query.md)<[DocumentData](./firestore_.documentdata.md)> - diff --git a/docs-exp/firestore_.collectionreference.doc.md b/docs-exp/firestore_.collectionreference.doc.md deleted file mode 100644 index d8d47a73f7e..00000000000 --- a/docs-exp/firestore_.collectionreference.doc.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [CollectionReference](./firestore_.collectionreference.md) > [doc](./firestore_.collectionreference.doc.md) - -## CollectionReference.doc() method - -Signature: - -```typescript -doc(documentPath?: string): DocumentReference; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| documentPath | string | | - -Returns: - -[DocumentReference](./firestore_.documentreference.md)<T> - diff --git a/docs-exp/firestore_.collectionreference.id.md b/docs-exp/firestore_.collectionreference.id.md deleted file mode 100644 index 691bd6a18d6..00000000000 --- a/docs-exp/firestore_.collectionreference.id.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [CollectionReference](./firestore_.collectionreference.md) > [id](./firestore_.collectionreference.id.md) - -## CollectionReference.id property - -Signature: - -```typescript -readonly id: string; -``` diff --git a/docs-exp/firestore_.collectionreference.md b/docs-exp/firestore_.collectionreference.md deleted file mode 100644 index 787f5cf4fbc..00000000000 --- a/docs-exp/firestore_.collectionreference.md +++ /dev/null @@ -1,29 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [CollectionReference](./firestore_.collectionreference.md) - -## CollectionReference class - -Signature: - -```typescript -export class CollectionReference extends Query -``` -Extends: [Query](./firestore_.query.md)<T> - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [id](./firestore_.collectionreference.id.md) | | string | | -| [parent](./firestore_.collectionreference.parent.md) | | [DocumentReference](./firestore_.documentreference.md)<[DocumentData](./firestore_.documentdata.md)> \| null | | -| [path](./firestore_.collectionreference.path.md) | | string | | -| [type](./firestore_.collectionreference.type.md) | | 'collection' | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [doc(documentPath)](./firestore_.collectionreference.doc.md) | | | -| [withConverter(converter)](./firestore_.collectionreference.withconverter.md) | | | - diff --git a/docs-exp/firestore_.collectionreference.parent.md b/docs-exp/firestore_.collectionreference.parent.md deleted file mode 100644 index cc86bd15b45..00000000000 --- a/docs-exp/firestore_.collectionreference.parent.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [CollectionReference](./firestore_.collectionreference.md) > [parent](./firestore_.collectionreference.parent.md) - -## CollectionReference.parent property - -Signature: - -```typescript -get parent(): DocumentReference | null; -``` diff --git a/docs-exp/firestore_.collectionreference.path.md b/docs-exp/firestore_.collectionreference.path.md deleted file mode 100644 index e764535833b..00000000000 --- a/docs-exp/firestore_.collectionreference.path.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [CollectionReference](./firestore_.collectionreference.md) > [path](./firestore_.collectionreference.path.md) - -## CollectionReference.path property - -Signature: - -```typescript -readonly path: string; -``` diff --git a/docs-exp/firestore_.collectionreference.type.md b/docs-exp/firestore_.collectionreference.type.md deleted file mode 100644 index 02a5c91a5eb..00000000000 --- a/docs-exp/firestore_.collectionreference.type.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [CollectionReference](./firestore_.collectionreference.md) > [type](./firestore_.collectionreference.type.md) - -## CollectionReference.type property - -Signature: - -```typescript -readonly type: 'collection'; -``` diff --git a/docs-exp/firestore_.collectionreference.withconverter.md b/docs-exp/firestore_.collectionreference.withconverter.md deleted file mode 100644 index 524460e197c..00000000000 --- a/docs-exp/firestore_.collectionreference.withconverter.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [CollectionReference](./firestore_.collectionreference.md) > [withConverter](./firestore_.collectionreference.withconverter.md) - -## CollectionReference.withConverter() method - -Signature: - -```typescript -withConverter( - converter: FirestoreDataConverter - ): CollectionReference; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| converter | [FirestoreDataConverter](./firestore_.firestoredataconverter.md)<U> | | - -Returns: - -[CollectionReference](./firestore_.collectionreference.md)<U> - diff --git a/docs-exp/firestore_.deletedoc.md b/docs-exp/firestore_.deletedoc.md deleted file mode 100644 index 8f70ea01137..00000000000 --- a/docs-exp/firestore_.deletedoc.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [deleteDoc](./firestore_.deletedoc.md) - -## deleteDoc() function - -Signature: - -```typescript -export function deleteDoc(reference: DocumentReference): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| reference | [DocumentReference](./firestore_.documentreference.md)<unknown> | | - -Returns: - -Promise<void> - diff --git a/docs-exp/firestore_.deletefield.md b/docs-exp/firestore_.deletefield.md deleted file mode 100644 index 8f05c63dff5..00000000000 --- a/docs-exp/firestore_.deletefield.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [deleteField](./firestore_.deletefield.md) - -## deleteField() function - -Signature: - -```typescript -export function deleteField(): FieldValue; -``` -Returns: - -[FieldValue](./firestore_.fieldvalue.md) - diff --git a/docs-exp/firestore_.disablenetwork.md b/docs-exp/firestore_.disablenetwork.md deleted file mode 100644 index af64b4b071d..00000000000 --- a/docs-exp/firestore_.disablenetwork.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [disableNetwork](./firestore_.disablenetwork.md) - -## disableNetwork() function - -Signature: - -```typescript -export function disableNetwork(firestore: FirebaseFirestore): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| firestore | [FirebaseFirestore](./firestore_.firebasefirestore.md) | | - -Returns: - -Promise<void> - diff --git a/docs-exp/firestore_.doc.md b/docs-exp/firestore_.doc.md deleted file mode 100644 index e0b9e126225..00000000000 --- a/docs-exp/firestore_.doc.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [doc](./firestore_.doc.md) - -## doc() function - -Signature: - -```typescript -export function doc( - firestore: FirebaseFirestore, - documentPath: string -): DocumentReference; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| firestore | [FirebaseFirestore](./firestore_.firebasefirestore.md) | | -| documentPath | string | | - -Returns: - -[DocumentReference](./firestore_.documentreference.md)<[DocumentData](./firestore_.documentdata.md)> - diff --git a/docs-exp/firestore_.doc_1.md b/docs-exp/firestore_.doc_1.md deleted file mode 100644 index cb4022ad93c..00000000000 --- a/docs-exp/firestore_.doc_1.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [doc](./firestore_.doc_1.md) - -## doc() function - -Signature: - -```typescript -export function doc( - reference: CollectionReference, - documentPath?: string -): DocumentReference; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| reference | [CollectionReference](./firestore_.collectionreference.md)<T> | | -| documentPath | string | | - -Returns: - -[DocumentReference](./firestore_.documentreference.md)<T> - diff --git a/docs-exp/firestore_.doc_2.md b/docs-exp/firestore_.doc_2.md deleted file mode 100644 index 9c6ebe61f13..00000000000 --- a/docs-exp/firestore_.doc_2.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [doc](./firestore_.doc_2.md) - -## doc() function - -Signature: - -```typescript -export function doc( - reference: DocumentReference, - documentPath: string -): DocumentReference; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| reference | [DocumentReference](./firestore_.documentreference.md)<unknown> | | -| documentPath | string | | - -Returns: - -[DocumentReference](./firestore_.documentreference.md)<[DocumentData](./firestore_.documentdata.md)> - diff --git a/docs-exp/firestore_.documentchange.doc.md b/docs-exp/firestore_.documentchange.doc.md deleted file mode 100644 index 13f52fb7f6b..00000000000 --- a/docs-exp/firestore_.documentchange.doc.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [DocumentChange](./firestore_.documentchange.md) > [doc](./firestore_.documentchange.doc.md) - -## DocumentChange.doc property - -Signature: - -```typescript -readonly doc: QueryDocumentSnapshot; -``` diff --git a/docs-exp/firestore_.documentchange.md b/docs-exp/firestore_.documentchange.md deleted file mode 100644 index f6649683481..00000000000 --- a/docs-exp/firestore_.documentchange.md +++ /dev/null @@ -1,21 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [DocumentChange](./firestore_.documentchange.md) - -## DocumentChange interface - -Signature: - -```typescript -export interface DocumentChange -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [doc](./firestore_.documentchange.doc.md) | [QueryDocumentSnapshot](./firestore_.querydocumentsnapshot.md)<T> | | -| [newIndex](./firestore_.documentchange.newindex.md) | number | | -| [oldIndex](./firestore_.documentchange.oldindex.md) | number | | -| [type](./firestore_.documentchange.type.md) | [DocumentChangeType](./firestore_.documentchangetype.md) | | - diff --git a/docs-exp/firestore_.documentchange.newindex.md b/docs-exp/firestore_.documentchange.newindex.md deleted file mode 100644 index 212b3dc85c2..00000000000 --- a/docs-exp/firestore_.documentchange.newindex.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [DocumentChange](./firestore_.documentchange.md) > [newIndex](./firestore_.documentchange.newindex.md) - -## DocumentChange.newIndex property - -Signature: - -```typescript -readonly newIndex: number; -``` diff --git a/docs-exp/firestore_.documentchange.oldindex.md b/docs-exp/firestore_.documentchange.oldindex.md deleted file mode 100644 index f430dc2d396..00000000000 --- a/docs-exp/firestore_.documentchange.oldindex.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [DocumentChange](./firestore_.documentchange.md) > [oldIndex](./firestore_.documentchange.oldindex.md) - -## DocumentChange.oldIndex property - -Signature: - -```typescript -readonly oldIndex: number; -``` diff --git a/docs-exp/firestore_.documentchange.type.md b/docs-exp/firestore_.documentchange.type.md deleted file mode 100644 index 0b5abb0f08b..00000000000 --- a/docs-exp/firestore_.documentchange.type.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [DocumentChange](./firestore_.documentchange.md) > [type](./firestore_.documentchange.type.md) - -## DocumentChange.type property - -Signature: - -```typescript -readonly type: DocumentChangeType; -``` diff --git a/docs-exp/firestore_.documentchangetype.md b/docs-exp/firestore_.documentchangetype.md deleted file mode 100644 index 8d1effbf54e..00000000000 --- a/docs-exp/firestore_.documentchangetype.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [DocumentChangeType](./firestore_.documentchangetype.md) - -## DocumentChangeType type - -Signature: - -```typescript -export type DocumentChangeType = 'added' | 'removed' | 'modified'; -``` diff --git a/docs-exp/firestore_.documentdata.md b/docs-exp/firestore_.documentdata.md deleted file mode 100644 index 8341f9b726f..00000000000 --- a/docs-exp/firestore_.documentdata.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [DocumentData](./firestore_.documentdata.md) - -## DocumentData interface - -Signature: - -```typescript -export interface DocumentData -``` diff --git a/docs-exp/firestore_.documentid.md b/docs-exp/firestore_.documentid.md deleted file mode 100644 index cdd8c76a24d..00000000000 --- a/docs-exp/firestore_.documentid.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [documentId](./firestore_.documentid.md) - -## documentId() function - -Signature: - -```typescript -export function documentId(): FieldPath; -``` -Returns: - -[FieldPath](./firestore_.fieldpath.md) - diff --git a/docs-exp/firestore_.documentreference.collection.md b/docs-exp/firestore_.documentreference.collection.md deleted file mode 100644 index 9d45e120984..00000000000 --- a/docs-exp/firestore_.documentreference.collection.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [DocumentReference](./firestore_.documentreference.md) > [collection](./firestore_.documentreference.collection.md) - -## DocumentReference.collection() method - -Signature: - -```typescript -collection(collectionPath: string): CollectionReference; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| collectionPath | string | | - -Returns: - -[CollectionReference](./firestore_.collectionreference.md)<[DocumentData](./firestore_.documentdata.md)> - diff --git a/docs-exp/firestore_.documentreference.converter.md b/docs-exp/firestore_.documentreference.converter.md deleted file mode 100644 index 54d97f21e13..00000000000 --- a/docs-exp/firestore_.documentreference.converter.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [DocumentReference](./firestore_.documentreference.md) > [converter](./firestore_.documentreference.converter.md) - -## DocumentReference.converter property - -Signature: - -```typescript -readonly converter: FirestoreDataConverter | null; -``` diff --git a/docs-exp/firestore_.documentreference.firestore.md b/docs-exp/firestore_.documentreference.firestore.md deleted file mode 100644 index 5b174a504f8..00000000000 --- a/docs-exp/firestore_.documentreference.firestore.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [DocumentReference](./firestore_.documentreference.md) > [firestore](./firestore_.documentreference.firestore.md) - -## DocumentReference.firestore property - -Signature: - -```typescript -readonly firestore: FirebaseFirestore; -``` diff --git a/docs-exp/firestore_.documentreference.id.md b/docs-exp/firestore_.documentreference.id.md deleted file mode 100644 index ee3495d2a53..00000000000 --- a/docs-exp/firestore_.documentreference.id.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [DocumentReference](./firestore_.documentreference.md) > [id](./firestore_.documentreference.id.md) - -## DocumentReference.id property - -Signature: - -```typescript -readonly id: string; -``` diff --git a/docs-exp/firestore_.documentreference.md b/docs-exp/firestore_.documentreference.md deleted file mode 100644 index 8207f1b73f8..00000000000 --- a/docs-exp/firestore_.documentreference.md +++ /dev/null @@ -1,30 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [DocumentReference](./firestore_.documentreference.md) - -## DocumentReference class - -Signature: - -```typescript -export class DocumentReference -``` - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [converter](./firestore_.documentreference.converter.md) | | [FirestoreDataConverter](./firestore_.firestoredataconverter.md)<T> \| null | | -| [firestore](./firestore_.documentreference.firestore.md) | | [FirebaseFirestore](./firestore_.firebasefirestore.md) | | -| [id](./firestore_.documentreference.id.md) | | string | | -| [parent](./firestore_.documentreference.parent.md) | | [CollectionReference](./firestore_.collectionreference.md)<T> | | -| [path](./firestore_.documentreference.path.md) | | string | | -| [type](./firestore_.documentreference.type.md) | | 'document' | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [collection(collectionPath)](./firestore_.documentreference.collection.md) | | | -| [withConverter(converter)](./firestore_.documentreference.withconverter.md) | | | - diff --git a/docs-exp/firestore_.documentreference.parent.md b/docs-exp/firestore_.documentreference.parent.md deleted file mode 100644 index f455dd06503..00000000000 --- a/docs-exp/firestore_.documentreference.parent.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [DocumentReference](./firestore_.documentreference.md) > [parent](./firestore_.documentreference.parent.md) - -## DocumentReference.parent property - -Signature: - -```typescript -get parent(): CollectionReference; -``` diff --git a/docs-exp/firestore_.documentreference.path.md b/docs-exp/firestore_.documentreference.path.md deleted file mode 100644 index f56aaae1bd5..00000000000 --- a/docs-exp/firestore_.documentreference.path.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [DocumentReference](./firestore_.documentreference.md) > [path](./firestore_.documentreference.path.md) - -## DocumentReference.path property - -Signature: - -```typescript -readonly path: string; -``` diff --git a/docs-exp/firestore_.documentreference.type.md b/docs-exp/firestore_.documentreference.type.md deleted file mode 100644 index f8720dc4585..00000000000 --- a/docs-exp/firestore_.documentreference.type.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [DocumentReference](./firestore_.documentreference.md) > [type](./firestore_.documentreference.type.md) - -## DocumentReference.type property - -Signature: - -```typescript -readonly type: 'document'; -``` diff --git a/docs-exp/firestore_.documentreference.withconverter.md b/docs-exp/firestore_.documentreference.withconverter.md deleted file mode 100644 index 71ffe7a623d..00000000000 --- a/docs-exp/firestore_.documentreference.withconverter.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [DocumentReference](./firestore_.documentreference.md) > [withConverter](./firestore_.documentreference.withconverter.md) - -## DocumentReference.withConverter() method - -Signature: - -```typescript -withConverter(converter: FirestoreDataConverter): DocumentReference; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| converter | [FirestoreDataConverter](./firestore_.firestoredataconverter.md)<U> | | - -Returns: - -[DocumentReference](./firestore_.documentreference.md)<U> - diff --git a/docs-exp/firestore_.documentsnapshot._constructor_.md b/docs-exp/firestore_.documentsnapshot._constructor_.md deleted file mode 100644 index e5b5168baa5..00000000000 --- a/docs-exp/firestore_.documentsnapshot._constructor_.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [DocumentSnapshot](./firestore_.documentsnapshot.md) > [(constructor)](./firestore_.documentsnapshot._constructor_.md) - -## DocumentSnapshot.(constructor) - -Signature: - -```typescript -protected constructor(); -``` diff --git a/docs-exp/firestore_.documentsnapshot.data.md b/docs-exp/firestore_.documentsnapshot.data.md deleted file mode 100644 index 3225db4793f..00000000000 --- a/docs-exp/firestore_.documentsnapshot.data.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [DocumentSnapshot](./firestore_.documentsnapshot.md) > [data](./firestore_.documentsnapshot.data.md) - -## DocumentSnapshot.data() method - -Signature: - -```typescript -data(options?: SnapshotOptions): T | undefined; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| options | [SnapshotOptions](./firestore_.snapshotoptions.md) | | - -Returns: - -T \| undefined - diff --git a/docs-exp/firestore_.documentsnapshot.exists.md b/docs-exp/firestore_.documentsnapshot.exists.md deleted file mode 100644 index 8d94d9d17f6..00000000000 --- a/docs-exp/firestore_.documentsnapshot.exists.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [DocumentSnapshot](./firestore_.documentsnapshot.md) > [exists](./firestore_.documentsnapshot.exists.md) - -## DocumentSnapshot.exists() method - -Signature: - -```typescript -exists(): this is QueryDocumentSnapshot; -``` -Returns: - -this is [QueryDocumentSnapshot](./firestore_.querydocumentsnapshot.md)<T> - diff --git a/docs-exp/firestore_.documentsnapshot.get.md b/docs-exp/firestore_.documentsnapshot.get.md deleted file mode 100644 index da89cd7f985..00000000000 --- a/docs-exp/firestore_.documentsnapshot.get.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [DocumentSnapshot](./firestore_.documentsnapshot.md) > [get](./firestore_.documentsnapshot.get.md) - -## DocumentSnapshot.get() method - -Signature: - -```typescript -get(fieldPath: string | FieldPath, options?: SnapshotOptions): any; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| fieldPath | string \| [FieldPath](./firestore_.fieldpath.md) | | -| options | [SnapshotOptions](./firestore_.snapshotoptions.md) | | - -Returns: - -any - diff --git a/docs-exp/firestore_.documentsnapshot.id.md b/docs-exp/firestore_.documentsnapshot.id.md deleted file mode 100644 index e0611e65a90..00000000000 --- a/docs-exp/firestore_.documentsnapshot.id.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [DocumentSnapshot](./firestore_.documentsnapshot.md) > [id](./firestore_.documentsnapshot.id.md) - -## DocumentSnapshot.id property - -Signature: - -```typescript -readonly id: string; -``` diff --git a/docs-exp/firestore_.documentsnapshot.md b/docs-exp/firestore_.documentsnapshot.md deleted file mode 100644 index 3a105cb4552..00000000000 --- a/docs-exp/firestore_.documentsnapshot.md +++ /dev/null @@ -1,34 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [DocumentSnapshot](./firestore_.documentsnapshot.md) - -## DocumentSnapshot class - -Signature: - -```typescript -export class DocumentSnapshot -``` - -## Constructors - -| Constructor | Modifiers | Description | -| --- | --- | --- | -| [(constructor)()](./firestore_.documentsnapshot._constructor_.md) | | | - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [id](./firestore_.documentsnapshot.id.md) | | string | | -| [metadata](./firestore_.documentsnapshot.metadata.md) | | [SnapshotMetadata](./firestore_.snapshotmetadata.md) | | -| [ref](./firestore_.documentsnapshot.ref.md) | | [DocumentReference](./firestore_.documentreference.md)<T> | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [data(options)](./firestore_.documentsnapshot.data.md) | | | -| [exists()](./firestore_.documentsnapshot.exists.md) | | | -| [get(fieldPath, options)](./firestore_.documentsnapshot.get.md) | | | - diff --git a/docs-exp/firestore_.documentsnapshot.metadata.md b/docs-exp/firestore_.documentsnapshot.metadata.md deleted file mode 100644 index c84211b9631..00000000000 --- a/docs-exp/firestore_.documentsnapshot.metadata.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [DocumentSnapshot](./firestore_.documentsnapshot.md) > [metadata](./firestore_.documentsnapshot.metadata.md) - -## DocumentSnapshot.metadata property - -Signature: - -```typescript -readonly metadata: SnapshotMetadata; -``` diff --git a/docs-exp/firestore_.documentsnapshot.ref.md b/docs-exp/firestore_.documentsnapshot.ref.md deleted file mode 100644 index d96eb3ee31f..00000000000 --- a/docs-exp/firestore_.documentsnapshot.ref.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [DocumentSnapshot](./firestore_.documentsnapshot.md) > [ref](./firestore_.documentsnapshot.ref.md) - -## DocumentSnapshot.ref property - -Signature: - -```typescript -readonly ref: DocumentReference; -``` diff --git a/docs-exp/firestore_.enableindexeddbpersistence.md b/docs-exp/firestore_.enableindexeddbpersistence.md deleted file mode 100644 index 48d37be6622..00000000000 --- a/docs-exp/firestore_.enableindexeddbpersistence.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [enableIndexedDbPersistence](./firestore_.enableindexeddbpersistence.md) - -## enableIndexedDbPersistence() function - -Signature: - -```typescript -export function enableIndexedDbPersistence( - firestore: FirebaseFirestore -): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| firestore | [FirebaseFirestore](./firestore_.firebasefirestore.md) | | - -Returns: - -Promise<void> - diff --git a/docs-exp/firestore_.enablemultitabindexeddbpersistence.md b/docs-exp/firestore_.enablemultitabindexeddbpersistence.md deleted file mode 100644 index 66f3aeaa763..00000000000 --- a/docs-exp/firestore_.enablemultitabindexeddbpersistence.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [enableMultiTabIndexedDbPersistence](./firestore_.enablemultitabindexeddbpersistence.md) - -## enableMultiTabIndexedDbPersistence() function - -Signature: - -```typescript -export function enableMultiTabIndexedDbPersistence( - firestore: FirebaseFirestore -): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| firestore | [FirebaseFirestore](./firestore_.firebasefirestore.md) | | - -Returns: - -Promise<void> - diff --git a/docs-exp/firestore_.enablenetwork.md b/docs-exp/firestore_.enablenetwork.md deleted file mode 100644 index 35ed420aa07..00000000000 --- a/docs-exp/firestore_.enablenetwork.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [enableNetwork](./firestore_.enablenetwork.md) - -## enableNetwork() function - -Signature: - -```typescript -export function enableNetwork(firestore: FirebaseFirestore): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| firestore | [FirebaseFirestore](./firestore_.firebasefirestore.md) | | - -Returns: - -Promise<void> - diff --git a/docs-exp/firestore_.endat.md b/docs-exp/firestore_.endat.md deleted file mode 100644 index 73ff5c606bd..00000000000 --- a/docs-exp/firestore_.endat.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [endAt](./firestore_.endat.md) - -## endAt() function - -Signature: - -```typescript -export function endAt(snapshot: DocumentSnapshot): QueryConstraint; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| snapshot | [DocumentSnapshot](./firestore_.documentsnapshot.md)<any> | | - -Returns: - -[QueryConstraint](./firestore_.queryconstraint.md) - diff --git a/docs-exp/firestore_.endat_1.md b/docs-exp/firestore_.endat_1.md deleted file mode 100644 index 0f01d88ac7d..00000000000 --- a/docs-exp/firestore_.endat_1.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [endAt](./firestore_.endat_1.md) - -## endAt() function - -Signature: - -```typescript -export function endAt(...fieldValues: any[]): QueryConstraint; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| fieldValues | any\[\] | | - -Returns: - -[QueryConstraint](./firestore_.queryconstraint.md) - diff --git a/docs-exp/firestore_.endbefore.md b/docs-exp/firestore_.endbefore.md deleted file mode 100644 index d4c752a5fe9..00000000000 --- a/docs-exp/firestore_.endbefore.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [endBefore](./firestore_.endbefore.md) - -## endBefore() function - -Signature: - -```typescript -export function endBefore(snapshot: DocumentSnapshot): QueryConstraint; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| snapshot | [DocumentSnapshot](./firestore_.documentsnapshot.md)<any> | | - -Returns: - -[QueryConstraint](./firestore_.queryconstraint.md) - diff --git a/docs-exp/firestore_.endbefore_1.md b/docs-exp/firestore_.endbefore_1.md deleted file mode 100644 index dbfe2c29f15..00000000000 --- a/docs-exp/firestore_.endbefore_1.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [endBefore](./firestore_.endbefore_1.md) - -## endBefore() function - -Signature: - -```typescript -export function endBefore(...fieldValues: any[]): QueryConstraint; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| fieldValues | any\[\] | | - -Returns: - -[QueryConstraint](./firestore_.queryconstraint.md) - diff --git a/docs-exp/firestore_.fieldpath._constructor_.md b/docs-exp/firestore_.fieldpath._constructor_.md deleted file mode 100644 index 7a344de5c88..00000000000 --- a/docs-exp/firestore_.fieldpath._constructor_.md +++ /dev/null @@ -1,18 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [FieldPath](./firestore_.fieldpath.md) > [(constructor)](./firestore_.fieldpath._constructor_.md) - -## FieldPath.(constructor) - -Signature: - -```typescript -constructor(...fieldNames: string[]); -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| fieldNames | string\[\] | | - diff --git a/docs-exp/firestore_.fieldpath.isequal.md b/docs-exp/firestore_.fieldpath.isequal.md deleted file mode 100644 index 97c9f686816..00000000000 --- a/docs-exp/firestore_.fieldpath.isequal.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [FieldPath](./firestore_.fieldpath.md) > [isEqual](./firestore_.fieldpath.isequal.md) - -## FieldPath.isEqual() method - -Signature: - -```typescript -isEqual(other: FieldPath): boolean; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| other | [FieldPath](./firestore_.fieldpath.md) | | - -Returns: - -boolean - diff --git a/docs-exp/firestore_.fieldpath.md b/docs-exp/firestore_.fieldpath.md deleted file mode 100644 index e911069af11..00000000000 --- a/docs-exp/firestore_.fieldpath.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [FieldPath](./firestore_.fieldpath.md) - -## FieldPath class - -Signature: - -```typescript -export class FieldPath -``` - -## Constructors - -| Constructor | Modifiers | Description | -| --- | --- | --- | -| [(constructor)(fieldNames)](./firestore_.fieldpath._constructor_.md) | | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [isEqual(other)](./firestore_.fieldpath.isequal.md) | | | - diff --git a/docs-exp/firestore_.fieldvalue.isequal.md b/docs-exp/firestore_.fieldvalue.isequal.md deleted file mode 100644 index fe6a1c8f514..00000000000 --- a/docs-exp/firestore_.fieldvalue.isequal.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [FieldValue](./firestore_.fieldvalue.md) > [isEqual](./firestore_.fieldvalue.isequal.md) - -## FieldValue.isEqual() method - -Signature: - -```typescript -isEqual(other: FieldValue): boolean; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| other | [FieldValue](./firestore_.fieldvalue.md) | | - -Returns: - -boolean - diff --git a/docs-exp/firestore_.fieldvalue.md b/docs-exp/firestore_.fieldvalue.md deleted file mode 100644 index fa237e60b0d..00000000000 --- a/docs-exp/firestore_.fieldvalue.md +++ /dev/null @@ -1,18 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [FieldValue](./firestore_.fieldvalue.md) - -## FieldValue class - -Signature: - -```typescript -export class FieldValue -``` - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [isEqual(other)](./firestore_.fieldvalue.isequal.md) | | | - diff --git a/docs-exp/firestore_.firebasefirestore.app.md b/docs-exp/firestore_.firebasefirestore.app.md deleted file mode 100644 index ba65bcc12f1..00000000000 --- a/docs-exp/firestore_.firebasefirestore.app.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [FirebaseFirestore](./firestore_.firebasefirestore.md) > [app](./firestore_.firebasefirestore.app.md) - -## FirebaseFirestore.app property - -Signature: - -```typescript -readonly app: FirebaseApp; -``` diff --git a/docs-exp/firestore_.firebasefirestore.md b/docs-exp/firestore_.firebasefirestore.md deleted file mode 100644 index 1a38aa52fad..00000000000 --- a/docs-exp/firestore_.firebasefirestore.md +++ /dev/null @@ -1,18 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [FirebaseFirestore](./firestore_.firebasefirestore.md) - -## FirebaseFirestore class - -Signature: - -```typescript -export class FirebaseFirestore -``` - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [app](./firestore_.firebasefirestore.app.md) | | [FirebaseApp](./app-types.firebaseapp.md) | | - diff --git a/docs-exp/firestore_.firestoredataconverter.fromfirestore.md b/docs-exp/firestore_.firestoredataconverter.fromfirestore.md deleted file mode 100644 index c9080c05cd3..00000000000 --- a/docs-exp/firestore_.firestoredataconverter.fromfirestore.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [FirestoreDataConverter](./firestore_.firestoredataconverter.md) > [fromFirestore](./firestore_.firestoredataconverter.fromfirestore.md) - -## FirestoreDataConverter.fromFirestore() method - -Signature: - -```typescript -fromFirestore( - snapshot: QueryDocumentSnapshot, - options?: SnapshotOptions - ): T; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| snapshot | [QueryDocumentSnapshot](./firestore_.querydocumentsnapshot.md)<[DocumentData](./firestore_.documentdata.md)> | | -| options | [SnapshotOptions](./firestore_.snapshotoptions.md) | | - -Returns: - -T - diff --git a/docs-exp/firestore_.firestoredataconverter.md b/docs-exp/firestore_.firestoredataconverter.md deleted file mode 100644 index f0b348c2a44..00000000000 --- a/docs-exp/firestore_.firestoredataconverter.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [FirestoreDataConverter](./firestore_.firestoredataconverter.md) - -## FirestoreDataConverter interface - -Signature: - -```typescript -export interface FirestoreDataConverter -``` - -## Methods - -| Method | Description | -| --- | --- | -| [fromFirestore(snapshot, options)](./firestore_.firestoredataconverter.fromfirestore.md) | | -| [toFirestore(modelObject)](./firestore_.firestoredataconverter.tofirestore.md) | | -| [toFirestore(modelObject, options)](./firestore_.firestoredataconverter.tofirestore_1.md) | | - diff --git a/docs-exp/firestore_.firestoredataconverter.tofirestore.md b/docs-exp/firestore_.firestoredataconverter.tofirestore.md deleted file mode 100644 index 996aea076f4..00000000000 --- a/docs-exp/firestore_.firestoredataconverter.tofirestore.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [FirestoreDataConverter](./firestore_.firestoredataconverter.md) > [toFirestore](./firestore_.firestoredataconverter.tofirestore.md) - -## FirestoreDataConverter.toFirestore() method - -Signature: - -```typescript -toFirestore(modelObject: T): DocumentData; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| modelObject | T | | - -Returns: - -[DocumentData](./firestore_.documentdata.md) - diff --git a/docs-exp/firestore_.firestoredataconverter.tofirestore_1.md b/docs-exp/firestore_.firestoredataconverter.tofirestore_1.md deleted file mode 100644 index fa2e0146620..00000000000 --- a/docs-exp/firestore_.firestoredataconverter.tofirestore_1.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [FirestoreDataConverter](./firestore_.firestoredataconverter.md) > [toFirestore](./firestore_.firestoredataconverter.tofirestore_1.md) - -## FirestoreDataConverter.toFirestore() method - -Signature: - -```typescript -toFirestore(modelObject: Partial, options: SetOptions): DocumentData; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| modelObject | Partial<T> | | -| options | [SetOptions](./firestore_.setoptions.md) | | - -Returns: - -[DocumentData](./firestore_.documentdata.md) - diff --git a/docs-exp/firestore_.firestoreerror.code.md b/docs-exp/firestore_.firestoreerror.code.md deleted file mode 100644 index 028cdcfab2d..00000000000 --- a/docs-exp/firestore_.firestoreerror.code.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [FirestoreError](./firestore_.firestoreerror.md) > [code](./firestore_.firestoreerror.code.md) - -## FirestoreError.code property - -Signature: - -```typescript -code: FirestoreErrorCode; -``` diff --git a/docs-exp/firestore_.firestoreerror.md b/docs-exp/firestore_.firestoreerror.md deleted file mode 100644 index a9e12ae20c7..00000000000 --- a/docs-exp/firestore_.firestoreerror.md +++ /dev/null @@ -1,21 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [FirestoreError](./firestore_.firestoreerror.md) - -## FirestoreError interface - -Signature: - -```typescript -export interface FirestoreError -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [code](./firestore_.firestoreerror.code.md) | [FirestoreErrorCode](./firestore_.firestoreerrorcode.md) | | -| [message](./firestore_.firestoreerror.message.md) | string | | -| [name](./firestore_.firestoreerror.name.md) | string | | -| [stack](./firestore_.firestoreerror.stack.md) | string | | - diff --git a/docs-exp/firestore_.firestoreerror.message.md b/docs-exp/firestore_.firestoreerror.message.md deleted file mode 100644 index 7a1d93d49a5..00000000000 --- a/docs-exp/firestore_.firestoreerror.message.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [FirestoreError](./firestore_.firestoreerror.md) > [message](./firestore_.firestoreerror.message.md) - -## FirestoreError.message property - -Signature: - -```typescript -message: string; -``` diff --git a/docs-exp/firestore_.firestoreerror.name.md b/docs-exp/firestore_.firestoreerror.name.md deleted file mode 100644 index a7453d50a23..00000000000 --- a/docs-exp/firestore_.firestoreerror.name.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [FirestoreError](./firestore_.firestoreerror.md) > [name](./firestore_.firestoreerror.name.md) - -## FirestoreError.name property - -Signature: - -```typescript -name: string; -``` diff --git a/docs-exp/firestore_.firestoreerror.stack.md b/docs-exp/firestore_.firestoreerror.stack.md deleted file mode 100644 index b44c6473c8f..00000000000 --- a/docs-exp/firestore_.firestoreerror.stack.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [FirestoreError](./firestore_.firestoreerror.md) > [stack](./firestore_.firestoreerror.stack.md) - -## FirestoreError.stack property - -Signature: - -```typescript -stack?: string; -``` diff --git a/docs-exp/firestore_.firestoreerrorcode.md b/docs-exp/firestore_.firestoreerrorcode.md deleted file mode 100644 index f86248b6cee..00000000000 --- a/docs-exp/firestore_.firestoreerrorcode.md +++ /dev/null @@ -1,27 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [FirestoreErrorCode](./firestore_.firestoreerrorcode.md) - -## FirestoreErrorCode type - -Signature: - -```typescript -export type FirestoreErrorCode = - | 'cancelled' - | 'unknown' - | 'invalid-argument' - | 'deadline-exceeded' - | 'not-found' - | 'already-exists' - | 'permission-denied' - | 'resource-exhausted' - | 'failed-precondition' - | 'aborted' - | 'out-of-range' - | 'unimplemented' - | 'internal' - | 'unavailable' - | 'data-loss' - | 'unauthenticated'; -``` diff --git a/docs-exp/firestore_.geopoint._constructor_.md b/docs-exp/firestore_.geopoint._constructor_.md deleted file mode 100644 index 4676f52a3c9..00000000000 --- a/docs-exp/firestore_.geopoint._constructor_.md +++ /dev/null @@ -1,19 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [GeoPoint](./firestore_.geopoint.md) > [(constructor)](./firestore_.geopoint._constructor_.md) - -## GeoPoint.(constructor) - -Signature: - -```typescript -constructor(latitude: number, longitude: number); -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| latitude | number | | -| longitude | number | | - diff --git a/docs-exp/firestore_.geopoint.isequal.md b/docs-exp/firestore_.geopoint.isequal.md deleted file mode 100644 index 319bda9ac93..00000000000 --- a/docs-exp/firestore_.geopoint.isequal.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [GeoPoint](./firestore_.geopoint.md) > [isEqual](./firestore_.geopoint.isequal.md) - -## GeoPoint.isEqual() method - -Signature: - -```typescript -isEqual(other: GeoPoint): boolean; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| other | [GeoPoint](./firestore_.geopoint.md) | | - -Returns: - -boolean - diff --git a/docs-exp/firestore_.geopoint.latitude.md b/docs-exp/firestore_.geopoint.latitude.md deleted file mode 100644 index 67034263c5b..00000000000 --- a/docs-exp/firestore_.geopoint.latitude.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [GeoPoint](./firestore_.geopoint.md) > [latitude](./firestore_.geopoint.latitude.md) - -## GeoPoint.latitude property - -Signature: - -```typescript -readonly latitude: number; -``` diff --git a/docs-exp/firestore_.geopoint.longitude.md b/docs-exp/firestore_.geopoint.longitude.md deleted file mode 100644 index c387545bfb0..00000000000 --- a/docs-exp/firestore_.geopoint.longitude.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [GeoPoint](./firestore_.geopoint.md) > [longitude](./firestore_.geopoint.longitude.md) - -## GeoPoint.longitude property - -Signature: - -```typescript -readonly longitude: number; -``` diff --git a/docs-exp/firestore_.geopoint.md b/docs-exp/firestore_.geopoint.md deleted file mode 100644 index bc24a5cbe54..00000000000 --- a/docs-exp/firestore_.geopoint.md +++ /dev/null @@ -1,31 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [GeoPoint](./firestore_.geopoint.md) - -## GeoPoint class - -Signature: - -```typescript -export class GeoPoint -``` - -## Constructors - -| Constructor | Modifiers | Description | -| --- | --- | --- | -| [(constructor)(latitude, longitude)](./firestore_.geopoint._constructor_.md) | | | - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [latitude](./firestore_.geopoint.latitude.md) | | number | | -| [longitude](./firestore_.geopoint.longitude.md) | | number | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [isEqual(other)](./firestore_.geopoint.isequal.md) | | | - diff --git a/docs-exp/firestore_.getdoc.md b/docs-exp/firestore_.getdoc.md deleted file mode 100644 index dda83dc9276..00000000000 --- a/docs-exp/firestore_.getdoc.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [getDoc](./firestore_.getdoc.md) - -## getDoc() function - -Signature: - -```typescript -export function getDoc( - reference: DocumentReference -): Promise>; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| reference | [DocumentReference](./firestore_.documentreference.md)<T> | | - -Returns: - -Promise<[DocumentSnapshot](./firestore_.documentsnapshot.md)<T>> - diff --git a/docs-exp/firestore_.getdocfromcache.md b/docs-exp/firestore_.getdocfromcache.md deleted file mode 100644 index 98e4d225aec..00000000000 --- a/docs-exp/firestore_.getdocfromcache.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [getDocFromCache](./firestore_.getdocfromcache.md) - -## getDocFromCache() function - -Signature: - -```typescript -export function getDocFromCache( - reference: DocumentReference -): Promise>; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| reference | [DocumentReference](./firestore_.documentreference.md)<T> | | - -Returns: - -Promise<[DocumentSnapshot](./firestore_.documentsnapshot.md)<T>> - diff --git a/docs-exp/firestore_.getdocfromserver.md b/docs-exp/firestore_.getdocfromserver.md deleted file mode 100644 index 90f8f69ef2a..00000000000 --- a/docs-exp/firestore_.getdocfromserver.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [getDocFromServer](./firestore_.getdocfromserver.md) - -## getDocFromServer() function - -Signature: - -```typescript -export function getDocFromServer( - reference: DocumentReference -): Promise>; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| reference | [DocumentReference](./firestore_.documentreference.md)<T> | | - -Returns: - -Promise<[DocumentSnapshot](./firestore_.documentsnapshot.md)<T>> - diff --git a/docs-exp/firestore_.getdocs.md b/docs-exp/firestore_.getdocs.md deleted file mode 100644 index 44677c29d32..00000000000 --- a/docs-exp/firestore_.getdocs.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [getDocs](./firestore_.getdocs.md) - -## getDocs() function - -Signature: - -```typescript -export function getDocs(query: Query): Promise>; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| query | [Query](./firestore_.query.md)<T> | | - -Returns: - -Promise<[QuerySnapshot](./firestore_.querysnapshot.md)<T>> - diff --git a/docs-exp/firestore_.getdocsfromcache.md b/docs-exp/firestore_.getdocsfromcache.md deleted file mode 100644 index 12cfaa93e4d..00000000000 --- a/docs-exp/firestore_.getdocsfromcache.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [getDocsFromCache](./firestore_.getdocsfromcache.md) - -## getDocsFromCache() function - -Signature: - -```typescript -export function getDocsFromCache(query: Query): Promise>; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| query | [Query](./firestore_.query.md)<T> | | - -Returns: - -Promise<[QuerySnapshot](./firestore_.querysnapshot.md)<T>> - diff --git a/docs-exp/firestore_.getdocsfromserver.md b/docs-exp/firestore_.getdocsfromserver.md deleted file mode 100644 index 3f0868034a5..00000000000 --- a/docs-exp/firestore_.getdocsfromserver.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [getDocsFromServer](./firestore_.getdocsfromserver.md) - -## getDocsFromServer() function - -Signature: - -```typescript -export function getDocsFromServer( - query: Query -): Promise>; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| query | [Query](./firestore_.query.md)<T> | | - -Returns: - -Promise<[QuerySnapshot](./firestore_.querysnapshot.md)<T>> - diff --git a/docs-exp/firestore_.getfirestore.md b/docs-exp/firestore_.getfirestore.md deleted file mode 100644 index de33c647c33..00000000000 --- a/docs-exp/firestore_.getfirestore.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [getFirestore](./firestore_.getfirestore.md) - -## getFirestore() function - -Signature: - -```typescript -export function getFirestore(app: FirebaseApp): FirebaseFirestore; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| app | [FirebaseApp](./app-types.firebaseapp.md) | | - -Returns: - -[FirebaseFirestore](./firestore_.firebasefirestore.md) - diff --git a/docs-exp/firestore_.increment.md b/docs-exp/firestore_.increment.md deleted file mode 100644 index 7a9374c64c4..00000000000 --- a/docs-exp/firestore_.increment.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [increment](./firestore_.increment.md) - -## increment() function - -Signature: - -```typescript -export function increment(n: number): FieldValue; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| n | number | | - -Returns: - -[FieldValue](./firestore_.fieldvalue.md) - diff --git a/docs-exp/firestore_.initializefirestore.md b/docs-exp/firestore_.initializefirestore.md deleted file mode 100644 index da887f15258..00000000000 --- a/docs-exp/firestore_.initializefirestore.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [initializeFirestore](./firestore_.initializefirestore.md) - -## initializeFirestore() function - -Signature: - -```typescript -export function initializeFirestore( - app: FirebaseApp, - settings: Settings -): FirebaseFirestore; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| app | [FirebaseApp](./app-types.firebaseapp.md) | | -| settings | [Settings](./firestore_.settings.md) | | - -Returns: - -[FirebaseFirestore](./firestore_.firebasefirestore.md) - diff --git a/docs-exp/firestore_.limit.md b/docs-exp/firestore_.limit.md deleted file mode 100644 index 2a4a6ce001c..00000000000 --- a/docs-exp/firestore_.limit.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [limit](./firestore_.limit.md) - -## limit() function - -Signature: - -```typescript -export function limit(limit: number): QueryConstraint; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| limit | number | | - -Returns: - -[QueryConstraint](./firestore_.queryconstraint.md) - diff --git a/docs-exp/firestore_.limittolast.md b/docs-exp/firestore_.limittolast.md deleted file mode 100644 index ecb9b7e2403..00000000000 --- a/docs-exp/firestore_.limittolast.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [limitToLast](./firestore_.limittolast.md) - -## limitToLast() function - -Signature: - -```typescript -export function limitToLast(limit: number): QueryConstraint; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| limit | number | | - -Returns: - -[QueryConstraint](./firestore_.queryconstraint.md) - diff --git a/docs-exp/firestore_.loglevel.md b/docs-exp/firestore_.loglevel.md deleted file mode 100644 index a31dcfbe559..00000000000 --- a/docs-exp/firestore_.loglevel.md +++ /dev/null @@ -1,17 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [LogLevel](./firestore_.loglevel.md) - -## LogLevel type - -Signature: - -```typescript -export type LogLevel = - | 'debug' - | 'error' - | 'silent' - | 'warn' - | 'info' - | 'verbose'; -``` diff --git a/docs-exp/firestore_.md b/docs-exp/firestore_.md deleted file mode 100644 index 6934c8a9a97..00000000000 --- a/docs-exp/firestore_.md +++ /dev/null @@ -1,126 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) - -## @firebase/firestore - -## Classes - -| Class | Description | -| --- | --- | -| [Bytes](./firestore_.bytes.md) | | -| [CollectionReference](./firestore_.collectionreference.md) | | -| [DocumentReference](./firestore_.documentreference.md) | | -| [DocumentSnapshot](./firestore_.documentsnapshot.md) | | -| [FieldPath](./firestore_.fieldpath.md) | | -| [FieldValue](./firestore_.fieldvalue.md) | | -| [FirebaseFirestore](./firestore_.firebasefirestore.md) | | -| [GeoPoint](./firestore_.geopoint.md) | | -| [Query](./firestore_.query.md) | | -| [QueryConstraint](./firestore_.queryconstraint.md) | | -| [QueryDocumentSnapshot](./firestore_.querydocumentsnapshot.md) | | -| [QuerySnapshot](./firestore_.querysnapshot.md) | | -| [SnapshotMetadata](./firestore_.snapshotmetadata.md) | | -| [Timestamp](./firestore_.timestamp.md) | | -| [Transaction](./firestore_.transaction.md) | | -| [WriteBatch](./firestore_.writebatch.md) | | - -## Functions - -| Function | Description | -| --- | --- | -| [addDoc(reference, data)](./firestore_.adddoc.md) | | -| [arrayRemove(elements)](./firestore_.arrayremove.md) | | -| [arrayUnion(elements)](./firestore_.arrayunion.md) | | -| [clearIndexedDbPersistence(firestore)](./firestore_.clearindexeddbpersistence.md) | | -| [collection(firestore, collectionPath)](./firestore_.collection.md) | | -| [collection(reference, collectionPath)](./firestore_.collection_1.md) | | -| [collection(reference, collectionPath)](./firestore_.collection_2.md) | | -| [collectionGroup(firestore, collectionId)](./firestore_.collectiongroup.md) | | -| [deleteDoc(reference)](./firestore_.deletedoc.md) | | -| [deleteField()](./firestore_.deletefield.md) | | -| [disableNetwork(firestore)](./firestore_.disablenetwork.md) | | -| [doc(firestore, documentPath)](./firestore_.doc.md) | | -| [doc(reference, documentPath)](./firestore_.doc_1.md) | | -| [doc(reference, documentPath)](./firestore_.doc_2.md) | | -| [documentId()](./firestore_.documentid.md) | | -| [enableIndexedDbPersistence(firestore)](./firestore_.enableindexeddbpersistence.md) | | -| [enableMultiTabIndexedDbPersistence(firestore)](./firestore_.enablemultitabindexeddbpersistence.md) | | -| [enableNetwork(firestore)](./firestore_.enablenetwork.md) | | -| [endAt(snapshot)](./firestore_.endat.md) | | -| [endAt(fieldValues)](./firestore_.endat_1.md) | | -| [endBefore(snapshot)](./firestore_.endbefore.md) | | -| [endBefore(fieldValues)](./firestore_.endbefore_1.md) | | -| [getDoc(reference)](./firestore_.getdoc.md) | | -| [getDocFromCache(reference)](./firestore_.getdocfromcache.md) | | -| [getDocFromServer(reference)](./firestore_.getdocfromserver.md) | | -| [getDocs(query)](./firestore_.getdocs.md) | | -| [getDocsFromCache(query)](./firestore_.getdocsfromcache.md) | | -| [getDocsFromServer(query)](./firestore_.getdocsfromserver.md) | | -| [getFirestore(app)](./firestore_.getfirestore.md) | | -| [increment(n)](./firestore_.increment.md) | | -| [initializeFirestore(app, settings)](./firestore_.initializefirestore.md) | | -| [limit(limit)](./firestore_.limit.md) | | -| [limitToLast(limit)](./firestore_.limittolast.md) | | -| [onSnapshot(reference, observer)](./firestore_.onsnapshot.md) | | -| [onSnapshot(reference, options, observer)](./firestore_.onsnapshot_1.md) | | -| [onSnapshot(reference, onNext, onError, onCompletion)](./firestore_.onsnapshot_2.md) | | -| [onSnapshot(reference, options, onNext, onError, onCompletion)](./firestore_.onsnapshot_3.md) | | -| [onSnapshot(query, observer)](./firestore_.onsnapshot_4.md) | | -| [onSnapshot(query, options, observer)](./firestore_.onsnapshot_5.md) | | -| [onSnapshot(query, onNext, onError, onCompletion)](./firestore_.onsnapshot_6.md) | | -| [onSnapshot(query, options, onNext, onError, onCompletion)](./firestore_.onsnapshot_7.md) | | -| [onSnapshotsInSync(firestore, observer)](./firestore_.onsnapshotsinsync.md) | | -| [onSnapshotsInSync(firestore, onSync)](./firestore_.onsnapshotsinsync_1.md) | | -| [orderBy(fieldPath, directionStr)](./firestore_.orderby.md) | | -| [query(query, constraints)](./firestore_.query.md) | | -| [queryEqual(left, right)](./firestore_.queryequal.md) | | -| [refEqual(left, right)](./firestore_.refequal.md) | | -| [runTransaction(firestore, updateFunction)](./firestore_.runtransaction.md) | | -| [serverTimestamp()](./firestore_.servertimestamp.md) | | -| [setDoc(reference, data)](./firestore_.setdoc.md) | | -| [setDoc(reference, data, options)](./firestore_.setdoc_1.md) | | -| [setLogLevel(logLevel)](./firestore_.setloglevel.md) | | -| [snapshotEqual(left, right)](./firestore_.snapshotequal.md) | | -| [startAfter(snapshot)](./firestore_.startafter.md) | | -| [startAfter(fieldValues)](./firestore_.startafter_1.md) | | -| [startAt(snapshot)](./firestore_.startat.md) | | -| [startAt(fieldValues)](./firestore_.startat_1.md) | | -| [terminate(firestore)](./firestore_.terminate.md) | | -| [updateDoc(reference, data)](./firestore_.updatedoc.md) | | -| [updateDoc(reference, field, value, moreFieldsAndValues)](./firestore_.updatedoc_1.md) | | -| [waitForPendingWrites(firestore)](./firestore_.waitforpendingwrites.md) | | -| [where(fieldPath, opStr, value)](./firestore_.where.md) | | -| [writeBatch(firestore)](./firestore_.writebatch.md) | | - -## Interfaces - -| Interface | Description | -| --- | --- | -| [DocumentChange](./firestore_.documentchange.md) | | -| [DocumentData](./firestore_.documentdata.md) | | -| [FirestoreDataConverter](./firestore_.firestoredataconverter.md) | | -| [FirestoreError](./firestore_.firestoreerror.md) | | -| [Settings](./firestore_.settings.md) | | -| [SnapshotListenOptions](./firestore_.snapshotlistenoptions.md) | | -| [SnapshotOptions](./firestore_.snapshotoptions.md) | | -| [UpdateData](./firestore_.updatedata.md) | | - -## Variables - -| Variable | Description | -| --- | --- | -| [CACHE\_SIZE\_UNLIMITED](./firestore_.cache_size_unlimited.md) | | - -## Type Aliases - -| Type Alias | Description | -| --- | --- | -| [DocumentChangeType](./firestore_.documentchangetype.md) | | -| [FirestoreErrorCode](./firestore_.firestoreerrorcode.md) | | -| [LogLevel](./firestore_.loglevel.md) | | -| [OrderByDirection](./firestore_.orderbydirection.md) | | -| [QueryConstraintType](./firestore_.queryconstrainttype.md) | | -| [SetOptions](./firestore_.setoptions.md) | | -| [WhereFilterOp](./firestore_.wherefilterop.md) | | - diff --git a/docs-exp/firestore_.onsnapshot.md b/docs-exp/firestore_.onsnapshot.md deleted file mode 100644 index e55d2f34071..00000000000 --- a/docs-exp/firestore_.onsnapshot.md +++ /dev/null @@ -1,30 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [onSnapshot](./firestore_.onsnapshot.md) - -## onSnapshot() function - -Signature: - -```typescript -export function onSnapshot( - reference: DocumentReference, - observer: { - next?: (snapshot: DocumentSnapshot) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; - } -): () => void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| reference | [DocumentReference](./firestore_.documentreference.md)<T> | | -| observer | { next?: (snapshot: [DocumentSnapshot](./firestore_.documentsnapshot.md)<T>) => void; error?: (error: [FirestoreError](./firestore_.firestoreerror.md)) => void; complete?: () => void; } | | - -Returns: - -() => void - diff --git a/docs-exp/firestore_.onsnapshot_1.md b/docs-exp/firestore_.onsnapshot_1.md deleted file mode 100644 index 62ca015ec46..00000000000 --- a/docs-exp/firestore_.onsnapshot_1.md +++ /dev/null @@ -1,32 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [onSnapshot](./firestore_.onsnapshot_1.md) - -## onSnapshot() function - -Signature: - -```typescript -export function onSnapshot( - reference: DocumentReference, - options: SnapshotListenOptions, - observer: { - next?: (snapshot: DocumentSnapshot) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; - } -): () => void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| reference | [DocumentReference](./firestore_.documentreference.md)<T> | | -| options | [SnapshotListenOptions](./firestore_.snapshotlistenoptions.md) | | -| observer | { next?: (snapshot: [DocumentSnapshot](./firestore_.documentsnapshot.md)<T>) => void; error?: (error: [FirestoreError](./firestore_.firestoreerror.md)) => void; complete?: () => void; } | | - -Returns: - -() => void - diff --git a/docs-exp/firestore_.onsnapshot_2.md b/docs-exp/firestore_.onsnapshot_2.md deleted file mode 100644 index 45c8c305adb..00000000000 --- a/docs-exp/firestore_.onsnapshot_2.md +++ /dev/null @@ -1,30 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [onSnapshot](./firestore_.onsnapshot_2.md) - -## onSnapshot() function - -Signature: - -```typescript -export function onSnapshot( - reference: DocumentReference, - onNext: (snapshot: DocumentSnapshot) => void, - onError?: (error: FirestoreError) => void, - onCompletion?: () => void -): () => void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| reference | [DocumentReference](./firestore_.documentreference.md)<T> | | -| onNext | (snapshot: [DocumentSnapshot](./firestore_.documentsnapshot.md)<T>) => void | | -| onError | (error: [FirestoreError](./firestore_.firestoreerror.md)) => void | | -| onCompletion | () => void | | - -Returns: - -() => void - diff --git a/docs-exp/firestore_.onsnapshot_3.md b/docs-exp/firestore_.onsnapshot_3.md deleted file mode 100644 index c0bd1c441fb..00000000000 --- a/docs-exp/firestore_.onsnapshot_3.md +++ /dev/null @@ -1,32 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [onSnapshot](./firestore_.onsnapshot_3.md) - -## onSnapshot() function - -Signature: - -```typescript -export function onSnapshot( - reference: DocumentReference, - options: SnapshotListenOptions, - onNext: (snapshot: DocumentSnapshot) => void, - onError?: (error: FirestoreError) => void, - onCompletion?: () => void -): () => void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| reference | [DocumentReference](./firestore_.documentreference.md)<T> | | -| options | [SnapshotListenOptions](./firestore_.snapshotlistenoptions.md) | | -| onNext | (snapshot: [DocumentSnapshot](./firestore_.documentsnapshot.md)<T>) => void | | -| onError | (error: [FirestoreError](./firestore_.firestoreerror.md)) => void | | -| onCompletion | () => void | | - -Returns: - -() => void - diff --git a/docs-exp/firestore_.onsnapshot_4.md b/docs-exp/firestore_.onsnapshot_4.md deleted file mode 100644 index b893ef4beca..00000000000 --- a/docs-exp/firestore_.onsnapshot_4.md +++ /dev/null @@ -1,30 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [onSnapshot](./firestore_.onsnapshot_4.md) - -## onSnapshot() function - -Signature: - -```typescript -export function onSnapshot( - query: Query, - observer: { - next?: (snapshot: QuerySnapshot) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; - } -): () => void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| query | [Query](./firestore_.query.md)<T> | | -| observer | { next?: (snapshot: [QuerySnapshot](./firestore_.querysnapshot.md)<T>) => void; error?: (error: [FirestoreError](./firestore_.firestoreerror.md)) => void; complete?: () => void; } | | - -Returns: - -() => void - diff --git a/docs-exp/firestore_.onsnapshot_5.md b/docs-exp/firestore_.onsnapshot_5.md deleted file mode 100644 index d65e2fe4eed..00000000000 --- a/docs-exp/firestore_.onsnapshot_5.md +++ /dev/null @@ -1,32 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [onSnapshot](./firestore_.onsnapshot_5.md) - -## onSnapshot() function - -Signature: - -```typescript -export function onSnapshot( - query: Query, - options: SnapshotListenOptions, - observer: { - next?: (snapshot: QuerySnapshot) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; - } -): () => void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| query | [Query](./firestore_.query.md)<T> | | -| options | [SnapshotListenOptions](./firestore_.snapshotlistenoptions.md) | | -| observer | { next?: (snapshot: [QuerySnapshot](./firestore_.querysnapshot.md)<T>) => void; error?: (error: [FirestoreError](./firestore_.firestoreerror.md)) => void; complete?: () => void; } | | - -Returns: - -() => void - diff --git a/docs-exp/firestore_.onsnapshot_6.md b/docs-exp/firestore_.onsnapshot_6.md deleted file mode 100644 index 2aafe1a124f..00000000000 --- a/docs-exp/firestore_.onsnapshot_6.md +++ /dev/null @@ -1,30 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [onSnapshot](./firestore_.onsnapshot_6.md) - -## onSnapshot() function - -Signature: - -```typescript -export function onSnapshot( - query: Query, - onNext: (snapshot: QuerySnapshot) => void, - onError?: (error: FirestoreError) => void, - onCompletion?: () => void -): () => void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| query | [Query](./firestore_.query.md)<T> | | -| onNext | (snapshot: [QuerySnapshot](./firestore_.querysnapshot.md)<T>) => void | | -| onError | (error: [FirestoreError](./firestore_.firestoreerror.md)) => void | | -| onCompletion | () => void | | - -Returns: - -() => void - diff --git a/docs-exp/firestore_.onsnapshot_7.md b/docs-exp/firestore_.onsnapshot_7.md deleted file mode 100644 index 1447e460297..00000000000 --- a/docs-exp/firestore_.onsnapshot_7.md +++ /dev/null @@ -1,32 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [onSnapshot](./firestore_.onsnapshot_7.md) - -## onSnapshot() function - -Signature: - -```typescript -export function onSnapshot( - query: Query, - options: SnapshotListenOptions, - onNext: (snapshot: QuerySnapshot) => void, - onError?: (error: FirestoreError) => void, - onCompletion?: () => void -): () => void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| query | [Query](./firestore_.query.md)<T> | | -| options | [SnapshotListenOptions](./firestore_.snapshotlistenoptions.md) | | -| onNext | (snapshot: [QuerySnapshot](./firestore_.querysnapshot.md)<T>) => void | | -| onError | (error: [FirestoreError](./firestore_.firestoreerror.md)) => void | | -| onCompletion | () => void | | - -Returns: - -() => void - diff --git a/docs-exp/firestore_.onsnapshotsinsync.md b/docs-exp/firestore_.onsnapshotsinsync.md deleted file mode 100644 index 567e7a91cfe..00000000000 --- a/docs-exp/firestore_.onsnapshotsinsync.md +++ /dev/null @@ -1,30 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [onSnapshotsInSync](./firestore_.onsnapshotsinsync.md) - -## onSnapshotsInSync() function - -Signature: - -```typescript -export function onSnapshotsInSync( - firestore: FirebaseFirestore, - observer: { - next?: (value: void) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; - } -): () => void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| firestore | [FirebaseFirestore](./firestore_.firebasefirestore.md) | | -| observer | { next?: (value: void) => void; error?: (error: [FirestoreError](./firestore_.firestoreerror.md)) => void; complete?: () => void; } | | - -Returns: - -() => void - diff --git a/docs-exp/firestore_.onsnapshotsinsync_1.md b/docs-exp/firestore_.onsnapshotsinsync_1.md deleted file mode 100644 index 4e47cce4da8..00000000000 --- a/docs-exp/firestore_.onsnapshotsinsync_1.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [onSnapshotsInSync](./firestore_.onsnapshotsinsync_1.md) - -## onSnapshotsInSync() function - -Signature: - -```typescript -export function onSnapshotsInSync( - firestore: FirebaseFirestore, - onSync: () => void -): () => void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| firestore | [FirebaseFirestore](./firestore_.firebasefirestore.md) | | -| onSync | () => void | | - -Returns: - -() => void - diff --git a/docs-exp/firestore_.orderby.md b/docs-exp/firestore_.orderby.md deleted file mode 100644 index 00524af1480..00000000000 --- a/docs-exp/firestore_.orderby.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [orderBy](./firestore_.orderby.md) - -## orderBy() function - -Signature: - -```typescript -export function orderBy( - fieldPath: string | FieldPath, - directionStr?: OrderByDirection -): QueryConstraint; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| fieldPath | string \| [FieldPath](./firestore_.fieldpath.md) | | -| directionStr | [OrderByDirection](./firestore_.orderbydirection.md) | | - -Returns: - -[QueryConstraint](./firestore_.queryconstraint.md) - diff --git a/docs-exp/firestore_.orderbydirection.md b/docs-exp/firestore_.orderbydirection.md deleted file mode 100644 index 5c793f0ec5b..00000000000 --- a/docs-exp/firestore_.orderbydirection.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [OrderByDirection](./firestore_.orderbydirection.md) - -## OrderByDirection type - -Signature: - -```typescript -export type OrderByDirection = 'desc' | 'asc'; -``` diff --git a/docs-exp/firestore_.query._constructor_.md b/docs-exp/firestore_.query._constructor_.md deleted file mode 100644 index ba6d9c296bf..00000000000 --- a/docs-exp/firestore_.query._constructor_.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Query](./firestore_.query.md) > [(constructor)](./firestore_.query._constructor_.md) - -## Query.(constructor) - -Signature: - -```typescript -protected constructor(); -``` diff --git a/docs-exp/firestore_.query.converter.md b/docs-exp/firestore_.query.converter.md deleted file mode 100644 index 78589dea397..00000000000 --- a/docs-exp/firestore_.query.converter.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Query](./firestore_.query.md) > [converter](./firestore_.query.converter.md) - -## Query.converter property - -Signature: - -```typescript -readonly converter: FirestoreDataConverter | null; -``` diff --git a/docs-exp/firestore_.query.firestore.md b/docs-exp/firestore_.query.firestore.md deleted file mode 100644 index a459c7a3a5a..00000000000 --- a/docs-exp/firestore_.query.firestore.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Query](./firestore_.query.md) > [firestore](./firestore_.query.firestore.md) - -## Query.firestore property - -Signature: - -```typescript -readonly firestore: FirebaseFirestore; -``` diff --git a/docs-exp/firestore_.query.md b/docs-exp/firestore_.query.md deleted file mode 100644 index 74367ad262c..00000000000 --- a/docs-exp/firestore_.query.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [query](./firestore_.query.md) - -## query() function - -Signature: - -```typescript -export function query( - query: CollectionReference | Query, - ...constraints: QueryConstraint[] -): Query; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| query | [CollectionReference](./firestore_.collectionreference.md)<T> \| [Query](./firestore_.query.md)<T> | | -| constraints | [QueryConstraint](./firestore_.queryconstraint.md)\[\] | | - -Returns: - -[Query](./firestore_.query.md)<T> - diff --git a/docs-exp/firestore_.query.type.md b/docs-exp/firestore_.query.type.md deleted file mode 100644 index 166a6fcff6a..00000000000 --- a/docs-exp/firestore_.query.type.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Query](./firestore_.query.md) > [type](./firestore_.query.type.md) - -## Query.type property - -Signature: - -```typescript -readonly type: 'query' | 'collection'; -``` diff --git a/docs-exp/firestore_.query.withconverter.md b/docs-exp/firestore_.query.withconverter.md deleted file mode 100644 index 8de0f02583e..00000000000 --- a/docs-exp/firestore_.query.withconverter.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Query](./firestore_.query.md) > [withConverter](./firestore_.query.withconverter.md) - -## Query.withConverter() method - -Signature: - -```typescript -withConverter(converter: FirestoreDataConverter): Query; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| converter | [FirestoreDataConverter](./firestore_.firestoredataconverter.md)<U> | | - -Returns: - -[Query](./firestore_.query.md)<U> - diff --git a/docs-exp/firestore_.queryconstraint.md b/docs-exp/firestore_.queryconstraint.md deleted file mode 100644 index 3fd4728b93d..00000000000 --- a/docs-exp/firestore_.queryconstraint.md +++ /dev/null @@ -1,18 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [QueryConstraint](./firestore_.queryconstraint.md) - -## QueryConstraint class - -Signature: - -```typescript -export class QueryConstraint -``` - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [type](./firestore_.queryconstraint.type.md) | | [QueryConstraintType](./firestore_.queryconstrainttype.md) | | - diff --git a/docs-exp/firestore_.queryconstraint.type.md b/docs-exp/firestore_.queryconstraint.type.md deleted file mode 100644 index f2d3426fccb..00000000000 --- a/docs-exp/firestore_.queryconstraint.type.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [QueryConstraint](./firestore_.queryconstraint.md) > [type](./firestore_.queryconstraint.type.md) - -## QueryConstraint.type property - -Signature: - -```typescript -readonly type: QueryConstraintType; -``` diff --git a/docs-exp/firestore_.queryconstrainttype.md b/docs-exp/firestore_.queryconstrainttype.md deleted file mode 100644 index 6dfeb2f8691..00000000000 --- a/docs-exp/firestore_.queryconstrainttype.md +++ /dev/null @@ -1,19 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [QueryConstraintType](./firestore_.queryconstrainttype.md) - -## QueryConstraintType type - -Signature: - -```typescript -export type QueryConstraintType = - | 'where' - | 'orderBy' - | 'limit' - | 'limitToLast' - | 'startAt' - | 'startAfter' - | 'endAt' - | 'endBefore'; -``` diff --git a/docs-exp/firestore_.querydocumentsnapshot.data.md b/docs-exp/firestore_.querydocumentsnapshot.data.md deleted file mode 100644 index fd5fea67c27..00000000000 --- a/docs-exp/firestore_.querydocumentsnapshot.data.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [QueryDocumentSnapshot](./firestore_.querydocumentsnapshot.md) > [data](./firestore_.querydocumentsnapshot.data.md) - -## QueryDocumentSnapshot.data() method - -Signature: - -```typescript -data(options?: SnapshotOptions): T; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| options | [SnapshotOptions](./firestore_.snapshotoptions.md) | | - -Returns: - -T - diff --git a/docs-exp/firestore_.querydocumentsnapshot.md b/docs-exp/firestore_.querydocumentsnapshot.md deleted file mode 100644 index 7407d7b553e..00000000000 --- a/docs-exp/firestore_.querydocumentsnapshot.md +++ /dev/null @@ -1,21 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [QueryDocumentSnapshot](./firestore_.querydocumentsnapshot.md) - -## QueryDocumentSnapshot class - -Signature: - -```typescript -export class QueryDocumentSnapshot extends DocumentSnapshot< - T -> -``` -Extends: [DocumentSnapshot](./firestore_.documentsnapshot.md)< T > - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [data(options)](./firestore_.querydocumentsnapshot.data.md) | | | - diff --git a/docs-exp/firestore_.queryequal.md b/docs-exp/firestore_.queryequal.md deleted file mode 100644 index 7ace7b773a8..00000000000 --- a/docs-exp/firestore_.queryequal.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [queryEqual](./firestore_.queryequal.md) - -## queryEqual() function - -Signature: - -```typescript -export function queryEqual(left: Query, right: Query): boolean; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| left | [Query](./firestore_.query.md)<T> | | -| right | [Query](./firestore_.query.md)<T> | | - -Returns: - -boolean - diff --git a/docs-exp/firestore_.querysnapshot.docchanges.md b/docs-exp/firestore_.querysnapshot.docchanges.md deleted file mode 100644 index bbee4eade52..00000000000 --- a/docs-exp/firestore_.querysnapshot.docchanges.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [QuerySnapshot](./firestore_.querysnapshot.md) > [docChanges](./firestore_.querysnapshot.docchanges.md) - -## QuerySnapshot.docChanges() method - -Signature: - -```typescript -docChanges(options?: SnapshotListenOptions): Array>; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| options | [SnapshotListenOptions](./firestore_.snapshotlistenoptions.md) | | - -Returns: - -Array<[DocumentChange](./firestore_.documentchange.md)<T>> - diff --git a/docs-exp/firestore_.querysnapshot.docs.md b/docs-exp/firestore_.querysnapshot.docs.md deleted file mode 100644 index 7d4726bd870..00000000000 --- a/docs-exp/firestore_.querysnapshot.docs.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [QuerySnapshot](./firestore_.querysnapshot.md) > [docs](./firestore_.querysnapshot.docs.md) - -## QuerySnapshot.docs property - -Signature: - -```typescript -readonly docs: Array>; -``` diff --git a/docs-exp/firestore_.querysnapshot.empty.md b/docs-exp/firestore_.querysnapshot.empty.md deleted file mode 100644 index f6820cffb3a..00000000000 --- a/docs-exp/firestore_.querysnapshot.empty.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [QuerySnapshot](./firestore_.querysnapshot.md) > [empty](./firestore_.querysnapshot.empty.md) - -## QuerySnapshot.empty property - -Signature: - -```typescript -readonly empty: boolean; -``` diff --git a/docs-exp/firestore_.querysnapshot.foreach.md b/docs-exp/firestore_.querysnapshot.foreach.md deleted file mode 100644 index f05bd706c69..00000000000 --- a/docs-exp/firestore_.querysnapshot.foreach.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [QuerySnapshot](./firestore_.querysnapshot.md) > [forEach](./firestore_.querysnapshot.foreach.md) - -## QuerySnapshot.forEach() method - -Signature: - -```typescript -forEach( - callback: (result: QueryDocumentSnapshot) => void, - thisArg?: any - ): void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| callback | (result: [QueryDocumentSnapshot](./firestore_.querydocumentsnapshot.md)<T>) => void | | -| thisArg | any | | - -Returns: - -void - diff --git a/docs-exp/firestore_.querysnapshot.md b/docs-exp/firestore_.querysnapshot.md deleted file mode 100644 index b22e75c5f14..00000000000 --- a/docs-exp/firestore_.querysnapshot.md +++ /dev/null @@ -1,29 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [QuerySnapshot](./firestore_.querysnapshot.md) - -## QuerySnapshot class - -Signature: - -```typescript -export class QuerySnapshot -``` - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [docs](./firestore_.querysnapshot.docs.md) | | Array<[QueryDocumentSnapshot](./firestore_.querydocumentsnapshot.md)<T>> | | -| [empty](./firestore_.querysnapshot.empty.md) | | boolean | | -| [metadata](./firestore_.querysnapshot.metadata.md) | | [SnapshotMetadata](./firestore_.snapshotmetadata.md) | | -| [query](./firestore_.querysnapshot.query.md) | | [Query](./firestore_.query.md)<T> | | -| [size](./firestore_.querysnapshot.size.md) | | number | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [docChanges(options)](./firestore_.querysnapshot.docchanges.md) | | | -| [forEach(callback, thisArg)](./firestore_.querysnapshot.foreach.md) | | | - diff --git a/docs-exp/firestore_.querysnapshot.metadata.md b/docs-exp/firestore_.querysnapshot.metadata.md deleted file mode 100644 index d41de5f301d..00000000000 --- a/docs-exp/firestore_.querysnapshot.metadata.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [QuerySnapshot](./firestore_.querysnapshot.md) > [metadata](./firestore_.querysnapshot.metadata.md) - -## QuerySnapshot.metadata property - -Signature: - -```typescript -readonly metadata: SnapshotMetadata; -``` diff --git a/docs-exp/firestore_.querysnapshot.query.md b/docs-exp/firestore_.querysnapshot.query.md deleted file mode 100644 index c0d6605f4c6..00000000000 --- a/docs-exp/firestore_.querysnapshot.query.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [QuerySnapshot](./firestore_.querysnapshot.md) > [query](./firestore_.querysnapshot.query.md) - -## QuerySnapshot.query property - -Signature: - -```typescript -readonly query: Query; -``` diff --git a/docs-exp/firestore_.querysnapshot.size.md b/docs-exp/firestore_.querysnapshot.size.md deleted file mode 100644 index dd5f1ca4d34..00000000000 --- a/docs-exp/firestore_.querysnapshot.size.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [QuerySnapshot](./firestore_.querysnapshot.md) > [size](./firestore_.querysnapshot.size.md) - -## QuerySnapshot.size property - -Signature: - -```typescript -readonly size: number; -``` diff --git a/docs-exp/firestore_.refequal.md b/docs-exp/firestore_.refequal.md deleted file mode 100644 index 2a3421df341..00000000000 --- a/docs-exp/firestore_.refequal.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [refEqual](./firestore_.refequal.md) - -## refEqual() function - -Signature: - -```typescript -export function refEqual( - left: DocumentReference | CollectionReference, - right: DocumentReference | CollectionReference -): boolean; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| left | [DocumentReference](./firestore_.documentreference.md)<T> \| [CollectionReference](./firestore_.collectionreference.md)<T> | | -| right | [DocumentReference](./firestore_.documentreference.md)<T> \| [CollectionReference](./firestore_.collectionreference.md)<T> | | - -Returns: - -boolean - diff --git a/docs-exp/firestore_.runtransaction.md b/docs-exp/firestore_.runtransaction.md deleted file mode 100644 index 784b21fff34..00000000000 --- a/docs-exp/firestore_.runtransaction.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [runTransaction](./firestore_.runtransaction.md) - -## runTransaction() function - -Signature: - -```typescript -export function runTransaction( - firestore: FirebaseFirestore, - updateFunction: (transaction: Transaction) => Promise -): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| firestore | [FirebaseFirestore](./firestore_.firebasefirestore.md) | | -| updateFunction | (transaction: [Transaction](./firestore_.transaction.md)) => Promise<T> | | - -Returns: - -Promise<T> - diff --git a/docs-exp/firestore_.servertimestamp.md b/docs-exp/firestore_.servertimestamp.md deleted file mode 100644 index ff7acd9456d..00000000000 --- a/docs-exp/firestore_.servertimestamp.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [serverTimestamp](./firestore_.servertimestamp.md) - -## serverTimestamp() function - -Signature: - -```typescript -export function serverTimestamp(): FieldValue; -``` -Returns: - -[FieldValue](./firestore_.fieldvalue.md) - diff --git a/docs-exp/firestore_.setdoc.md b/docs-exp/firestore_.setdoc.md deleted file mode 100644 index b22acded9d7..00000000000 --- a/docs-exp/firestore_.setdoc.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [setDoc](./firestore_.setdoc.md) - -## setDoc() function - -Signature: - -```typescript -export function setDoc( - reference: DocumentReference, - data: T -): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| reference | [DocumentReference](./firestore_.documentreference.md)<T> | | -| data | T | | - -Returns: - -Promise<void> - diff --git a/docs-exp/firestore_.setdoc_1.md b/docs-exp/firestore_.setdoc_1.md deleted file mode 100644 index aa6758d3da5..00000000000 --- a/docs-exp/firestore_.setdoc_1.md +++ /dev/null @@ -1,28 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [setDoc](./firestore_.setdoc_1.md) - -## setDoc() function - -Signature: - -```typescript -export function setDoc( - reference: DocumentReference, - data: Partial, - options: SetOptions -): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| reference | [DocumentReference](./firestore_.documentreference.md)<T> | | -| data | Partial<T> | | -| options | [SetOptions](./firestore_.setoptions.md) | | - -Returns: - -Promise<void> - diff --git a/docs-exp/firestore_.setloglevel.md b/docs-exp/firestore_.setloglevel.md deleted file mode 100644 index 233256ebb73..00000000000 --- a/docs-exp/firestore_.setloglevel.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [setLogLevel](./firestore_.setloglevel.md) - -## setLogLevel() function - -Signature: - -```typescript -export function setLogLevel(logLevel: LogLevel): void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| logLevel | [LogLevel](./firestore_.loglevel.md) | | - -Returns: - -void - diff --git a/docs-exp/firestore_.setoptions.md b/docs-exp/firestore_.setoptions.md deleted file mode 100644 index 7bd0d3ac00a..00000000000 --- a/docs-exp/firestore_.setoptions.md +++ /dev/null @@ -1,17 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [SetOptions](./firestore_.setoptions.md) - -## SetOptions type - -Signature: - -```typescript -export type SetOptions = - | { - readonly merge?: boolean; - } - | { - readonly mergeFields?: Array; - }; -``` diff --git a/docs-exp/firestore_.settings.cachesizebytes.md b/docs-exp/firestore_.settings.cachesizebytes.md deleted file mode 100644 index 064c25554ba..00000000000 --- a/docs-exp/firestore_.settings.cachesizebytes.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Settings](./firestore_.settings.md) > [cacheSizeBytes](./firestore_.settings.cachesizebytes.md) - -## Settings.cacheSizeBytes property - -Signature: - -```typescript -cacheSizeBytes?: number; -``` diff --git a/docs-exp/firestore_.settings.host.md b/docs-exp/firestore_.settings.host.md deleted file mode 100644 index 53d1bd71b4c..00000000000 --- a/docs-exp/firestore_.settings.host.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Settings](./firestore_.settings.md) > [host](./firestore_.settings.host.md) - -## Settings.host property - -Signature: - -```typescript -host?: string; -``` diff --git a/docs-exp/firestore_.settings.ignoreundefinedproperties.md b/docs-exp/firestore_.settings.ignoreundefinedproperties.md deleted file mode 100644 index 6d4476eb275..00000000000 --- a/docs-exp/firestore_.settings.ignoreundefinedproperties.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Settings](./firestore_.settings.md) > [ignoreUndefinedProperties](./firestore_.settings.ignoreundefinedproperties.md) - -## Settings.ignoreUndefinedProperties property - -Signature: - -```typescript -ignoreUndefinedProperties?: boolean; -``` diff --git a/docs-exp/firestore_.settings.md b/docs-exp/firestore_.settings.md deleted file mode 100644 index 09f6430ea45..00000000000 --- a/docs-exp/firestore_.settings.md +++ /dev/null @@ -1,21 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Settings](./firestore_.settings.md) - -## Settings interface - -Signature: - -```typescript -export interface Settings -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [cacheSizeBytes](./firestore_.settings.cachesizebytes.md) | number | | -| [host](./firestore_.settings.host.md) | string | | -| [ignoreUndefinedProperties](./firestore_.settings.ignoreundefinedproperties.md) | boolean | | -| [ssl](./firestore_.settings.ssl.md) | boolean | | - diff --git a/docs-exp/firestore_.settings.ssl.md b/docs-exp/firestore_.settings.ssl.md deleted file mode 100644 index 79ac7c8e491..00000000000 --- a/docs-exp/firestore_.settings.ssl.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Settings](./firestore_.settings.md) > [ssl](./firestore_.settings.ssl.md) - -## Settings.ssl property - -Signature: - -```typescript -ssl?: boolean; -``` diff --git a/docs-exp/firestore_.snapshotequal.md b/docs-exp/firestore_.snapshotequal.md deleted file mode 100644 index d5a26442d50..00000000000 --- a/docs-exp/firestore_.snapshotequal.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [snapshotEqual](./firestore_.snapshotequal.md) - -## snapshotEqual() function - -Signature: - -```typescript -export function snapshotEqual( - left: DocumentSnapshot | QuerySnapshot, - right: DocumentSnapshot | QuerySnapshot -): boolean; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| left | [DocumentSnapshot](./firestore_.documentsnapshot.md)<T> \| [QuerySnapshot](./firestore_.querysnapshot.md)<T> | | -| right | [DocumentSnapshot](./firestore_.documentsnapshot.md)<T> \| [QuerySnapshot](./firestore_.querysnapshot.md)<T> | | - -Returns: - -boolean - diff --git a/docs-exp/firestore_.snapshotlistenoptions.includemetadatachanges.md b/docs-exp/firestore_.snapshotlistenoptions.includemetadatachanges.md deleted file mode 100644 index cdb979d146b..00000000000 --- a/docs-exp/firestore_.snapshotlistenoptions.includemetadatachanges.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [SnapshotListenOptions](./firestore_.snapshotlistenoptions.md) > [includeMetadataChanges](./firestore_.snapshotlistenoptions.includemetadatachanges.md) - -## SnapshotListenOptions.includeMetadataChanges property - -Signature: - -```typescript -readonly includeMetadataChanges?: boolean; -``` diff --git a/docs-exp/firestore_.snapshotlistenoptions.md b/docs-exp/firestore_.snapshotlistenoptions.md deleted file mode 100644 index 904c94d659e..00000000000 --- a/docs-exp/firestore_.snapshotlistenoptions.md +++ /dev/null @@ -1,18 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [SnapshotListenOptions](./firestore_.snapshotlistenoptions.md) - -## SnapshotListenOptions interface - -Signature: - -```typescript -export interface SnapshotListenOptions -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [includeMetadataChanges](./firestore_.snapshotlistenoptions.includemetadatachanges.md) | boolean | | - diff --git a/docs-exp/firestore_.snapshotmetadata.fromcache.md b/docs-exp/firestore_.snapshotmetadata.fromcache.md deleted file mode 100644 index c0a460b3ec6..00000000000 --- a/docs-exp/firestore_.snapshotmetadata.fromcache.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [SnapshotMetadata](./firestore_.snapshotmetadata.md) > [fromCache](./firestore_.snapshotmetadata.fromcache.md) - -## SnapshotMetadata.fromCache property - -Signature: - -```typescript -readonly fromCache: boolean; -``` diff --git a/docs-exp/firestore_.snapshotmetadata.haspendingwrites.md b/docs-exp/firestore_.snapshotmetadata.haspendingwrites.md deleted file mode 100644 index 6396e49a2f9..00000000000 --- a/docs-exp/firestore_.snapshotmetadata.haspendingwrites.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [SnapshotMetadata](./firestore_.snapshotmetadata.md) > [hasPendingWrites](./firestore_.snapshotmetadata.haspendingwrites.md) - -## SnapshotMetadata.hasPendingWrites property - -Signature: - -```typescript -readonly hasPendingWrites: boolean; -``` diff --git a/docs-exp/firestore_.snapshotmetadata.isequal.md b/docs-exp/firestore_.snapshotmetadata.isequal.md deleted file mode 100644 index 87c30dffb01..00000000000 --- a/docs-exp/firestore_.snapshotmetadata.isequal.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [SnapshotMetadata](./firestore_.snapshotmetadata.md) > [isEqual](./firestore_.snapshotmetadata.isequal.md) - -## SnapshotMetadata.isEqual() method - -Signature: - -```typescript -isEqual(other: SnapshotMetadata): boolean; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| other | [SnapshotMetadata](./firestore_.snapshotmetadata.md) | | - -Returns: - -boolean - diff --git a/docs-exp/firestore_.snapshotmetadata.md b/docs-exp/firestore_.snapshotmetadata.md deleted file mode 100644 index af3f3c9f3af..00000000000 --- a/docs-exp/firestore_.snapshotmetadata.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [SnapshotMetadata](./firestore_.snapshotmetadata.md) - -## SnapshotMetadata class - -Signature: - -```typescript -export class SnapshotMetadata -``` - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [fromCache](./firestore_.snapshotmetadata.fromcache.md) | | boolean | | -| [hasPendingWrites](./firestore_.snapshotmetadata.haspendingwrites.md) | | boolean | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [isEqual(other)](./firestore_.snapshotmetadata.isequal.md) | | | - diff --git a/docs-exp/firestore_.snapshotoptions.md b/docs-exp/firestore_.snapshotoptions.md deleted file mode 100644 index f8835532827..00000000000 --- a/docs-exp/firestore_.snapshotoptions.md +++ /dev/null @@ -1,18 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [SnapshotOptions](./firestore_.snapshotoptions.md) - -## SnapshotOptions interface - -Signature: - -```typescript -export interface SnapshotOptions -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [serverTimestamps](./firestore_.snapshotoptions.servertimestamps.md) | 'estimate' \| 'previous' \| 'none' | | - diff --git a/docs-exp/firestore_.snapshotoptions.servertimestamps.md b/docs-exp/firestore_.snapshotoptions.servertimestamps.md deleted file mode 100644 index 0e8031539d5..00000000000 --- a/docs-exp/firestore_.snapshotoptions.servertimestamps.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [SnapshotOptions](./firestore_.snapshotoptions.md) > [serverTimestamps](./firestore_.snapshotoptions.servertimestamps.md) - -## SnapshotOptions.serverTimestamps property - -Signature: - -```typescript -readonly serverTimestamps?: 'estimate' | 'previous' | 'none'; -``` diff --git a/docs-exp/firestore_.startafter.md b/docs-exp/firestore_.startafter.md deleted file mode 100644 index f6f88fdcb9a..00000000000 --- a/docs-exp/firestore_.startafter.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [startAfter](./firestore_.startafter.md) - -## startAfter() function - -Signature: - -```typescript -export function startAfter(snapshot: DocumentSnapshot): QueryConstraint; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| snapshot | [DocumentSnapshot](./firestore_.documentsnapshot.md)<any> | | - -Returns: - -[QueryConstraint](./firestore_.queryconstraint.md) - diff --git a/docs-exp/firestore_.startafter_1.md b/docs-exp/firestore_.startafter_1.md deleted file mode 100644 index 4520a88f96b..00000000000 --- a/docs-exp/firestore_.startafter_1.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [startAfter](./firestore_.startafter_1.md) - -## startAfter() function - -Signature: - -```typescript -export function startAfter(...fieldValues: any[]): QueryConstraint; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| fieldValues | any\[\] | | - -Returns: - -[QueryConstraint](./firestore_.queryconstraint.md) - diff --git a/docs-exp/firestore_.startat.md b/docs-exp/firestore_.startat.md deleted file mode 100644 index 80b6b5340f1..00000000000 --- a/docs-exp/firestore_.startat.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [startAt](./firestore_.startat.md) - -## startAt() function - -Signature: - -```typescript -export function startAt(snapshot: DocumentSnapshot): QueryConstraint; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| snapshot | [DocumentSnapshot](./firestore_.documentsnapshot.md)<any> | | - -Returns: - -[QueryConstraint](./firestore_.queryconstraint.md) - diff --git a/docs-exp/firestore_.startat_1.md b/docs-exp/firestore_.startat_1.md deleted file mode 100644 index 5fe59c798f4..00000000000 --- a/docs-exp/firestore_.startat_1.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [startAt](./firestore_.startat_1.md) - -## startAt() function - -Signature: - -```typescript -export function startAt(...fieldValues: any[]): QueryConstraint; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| fieldValues | any\[\] | | - -Returns: - -[QueryConstraint](./firestore_.queryconstraint.md) - diff --git a/docs-exp/firestore_.terminate.md b/docs-exp/firestore_.terminate.md deleted file mode 100644 index 91308bb7f23..00000000000 --- a/docs-exp/firestore_.terminate.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [terminate](./firestore_.terminate.md) - -## terminate() function - -Signature: - -```typescript -export function terminate(firestore: FirebaseFirestore): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| firestore | [FirebaseFirestore](./firestore_.firebasefirestore.md) | | - -Returns: - -Promise<void> - diff --git a/docs-exp/firestore_.timestamp._constructor_.md b/docs-exp/firestore_.timestamp._constructor_.md deleted file mode 100644 index 876789f8215..00000000000 --- a/docs-exp/firestore_.timestamp._constructor_.md +++ /dev/null @@ -1,19 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Timestamp](./firestore_.timestamp.md) > [(constructor)](./firestore_.timestamp._constructor_.md) - -## Timestamp.(constructor) - -Signature: - -```typescript -constructor(seconds: number, nanoseconds: number); -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| seconds | number | | -| nanoseconds | number | | - diff --git a/docs-exp/firestore_.timestamp.fromdate.md b/docs-exp/firestore_.timestamp.fromdate.md deleted file mode 100644 index 9459c3b0e86..00000000000 --- a/docs-exp/firestore_.timestamp.fromdate.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Timestamp](./firestore_.timestamp.md) > [fromDate](./firestore_.timestamp.fromdate.md) - -## Timestamp.fromDate() method - -Signature: - -```typescript -static fromDate(date: Date): Timestamp; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| date | Date | | - -Returns: - -[Timestamp](./firestore_.timestamp.md) - diff --git a/docs-exp/firestore_.timestamp.frommillis.md b/docs-exp/firestore_.timestamp.frommillis.md deleted file mode 100644 index 201fed0c3c4..00000000000 --- a/docs-exp/firestore_.timestamp.frommillis.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Timestamp](./firestore_.timestamp.md) > [fromMillis](./firestore_.timestamp.frommillis.md) - -## Timestamp.fromMillis() method - -Signature: - -```typescript -static fromMillis(milliseconds: number): Timestamp; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| milliseconds | number | | - -Returns: - -[Timestamp](./firestore_.timestamp.md) - diff --git a/docs-exp/firestore_.timestamp.isequal.md b/docs-exp/firestore_.timestamp.isequal.md deleted file mode 100644 index 25cec5a6f98..00000000000 --- a/docs-exp/firestore_.timestamp.isequal.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Timestamp](./firestore_.timestamp.md) > [isEqual](./firestore_.timestamp.isequal.md) - -## Timestamp.isEqual() method - -Signature: - -```typescript -isEqual(other: Timestamp): boolean; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| other | [Timestamp](./firestore_.timestamp.md) | | - -Returns: - -boolean - diff --git a/docs-exp/firestore_.timestamp.md b/docs-exp/firestore_.timestamp.md deleted file mode 100644 index 96e0de5ffa0..00000000000 --- a/docs-exp/firestore_.timestamp.md +++ /dev/null @@ -1,37 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Timestamp](./firestore_.timestamp.md) - -## Timestamp class - -Signature: - -```typescript -export class Timestamp -``` - -## Constructors - -| Constructor | Modifiers | Description | -| --- | --- | --- | -| [(constructor)(seconds, nanoseconds)](./firestore_.timestamp._constructor_.md) | | | - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [nanoseconds](./firestore_.timestamp.nanoseconds.md) | | number | | -| [seconds](./firestore_.timestamp.seconds.md) | | number | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [fromDate(date)](./firestore_.timestamp.fromdate.md) | static | | -| [fromMillis(milliseconds)](./firestore_.timestamp.frommillis.md) | static | | -| [isEqual(other)](./firestore_.timestamp.isequal.md) | | | -| [now()](./firestore_.timestamp.now.md) | static | | -| [toDate()](./firestore_.timestamp.todate.md) | | | -| [toMillis()](./firestore_.timestamp.tomillis.md) | | | -| [valueOf()](./firestore_.timestamp.valueof.md) | | | - diff --git a/docs-exp/firestore_.timestamp.nanoseconds.md b/docs-exp/firestore_.timestamp.nanoseconds.md deleted file mode 100644 index 5bed356ff02..00000000000 --- a/docs-exp/firestore_.timestamp.nanoseconds.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Timestamp](./firestore_.timestamp.md) > [nanoseconds](./firestore_.timestamp.nanoseconds.md) - -## Timestamp.nanoseconds property - -Signature: - -```typescript -readonly nanoseconds: number; -``` diff --git a/docs-exp/firestore_.timestamp.now.md b/docs-exp/firestore_.timestamp.now.md deleted file mode 100644 index bf7e72b1b71..00000000000 --- a/docs-exp/firestore_.timestamp.now.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Timestamp](./firestore_.timestamp.md) > [now](./firestore_.timestamp.now.md) - -## Timestamp.now() method - -Signature: - -```typescript -static now(): Timestamp; -``` -Returns: - -[Timestamp](./firestore_.timestamp.md) - diff --git a/docs-exp/firestore_.timestamp.seconds.md b/docs-exp/firestore_.timestamp.seconds.md deleted file mode 100644 index 9318596b9b7..00000000000 --- a/docs-exp/firestore_.timestamp.seconds.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Timestamp](./firestore_.timestamp.md) > [seconds](./firestore_.timestamp.seconds.md) - -## Timestamp.seconds property - -Signature: - -```typescript -readonly seconds: number; -``` diff --git a/docs-exp/firestore_.timestamp.todate.md b/docs-exp/firestore_.timestamp.todate.md deleted file mode 100644 index 95728eabf91..00000000000 --- a/docs-exp/firestore_.timestamp.todate.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Timestamp](./firestore_.timestamp.md) > [toDate](./firestore_.timestamp.todate.md) - -## Timestamp.toDate() method - -Signature: - -```typescript -toDate(): Date; -``` -Returns: - -Date - diff --git a/docs-exp/firestore_.timestamp.tomillis.md b/docs-exp/firestore_.timestamp.tomillis.md deleted file mode 100644 index 140f253c561..00000000000 --- a/docs-exp/firestore_.timestamp.tomillis.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Timestamp](./firestore_.timestamp.md) > [toMillis](./firestore_.timestamp.tomillis.md) - -## Timestamp.toMillis() method - -Signature: - -```typescript -toMillis(): number; -``` -Returns: - -number - diff --git a/docs-exp/firestore_.timestamp.valueof.md b/docs-exp/firestore_.timestamp.valueof.md deleted file mode 100644 index d598bdb96ea..00000000000 --- a/docs-exp/firestore_.timestamp.valueof.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Timestamp](./firestore_.timestamp.md) > [valueOf](./firestore_.timestamp.valueof.md) - -## Timestamp.valueOf() method - -Signature: - -```typescript -valueOf(): string; -``` -Returns: - -string - diff --git a/docs-exp/firestore_.transaction.delete.md b/docs-exp/firestore_.transaction.delete.md deleted file mode 100644 index 8a135c2552c..00000000000 --- a/docs-exp/firestore_.transaction.delete.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Transaction](./firestore_.transaction.md) > [delete](./firestore_.transaction.delete.md) - -## Transaction.delete() method - -Signature: - -```typescript -delete(documentRef: DocumentReference): Transaction; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| documentRef | [DocumentReference](./firestore_.documentreference.md)<any> | | - -Returns: - -[Transaction](./firestore_.transaction.md) - diff --git a/docs-exp/firestore_.transaction.get.md b/docs-exp/firestore_.transaction.get.md deleted file mode 100644 index e8389915c6c..00000000000 --- a/docs-exp/firestore_.transaction.get.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Transaction](./firestore_.transaction.md) > [get](./firestore_.transaction.get.md) - -## Transaction.get() method - -Signature: - -```typescript -get(documentRef: DocumentReference): Promise>; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| documentRef | [DocumentReference](./firestore_.documentreference.md)<T> | | - -Returns: - -Promise<[DocumentSnapshot](./firestore_.documentsnapshot.md)<T>> - diff --git a/docs-exp/firestore_.transaction.md b/docs-exp/firestore_.transaction.md deleted file mode 100644 index 7de25da7919..00000000000 --- a/docs-exp/firestore_.transaction.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Transaction](./firestore_.transaction.md) - -## Transaction class - -Signature: - -```typescript -export class Transaction -``` - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [delete(documentRef)](./firestore_.transaction.delete.md) | | | -| [get(documentRef)](./firestore_.transaction.get.md) | | | -| [set(documentRef, data)](./firestore_.transaction.set.md) | | | -| [set(documentRef, data, options)](./firestore_.transaction.set_1.md) | | | -| [update(documentRef, data)](./firestore_.transaction.update.md) | | | -| [update(documentRef, field, value, moreFieldsAndValues)](./firestore_.transaction.update_1.md) | | | - diff --git a/docs-exp/firestore_.transaction.set.md b/docs-exp/firestore_.transaction.set.md deleted file mode 100644 index 7c091b0f138..00000000000 --- a/docs-exp/firestore_.transaction.set.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Transaction](./firestore_.transaction.md) > [set](./firestore_.transaction.set.md) - -## Transaction.set() method - -Signature: - -```typescript -set(documentRef: DocumentReference, data: T): Transaction; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| documentRef | [DocumentReference](./firestore_.documentreference.md)<T> | | -| data | T | | - -Returns: - -[Transaction](./firestore_.transaction.md) - diff --git a/docs-exp/firestore_.transaction.set_1.md b/docs-exp/firestore_.transaction.set_1.md deleted file mode 100644 index f4c80e140ac..00000000000 --- a/docs-exp/firestore_.transaction.set_1.md +++ /dev/null @@ -1,28 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Transaction](./firestore_.transaction.md) > [set](./firestore_.transaction.set_1.md) - -## Transaction.set() method - -Signature: - -```typescript -set( - documentRef: DocumentReference, - data: Partial, - options: SetOptions - ): Transaction; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| documentRef | [DocumentReference](./firestore_.documentreference.md)<T> | | -| data | Partial<T> | | -| options | [SetOptions](./firestore_.setoptions.md) | | - -Returns: - -[Transaction](./firestore_.transaction.md) - diff --git a/docs-exp/firestore_.transaction.update.md b/docs-exp/firestore_.transaction.update.md deleted file mode 100644 index b0c3abe015c..00000000000 --- a/docs-exp/firestore_.transaction.update.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Transaction](./firestore_.transaction.md) > [update](./firestore_.transaction.update.md) - -## Transaction.update() method - -Signature: - -```typescript -update(documentRef: DocumentReference, data: UpdateData): Transaction; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| documentRef | [DocumentReference](./firestore_.documentreference.md)<any> | | -| data | [UpdateData](./firestore_.updatedata.md) | | - -Returns: - -[Transaction](./firestore_.transaction.md) - diff --git a/docs-exp/firestore_.transaction.update_1.md b/docs-exp/firestore_.transaction.update_1.md deleted file mode 100644 index a830461b71f..00000000000 --- a/docs-exp/firestore_.transaction.update_1.md +++ /dev/null @@ -1,30 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [Transaction](./firestore_.transaction.md) > [update](./firestore_.transaction.update_1.md) - -## Transaction.update() method - -Signature: - -```typescript -update( - documentRef: DocumentReference, - field: string | FieldPath, - value: any, - ...moreFieldsAndValues: any[] - ): Transaction; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| documentRef | [DocumentReference](./firestore_.documentreference.md)<any> | | -| field | string \| [FieldPath](./firestore_.fieldpath.md) | | -| value | any | | -| moreFieldsAndValues | any\[\] | | - -Returns: - -[Transaction](./firestore_.transaction.md) - diff --git a/docs-exp/firestore_.updatedata.md b/docs-exp/firestore_.updatedata.md deleted file mode 100644 index 42d3cfaf797..00000000000 --- a/docs-exp/firestore_.updatedata.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [UpdateData](./firestore_.updatedata.md) - -## UpdateData interface - -Signature: - -```typescript -export interface UpdateData -``` diff --git a/docs-exp/firestore_.updatedoc.md b/docs-exp/firestore_.updatedoc.md deleted file mode 100644 index 78d1362b56a..00000000000 --- a/docs-exp/firestore_.updatedoc.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [updateDoc](./firestore_.updatedoc.md) - -## updateDoc() function - -Signature: - -```typescript -export function updateDoc( - reference: DocumentReference, - data: UpdateData -): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| reference | [DocumentReference](./firestore_.documentreference.md)<unknown> | | -| data | [UpdateData](./firestore_.updatedata.md) | | - -Returns: - -Promise<void> - diff --git a/docs-exp/firestore_.updatedoc_1.md b/docs-exp/firestore_.updatedoc_1.md deleted file mode 100644 index ce5a62f5174..00000000000 --- a/docs-exp/firestore_.updatedoc_1.md +++ /dev/null @@ -1,30 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [updateDoc](./firestore_.updatedoc_1.md) - -## updateDoc() function - -Signature: - -```typescript -export function updateDoc( - reference: DocumentReference, - field: string | FieldPath, - value: any, - ...moreFieldsAndValues: any[] -): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| reference | [DocumentReference](./firestore_.documentreference.md)<unknown> | | -| field | string \| [FieldPath](./firestore_.fieldpath.md) | | -| value | any | | -| moreFieldsAndValues | any\[\] | | - -Returns: - -Promise<void> - diff --git a/docs-exp/firestore_.waitforpendingwrites.md b/docs-exp/firestore_.waitforpendingwrites.md deleted file mode 100644 index e581685ddd1..00000000000 --- a/docs-exp/firestore_.waitforpendingwrites.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [waitForPendingWrites](./firestore_.waitforpendingwrites.md) - -## waitForPendingWrites() function - -Signature: - -```typescript -export function waitForPendingWrites( - firestore: FirebaseFirestore -): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| firestore | [FirebaseFirestore](./firestore_.firebasefirestore.md) | | - -Returns: - -Promise<void> - diff --git a/docs-exp/firestore_.where.md b/docs-exp/firestore_.where.md deleted file mode 100644 index d94d506e296..00000000000 --- a/docs-exp/firestore_.where.md +++ /dev/null @@ -1,28 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [where](./firestore_.where.md) - -## where() function - -Signature: - -```typescript -export function where( - fieldPath: string | FieldPath, - opStr: WhereFilterOp, - value: any -): QueryConstraint; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| fieldPath | string \| [FieldPath](./firestore_.fieldpath.md) | | -| opStr | [WhereFilterOp](./firestore_.wherefilterop.md) | | -| value | any | | - -Returns: - -[QueryConstraint](./firestore_.queryconstraint.md) - diff --git a/docs-exp/firestore_.wherefilterop.md b/docs-exp/firestore_.wherefilterop.md deleted file mode 100644 index bd8696cd955..00000000000 --- a/docs-exp/firestore_.wherefilterop.md +++ /dev/null @@ -1,19 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [WhereFilterOp](./firestore_.wherefilterop.md) - -## WhereFilterOp type - -Signature: - -```typescript -export type WhereFilterOp = - | '<' - | '<=' - | '==' - | '>=' - | '>' - | 'array-contains' - | 'in' - | 'array-contains-any'; -``` diff --git a/docs-exp/firestore_.writebatch.commit.md b/docs-exp/firestore_.writebatch.commit.md deleted file mode 100644 index b3051315730..00000000000 --- a/docs-exp/firestore_.writebatch.commit.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [WriteBatch](./firestore_.writebatch.md) > [commit](./firestore_.writebatch.commit.md) - -## WriteBatch.commit() method - -Signature: - -```typescript -commit(): Promise; -``` -Returns: - -Promise<void> - diff --git a/docs-exp/firestore_.writebatch.delete.md b/docs-exp/firestore_.writebatch.delete.md deleted file mode 100644 index 8189e7dbcfa..00000000000 --- a/docs-exp/firestore_.writebatch.delete.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [WriteBatch](./firestore_.writebatch.md) > [delete](./firestore_.writebatch.delete.md) - -## WriteBatch.delete() method - -Signature: - -```typescript -delete(documentRef: DocumentReference): WriteBatch; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| documentRef | [DocumentReference](./firestore_.documentreference.md)<any> | | - -Returns: - -[WriteBatch](./firestore_.writebatch.md) - diff --git a/docs-exp/firestore_.writebatch.md b/docs-exp/firestore_.writebatch.md deleted file mode 100644 index bde59c8a57d..00000000000 --- a/docs-exp/firestore_.writebatch.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [writeBatch](./firestore_.writebatch.md) - -## writeBatch() function - -Signature: - -```typescript -export function writeBatch(firestore: FirebaseFirestore): WriteBatch; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| firestore | [FirebaseFirestore](./firestore_.firebasefirestore.md) | | - -Returns: - -[WriteBatch](./firestore_.writebatch.md) - diff --git a/docs-exp/firestore_.writebatch.set.md b/docs-exp/firestore_.writebatch.set.md deleted file mode 100644 index 5260d758f10..00000000000 --- a/docs-exp/firestore_.writebatch.set.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [WriteBatch](./firestore_.writebatch.md) > [set](./firestore_.writebatch.set.md) - -## WriteBatch.set() method - -Signature: - -```typescript -set(documentRef: DocumentReference, data: T): WriteBatch; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| documentRef | [DocumentReference](./firestore_.documentreference.md)<T> | | -| data | T | | - -Returns: - -[WriteBatch](./firestore_.writebatch.md) - diff --git a/docs-exp/firestore_.writebatch.set_1.md b/docs-exp/firestore_.writebatch.set_1.md deleted file mode 100644 index e43aa18efb9..00000000000 --- a/docs-exp/firestore_.writebatch.set_1.md +++ /dev/null @@ -1,28 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [WriteBatch](./firestore_.writebatch.md) > [set](./firestore_.writebatch.set_1.md) - -## WriteBatch.set() method - -Signature: - -```typescript -set( - documentRef: DocumentReference, - data: Partial, - options: SetOptions - ): WriteBatch; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| documentRef | [DocumentReference](./firestore_.documentreference.md)<T> | | -| data | Partial<T> | | -| options | [SetOptions](./firestore_.setoptions.md) | | - -Returns: - -[WriteBatch](./firestore_.writebatch.md) - diff --git a/docs-exp/firestore_.writebatch.update.md b/docs-exp/firestore_.writebatch.update.md deleted file mode 100644 index 555e55f3e0b..00000000000 --- a/docs-exp/firestore_.writebatch.update.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [WriteBatch](./firestore_.writebatch.md) > [update](./firestore_.writebatch.update.md) - -## WriteBatch.update() method - -Signature: - -```typescript -update(documentRef: DocumentReference, data: UpdateData): WriteBatch; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| documentRef | [DocumentReference](./firestore_.documentreference.md)<any> | | -| data | [UpdateData](./firestore_.updatedata.md) | | - -Returns: - -[WriteBatch](./firestore_.writebatch.md) - diff --git a/docs-exp/firestore_.writebatch.update_1.md b/docs-exp/firestore_.writebatch.update_1.md deleted file mode 100644 index 19254444539..00000000000 --- a/docs-exp/firestore_.writebatch.update_1.md +++ /dev/null @@ -1,30 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [/](./firestore_.md) > [WriteBatch](./firestore_.writebatch.md) > [update](./firestore_.writebatch.update_1.md) - -## WriteBatch.update() method - -Signature: - -```typescript -update( - documentRef: DocumentReference, - field: string | FieldPath, - value: any, - ...moreFieldsAndValues: any[] - ): WriteBatch; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| documentRef | [DocumentReference](./firestore_.documentreference.md)<any> | | -| field | string \| [FieldPath](./firestore_.fieldpath.md) | | -| value | any | | -| moreFieldsAndValues | any\[\] | | - -Returns: - -[WriteBatch](./firestore_.writebatch.md) - diff --git a/docs-exp/firestore_lite.adddoc.md b/docs-exp/firestore_lite.adddoc.md deleted file mode 100644 index 338b9bf1923..00000000000 --- a/docs-exp/firestore_lite.adddoc.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [addDoc](./firestore_lite.adddoc.md) - -## addDoc() function - -Signature: - -```typescript -export function addDoc( - reference: CollectionReference, - data: T -): Promise>; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| reference | [CollectionReference](./firestore_lite.collectionreference.md)<T> | | -| data | T | | - -Returns: - -Promise<[DocumentReference](./firestore_lite.documentreference.md)<T>> - diff --git a/docs-exp/firestore_lite.arrayremove.md b/docs-exp/firestore_lite.arrayremove.md deleted file mode 100644 index 187fa601e38..00000000000 --- a/docs-exp/firestore_lite.arrayremove.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [arrayRemove](./firestore_lite.arrayremove.md) - -## arrayRemove() function - -Signature: - -```typescript -export function arrayRemove(...elements: any[]): FieldValue; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| elements | any\[\] | | - -Returns: - -[FieldValue](./firestore_lite.fieldvalue.md) - diff --git a/docs-exp/firestore_lite.arrayunion.md b/docs-exp/firestore_lite.arrayunion.md deleted file mode 100644 index cb4d51e4b0d..00000000000 --- a/docs-exp/firestore_lite.arrayunion.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [arrayUnion](./firestore_lite.arrayunion.md) - -## arrayUnion() function - -Signature: - -```typescript -export function arrayUnion(...elements: any[]): FieldValue; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| elements | any\[\] | | - -Returns: - -[FieldValue](./firestore_lite.fieldvalue.md) - diff --git a/docs-exp/firestore_lite.bytes.frombase64string.md b/docs-exp/firestore_lite.bytes.frombase64string.md deleted file mode 100644 index 988ede145f1..00000000000 --- a/docs-exp/firestore_lite.bytes.frombase64string.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Bytes](./firestore_lite.bytes.md) > [fromBase64String](./firestore_lite.bytes.frombase64string.md) - -## Bytes.fromBase64String() method - -Signature: - -```typescript -static fromBase64String(base64: string): Blob; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| base64 | string | | - -Returns: - -Blob - diff --git a/docs-exp/firestore_lite.bytes.fromuint8array.md b/docs-exp/firestore_lite.bytes.fromuint8array.md deleted file mode 100644 index c8959c945d5..00000000000 --- a/docs-exp/firestore_lite.bytes.fromuint8array.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Bytes](./firestore_lite.bytes.md) > [fromUint8Array](./firestore_lite.bytes.fromuint8array.md) - -## Bytes.fromUint8Array() method - -Signature: - -```typescript -static fromUint8Array(array: Uint8Array): Blob; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| array | Uint8Array | | - -Returns: - -Blob - diff --git a/docs-exp/firestore_lite.bytes.isequal.md b/docs-exp/firestore_lite.bytes.isequal.md deleted file mode 100644 index 213dd3c0f7f..00000000000 --- a/docs-exp/firestore_lite.bytes.isequal.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Bytes](./firestore_lite.bytes.md) > [isEqual](./firestore_lite.bytes.isequal.md) - -## Bytes.isEqual() method - -Signature: - -```typescript -isEqual(other: Blob): boolean; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| other | Blob | | - -Returns: - -boolean - diff --git a/docs-exp/firestore_lite.bytes.md b/docs-exp/firestore_lite.bytes.md deleted file mode 100644 index 4ab525c588f..00000000000 --- a/docs-exp/firestore_lite.bytes.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Bytes](./firestore_lite.bytes.md) - -## Bytes class - -Signature: - -```typescript -export class Bytes -``` - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [fromBase64String(base64)](./firestore_lite.bytes.frombase64string.md) | static | | -| [fromUint8Array(array)](./firestore_lite.bytes.fromuint8array.md) | static | | -| [isEqual(other)](./firestore_lite.bytes.isequal.md) | | | -| [toBase64()](./firestore_lite.bytes.tobase64.md) | | | -| [toUint8Array()](./firestore_lite.bytes.touint8array.md) | | | - diff --git a/docs-exp/firestore_lite.bytes.tobase64.md b/docs-exp/firestore_lite.bytes.tobase64.md deleted file mode 100644 index 861b73fe68f..00000000000 --- a/docs-exp/firestore_lite.bytes.tobase64.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Bytes](./firestore_lite.bytes.md) > [toBase64](./firestore_lite.bytes.tobase64.md) - -## Bytes.toBase64() method - -Signature: - -```typescript -toBase64(): string; -``` -Returns: - -string - diff --git a/docs-exp/firestore_lite.bytes.touint8array.md b/docs-exp/firestore_lite.bytes.touint8array.md deleted file mode 100644 index 0d14cee1cff..00000000000 --- a/docs-exp/firestore_lite.bytes.touint8array.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Bytes](./firestore_lite.bytes.md) > [toUint8Array](./firestore_lite.bytes.touint8array.md) - -## Bytes.toUint8Array() method - -Signature: - -```typescript -toUint8Array(): Uint8Array; -``` -Returns: - -Uint8Array - diff --git a/docs-exp/firestore_lite.collection.md b/docs-exp/firestore_lite.collection.md deleted file mode 100644 index d482c080904..00000000000 --- a/docs-exp/firestore_lite.collection.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [collection](./firestore_lite.collection.md) - -## collection() function - -Signature: - -```typescript -export function collection( - firestore: FirebaseFirestore, - collectionPath: string -): CollectionReference; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| firestore | [FirebaseFirestore](./firestore_lite.firebasefirestore.md) | | -| collectionPath | string | | - -Returns: - -[CollectionReference](./firestore_lite.collectionreference.md)<[DocumentData](./firestore_lite.documentdata.md)> - diff --git a/docs-exp/firestore_lite.collection_1.md b/docs-exp/firestore_lite.collection_1.md deleted file mode 100644 index 29911512f75..00000000000 --- a/docs-exp/firestore_lite.collection_1.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [collection](./firestore_lite.collection_1.md) - -## collection() function - -Signature: - -```typescript -export function collection( - reference: CollectionReference, - collectionPath: string -): CollectionReference; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| reference | [CollectionReference](./firestore_lite.collectionreference.md)<unknown> | | -| collectionPath | string | | - -Returns: - -[CollectionReference](./firestore_lite.collectionreference.md)<[DocumentData](./firestore_lite.documentdata.md)> - diff --git a/docs-exp/firestore_lite.collection_2.md b/docs-exp/firestore_lite.collection_2.md deleted file mode 100644 index fd5a2cee1e3..00000000000 --- a/docs-exp/firestore_lite.collection_2.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [collection](./firestore_lite.collection_2.md) - -## collection() function - -Signature: - -```typescript -export function collection( - reference: DocumentReference, - collectionPath: string -): CollectionReference; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| reference | [DocumentReference](./firestore_lite.documentreference.md) | | -| collectionPath | string | | - -Returns: - -[CollectionReference](./firestore_lite.collectionreference.md)<[DocumentData](./firestore_lite.documentdata.md)> - diff --git a/docs-exp/firestore_lite.collectiongroup.md b/docs-exp/firestore_lite.collectiongroup.md deleted file mode 100644 index ab505960d06..00000000000 --- a/docs-exp/firestore_lite.collectiongroup.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [collectionGroup](./firestore_lite.collectiongroup.md) - -## collectionGroup() function - -Signature: - -```typescript -export function collectionGroup( - firestore: FirebaseFirestore, - collectionId: string -): Query; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| firestore | [FirebaseFirestore](./firestore_lite.firebasefirestore.md) | | -| collectionId | string | | - -Returns: - -[Query](./firestore_lite.query.md)<[DocumentData](./firestore_lite.documentdata.md)> - diff --git a/docs-exp/firestore_lite.collectionreference.doc.md b/docs-exp/firestore_lite.collectionreference.doc.md deleted file mode 100644 index 277c4ba35fd..00000000000 --- a/docs-exp/firestore_lite.collectionreference.doc.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [CollectionReference](./firestore_lite.collectionreference.md) > [doc](./firestore_lite.collectionreference.doc.md) - -## CollectionReference.doc() method - -Signature: - -```typescript -doc(documentPath?: string): DocumentReference; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| documentPath | string | | - -Returns: - -[DocumentReference](./firestore_lite.documentreference.md)<T> - diff --git a/docs-exp/firestore_lite.collectionreference.id.md b/docs-exp/firestore_lite.collectionreference.id.md deleted file mode 100644 index c8f4acd3547..00000000000 --- a/docs-exp/firestore_lite.collectionreference.id.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [CollectionReference](./firestore_lite.collectionreference.md) > [id](./firestore_lite.collectionreference.id.md) - -## CollectionReference.id property - -Signature: - -```typescript -readonly id: string; -``` diff --git a/docs-exp/firestore_lite.collectionreference.md b/docs-exp/firestore_lite.collectionreference.md deleted file mode 100644 index 77c5bf1c68f..00000000000 --- a/docs-exp/firestore_lite.collectionreference.md +++ /dev/null @@ -1,29 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [CollectionReference](./firestore_lite.collectionreference.md) - -## CollectionReference class - -Signature: - -```typescript -export class CollectionReference extends Query -``` -Extends: [Query](./firestore_lite.query.md)<T> - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [id](./firestore_lite.collectionreference.id.md) | | string | | -| [parent](./firestore_lite.collectionreference.parent.md) | | [DocumentReference](./firestore_lite.documentreference.md)<[DocumentData](./firestore_lite.documentdata.md)> \| null | | -| [path](./firestore_lite.collectionreference.path.md) | | string | | -| [type](./firestore_lite.collectionreference.type.md) | | 'collection' | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [doc(documentPath)](./firestore_lite.collectionreference.doc.md) | | | -| [withConverter(converter)](./firestore_lite.collectionreference.withconverter.md) | | | - diff --git a/docs-exp/firestore_lite.collectionreference.parent.md b/docs-exp/firestore_lite.collectionreference.parent.md deleted file mode 100644 index 88acac4bb5d..00000000000 --- a/docs-exp/firestore_lite.collectionreference.parent.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [CollectionReference](./firestore_lite.collectionreference.md) > [parent](./firestore_lite.collectionreference.parent.md) - -## CollectionReference.parent property - -Signature: - -```typescript -get parent(): DocumentReference | null; -``` diff --git a/docs-exp/firestore_lite.collectionreference.path.md b/docs-exp/firestore_lite.collectionreference.path.md deleted file mode 100644 index db6946264b5..00000000000 --- a/docs-exp/firestore_lite.collectionreference.path.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [CollectionReference](./firestore_lite.collectionreference.md) > [path](./firestore_lite.collectionreference.path.md) - -## CollectionReference.path property - -Signature: - -```typescript -readonly path: string; -``` diff --git a/docs-exp/firestore_lite.collectionreference.type.md b/docs-exp/firestore_lite.collectionreference.type.md deleted file mode 100644 index 9f6e1a5f7ab..00000000000 --- a/docs-exp/firestore_lite.collectionreference.type.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [CollectionReference](./firestore_lite.collectionreference.md) > [type](./firestore_lite.collectionreference.type.md) - -## CollectionReference.type property - -Signature: - -```typescript -readonly type: 'collection'; -``` diff --git a/docs-exp/firestore_lite.collectionreference.withconverter.md b/docs-exp/firestore_lite.collectionreference.withconverter.md deleted file mode 100644 index 0eb067e2b98..00000000000 --- a/docs-exp/firestore_lite.collectionreference.withconverter.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [CollectionReference](./firestore_lite.collectionreference.md) > [withConverter](./firestore_lite.collectionreference.withconverter.md) - -## CollectionReference.withConverter() method - -Signature: - -```typescript -withConverter( - converter: FirestoreDataConverter - ): CollectionReference; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| converter | [FirestoreDataConverter](./firestore_lite.firestoredataconverter.md)<U> | | - -Returns: - -[CollectionReference](./firestore_lite.collectionreference.md)<U> - diff --git a/docs-exp/firestore_lite.deletedoc.md b/docs-exp/firestore_lite.deletedoc.md deleted file mode 100644 index ccd09b3d8fa..00000000000 --- a/docs-exp/firestore_lite.deletedoc.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [deleteDoc](./firestore_lite.deletedoc.md) - -## deleteDoc() function - -Signature: - -```typescript -export function deleteDoc(reference: DocumentReference): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| reference | [DocumentReference](./firestore_lite.documentreference.md)<unknown> | | - -Returns: - -Promise<void> - diff --git a/docs-exp/firestore_lite.deletefield.md b/docs-exp/firestore_lite.deletefield.md deleted file mode 100644 index cc84bed703f..00000000000 --- a/docs-exp/firestore_lite.deletefield.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [deleteField](./firestore_lite.deletefield.md) - -## deleteField() function - -Signature: - -```typescript -export function deleteField(): FieldValue; -``` -Returns: - -[FieldValue](./firestore_lite.fieldvalue.md) - diff --git a/docs-exp/firestore_lite.doc.md b/docs-exp/firestore_lite.doc.md deleted file mode 100644 index a53b7377a7b..00000000000 --- a/docs-exp/firestore_lite.doc.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [doc](./firestore_lite.doc.md) - -## doc() function - -Signature: - -```typescript -export function doc( - firestore: FirebaseFirestore, - documentPath: string -): DocumentReference; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| firestore | [FirebaseFirestore](./firestore_lite.firebasefirestore.md) | | -| documentPath | string | | - -Returns: - -[DocumentReference](./firestore_lite.documentreference.md)<[DocumentData](./firestore_lite.documentdata.md)> - diff --git a/docs-exp/firestore_lite.doc_1.md b/docs-exp/firestore_lite.doc_1.md deleted file mode 100644 index 2dc484f0aee..00000000000 --- a/docs-exp/firestore_lite.doc_1.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [doc](./firestore_lite.doc_1.md) - -## doc() function - -Signature: - -```typescript -export function doc( - reference: CollectionReference, - documentPath?: string -): DocumentReference; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| reference | [CollectionReference](./firestore_lite.collectionreference.md)<T> | | -| documentPath | string | | - -Returns: - -[DocumentReference](./firestore_lite.documentreference.md)<T> - diff --git a/docs-exp/firestore_lite.doc_2.md b/docs-exp/firestore_lite.doc_2.md deleted file mode 100644 index 8da69a9c466..00000000000 --- a/docs-exp/firestore_lite.doc_2.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [doc](./firestore_lite.doc_2.md) - -## doc() function - -Signature: - -```typescript -export function doc( - reference: DocumentReference, - documentPath: string -): DocumentReference; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| reference | [DocumentReference](./firestore_lite.documentreference.md)<unknown> | | -| documentPath | string | | - -Returns: - -[DocumentReference](./firestore_lite.documentreference.md)<[DocumentData](./firestore_lite.documentdata.md)> - diff --git a/docs-exp/firestore_lite.documentdata.md b/docs-exp/firestore_lite.documentdata.md deleted file mode 100644 index f614853651a..00000000000 --- a/docs-exp/firestore_lite.documentdata.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [DocumentData](./firestore_lite.documentdata.md) - -## DocumentData interface - -Signature: - -```typescript -export interface DocumentData -``` diff --git a/docs-exp/firestore_lite.documentid.md b/docs-exp/firestore_lite.documentid.md deleted file mode 100644 index 901efc3f2bd..00000000000 --- a/docs-exp/firestore_lite.documentid.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [documentId](./firestore_lite.documentid.md) - -## documentId() function - -Signature: - -```typescript -export function documentId(): FieldPath; -``` -Returns: - -[FieldPath](./firestore_lite.fieldpath.md) - diff --git a/docs-exp/firestore_lite.documentreference.collection.md b/docs-exp/firestore_lite.documentreference.collection.md deleted file mode 100644 index 9bbc06863e5..00000000000 --- a/docs-exp/firestore_lite.documentreference.collection.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [DocumentReference](./firestore_lite.documentreference.md) > [collection](./firestore_lite.documentreference.collection.md) - -## DocumentReference.collection() method - -Signature: - -```typescript -collection(collectionPath: string): CollectionReference; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| collectionPath | string | | - -Returns: - -[CollectionReference](./firestore_lite.collectionreference.md)<[DocumentData](./firestore_lite.documentdata.md)> - diff --git a/docs-exp/firestore_lite.documentreference.converter.md b/docs-exp/firestore_lite.documentreference.converter.md deleted file mode 100644 index 3ec794b37a5..00000000000 --- a/docs-exp/firestore_lite.documentreference.converter.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [DocumentReference](./firestore_lite.documentreference.md) > [converter](./firestore_lite.documentreference.converter.md) - -## DocumentReference.converter property - -Signature: - -```typescript -readonly converter: FirestoreDataConverter | null; -``` diff --git a/docs-exp/firestore_lite.documentreference.firestore.md b/docs-exp/firestore_lite.documentreference.firestore.md deleted file mode 100644 index 7b72685386d..00000000000 --- a/docs-exp/firestore_lite.documentreference.firestore.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [DocumentReference](./firestore_lite.documentreference.md) > [firestore](./firestore_lite.documentreference.firestore.md) - -## DocumentReference.firestore property - -Signature: - -```typescript -readonly firestore: FirebaseFirestore; -``` diff --git a/docs-exp/firestore_lite.documentreference.id.md b/docs-exp/firestore_lite.documentreference.id.md deleted file mode 100644 index 256aa7f6c22..00000000000 --- a/docs-exp/firestore_lite.documentreference.id.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [DocumentReference](./firestore_lite.documentreference.md) > [id](./firestore_lite.documentreference.id.md) - -## DocumentReference.id property - -Signature: - -```typescript -readonly id: string; -``` diff --git a/docs-exp/firestore_lite.documentreference.md b/docs-exp/firestore_lite.documentreference.md deleted file mode 100644 index 41a407e5c8a..00000000000 --- a/docs-exp/firestore_lite.documentreference.md +++ /dev/null @@ -1,30 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [DocumentReference](./firestore_lite.documentreference.md) - -## DocumentReference class - -Signature: - -```typescript -export class DocumentReference -``` - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [converter](./firestore_lite.documentreference.converter.md) | | [FirestoreDataConverter](./firestore_lite.firestoredataconverter.md)<T> \| null | | -| [firestore](./firestore_lite.documentreference.firestore.md) | | [FirebaseFirestore](./firestore_lite.firebasefirestore.md) | | -| [id](./firestore_lite.documentreference.id.md) | | string | | -| [parent](./firestore_lite.documentreference.parent.md) | | [CollectionReference](./firestore_lite.collectionreference.md)<T> | | -| [path](./firestore_lite.documentreference.path.md) | | string | | -| [type](./firestore_lite.documentreference.type.md) | | 'document' | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [collection(collectionPath)](./firestore_lite.documentreference.collection.md) | | | -| [withConverter(converter)](./firestore_lite.documentreference.withconverter.md) | | | - diff --git a/docs-exp/firestore_lite.documentreference.parent.md b/docs-exp/firestore_lite.documentreference.parent.md deleted file mode 100644 index 8d85dbc7824..00000000000 --- a/docs-exp/firestore_lite.documentreference.parent.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [DocumentReference](./firestore_lite.documentreference.md) > [parent](./firestore_lite.documentreference.parent.md) - -## DocumentReference.parent property - -Signature: - -```typescript -get parent(): CollectionReference; -``` diff --git a/docs-exp/firestore_lite.documentreference.path.md b/docs-exp/firestore_lite.documentreference.path.md deleted file mode 100644 index b90129f7a21..00000000000 --- a/docs-exp/firestore_lite.documentreference.path.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [DocumentReference](./firestore_lite.documentreference.md) > [path](./firestore_lite.documentreference.path.md) - -## DocumentReference.path property - -Signature: - -```typescript -readonly path: string; -``` diff --git a/docs-exp/firestore_lite.documentreference.type.md b/docs-exp/firestore_lite.documentreference.type.md deleted file mode 100644 index 308858cfbb5..00000000000 --- a/docs-exp/firestore_lite.documentreference.type.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [DocumentReference](./firestore_lite.documentreference.md) > [type](./firestore_lite.documentreference.type.md) - -## DocumentReference.type property - -Signature: - -```typescript -readonly type: 'document'; -``` diff --git a/docs-exp/firestore_lite.documentreference.withconverter.md b/docs-exp/firestore_lite.documentreference.withconverter.md deleted file mode 100644 index b0b7db17ab1..00000000000 --- a/docs-exp/firestore_lite.documentreference.withconverter.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [DocumentReference](./firestore_lite.documentreference.md) > [withConverter](./firestore_lite.documentreference.withconverter.md) - -## DocumentReference.withConverter() method - -Signature: - -```typescript -withConverter(converter: FirestoreDataConverter): DocumentReference; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| converter | [FirestoreDataConverter](./firestore_lite.firestoredataconverter.md)<U> | | - -Returns: - -[DocumentReference](./firestore_lite.documentreference.md)<U> - diff --git a/docs-exp/firestore_lite.documentsnapshot.data.md b/docs-exp/firestore_lite.documentsnapshot.data.md deleted file mode 100644 index 541a77196ae..00000000000 --- a/docs-exp/firestore_lite.documentsnapshot.data.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [DocumentSnapshot](./firestore_lite.documentsnapshot.md) > [data](./firestore_lite.documentsnapshot.data.md) - -## DocumentSnapshot.data() method - -Signature: - -```typescript -data(): T | undefined; -``` -Returns: - -T \| undefined - diff --git a/docs-exp/firestore_lite.documentsnapshot.exists.md b/docs-exp/firestore_lite.documentsnapshot.exists.md deleted file mode 100644 index a2ecc70c4c8..00000000000 --- a/docs-exp/firestore_lite.documentsnapshot.exists.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [DocumentSnapshot](./firestore_lite.documentsnapshot.md) > [exists](./firestore_lite.documentsnapshot.exists.md) - -## DocumentSnapshot.exists() method - -Signature: - -```typescript -exists(): this is QueryDocumentSnapshot; -``` -Returns: - -this is [QueryDocumentSnapshot](./firestore_lite.querydocumentsnapshot.md)<T> - diff --git a/docs-exp/firestore_lite.documentsnapshot.get.md b/docs-exp/firestore_lite.documentsnapshot.get.md deleted file mode 100644 index a8d913a9d8b..00000000000 --- a/docs-exp/firestore_lite.documentsnapshot.get.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [DocumentSnapshot](./firestore_lite.documentsnapshot.md) > [get](./firestore_lite.documentsnapshot.get.md) - -## DocumentSnapshot.get() method - -Signature: - -```typescript -get(fieldPath: string | FieldPath): any; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| fieldPath | string \| [FieldPath](./firestore_lite.fieldpath.md) | | - -Returns: - -any - diff --git a/docs-exp/firestore_lite.documentsnapshot.id.md b/docs-exp/firestore_lite.documentsnapshot.id.md deleted file mode 100644 index a56a4b2663e..00000000000 --- a/docs-exp/firestore_lite.documentsnapshot.id.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [DocumentSnapshot](./firestore_lite.documentsnapshot.md) > [id](./firestore_lite.documentsnapshot.id.md) - -## DocumentSnapshot.id property - -Signature: - -```typescript -readonly id: string; -``` diff --git a/docs-exp/firestore_lite.documentsnapshot.md b/docs-exp/firestore_lite.documentsnapshot.md deleted file mode 100644 index e29023c42d9..00000000000 --- a/docs-exp/firestore_lite.documentsnapshot.md +++ /dev/null @@ -1,27 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [DocumentSnapshot](./firestore_lite.documentsnapshot.md) - -## DocumentSnapshot class - -Signature: - -```typescript -export class DocumentSnapshot -``` - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [id](./firestore_lite.documentsnapshot.id.md) | | string | | -| [ref](./firestore_lite.documentsnapshot.ref.md) | | [DocumentReference](./firestore_lite.documentreference.md)<T> | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [data()](./firestore_lite.documentsnapshot.data.md) | | | -| [exists()](./firestore_lite.documentsnapshot.exists.md) | | | -| [get(fieldPath)](./firestore_lite.documentsnapshot.get.md) | | | - diff --git a/docs-exp/firestore_lite.documentsnapshot.ref.md b/docs-exp/firestore_lite.documentsnapshot.ref.md deleted file mode 100644 index b45bc401fb4..00000000000 --- a/docs-exp/firestore_lite.documentsnapshot.ref.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [DocumentSnapshot](./firestore_lite.documentsnapshot.md) > [ref](./firestore_lite.documentsnapshot.ref.md) - -## DocumentSnapshot.ref property - -Signature: - -```typescript -readonly ref: DocumentReference; -``` diff --git a/docs-exp/firestore_lite.endat.md b/docs-exp/firestore_lite.endat.md deleted file mode 100644 index 446237ea57f..00000000000 --- a/docs-exp/firestore_lite.endat.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [endAt](./firestore_lite.endat.md) - -## endAt() function - -Signature: - -```typescript -export function endAt(snapshot: DocumentSnapshot): QueryConstraint; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| snapshot | [DocumentSnapshot](./firestore_lite.documentsnapshot.md)<any> | | - -Returns: - -[QueryConstraint](./firestore_lite.queryconstraint.md) - diff --git a/docs-exp/firestore_lite.endat_1.md b/docs-exp/firestore_lite.endat_1.md deleted file mode 100644 index d23987b1779..00000000000 --- a/docs-exp/firestore_lite.endat_1.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [endAt](./firestore_lite.endat_1.md) - -## endAt() function - -Signature: - -```typescript -export function endAt(...fieldValues: any[]): QueryConstraint; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| fieldValues | any\[\] | | - -Returns: - -[QueryConstraint](./firestore_lite.queryconstraint.md) - diff --git a/docs-exp/firestore_lite.endbefore.md b/docs-exp/firestore_lite.endbefore.md deleted file mode 100644 index f1bd7693ce4..00000000000 --- a/docs-exp/firestore_lite.endbefore.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [endBefore](./firestore_lite.endbefore.md) - -## endBefore() function - -Signature: - -```typescript -export function endBefore(snapshot: DocumentSnapshot): QueryConstraint; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| snapshot | [DocumentSnapshot](./firestore_lite.documentsnapshot.md)<any> | | - -Returns: - -[QueryConstraint](./firestore_lite.queryconstraint.md) - diff --git a/docs-exp/firestore_lite.endbefore_1.md b/docs-exp/firestore_lite.endbefore_1.md deleted file mode 100644 index 5ff3fca615e..00000000000 --- a/docs-exp/firestore_lite.endbefore_1.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [endBefore](./firestore_lite.endbefore_1.md) - -## endBefore() function - -Signature: - -```typescript -export function endBefore(...fieldValues: any[]): QueryConstraint; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| fieldValues | any\[\] | | - -Returns: - -[QueryConstraint](./firestore_lite.queryconstraint.md) - diff --git a/docs-exp/firestore_lite.fieldpath._constructor_.md b/docs-exp/firestore_lite.fieldpath._constructor_.md deleted file mode 100644 index aee1913d910..00000000000 --- a/docs-exp/firestore_lite.fieldpath._constructor_.md +++ /dev/null @@ -1,18 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [FieldPath](./firestore_lite.fieldpath.md) > [(constructor)](./firestore_lite.fieldpath._constructor_.md) - -## FieldPath.(constructor) - -Signature: - -```typescript -constructor(...fieldNames: string[]); -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| fieldNames | string\[\] | | - diff --git a/docs-exp/firestore_lite.fieldpath.isequal.md b/docs-exp/firestore_lite.fieldpath.isequal.md deleted file mode 100644 index f551cc86ed0..00000000000 --- a/docs-exp/firestore_lite.fieldpath.isequal.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [FieldPath](./firestore_lite.fieldpath.md) > [isEqual](./firestore_lite.fieldpath.isequal.md) - -## FieldPath.isEqual() method - -Signature: - -```typescript -isEqual(other: FieldPath): boolean; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| other | [FieldPath](./firestore_lite.fieldpath.md) | | - -Returns: - -boolean - diff --git a/docs-exp/firestore_lite.fieldpath.md b/docs-exp/firestore_lite.fieldpath.md deleted file mode 100644 index 91c6ec29fa2..00000000000 --- a/docs-exp/firestore_lite.fieldpath.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [FieldPath](./firestore_lite.fieldpath.md) - -## FieldPath class - -Signature: - -```typescript -export class FieldPath -``` - -## Constructors - -| Constructor | Modifiers | Description | -| --- | --- | --- | -| [(constructor)(fieldNames)](./firestore_lite.fieldpath._constructor_.md) | | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [isEqual(other)](./firestore_lite.fieldpath.isequal.md) | | | - diff --git a/docs-exp/firestore_lite.fieldvalue.isequal.md b/docs-exp/firestore_lite.fieldvalue.isequal.md deleted file mode 100644 index 71437eec584..00000000000 --- a/docs-exp/firestore_lite.fieldvalue.isequal.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [FieldValue](./firestore_lite.fieldvalue.md) > [isEqual](./firestore_lite.fieldvalue.isequal.md) - -## FieldValue.isEqual() method - -Signature: - -```typescript -isEqual(other: FieldValue): boolean; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| other | [FieldValue](./firestore_lite.fieldvalue.md) | | - -Returns: - -boolean - diff --git a/docs-exp/firestore_lite.fieldvalue.md b/docs-exp/firestore_lite.fieldvalue.md deleted file mode 100644 index 91264128a0a..00000000000 --- a/docs-exp/firestore_lite.fieldvalue.md +++ /dev/null @@ -1,18 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [FieldValue](./firestore_lite.fieldvalue.md) - -## FieldValue class - -Signature: - -```typescript -export class FieldValue -``` - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [isEqual(other)](./firestore_lite.fieldvalue.isequal.md) | | | - diff --git a/docs-exp/firestore_lite.firebasefirestore.app.md b/docs-exp/firestore_lite.firebasefirestore.app.md deleted file mode 100644 index bf1cfd23bad..00000000000 --- a/docs-exp/firestore_lite.firebasefirestore.app.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [FirebaseFirestore](./firestore_lite.firebasefirestore.md) > [app](./firestore_lite.firebasefirestore.app.md) - -## FirebaseFirestore.app property - -Signature: - -```typescript -readonly app: FirebaseApp; -``` diff --git a/docs-exp/firestore_lite.firebasefirestore.md b/docs-exp/firestore_lite.firebasefirestore.md deleted file mode 100644 index 340f20b6f5d..00000000000 --- a/docs-exp/firestore_lite.firebasefirestore.md +++ /dev/null @@ -1,18 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [FirebaseFirestore](./firestore_lite.firebasefirestore.md) - -## FirebaseFirestore class - -Signature: - -```typescript -export class FirebaseFirestore -``` - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [app](./firestore_lite.firebasefirestore.app.md) | | [FirebaseApp](./app-types.firebaseapp.md) | | - diff --git a/docs-exp/firestore_lite.firestoredataconverter.fromfirestore.md b/docs-exp/firestore_lite.firestoredataconverter.fromfirestore.md deleted file mode 100644 index 27f93909240..00000000000 --- a/docs-exp/firestore_lite.firestoredataconverter.fromfirestore.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [FirestoreDataConverter](./firestore_lite.firestoredataconverter.md) > [fromFirestore](./firestore_lite.firestoredataconverter.fromfirestore.md) - -## FirestoreDataConverter.fromFirestore() method - -Signature: - -```typescript -fromFirestore(snapshot: QueryDocumentSnapshot): T; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| snapshot | [QueryDocumentSnapshot](./firestore_lite.querydocumentsnapshot.md)<[DocumentData](./firestore_lite.documentdata.md)> | | - -Returns: - -T - diff --git a/docs-exp/firestore_lite.firestoredataconverter.md b/docs-exp/firestore_lite.firestoredataconverter.md deleted file mode 100644 index 7a9d0b0f62e..00000000000 --- a/docs-exp/firestore_lite.firestoredataconverter.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [FirestoreDataConverter](./firestore_lite.firestoredataconverter.md) - -## FirestoreDataConverter interface - -Signature: - -```typescript -export interface FirestoreDataConverter -``` - -## Methods - -| Method | Description | -| --- | --- | -| [fromFirestore(snapshot)](./firestore_lite.firestoredataconverter.fromfirestore.md) | | -| [toFirestore(modelObject)](./firestore_lite.firestoredataconverter.tofirestore.md) | | -| [toFirestore(modelObject, options)](./firestore_lite.firestoredataconverter.tofirestore_1.md) | | - diff --git a/docs-exp/firestore_lite.firestoredataconverter.tofirestore.md b/docs-exp/firestore_lite.firestoredataconverter.tofirestore.md deleted file mode 100644 index 2859e49953a..00000000000 --- a/docs-exp/firestore_lite.firestoredataconverter.tofirestore.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [FirestoreDataConverter](./firestore_lite.firestoredataconverter.md) > [toFirestore](./firestore_lite.firestoredataconverter.tofirestore.md) - -## FirestoreDataConverter.toFirestore() method - -Signature: - -```typescript -toFirestore(modelObject: T): DocumentData; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| modelObject | T | | - -Returns: - -[DocumentData](./firestore_lite.documentdata.md) - diff --git a/docs-exp/firestore_lite.firestoredataconverter.tofirestore_1.md b/docs-exp/firestore_lite.firestoredataconverter.tofirestore_1.md deleted file mode 100644 index f1b72f42c3d..00000000000 --- a/docs-exp/firestore_lite.firestoredataconverter.tofirestore_1.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [FirestoreDataConverter](./firestore_lite.firestoredataconverter.md) > [toFirestore](./firestore_lite.firestoredataconverter.tofirestore_1.md) - -## FirestoreDataConverter.toFirestore() method - -Signature: - -```typescript -toFirestore(modelObject: Partial, options: SetOptions): DocumentData; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| modelObject | Partial<T> | | -| options | [SetOptions](./firestore_lite.setoptions.md) | | - -Returns: - -[DocumentData](./firestore_lite.documentdata.md) - diff --git a/docs-exp/firestore_lite.firestoreerror.code.md b/docs-exp/firestore_lite.firestoreerror.code.md deleted file mode 100644 index 9ace2634770..00000000000 --- a/docs-exp/firestore_lite.firestoreerror.code.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [FirestoreError](./firestore_lite.firestoreerror.md) > [code](./firestore_lite.firestoreerror.code.md) - -## FirestoreError.code property - -Signature: - -```typescript -code: FirestoreErrorCode; -``` diff --git a/docs-exp/firestore_lite.firestoreerror.md b/docs-exp/firestore_lite.firestoreerror.md deleted file mode 100644 index c15800cd740..00000000000 --- a/docs-exp/firestore_lite.firestoreerror.md +++ /dev/null @@ -1,21 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [FirestoreError](./firestore_lite.firestoreerror.md) - -## FirestoreError interface - -Signature: - -```typescript -export interface FirestoreError -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [code](./firestore_lite.firestoreerror.code.md) | [FirestoreErrorCode](./firestore_lite.firestoreerrorcode.md) | | -| [message](./firestore_lite.firestoreerror.message.md) | string | | -| [name](./firestore_lite.firestoreerror.name.md) | string | | -| [stack](./firestore_lite.firestoreerror.stack.md) | string | | - diff --git a/docs-exp/firestore_lite.firestoreerror.message.md b/docs-exp/firestore_lite.firestoreerror.message.md deleted file mode 100644 index 713c896147e..00000000000 --- a/docs-exp/firestore_lite.firestoreerror.message.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [FirestoreError](./firestore_lite.firestoreerror.md) > [message](./firestore_lite.firestoreerror.message.md) - -## FirestoreError.message property - -Signature: - -```typescript -message: string; -``` diff --git a/docs-exp/firestore_lite.firestoreerror.name.md b/docs-exp/firestore_lite.firestoreerror.name.md deleted file mode 100644 index d9a6b1a9dde..00000000000 --- a/docs-exp/firestore_lite.firestoreerror.name.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [FirestoreError](./firestore_lite.firestoreerror.md) > [name](./firestore_lite.firestoreerror.name.md) - -## FirestoreError.name property - -Signature: - -```typescript -name: string; -``` diff --git a/docs-exp/firestore_lite.firestoreerror.stack.md b/docs-exp/firestore_lite.firestoreerror.stack.md deleted file mode 100644 index 3d5e9883319..00000000000 --- a/docs-exp/firestore_lite.firestoreerror.stack.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [FirestoreError](./firestore_lite.firestoreerror.md) > [stack](./firestore_lite.firestoreerror.stack.md) - -## FirestoreError.stack property - -Signature: - -```typescript -stack?: string; -``` diff --git a/docs-exp/firestore_lite.firestoreerrorcode.md b/docs-exp/firestore_lite.firestoreerrorcode.md deleted file mode 100644 index 8aff08b777b..00000000000 --- a/docs-exp/firestore_lite.firestoreerrorcode.md +++ /dev/null @@ -1,27 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [FirestoreErrorCode](./firestore_lite.firestoreerrorcode.md) - -## FirestoreErrorCode type - -Signature: - -```typescript -export type FirestoreErrorCode = - | 'cancelled' - | 'unknown' - | 'invalid-argument' - | 'deadline-exceeded' - | 'not-found' - | 'already-exists' - | 'permission-denied' - | 'resource-exhausted' - | 'failed-precondition' - | 'aborted' - | 'out-of-range' - | 'unimplemented' - | 'internal' - | 'unavailable' - | 'data-loss' - | 'unauthenticated'; -``` diff --git a/docs-exp/firestore_lite.geopoint._constructor_.md b/docs-exp/firestore_lite.geopoint._constructor_.md deleted file mode 100644 index 28d61d1288c..00000000000 --- a/docs-exp/firestore_lite.geopoint._constructor_.md +++ /dev/null @@ -1,19 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [GeoPoint](./firestore_lite.geopoint.md) > [(constructor)](./firestore_lite.geopoint._constructor_.md) - -## GeoPoint.(constructor) - -Signature: - -```typescript -constructor(latitude: number, longitude: number); -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| latitude | number | | -| longitude | number | | - diff --git a/docs-exp/firestore_lite.geopoint.isequal.md b/docs-exp/firestore_lite.geopoint.isequal.md deleted file mode 100644 index 86307e96a64..00000000000 --- a/docs-exp/firestore_lite.geopoint.isequal.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [GeoPoint](./firestore_lite.geopoint.md) > [isEqual](./firestore_lite.geopoint.isequal.md) - -## GeoPoint.isEqual() method - -Signature: - -```typescript -isEqual(other: GeoPoint): boolean; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| other | [GeoPoint](./firestore_lite.geopoint.md) | | - -Returns: - -boolean - diff --git a/docs-exp/firestore_lite.geopoint.latitude.md b/docs-exp/firestore_lite.geopoint.latitude.md deleted file mode 100644 index dac060d755d..00000000000 --- a/docs-exp/firestore_lite.geopoint.latitude.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [GeoPoint](./firestore_lite.geopoint.md) > [latitude](./firestore_lite.geopoint.latitude.md) - -## GeoPoint.latitude property - -Signature: - -```typescript -readonly latitude: number; -``` diff --git a/docs-exp/firestore_lite.geopoint.longitude.md b/docs-exp/firestore_lite.geopoint.longitude.md deleted file mode 100644 index dcf3493fb77..00000000000 --- a/docs-exp/firestore_lite.geopoint.longitude.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [GeoPoint](./firestore_lite.geopoint.md) > [longitude](./firestore_lite.geopoint.longitude.md) - -## GeoPoint.longitude property - -Signature: - -```typescript -readonly longitude: number; -``` diff --git a/docs-exp/firestore_lite.geopoint.md b/docs-exp/firestore_lite.geopoint.md deleted file mode 100644 index c8f82dfac92..00000000000 --- a/docs-exp/firestore_lite.geopoint.md +++ /dev/null @@ -1,31 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [GeoPoint](./firestore_lite.geopoint.md) - -## GeoPoint class - -Signature: - -```typescript -export class GeoPoint -``` - -## Constructors - -| Constructor | Modifiers | Description | -| --- | --- | --- | -| [(constructor)(latitude, longitude)](./firestore_lite.geopoint._constructor_.md) | | | - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [latitude](./firestore_lite.geopoint.latitude.md) | | number | | -| [longitude](./firestore_lite.geopoint.longitude.md) | | number | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [isEqual(other)](./firestore_lite.geopoint.isequal.md) | | | - diff --git a/docs-exp/firestore_lite.getdoc.md b/docs-exp/firestore_lite.getdoc.md deleted file mode 100644 index 1edbb5ae933..00000000000 --- a/docs-exp/firestore_lite.getdoc.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [getDoc](./firestore_lite.getdoc.md) - -## getDoc() function - -Signature: - -```typescript -export function getDoc( - reference: DocumentReference -): Promise>; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| reference | [DocumentReference](./firestore_lite.documentreference.md)<T> | | - -Returns: - -Promise<[DocumentSnapshot](./firestore_lite.documentsnapshot.md)<T>> - diff --git a/docs-exp/firestore_lite.getdocs.md b/docs-exp/firestore_lite.getdocs.md deleted file mode 100644 index 9e5e870c23d..00000000000 --- a/docs-exp/firestore_lite.getdocs.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [getDocs](./firestore_lite.getdocs.md) - -## getDocs() function - -Signature: - -```typescript -export function getDocs(query: Query): Promise>; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| query | [Query](./firestore_lite.query.md)<T> | | - -Returns: - -Promise<[QuerySnapshot](./firestore_lite.querysnapshot.md)<T>> - diff --git a/docs-exp/firestore_lite.getfirestore.md b/docs-exp/firestore_lite.getfirestore.md deleted file mode 100644 index 9476eb90f05..00000000000 --- a/docs-exp/firestore_lite.getfirestore.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [getFirestore](./firestore_lite.getfirestore.md) - -## getFirestore() function - -Signature: - -```typescript -export function getFirestore(app: FirebaseApp): FirebaseFirestore; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| app | [FirebaseApp](./app-types.firebaseapp.md) | | - -Returns: - -[FirebaseFirestore](./firestore_lite.firebasefirestore.md) - diff --git a/docs-exp/firestore_lite.increment.md b/docs-exp/firestore_lite.increment.md deleted file mode 100644 index d6f9a93f0d3..00000000000 --- a/docs-exp/firestore_lite.increment.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [increment](./firestore_lite.increment.md) - -## increment() function - -Signature: - -```typescript -export function increment(n: number): FieldValue; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| n | number | | - -Returns: - -[FieldValue](./firestore_lite.fieldvalue.md) - diff --git a/docs-exp/firestore_lite.initializefirestore.md b/docs-exp/firestore_lite.initializefirestore.md deleted file mode 100644 index 927785c3077..00000000000 --- a/docs-exp/firestore_lite.initializefirestore.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [initializeFirestore](./firestore_lite.initializefirestore.md) - -## initializeFirestore() function - -Signature: - -```typescript -export function initializeFirestore( - app: FirebaseApp, - settings: Settings -): FirebaseFirestore; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| app | [FirebaseApp](./app-types.firebaseapp.md) | | -| settings | [Settings](./firestore_lite.settings.md) | | - -Returns: - -[FirebaseFirestore](./firestore_lite.firebasefirestore.md) - diff --git a/docs-exp/firestore_lite.limit.md b/docs-exp/firestore_lite.limit.md deleted file mode 100644 index df69fafb55c..00000000000 --- a/docs-exp/firestore_lite.limit.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [limit](./firestore_lite.limit.md) - -## limit() function - -Signature: - -```typescript -export function limit(limit: number): QueryConstraint; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| limit | number | | - -Returns: - -[QueryConstraint](./firestore_lite.queryconstraint.md) - diff --git a/docs-exp/firestore_lite.limittolast.md b/docs-exp/firestore_lite.limittolast.md deleted file mode 100644 index 448170d431d..00000000000 --- a/docs-exp/firestore_lite.limittolast.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [limitToLast](./firestore_lite.limittolast.md) - -## limitToLast() function - -Signature: - -```typescript -export function limitToLast(limit: number): QueryConstraint; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| limit | number | | - -Returns: - -[QueryConstraint](./firestore_lite.queryconstraint.md) - diff --git a/docs-exp/firestore_lite.loglevel.md b/docs-exp/firestore_lite.loglevel.md deleted file mode 100644 index 7954af7e4f5..00000000000 --- a/docs-exp/firestore_lite.loglevel.md +++ /dev/null @@ -1,17 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [LogLevel](./firestore_lite.loglevel.md) - -## LogLevel type - -Signature: - -```typescript -export type LogLevel = - | 'debug' - | 'error' - | 'silent' - | 'warn' - | 'info' - | 'verbose'; -``` diff --git a/docs-exp/firestore_lite.md b/docs-exp/firestore_lite.md deleted file mode 100644 index 5e683a5d9a8..00000000000 --- a/docs-exp/firestore_lite.md +++ /dev/null @@ -1,95 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) - -## @firebase/firestore/lite - -## Classes - -| Class | Description | -| --- | --- | -| [Bytes](./firestore_lite.bytes.md) | | -| [CollectionReference](./firestore_lite.collectionreference.md) | | -| [DocumentReference](./firestore_lite.documentreference.md) | | -| [DocumentSnapshot](./firestore_lite.documentsnapshot.md) | | -| [FieldPath](./firestore_lite.fieldpath.md) | | -| [FieldValue](./firestore_lite.fieldvalue.md) | | -| [FirebaseFirestore](./firestore_lite.firebasefirestore.md) | | -| [GeoPoint](./firestore_lite.geopoint.md) | | -| [Query](./firestore_lite.query.md) | | -| [QueryConstraint](./firestore_lite.queryconstraint.md) | | -| [QueryDocumentSnapshot](./firestore_lite.querydocumentsnapshot.md) | | -| [QuerySnapshot](./firestore_lite.querysnapshot.md) | | -| [Timestamp](./firestore_lite.timestamp.md) | | -| [Transaction](./firestore_lite.transaction.md) | | -| [WriteBatch](./firestore_lite.writebatch.md) | | - -## Functions - -| Function | Description | -| --- | --- | -| [addDoc(reference, data)](./firestore_lite.adddoc.md) | | -| [arrayRemove(elements)](./firestore_lite.arrayremove.md) | | -| [arrayUnion(elements)](./firestore_lite.arrayunion.md) | | -| [collection(firestore, collectionPath)](./firestore_lite.collection.md) | | -| [collection(reference, collectionPath)](./firestore_lite.collection_1.md) | | -| [collection(reference, collectionPath)](./firestore_lite.collection_2.md) | | -| [collectionGroup(firestore, collectionId)](./firestore_lite.collectiongroup.md) | | -| [deleteDoc(reference)](./firestore_lite.deletedoc.md) | | -| [deleteField()](./firestore_lite.deletefield.md) | | -| [doc(firestore, documentPath)](./firestore_lite.doc.md) | | -| [doc(reference, documentPath)](./firestore_lite.doc_1.md) | | -| [doc(reference, documentPath)](./firestore_lite.doc_2.md) | | -| [documentId()](./firestore_lite.documentid.md) | | -| [endAt(snapshot)](./firestore_lite.endat.md) | | -| [endAt(fieldValues)](./firestore_lite.endat_1.md) | | -| [endBefore(snapshot)](./firestore_lite.endbefore.md) | | -| [endBefore(fieldValues)](./firestore_lite.endbefore_1.md) | | -| [getDoc(reference)](./firestore_lite.getdoc.md) | | -| [getDocs(query)](./firestore_lite.getdocs.md) | | -| [getFirestore(app)](./firestore_lite.getfirestore.md) | | -| [increment(n)](./firestore_lite.increment.md) | | -| [initializeFirestore(app, settings)](./firestore_lite.initializefirestore.md) | | -| [limit(limit)](./firestore_lite.limit.md) | | -| [limitToLast(limit)](./firestore_lite.limittolast.md) | | -| [orderBy(fieldPath, directionStr)](./firestore_lite.orderby.md) | | -| [query(query, constraints)](./firestore_lite.query.md) | | -| [queryEqual(left, right)](./firestore_lite.queryequal.md) | | -| [refEqual(left, right)](./firestore_lite.refequal.md) | | -| [runTransaction(firestore, updateFunction)](./firestore_lite.runtransaction.md) | | -| [serverTimestamp()](./firestore_lite.servertimestamp.md) | | -| [setDoc(reference, data)](./firestore_lite.setdoc.md) | | -| [setDoc(reference, data, options)](./firestore_lite.setdoc_1.md) | | -| [setLogLevel(logLevel)](./firestore_lite.setloglevel.md) | | -| [snapshotEqual(left, right)](./firestore_lite.snapshotequal.md) | | -| [startAfter(snapshot)](./firestore_lite.startafter.md) | | -| [startAfter(fieldValues)](./firestore_lite.startafter_1.md) | | -| [startAt(snapshot)](./firestore_lite.startat.md) | | -| [startAt(fieldValues)](./firestore_lite.startat_1.md) | | -| [terminate(firestore)](./firestore_lite.terminate.md) | | -| [updateDoc(reference, data)](./firestore_lite.updatedoc.md) | | -| [updateDoc(reference, field, value, moreFieldsAndValues)](./firestore_lite.updatedoc_1.md) | | -| [where(fieldPath, opStr, value)](./firestore_lite.where.md) | | -| [writeBatch(firestore)](./firestore_lite.writebatch.md) | | - -## Interfaces - -| Interface | Description | -| --- | --- | -| [DocumentData](./firestore_lite.documentdata.md) | | -| [FirestoreDataConverter](./firestore_lite.firestoredataconverter.md) | | -| [FirestoreError](./firestore_lite.firestoreerror.md) | | -| [Settings](./firestore_lite.settings.md) | | -| [UpdateData](./firestore_lite.updatedata.md) | | - -## Type Aliases - -| Type Alias | Description | -| --- | --- | -| [FirestoreErrorCode](./firestore_lite.firestoreerrorcode.md) | | -| [LogLevel](./firestore_lite.loglevel.md) | | -| [OrderByDirection](./firestore_lite.orderbydirection.md) | | -| [QueryConstraintType](./firestore_lite.queryconstrainttype.md) | | -| [SetOptions](./firestore_lite.setoptions.md) | | -| [WhereFilterOp](./firestore_lite.wherefilterop.md) | | - diff --git a/docs-exp/firestore_lite.orderby.md b/docs-exp/firestore_lite.orderby.md deleted file mode 100644 index d36ca36146d..00000000000 --- a/docs-exp/firestore_lite.orderby.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [orderBy](./firestore_lite.orderby.md) - -## orderBy() function - -Signature: - -```typescript -export function orderBy( - fieldPath: string | FieldPath, - directionStr?: OrderByDirection -): QueryConstraint; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| fieldPath | string \| [FieldPath](./firestore_lite.fieldpath.md) | | -| directionStr | [OrderByDirection](./firestore_lite.orderbydirection.md) | | - -Returns: - -[QueryConstraint](./firestore_lite.queryconstraint.md) - diff --git a/docs-exp/firestore_lite.orderbydirection.md b/docs-exp/firestore_lite.orderbydirection.md deleted file mode 100644 index 3c27890fc38..00000000000 --- a/docs-exp/firestore_lite.orderbydirection.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [OrderByDirection](./firestore_lite.orderbydirection.md) - -## OrderByDirection type - -Signature: - -```typescript -export type OrderByDirection = 'desc' | 'asc'; -``` diff --git a/docs-exp/firestore_lite.query._constructor_.md b/docs-exp/firestore_lite.query._constructor_.md deleted file mode 100644 index 2e9b0b75b75..00000000000 --- a/docs-exp/firestore_lite.query._constructor_.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Query](./firestore_lite.query.md) > [(constructor)](./firestore_lite.query._constructor_.md) - -## Query.(constructor) - -Signature: - -```typescript -protected constructor(); -``` diff --git a/docs-exp/firestore_lite.query.converter.md b/docs-exp/firestore_lite.query.converter.md deleted file mode 100644 index 6e0d246e8c9..00000000000 --- a/docs-exp/firestore_lite.query.converter.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Query](./firestore_lite.query.md) > [converter](./firestore_lite.query.converter.md) - -## Query.converter property - -Signature: - -```typescript -readonly converter: FirestoreDataConverter | null; -``` diff --git a/docs-exp/firestore_lite.query.firestore.md b/docs-exp/firestore_lite.query.firestore.md deleted file mode 100644 index 17a7e527def..00000000000 --- a/docs-exp/firestore_lite.query.firestore.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Query](./firestore_lite.query.md) > [firestore](./firestore_lite.query.firestore.md) - -## Query.firestore property - -Signature: - -```typescript -readonly firestore: FirebaseFirestore; -``` diff --git a/docs-exp/firestore_lite.query.md b/docs-exp/firestore_lite.query.md deleted file mode 100644 index c0b5a2199f9..00000000000 --- a/docs-exp/firestore_lite.query.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [query](./firestore_lite.query.md) - -## query() function - -Signature: - -```typescript -export function query( - query: CollectionReference | Query, - ...constraints: QueryConstraint[] -): Query; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| query | [CollectionReference](./firestore_lite.collectionreference.md)<T> \| [Query](./firestore_lite.query.md)<T> | | -| constraints | [QueryConstraint](./firestore_lite.queryconstraint.md)\[\] | | - -Returns: - -[Query](./firestore_lite.query.md)<T> - diff --git a/docs-exp/firestore_lite.query.type.md b/docs-exp/firestore_lite.query.type.md deleted file mode 100644 index bef499142f9..00000000000 --- a/docs-exp/firestore_lite.query.type.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Query](./firestore_lite.query.md) > [type](./firestore_lite.query.type.md) - -## Query.type property - -Signature: - -```typescript -readonly type: 'query' | 'collection'; -``` diff --git a/docs-exp/firestore_lite.query.withconverter.md b/docs-exp/firestore_lite.query.withconverter.md deleted file mode 100644 index 31b084a42fa..00000000000 --- a/docs-exp/firestore_lite.query.withconverter.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Query](./firestore_lite.query.md) > [withConverter](./firestore_lite.query.withconverter.md) - -## Query.withConverter() method - -Signature: - -```typescript -withConverter(converter: FirestoreDataConverter): Query; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| converter | [FirestoreDataConverter](./firestore_lite.firestoredataconverter.md)<U> | | - -Returns: - -[Query](./firestore_lite.query.md)<U> - diff --git a/docs-exp/firestore_lite.queryconstraint.md b/docs-exp/firestore_lite.queryconstraint.md deleted file mode 100644 index 22e09dd18f9..00000000000 --- a/docs-exp/firestore_lite.queryconstraint.md +++ /dev/null @@ -1,18 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [QueryConstraint](./firestore_lite.queryconstraint.md) - -## QueryConstraint class - -Signature: - -```typescript -export class QueryConstraint -``` - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [type](./firestore_lite.queryconstraint.type.md) | | [QueryConstraintType](./firestore_lite.queryconstrainttype.md) | | - diff --git a/docs-exp/firestore_lite.queryconstraint.type.md b/docs-exp/firestore_lite.queryconstraint.type.md deleted file mode 100644 index 3cd45447e38..00000000000 --- a/docs-exp/firestore_lite.queryconstraint.type.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [QueryConstraint](./firestore_lite.queryconstraint.md) > [type](./firestore_lite.queryconstraint.type.md) - -## QueryConstraint.type property - -Signature: - -```typescript -readonly type: QueryConstraintType; -``` diff --git a/docs-exp/firestore_lite.queryconstrainttype.md b/docs-exp/firestore_lite.queryconstrainttype.md deleted file mode 100644 index afc5e9f0790..00000000000 --- a/docs-exp/firestore_lite.queryconstrainttype.md +++ /dev/null @@ -1,19 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [QueryConstraintType](./firestore_lite.queryconstrainttype.md) - -## QueryConstraintType type - -Signature: - -```typescript -export type QueryConstraintType = - | 'where' - | 'orderBy' - | 'limit' - | 'limitToLast' - | 'startAt' - | 'startAfter' - | 'endAt' - | 'endBefore'; -``` diff --git a/docs-exp/firestore_lite.querydocumentsnapshot.data.md b/docs-exp/firestore_lite.querydocumentsnapshot.data.md deleted file mode 100644 index a61004b2c9f..00000000000 --- a/docs-exp/firestore_lite.querydocumentsnapshot.data.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [QueryDocumentSnapshot](./firestore_lite.querydocumentsnapshot.md) > [data](./firestore_lite.querydocumentsnapshot.data.md) - -## QueryDocumentSnapshot.data() method - -Signature: - -```typescript -data(): T; -``` -Returns: - -T - diff --git a/docs-exp/firestore_lite.querydocumentsnapshot.md b/docs-exp/firestore_lite.querydocumentsnapshot.md deleted file mode 100644 index 3e1839e837c..00000000000 --- a/docs-exp/firestore_lite.querydocumentsnapshot.md +++ /dev/null @@ -1,21 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [QueryDocumentSnapshot](./firestore_lite.querydocumentsnapshot.md) - -## QueryDocumentSnapshot class - -Signature: - -```typescript -export class QueryDocumentSnapshot extends DocumentSnapshot< - T -> -``` -Extends: [DocumentSnapshot](./firestore_lite.documentsnapshot.md)< T > - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [data()](./firestore_lite.querydocumentsnapshot.data.md) | | | - diff --git a/docs-exp/firestore_lite.queryequal.md b/docs-exp/firestore_lite.queryequal.md deleted file mode 100644 index d9dfd2a6896..00000000000 --- a/docs-exp/firestore_lite.queryequal.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [queryEqual](./firestore_lite.queryequal.md) - -## queryEqual() function - -Signature: - -```typescript -export function queryEqual(left: Query, right: Query): boolean; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| left | [Query](./firestore_lite.query.md)<T> | | -| right | [Query](./firestore_lite.query.md)<T> | | - -Returns: - -boolean - diff --git a/docs-exp/firestore_lite.querysnapshot.docs.md b/docs-exp/firestore_lite.querysnapshot.docs.md deleted file mode 100644 index 9cba367ee68..00000000000 --- a/docs-exp/firestore_lite.querysnapshot.docs.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [QuerySnapshot](./firestore_lite.querysnapshot.md) > [docs](./firestore_lite.querysnapshot.docs.md) - -## QuerySnapshot.docs property - -Signature: - -```typescript -readonly docs: Array>; -``` diff --git a/docs-exp/firestore_lite.querysnapshot.empty.md b/docs-exp/firestore_lite.querysnapshot.empty.md deleted file mode 100644 index ad3ebdee269..00000000000 --- a/docs-exp/firestore_lite.querysnapshot.empty.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [QuerySnapshot](./firestore_lite.querysnapshot.md) > [empty](./firestore_lite.querysnapshot.empty.md) - -## QuerySnapshot.empty property - -Signature: - -```typescript -readonly empty: boolean; -``` diff --git a/docs-exp/firestore_lite.querysnapshot.foreach.md b/docs-exp/firestore_lite.querysnapshot.foreach.md deleted file mode 100644 index ec293169fda..00000000000 --- a/docs-exp/firestore_lite.querysnapshot.foreach.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [QuerySnapshot](./firestore_lite.querysnapshot.md) > [forEach](./firestore_lite.querysnapshot.foreach.md) - -## QuerySnapshot.forEach() method - -Signature: - -```typescript -forEach( - callback: (result: QueryDocumentSnapshot) => void, - thisArg?: any - ): void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| callback | (result: [QueryDocumentSnapshot](./firestore_lite.querydocumentsnapshot.md)<T>) => void | | -| thisArg | any | | - -Returns: - -void - diff --git a/docs-exp/firestore_lite.querysnapshot.md b/docs-exp/firestore_lite.querysnapshot.md deleted file mode 100644 index 0f46718ceda..00000000000 --- a/docs-exp/firestore_lite.querysnapshot.md +++ /dev/null @@ -1,27 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [QuerySnapshot](./firestore_lite.querysnapshot.md) - -## QuerySnapshot class - -Signature: - -```typescript -export class QuerySnapshot -``` - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [docs](./firestore_lite.querysnapshot.docs.md) | | Array<[QueryDocumentSnapshot](./firestore_lite.querydocumentsnapshot.md)<T>> | | -| [empty](./firestore_lite.querysnapshot.empty.md) | | boolean | | -| [query](./firestore_lite.querysnapshot.query.md) | | [Query](./firestore_lite.query.md)<T> | | -| [size](./firestore_lite.querysnapshot.size.md) | | number | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [forEach(callback, thisArg)](./firestore_lite.querysnapshot.foreach.md) | | | - diff --git a/docs-exp/firestore_lite.querysnapshot.query.md b/docs-exp/firestore_lite.querysnapshot.query.md deleted file mode 100644 index 997f5779415..00000000000 --- a/docs-exp/firestore_lite.querysnapshot.query.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [QuerySnapshot](./firestore_lite.querysnapshot.md) > [query](./firestore_lite.querysnapshot.query.md) - -## QuerySnapshot.query property - -Signature: - -```typescript -readonly query: Query; -``` diff --git a/docs-exp/firestore_lite.querysnapshot.size.md b/docs-exp/firestore_lite.querysnapshot.size.md deleted file mode 100644 index 632366cb1a4..00000000000 --- a/docs-exp/firestore_lite.querysnapshot.size.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [QuerySnapshot](./firestore_lite.querysnapshot.md) > [size](./firestore_lite.querysnapshot.size.md) - -## QuerySnapshot.size property - -Signature: - -```typescript -readonly size: number; -``` diff --git a/docs-exp/firestore_lite.refequal.md b/docs-exp/firestore_lite.refequal.md deleted file mode 100644 index 62f3176a0a2..00000000000 --- a/docs-exp/firestore_lite.refequal.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [refEqual](./firestore_lite.refequal.md) - -## refEqual() function - -Signature: - -```typescript -export function refEqual( - left: DocumentReference | CollectionReference, - right: DocumentReference | CollectionReference -): boolean; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| left | [DocumentReference](./firestore_lite.documentreference.md)<T> \| [CollectionReference](./firestore_lite.collectionreference.md)<T> | | -| right | [DocumentReference](./firestore_lite.documentreference.md)<T> \| [CollectionReference](./firestore_lite.collectionreference.md)<T> | | - -Returns: - -boolean - diff --git a/docs-exp/firestore_lite.runtransaction.md b/docs-exp/firestore_lite.runtransaction.md deleted file mode 100644 index c8809ae4aa2..00000000000 --- a/docs-exp/firestore_lite.runtransaction.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [runTransaction](./firestore_lite.runtransaction.md) - -## runTransaction() function - -Signature: - -```typescript -export function runTransaction( - firestore: FirebaseFirestore, - updateFunction: (transaction: Transaction) => Promise -): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| firestore | [FirebaseFirestore](./firestore_lite.firebasefirestore.md) | | -| updateFunction | (transaction: [Transaction](./firestore_lite.transaction.md)) => Promise<T> | | - -Returns: - -Promise<T> - diff --git a/docs-exp/firestore_lite.servertimestamp.md b/docs-exp/firestore_lite.servertimestamp.md deleted file mode 100644 index 19210c0e3d2..00000000000 --- a/docs-exp/firestore_lite.servertimestamp.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [serverTimestamp](./firestore_lite.servertimestamp.md) - -## serverTimestamp() function - -Signature: - -```typescript -export function serverTimestamp(): FieldValue; -``` -Returns: - -[FieldValue](./firestore_lite.fieldvalue.md) - diff --git a/docs-exp/firestore_lite.setdoc.md b/docs-exp/firestore_lite.setdoc.md deleted file mode 100644 index c6d91db9c51..00000000000 --- a/docs-exp/firestore_lite.setdoc.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [setDoc](./firestore_lite.setdoc.md) - -## setDoc() function - -Signature: - -```typescript -export function setDoc( - reference: DocumentReference, - data: T -): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| reference | [DocumentReference](./firestore_lite.documentreference.md)<T> | | -| data | T | | - -Returns: - -Promise<void> - diff --git a/docs-exp/firestore_lite.setdoc_1.md b/docs-exp/firestore_lite.setdoc_1.md deleted file mode 100644 index 26627d2abe9..00000000000 --- a/docs-exp/firestore_lite.setdoc_1.md +++ /dev/null @@ -1,28 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [setDoc](./firestore_lite.setdoc_1.md) - -## setDoc() function - -Signature: - -```typescript -export function setDoc( - reference: DocumentReference, - data: Partial, - options: SetOptions -): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| reference | [DocumentReference](./firestore_lite.documentreference.md)<T> | | -| data | Partial<T> | | -| options | [SetOptions](./firestore_lite.setoptions.md) | | - -Returns: - -Promise<void> - diff --git a/docs-exp/firestore_lite.setloglevel.md b/docs-exp/firestore_lite.setloglevel.md deleted file mode 100644 index 254ec37128d..00000000000 --- a/docs-exp/firestore_lite.setloglevel.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [setLogLevel](./firestore_lite.setloglevel.md) - -## setLogLevel() function - -Signature: - -```typescript -export function setLogLevel(logLevel: LogLevel): void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| logLevel | [LogLevel](./firestore_lite.loglevel.md) | | - -Returns: - -void - diff --git a/docs-exp/firestore_lite.setoptions.md b/docs-exp/firestore_lite.setoptions.md deleted file mode 100644 index bc3989153b3..00000000000 --- a/docs-exp/firestore_lite.setoptions.md +++ /dev/null @@ -1,17 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [SetOptions](./firestore_lite.setoptions.md) - -## SetOptions type - -Signature: - -```typescript -export type SetOptions = - | { - readonly merge?: boolean; - } - | { - readonly mergeFields?: Array; - }; -``` diff --git a/docs-exp/firestore_lite.settings.host.md b/docs-exp/firestore_lite.settings.host.md deleted file mode 100644 index 797a83d47c3..00000000000 --- a/docs-exp/firestore_lite.settings.host.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Settings](./firestore_lite.settings.md) > [host](./firestore_lite.settings.host.md) - -## Settings.host property - -Signature: - -```typescript -host?: string; -``` diff --git a/docs-exp/firestore_lite.settings.ignoreundefinedproperties.md b/docs-exp/firestore_lite.settings.ignoreundefinedproperties.md deleted file mode 100644 index d9d73c055e3..00000000000 --- a/docs-exp/firestore_lite.settings.ignoreundefinedproperties.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Settings](./firestore_lite.settings.md) > [ignoreUndefinedProperties](./firestore_lite.settings.ignoreundefinedproperties.md) - -## Settings.ignoreUndefinedProperties property - -Signature: - -```typescript -ignoreUndefinedProperties?: boolean; -``` diff --git a/docs-exp/firestore_lite.settings.md b/docs-exp/firestore_lite.settings.md deleted file mode 100644 index 2c537b6b55c..00000000000 --- a/docs-exp/firestore_lite.settings.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Settings](./firestore_lite.settings.md) - -## Settings interface - -Signature: - -```typescript -export interface Settings -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [host](./firestore_lite.settings.host.md) | string | | -| [ignoreUndefinedProperties](./firestore_lite.settings.ignoreundefinedproperties.md) | boolean | | -| [ssl](./firestore_lite.settings.ssl.md) | boolean | | - diff --git a/docs-exp/firestore_lite.settings.ssl.md b/docs-exp/firestore_lite.settings.ssl.md deleted file mode 100644 index 8f00402bfdc..00000000000 --- a/docs-exp/firestore_lite.settings.ssl.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Settings](./firestore_lite.settings.md) > [ssl](./firestore_lite.settings.ssl.md) - -## Settings.ssl property - -Signature: - -```typescript -ssl?: boolean; -``` diff --git a/docs-exp/firestore_lite.snapshotequal.md b/docs-exp/firestore_lite.snapshotequal.md deleted file mode 100644 index f87eca0a815..00000000000 --- a/docs-exp/firestore_lite.snapshotequal.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [snapshotEqual](./firestore_lite.snapshotequal.md) - -## snapshotEqual() function - -Signature: - -```typescript -export function snapshotEqual( - left: DocumentSnapshot | QuerySnapshot, - right: DocumentSnapshot | QuerySnapshot -): boolean; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| left | [DocumentSnapshot](./firestore_lite.documentsnapshot.md)<T> \| [QuerySnapshot](./firestore_lite.querysnapshot.md)<T> | | -| right | [DocumentSnapshot](./firestore_lite.documentsnapshot.md)<T> \| [QuerySnapshot](./firestore_lite.querysnapshot.md)<T> | | - -Returns: - -boolean - diff --git a/docs-exp/firestore_lite.startafter.md b/docs-exp/firestore_lite.startafter.md deleted file mode 100644 index 8de2d5b37d3..00000000000 --- a/docs-exp/firestore_lite.startafter.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [startAfter](./firestore_lite.startafter.md) - -## startAfter() function - -Signature: - -```typescript -export function startAfter(snapshot: DocumentSnapshot): QueryConstraint; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| snapshot | [DocumentSnapshot](./firestore_lite.documentsnapshot.md)<any> | | - -Returns: - -[QueryConstraint](./firestore_lite.queryconstraint.md) - diff --git a/docs-exp/firestore_lite.startafter_1.md b/docs-exp/firestore_lite.startafter_1.md deleted file mode 100644 index 0ec1d156572..00000000000 --- a/docs-exp/firestore_lite.startafter_1.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [startAfter](./firestore_lite.startafter_1.md) - -## startAfter() function - -Signature: - -```typescript -export function startAfter(...fieldValues: any[]): QueryConstraint; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| fieldValues | any\[\] | | - -Returns: - -[QueryConstraint](./firestore_lite.queryconstraint.md) - diff --git a/docs-exp/firestore_lite.startat.md b/docs-exp/firestore_lite.startat.md deleted file mode 100644 index 71d129dbb51..00000000000 --- a/docs-exp/firestore_lite.startat.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [startAt](./firestore_lite.startat.md) - -## startAt() function - -Signature: - -```typescript -export function startAt(snapshot: DocumentSnapshot): QueryConstraint; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| snapshot | [DocumentSnapshot](./firestore_lite.documentsnapshot.md)<any> | | - -Returns: - -[QueryConstraint](./firestore_lite.queryconstraint.md) - diff --git a/docs-exp/firestore_lite.startat_1.md b/docs-exp/firestore_lite.startat_1.md deleted file mode 100644 index 34b6d281921..00000000000 --- a/docs-exp/firestore_lite.startat_1.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [startAt](./firestore_lite.startat_1.md) - -## startAt() function - -Signature: - -```typescript -export function startAt(...fieldValues: any[]): QueryConstraint; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| fieldValues | any\[\] | | - -Returns: - -[QueryConstraint](./firestore_lite.queryconstraint.md) - diff --git a/docs-exp/firestore_lite.terminate.md b/docs-exp/firestore_lite.terminate.md deleted file mode 100644 index 2545b43d7b4..00000000000 --- a/docs-exp/firestore_lite.terminate.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [terminate](./firestore_lite.terminate.md) - -## terminate() function - -Signature: - -```typescript -export function terminate(firestore: FirebaseFirestore): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| firestore | [FirebaseFirestore](./firestore_lite.firebasefirestore.md) | | - -Returns: - -Promise<void> - diff --git a/docs-exp/firestore_lite.timestamp._constructor_.md b/docs-exp/firestore_lite.timestamp._constructor_.md deleted file mode 100644 index 38a9074a800..00000000000 --- a/docs-exp/firestore_lite.timestamp._constructor_.md +++ /dev/null @@ -1,19 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Timestamp](./firestore_lite.timestamp.md) > [(constructor)](./firestore_lite.timestamp._constructor_.md) - -## Timestamp.(constructor) - -Signature: - -```typescript -constructor(seconds: number, nanoseconds: number); -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| seconds | number | | -| nanoseconds | number | | - diff --git a/docs-exp/firestore_lite.timestamp.fromdate.md b/docs-exp/firestore_lite.timestamp.fromdate.md deleted file mode 100644 index 1cc881cccbe..00000000000 --- a/docs-exp/firestore_lite.timestamp.fromdate.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Timestamp](./firestore_lite.timestamp.md) > [fromDate](./firestore_lite.timestamp.fromdate.md) - -## Timestamp.fromDate() method - -Signature: - -```typescript -static fromDate(date: Date): Timestamp; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| date | Date | | - -Returns: - -[Timestamp](./firestore_lite.timestamp.md) - diff --git a/docs-exp/firestore_lite.timestamp.frommillis.md b/docs-exp/firestore_lite.timestamp.frommillis.md deleted file mode 100644 index 06b4fed290b..00000000000 --- a/docs-exp/firestore_lite.timestamp.frommillis.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Timestamp](./firestore_lite.timestamp.md) > [fromMillis](./firestore_lite.timestamp.frommillis.md) - -## Timestamp.fromMillis() method - -Signature: - -```typescript -static fromMillis(milliseconds: number): Timestamp; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| milliseconds | number | | - -Returns: - -[Timestamp](./firestore_lite.timestamp.md) - diff --git a/docs-exp/firestore_lite.timestamp.isequal.md b/docs-exp/firestore_lite.timestamp.isequal.md deleted file mode 100644 index d9f924e719d..00000000000 --- a/docs-exp/firestore_lite.timestamp.isequal.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Timestamp](./firestore_lite.timestamp.md) > [isEqual](./firestore_lite.timestamp.isequal.md) - -## Timestamp.isEqual() method - -Signature: - -```typescript -isEqual(other: Timestamp): boolean; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| other | [Timestamp](./firestore_lite.timestamp.md) | | - -Returns: - -boolean - diff --git a/docs-exp/firestore_lite.timestamp.md b/docs-exp/firestore_lite.timestamp.md deleted file mode 100644 index 5ff29ab6959..00000000000 --- a/docs-exp/firestore_lite.timestamp.md +++ /dev/null @@ -1,37 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Timestamp](./firestore_lite.timestamp.md) - -## Timestamp class - -Signature: - -```typescript -export class Timestamp -``` - -## Constructors - -| Constructor | Modifiers | Description | -| --- | --- | --- | -| [(constructor)(seconds, nanoseconds)](./firestore_lite.timestamp._constructor_.md) | | | - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [nanoseconds](./firestore_lite.timestamp.nanoseconds.md) | | number | | -| [seconds](./firestore_lite.timestamp.seconds.md) | | number | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [fromDate(date)](./firestore_lite.timestamp.fromdate.md) | static | | -| [fromMillis(milliseconds)](./firestore_lite.timestamp.frommillis.md) | static | | -| [isEqual(other)](./firestore_lite.timestamp.isequal.md) | | | -| [now()](./firestore_lite.timestamp.now.md) | static | | -| [toDate()](./firestore_lite.timestamp.todate.md) | | | -| [toMillis()](./firestore_lite.timestamp.tomillis.md) | | | -| [valueOf()](./firestore_lite.timestamp.valueof.md) | | | - diff --git a/docs-exp/firestore_lite.timestamp.nanoseconds.md b/docs-exp/firestore_lite.timestamp.nanoseconds.md deleted file mode 100644 index 412cf682260..00000000000 --- a/docs-exp/firestore_lite.timestamp.nanoseconds.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Timestamp](./firestore_lite.timestamp.md) > [nanoseconds](./firestore_lite.timestamp.nanoseconds.md) - -## Timestamp.nanoseconds property - -Signature: - -```typescript -readonly nanoseconds: number; -``` diff --git a/docs-exp/firestore_lite.timestamp.now.md b/docs-exp/firestore_lite.timestamp.now.md deleted file mode 100644 index d357a543ff0..00000000000 --- a/docs-exp/firestore_lite.timestamp.now.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Timestamp](./firestore_lite.timestamp.md) > [now](./firestore_lite.timestamp.now.md) - -## Timestamp.now() method - -Signature: - -```typescript -static now(): Timestamp; -``` -Returns: - -[Timestamp](./firestore_lite.timestamp.md) - diff --git a/docs-exp/firestore_lite.timestamp.seconds.md b/docs-exp/firestore_lite.timestamp.seconds.md deleted file mode 100644 index 8f37474e7b9..00000000000 --- a/docs-exp/firestore_lite.timestamp.seconds.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Timestamp](./firestore_lite.timestamp.md) > [seconds](./firestore_lite.timestamp.seconds.md) - -## Timestamp.seconds property - -Signature: - -```typescript -readonly seconds: number; -``` diff --git a/docs-exp/firestore_lite.timestamp.todate.md b/docs-exp/firestore_lite.timestamp.todate.md deleted file mode 100644 index f9dc7a33e9d..00000000000 --- a/docs-exp/firestore_lite.timestamp.todate.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Timestamp](./firestore_lite.timestamp.md) > [toDate](./firestore_lite.timestamp.todate.md) - -## Timestamp.toDate() method - -Signature: - -```typescript -toDate(): Date; -``` -Returns: - -Date - diff --git a/docs-exp/firestore_lite.timestamp.tomillis.md b/docs-exp/firestore_lite.timestamp.tomillis.md deleted file mode 100644 index d873d23ee49..00000000000 --- a/docs-exp/firestore_lite.timestamp.tomillis.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Timestamp](./firestore_lite.timestamp.md) > [toMillis](./firestore_lite.timestamp.tomillis.md) - -## Timestamp.toMillis() method - -Signature: - -```typescript -toMillis(): number; -``` -Returns: - -number - diff --git a/docs-exp/firestore_lite.timestamp.valueof.md b/docs-exp/firestore_lite.timestamp.valueof.md deleted file mode 100644 index ced28724e40..00000000000 --- a/docs-exp/firestore_lite.timestamp.valueof.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Timestamp](./firestore_lite.timestamp.md) > [valueOf](./firestore_lite.timestamp.valueof.md) - -## Timestamp.valueOf() method - -Signature: - -```typescript -valueOf(): string; -``` -Returns: - -string - diff --git a/docs-exp/firestore_lite.transaction.delete.md b/docs-exp/firestore_lite.transaction.delete.md deleted file mode 100644 index 2baa8b86a9c..00000000000 --- a/docs-exp/firestore_lite.transaction.delete.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Transaction](./firestore_lite.transaction.md) > [delete](./firestore_lite.transaction.delete.md) - -## Transaction.delete() method - -Signature: - -```typescript -delete(documentRef: DocumentReference): Transaction; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| documentRef | [DocumentReference](./firestore_lite.documentreference.md)<any> | | - -Returns: - -[Transaction](./firestore_lite.transaction.md) - diff --git a/docs-exp/firestore_lite.transaction.get.md b/docs-exp/firestore_lite.transaction.get.md deleted file mode 100644 index 61b23f41b16..00000000000 --- a/docs-exp/firestore_lite.transaction.get.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Transaction](./firestore_lite.transaction.md) > [get](./firestore_lite.transaction.get.md) - -## Transaction.get() method - -Signature: - -```typescript -get(documentRef: DocumentReference): Promise>; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| documentRef | [DocumentReference](./firestore_lite.documentreference.md)<T> | | - -Returns: - -Promise<[DocumentSnapshot](./firestore_lite.documentsnapshot.md)<T>> - diff --git a/docs-exp/firestore_lite.transaction.md b/docs-exp/firestore_lite.transaction.md deleted file mode 100644 index 98d2a7eac3e..00000000000 --- a/docs-exp/firestore_lite.transaction.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Transaction](./firestore_lite.transaction.md) - -## Transaction class - -Signature: - -```typescript -export class Transaction -``` - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [delete(documentRef)](./firestore_lite.transaction.delete.md) | | | -| [get(documentRef)](./firestore_lite.transaction.get.md) | | | -| [set(documentRef, data)](./firestore_lite.transaction.set.md) | | | -| [set(documentRef, data, options)](./firestore_lite.transaction.set_1.md) | | | -| [update(documentRef, data)](./firestore_lite.transaction.update.md) | | | -| [update(documentRef, field, value, moreFieldsAndValues)](./firestore_lite.transaction.update_1.md) | | | - diff --git a/docs-exp/firestore_lite.transaction.set.md b/docs-exp/firestore_lite.transaction.set.md deleted file mode 100644 index 40b54cd0e24..00000000000 --- a/docs-exp/firestore_lite.transaction.set.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Transaction](./firestore_lite.transaction.md) > [set](./firestore_lite.transaction.set.md) - -## Transaction.set() method - -Signature: - -```typescript -set(documentRef: DocumentReference, data: T): Transaction; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| documentRef | [DocumentReference](./firestore_lite.documentreference.md)<T> | | -| data | T | | - -Returns: - -[Transaction](./firestore_lite.transaction.md) - diff --git a/docs-exp/firestore_lite.transaction.set_1.md b/docs-exp/firestore_lite.transaction.set_1.md deleted file mode 100644 index f68b9923ac6..00000000000 --- a/docs-exp/firestore_lite.transaction.set_1.md +++ /dev/null @@ -1,28 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Transaction](./firestore_lite.transaction.md) > [set](./firestore_lite.transaction.set_1.md) - -## Transaction.set() method - -Signature: - -```typescript -set( - documentRef: DocumentReference, - data: Partial, - options: SetOptions - ): Transaction; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| documentRef | [DocumentReference](./firestore_lite.documentreference.md)<T> | | -| data | Partial<T> | | -| options | [SetOptions](./firestore_lite.setoptions.md) | | - -Returns: - -[Transaction](./firestore_lite.transaction.md) - diff --git a/docs-exp/firestore_lite.transaction.update.md b/docs-exp/firestore_lite.transaction.update.md deleted file mode 100644 index 818ae957c7d..00000000000 --- a/docs-exp/firestore_lite.transaction.update.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Transaction](./firestore_lite.transaction.md) > [update](./firestore_lite.transaction.update.md) - -## Transaction.update() method - -Signature: - -```typescript -update(documentRef: DocumentReference, data: UpdateData): Transaction; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| documentRef | [DocumentReference](./firestore_lite.documentreference.md)<any> | | -| data | [UpdateData](./firestore_lite.updatedata.md) | | - -Returns: - -[Transaction](./firestore_lite.transaction.md) - diff --git a/docs-exp/firestore_lite.transaction.update_1.md b/docs-exp/firestore_lite.transaction.update_1.md deleted file mode 100644 index 02465df0096..00000000000 --- a/docs-exp/firestore_lite.transaction.update_1.md +++ /dev/null @@ -1,30 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [Transaction](./firestore_lite.transaction.md) > [update](./firestore_lite.transaction.update_1.md) - -## Transaction.update() method - -Signature: - -```typescript -update( - documentRef: DocumentReference, - field: string | FieldPath, - value: any, - ...moreFieldsAndValues: any[] - ): Transaction; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| documentRef | [DocumentReference](./firestore_lite.documentreference.md)<any> | | -| field | string \| [FieldPath](./firestore_lite.fieldpath.md) | | -| value | any | | -| moreFieldsAndValues | any\[\] | | - -Returns: - -[Transaction](./firestore_lite.transaction.md) - diff --git a/docs-exp/firestore_lite.updatedata.md b/docs-exp/firestore_lite.updatedata.md deleted file mode 100644 index 7f646f27ea2..00000000000 --- a/docs-exp/firestore_lite.updatedata.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [UpdateData](./firestore_lite.updatedata.md) - -## UpdateData interface - -Signature: - -```typescript -export interface UpdateData -``` diff --git a/docs-exp/firestore_lite.updatedoc.md b/docs-exp/firestore_lite.updatedoc.md deleted file mode 100644 index d1a754e8ea5..00000000000 --- a/docs-exp/firestore_lite.updatedoc.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [updateDoc](./firestore_lite.updatedoc.md) - -## updateDoc() function - -Signature: - -```typescript -export function updateDoc( - reference: DocumentReference, - data: UpdateData -): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| reference | [DocumentReference](./firestore_lite.documentreference.md)<unknown> | | -| data | [UpdateData](./firestore_lite.updatedata.md) | | - -Returns: - -Promise<void> - diff --git a/docs-exp/firestore_lite.updatedoc_1.md b/docs-exp/firestore_lite.updatedoc_1.md deleted file mode 100644 index f81173605f1..00000000000 --- a/docs-exp/firestore_lite.updatedoc_1.md +++ /dev/null @@ -1,30 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [updateDoc](./firestore_lite.updatedoc_1.md) - -## updateDoc() function - -Signature: - -```typescript -export function updateDoc( - reference: DocumentReference, - field: string | FieldPath, - value: any, - ...moreFieldsAndValues: any[] -): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| reference | [DocumentReference](./firestore_lite.documentreference.md)<unknown> | | -| field | string \| [FieldPath](./firestore_lite.fieldpath.md) | | -| value | any | | -| moreFieldsAndValues | any\[\] | | - -Returns: - -Promise<void> - diff --git a/docs-exp/firestore_lite.where.md b/docs-exp/firestore_lite.where.md deleted file mode 100644 index 0e1c6938c72..00000000000 --- a/docs-exp/firestore_lite.where.md +++ /dev/null @@ -1,28 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [where](./firestore_lite.where.md) - -## where() function - -Signature: - -```typescript -export function where( - fieldPath: string | FieldPath, - opStr: WhereFilterOp, - value: any -): QueryConstraint; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| fieldPath | string \| [FieldPath](./firestore_lite.fieldpath.md) | | -| opStr | [WhereFilterOp](./firestore_lite.wherefilterop.md) | | -| value | any | | - -Returns: - -[QueryConstraint](./firestore_lite.queryconstraint.md) - diff --git a/docs-exp/firestore_lite.wherefilterop.md b/docs-exp/firestore_lite.wherefilterop.md deleted file mode 100644 index 907dca9e45d..00000000000 --- a/docs-exp/firestore_lite.wherefilterop.md +++ /dev/null @@ -1,19 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [WhereFilterOp](./firestore_lite.wherefilterop.md) - -## WhereFilterOp type - -Signature: - -```typescript -export type WhereFilterOp = - | '<' - | '<=' - | '==' - | '>=' - | '>' - | 'array-contains' - | 'in' - | 'array-contains-any'; -``` diff --git a/docs-exp/firestore_lite.writebatch.commit.md b/docs-exp/firestore_lite.writebatch.commit.md deleted file mode 100644 index 16072758026..00000000000 --- a/docs-exp/firestore_lite.writebatch.commit.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [WriteBatch](./firestore_lite.writebatch.md) > [commit](./firestore_lite.writebatch.commit.md) - -## WriteBatch.commit() method - -Signature: - -```typescript -commit(): Promise; -``` -Returns: - -Promise<void> - diff --git a/docs-exp/firestore_lite.writebatch.delete.md b/docs-exp/firestore_lite.writebatch.delete.md deleted file mode 100644 index 88cbc7ee225..00000000000 --- a/docs-exp/firestore_lite.writebatch.delete.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [WriteBatch](./firestore_lite.writebatch.md) > [delete](./firestore_lite.writebatch.delete.md) - -## WriteBatch.delete() method - -Signature: - -```typescript -delete(documentRef: DocumentReference): WriteBatch; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| documentRef | [DocumentReference](./firestore_lite.documentreference.md)<any> | | - -Returns: - -[WriteBatch](./firestore_lite.writebatch.md) - diff --git a/docs-exp/firestore_lite.writebatch.md b/docs-exp/firestore_lite.writebatch.md deleted file mode 100644 index 0d9451d08ad..00000000000 --- a/docs-exp/firestore_lite.writebatch.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [writeBatch](./firestore_lite.writebatch.md) - -## writeBatch() function - -Signature: - -```typescript -export function writeBatch(firestore: FirebaseFirestore): WriteBatch; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| firestore | [FirebaseFirestore](./firestore_lite.firebasefirestore.md) | | - -Returns: - -[WriteBatch](./firestore_lite.writebatch.md) - diff --git a/docs-exp/firestore_lite.writebatch.set.md b/docs-exp/firestore_lite.writebatch.set.md deleted file mode 100644 index 2fc3bb3a8f1..00000000000 --- a/docs-exp/firestore_lite.writebatch.set.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [WriteBatch](./firestore_lite.writebatch.md) > [set](./firestore_lite.writebatch.set.md) - -## WriteBatch.set() method - -Signature: - -```typescript -set(documentRef: DocumentReference, data: T): WriteBatch; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| documentRef | [DocumentReference](./firestore_lite.documentreference.md)<T> | | -| data | T | | - -Returns: - -[WriteBatch](./firestore_lite.writebatch.md) - diff --git a/docs-exp/firestore_lite.writebatch.set_1.md b/docs-exp/firestore_lite.writebatch.set_1.md deleted file mode 100644 index 4fc9a265034..00000000000 --- a/docs-exp/firestore_lite.writebatch.set_1.md +++ /dev/null @@ -1,28 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [WriteBatch](./firestore_lite.writebatch.md) > [set](./firestore_lite.writebatch.set_1.md) - -## WriteBatch.set() method - -Signature: - -```typescript -set( - documentRef: DocumentReference, - data: Partial, - options: SetOptions - ): WriteBatch; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| documentRef | [DocumentReference](./firestore_lite.documentreference.md)<T> | | -| data | Partial<T> | | -| options | [SetOptions](./firestore_lite.setoptions.md) | | - -Returns: - -[WriteBatch](./firestore_lite.writebatch.md) - diff --git a/docs-exp/firestore_lite.writebatch.update.md b/docs-exp/firestore_lite.writebatch.update.md deleted file mode 100644 index 558f4e09ae5..00000000000 --- a/docs-exp/firestore_lite.writebatch.update.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [WriteBatch](./firestore_lite.writebatch.md) > [update](./firestore_lite.writebatch.update.md) - -## WriteBatch.update() method - -Signature: - -```typescript -update(documentRef: DocumentReference, data: UpdateData): WriteBatch; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| documentRef | [DocumentReference](./firestore_lite.documentreference.md)<any> | | -| data | [UpdateData](./firestore_lite.updatedata.md) | | - -Returns: - -[WriteBatch](./firestore_lite.writebatch.md) - diff --git a/docs-exp/firestore_lite.writebatch.update_1.md b/docs-exp/firestore_lite.writebatch.update_1.md deleted file mode 100644 index ceaf1a4049f..00000000000 --- a/docs-exp/firestore_lite.writebatch.update_1.md +++ /dev/null @@ -1,30 +0,0 @@ - - -[Home](./index.md) > [@firebase/firestore](./firestore.md) > [lite](./firestore_lite.md) > [WriteBatch](./firestore_lite.writebatch.md) > [update](./firestore_lite.writebatch.update_1.md) - -## WriteBatch.update() method - -Signature: - -```typescript -update( - documentRef: DocumentReference, - field: string | FieldPath, - value: any, - ...moreFieldsAndValues: any[] - ): WriteBatch; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| documentRef | [DocumentReference](./firestore_lite.documentreference.md)<any> | | -| field | string \| [FieldPath](./firestore_lite.fieldpath.md) | | -| value | any | | -| moreFieldsAndValues | any\[\] | | - -Returns: - -[WriteBatch](./firestore_lite.writebatch.md) - diff --git a/docs-exp/functions-types.functions.app.md b/docs-exp/functions-types.functions.app.md deleted file mode 100644 index 1016d2cc059..00000000000 --- a/docs-exp/functions-types.functions.app.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/functions-types](./functions-types.md) > [Functions](./functions-types.functions.md) > [app](./functions-types.functions.app.md) - -## Functions.app property - -The FirebaseApp this Functions instance is associated with. - -Signature: - -```typescript -app: FirebaseApp; -``` diff --git a/docs-exp/functions-types.functions.customdomain.md b/docs-exp/functions-types.functions.customdomain.md deleted file mode 100644 index 70cb1d47151..00000000000 --- a/docs-exp/functions-types.functions.customdomain.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/functions-types](./functions-types.md) > [Functions](./functions-types.functions.md) > [customDomain](./functions-types.functions.customdomain.md) - -## Functions.customDomain property - -A custom domain hosting the callable Cloud Functions. ex: https://mydomain.com - -Signature: - -```typescript -customDomain: string | null; -``` diff --git a/docs-exp/functions-types.functions.md b/docs-exp/functions-types.functions.md deleted file mode 100644 index ae59c5b0d3d..00000000000 --- a/docs-exp/functions-types.functions.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/functions-types](./functions-types.md) > [Functions](./functions-types.functions.md) - -## Functions interface - -`Functions` represents a Functions instance, and is a required argument for all Functions operations. - -Signature: - -```typescript -export interface Functions -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [app](./functions-types.functions.app.md) | [FirebaseApp](./app-types.firebaseapp.md) | The FirebaseApp this Functions instance is associated with. | -| [customDomain](./functions-types.functions.customdomain.md) | string \| null | A custom domain hosting the callable Cloud Functions. ex: https://mydomain.com | -| [region](./functions-types.functions.region.md) | string | The region the callable Cloud Functions are located in. Default is us-central-1. | - diff --git a/docs-exp/functions-types.functions.region.md b/docs-exp/functions-types.functions.region.md deleted file mode 100644 index 5b76aa4101c..00000000000 --- a/docs-exp/functions-types.functions.region.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/functions-types](./functions-types.md) > [Functions](./functions-types.functions.md) > [region](./functions-types.functions.region.md) - -## Functions.region property - -The region the callable Cloud Functions are located in. Default is `us-central-1`. - -Signature: - -```typescript -region: string; -``` diff --git a/docs-exp/functions-types.functionserror.code.md b/docs-exp/functions-types.functionserror.code.md deleted file mode 100644 index a1219c39ec3..00000000000 --- a/docs-exp/functions-types.functionserror.code.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/functions-types](./functions-types.md) > [FunctionsError](./functions-types.functionserror.md) > [code](./functions-types.functionserror.code.md) - -## FunctionsError.code property - -A standard error code that will be returned to the client. This also determines the HTTP status code of the response, as defined in code.proto. - -Signature: - -```typescript -readonly code: FunctionsErrorCode; -``` diff --git a/docs-exp/functions-types.functionserror.details.md b/docs-exp/functions-types.functionserror.details.md deleted file mode 100644 index 4cb5b48be99..00000000000 --- a/docs-exp/functions-types.functionserror.details.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/functions-types](./functions-types.md) > [FunctionsError](./functions-types.functionserror.md) > [details](./functions-types.functionserror.details.md) - -## FunctionsError.details property - -Extra data to be converted to JSON and included in the error response. - -Signature: - -```typescript -readonly details?: any; -``` diff --git a/docs-exp/functions-types.functionserror.md b/docs-exp/functions-types.functionserror.md deleted file mode 100644 index 1b5aaf9d124..00000000000 --- a/docs-exp/functions-types.functionserror.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [@firebase/functions-types](./functions-types.md) > [FunctionsError](./functions-types.functionserror.md) - -## FunctionsError interface - -Signature: - -```typescript -export interface FunctionsError extends FirebaseError -``` -Extends: FirebaseError - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [code](./functions-types.functionserror.code.md) | [FunctionsErrorCode](./functions-types.functionserrorcode.md) | A standard error code that will be returned to the client. This also determines the HTTP status code of the response, as defined in code.proto. | -| [details](./functions-types.functionserror.details.md) | any | Extra data to be converted to JSON and included in the error response. | - diff --git a/docs-exp/functions-types.functionserrorcode.md b/docs-exp/functions-types.functionserrorcode.md deleted file mode 100644 index fc5915e5d19..00000000000 --- a/docs-exp/functions-types.functionserrorcode.md +++ /dev/null @@ -1,32 +0,0 @@ - - -[Home](./index.md) > [@firebase/functions-types](./functions-types.md) > [FunctionsErrorCode](./functions-types.functionserrorcode.md) - -## FunctionsErrorCode type - -The set of Firebase Functions status codes. The codes are the same at the ones exposed by gRPC here: https://github.com/grpc/grpc/blob/master/doc/statuscodes.md - -Possible values: - 'cancelled': The operation was cancelled (typically by the caller). - 'unknown': Unknown error or an error from a different error domain. - 'invalid-argument': Client specified an invalid argument. Note that this differs from 'failed-precondition'. 'invalid-argument' indicates arguments that are problematic regardless of the state of the system (e.g. an invalid field name). - 'deadline-exceeded': Deadline expired before operation could complete. For operations that change the state of the system, this error may be returned even if the operation has completed successfully. For example, a successful response from a server could have been delayed long enough for the deadline to expire. - 'not-found': Some requested document was not found. - 'already-exists': Some document that we attempted to create already exists. - 'permission-denied': The caller does not have permission to execute the specified operation. - 'resource-exhausted': Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system is out of space. - 'failed-precondition': Operation was rejected because the system is not in a state required for the operation's execution. - 'aborted': The operation was aborted, typically due to a concurrency issue like transaction aborts, etc. - 'out-of-range': Operation was attempted past the valid range. - 'unimplemented': Operation is not implemented or not supported/enabled. - 'internal': Internal errors. Means some invariants expected by underlying system has been broken. If you see one of these errors, something is very broken. - 'unavailable': The service is currently unavailable. This is most likely a transient condition and may be corrected by retrying with a backoff. - 'data-loss': Unrecoverable data loss or corruption. - 'unauthenticated': The request does not have valid authentication credentials for the operation. - -Signature: - -```typescript -export type FunctionsErrorCode = - | 'ok' - | 'cancelled' - | 'unknown' - | 'invalid-argument' - | 'deadline-exceeded' - | 'not-found' - | 'already-exists' - | 'permission-denied' - | 'resource-exhausted' - | 'failed-precondition' - | 'aborted' - | 'out-of-range' - | 'unimplemented' - | 'internal' - | 'unavailable' - | 'data-loss' - | 'unauthenticated'; -``` diff --git a/docs-exp/functions-types.httpscallable.md b/docs-exp/functions-types.httpscallable.md deleted file mode 100644 index 7982ac778d0..00000000000 --- a/docs-exp/functions-types.httpscallable.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/functions-types](./functions-types.md) > [HttpsCallable](./functions-types.httpscallable.md) - -## HttpsCallable interface - -An HttpsCallable is a reference to a "callable" http trigger in Google Cloud Functions. - -Signature: - -```typescript -export interface HttpsCallable -``` diff --git a/docs-exp/functions-types.httpscallableoptions.md b/docs-exp/functions-types.httpscallableoptions.md deleted file mode 100644 index a311f432db5..00000000000 --- a/docs-exp/functions-types.httpscallableoptions.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [@firebase/functions-types](./functions-types.md) > [HttpsCallableOptions](./functions-types.httpscallableoptions.md) - -## HttpsCallableOptions interface - -HttpsCallableOptions specify metadata about how calls should be executed. - -Signature: - -```typescript -export interface HttpsCallableOptions -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [timeout](./functions-types.httpscallableoptions.timeout.md) | number | | - diff --git a/docs-exp/functions-types.httpscallableoptions.timeout.md b/docs-exp/functions-types.httpscallableoptions.timeout.md deleted file mode 100644 index bef0266c3ca..00000000000 --- a/docs-exp/functions-types.httpscallableoptions.timeout.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/functions-types](./functions-types.md) > [HttpsCallableOptions](./functions-types.httpscallableoptions.md) > [timeout](./functions-types.httpscallableoptions.timeout.md) - -## HttpsCallableOptions.timeout property - -Signature: - -```typescript -timeout?: number; -``` diff --git a/docs-exp/functions-types.httpscallableresult.data.md b/docs-exp/functions-types.httpscallableresult.data.md deleted file mode 100644 index 5795095831a..00000000000 --- a/docs-exp/functions-types.httpscallableresult.data.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [@firebase/functions-types](./functions-types.md) > [HttpsCallableResult](./functions-types.httpscallableresult.md) > [data](./functions-types.httpscallableresult.data.md) - -## HttpsCallableResult.data property - -Signature: - -```typescript -readonly data: any; -``` diff --git a/docs-exp/functions-types.httpscallableresult.md b/docs-exp/functions-types.httpscallableresult.md deleted file mode 100644 index d8afa7988cd..00000000000 --- a/docs-exp/functions-types.httpscallableresult.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [@firebase/functions-types](./functions-types.md) > [HttpsCallableResult](./functions-types.httpscallableresult.md) - -## HttpsCallableResult interface - -An HttpsCallableResult wraps a single result from a function call. - -Signature: - -```typescript -export interface HttpsCallableResult -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [data](./functions-types.httpscallableresult.data.md) | any | | - diff --git a/docs-exp/functions-types.md b/docs-exp/functions-types.md deleted file mode 100644 index 6e596d5f512..00000000000 --- a/docs-exp/functions-types.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [@firebase/functions-types](./functions-types.md) - -## functions-types package - -## Interfaces - -| Interface | Description | -| --- | --- | -| [Functions](./functions-types.functions.md) | Functions represents a Functions instance, and is a required argument for all Functions operations. | -| [FunctionsError](./functions-types.functionserror.md) | | -| [HttpsCallable](./functions-types.httpscallable.md) | An HttpsCallable is a reference to a "callable" http trigger in Google Cloud Functions. | -| [HttpsCallableOptions](./functions-types.httpscallableoptions.md) | HttpsCallableOptions specify metadata about how calls should be executed. | -| [HttpsCallableResult](./functions-types.httpscallableresult.md) | An HttpsCallableResult wraps a single result from a function call. | - -## Type Aliases - -| Type Alias | Description | -| --- | --- | -| [FunctionsErrorCode](./functions-types.functionserrorcode.md) | The set of Firebase Functions status codes. The codes are the same at the ones exposed by gRPC here: https://github.com/grpc/grpc/blob/master/doc/statuscodes.mdPossible values: - 'cancelled': The operation was cancelled (typically by the caller). - 'unknown': Unknown error or an error from a different error domain. - 'invalid-argument': Client specified an invalid argument. Note that this differs from 'failed-precondition'. 'invalid-argument' indicates arguments that are problematic regardless of the state of the system (e.g. an invalid field name). - 'deadline-exceeded': Deadline expired before operation could complete. For operations that change the state of the system, this error may be returned even if the operation has completed successfully. For example, a successful response from a server could have been delayed long enough for the deadline to expire. - 'not-found': Some requested document was not found. - 'already-exists': Some document that we attempted to create already exists. - 'permission-denied': The caller does not have permission to execute the specified operation. - 'resource-exhausted': Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system is out of space. - 'failed-precondition': Operation was rejected because the system is not in a state required for the operation's execution. - 'aborted': The operation was aborted, typically due to a concurrency issue like transaction aborts, etc. - 'out-of-range': Operation was attempted past the valid range. - 'unimplemented': Operation is not implemented or not supported/enabled. - 'internal': Internal errors. Means some invariants expected by underlying system has been broken. If you see one of these errors, something is very broken. - 'unavailable': The service is currently unavailable. This is most likely a transient condition and may be corrected by retrying with a backoff. - 'data-loss': Unrecoverable data loss or corruption. - 'unauthenticated': The request does not have valid authentication credentials for the operation. | - diff --git a/docs-exp/functions.getfunctions.md b/docs-exp/functions.getfunctions.md deleted file mode 100644 index 0cf81c51614..00000000000 --- a/docs-exp/functions.getfunctions.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [@firebase/functions](./functions.md) > [getFunctions](./functions.getfunctions.md) - -## getFunctions() function - -Returns a Functions instance for the given app. - -Signature: - -```typescript -export declare function getFunctions(app: FirebaseApp, regionOrCustomDomain?: string): Functions; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| app | [FirebaseApp](./app-types.firebaseapp.md) | The FirebaseApp to use. | -| regionOrCustomDomain | string | one of: a) The region the callable functions are located in (ex: us-central1) b) A custom domain hosting the callable functions (ex: https://mydomain.com) | - -Returns: - -[Functions](./functions-types.functions.md) - diff --git a/docs-exp/functions.httpscallable.md b/docs-exp/functions.httpscallable.md deleted file mode 100644 index 143b1d01100..00000000000 --- a/docs-exp/functions.httpscallable.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [@firebase/functions](./functions.md) > [httpsCallable](./functions.httpscallable.md) - -## httpsCallable() function - -Returns a reference to the callable https trigger with the given name. - -Signature: - -```typescript -export declare function httpsCallable(functionsInstance: Functions, name: string, options?: HttpsCallableOptions): HttpsCallable; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| functionsInstance | [Functions](./functions-types.functions.md) | | -| name | string | The name of the trigger. | -| options | [HttpsCallableOptions](./functions-types.httpscallableoptions.md) | | - -Returns: - -[HttpsCallable](./functions-types.httpscallable.md) - diff --git a/docs-exp/functions.md b/docs-exp/functions.md deleted file mode 100644 index 83f644746ad..00000000000 --- a/docs-exp/functions.md +++ /dev/null @@ -1,14 +0,0 @@ - - -[Home](./index.md) > [@firebase/functions](./functions.md) - -## functions package - -## Functions - -| Function | Description | -| --- | --- | -| [getFunctions(app, regionOrCustomDomain)](./functions.getfunctions.md) | Returns a Functions instance for the given app. | -| [httpsCallable(functionsInstance, name, options)](./functions.httpscallable.md) | Returns a reference to the callable https trigger with the given name. | -| [useFunctionsEmulator(functionsInstance, origin)](./functions.usefunctionsemulator.md) | Changes this instance to point to a Cloud Functions emulator running locally. See https://firebase.google.com/docs/functions/local-emulator | - diff --git a/docs-exp/functions.usefunctionsemulator.md b/docs-exp/functions.usefunctionsemulator.md deleted file mode 100644 index b7798ce9a13..00000000000 --- a/docs-exp/functions.usefunctionsemulator.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [@firebase/functions](./functions.md) > [useFunctionsEmulator](./functions.usefunctionsemulator.md) - -## useFunctionsEmulator() function - -Changes this instance to point to a Cloud Functions emulator running locally. See https://firebase.google.com/docs/functions/local-emulator - -Signature: - -```typescript -export declare function useFunctionsEmulator(functionsInstance: Functions, origin: string): void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| functionsInstance | [Functions](./functions-types.functions.md) | | -| origin | string | The origin of the local emulator, such as "http://localhost:5005". | - -Returns: - -void - diff --git a/docs-exp/index.md b/docs-exp/index.md deleted file mode 100644 index 528eb181002..00000000000 --- a/docs-exp/index.md +++ /dev/null @@ -1,21 +0,0 @@ - - -[Home](./index.md) - -## API Reference - -## Packages - -| Package | Description | -| --- | --- | -| [@firebase/app](./app.md) | Firebase App | -| [@firebase/app-types](./app-types.md) | | -| [@firebase/auth](./auth.md) | | -| [@firebase/auth-types](./auth-types.md) | | -| [@firebase/functions](./functions.md) | | -| [@firebase/functions-types](./functions-types.md) | | -| [@firebase/installations](./installations.md) | | -| [@firebase/installations-types](./installations-types.md) | | -| [@firebase/performance](./performance.md) | | -| [@firebase/performance-types](./performance-types.md) | | - diff --git a/docs-exp/installations-types.firebaseinstallations.md b/docs-exp/installations-types.firebaseinstallations.md deleted file mode 100644 index 3f0a2450c9e..00000000000 --- a/docs-exp/installations-types.firebaseinstallations.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/installations-types](./installations-types.md) > [FirebaseInstallations](./installations-types.firebaseinstallations.md) - -## FirebaseInstallations interface - -Public interface of the FirebaseInstallations SDK. - -Signature: - -```typescript -export interface FirebaseInstallations -``` diff --git a/docs-exp/installations-types.md b/docs-exp/installations-types.md deleted file mode 100644 index 19584ef24d6..00000000000 --- a/docs-exp/installations-types.md +++ /dev/null @@ -1,12 +0,0 @@ - - -[Home](./index.md) > [@firebase/installations-types](./installations-types.md) - -## installations-types package - -## Interfaces - -| Interface | Description | -| --- | --- | -| [FirebaseInstallations](./installations-types.firebaseinstallations.md) | Public interface of the FirebaseInstallations SDK. | - diff --git a/docs-exp/installations.deleteinstallations.md b/docs-exp/installations.deleteinstallations.md deleted file mode 100644 index 0a97f1c9823..00000000000 --- a/docs-exp/installations.deleteinstallations.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/installations](./installations.md) > [deleteInstallations](./installations.deleteinstallations.md) - -## deleteInstallations() function - -Deletes the Firebase Installation and all associated data. - -Signature: - -```typescript -export declare function deleteInstallations(installations: FirebaseInstallations): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| installations | [FirebaseInstallations](./installations-types.firebaseinstallations.md) | | - -Returns: - -Promise<void> - diff --git a/docs-exp/installations.getid.md b/docs-exp/installations.getid.md deleted file mode 100644 index 18122ab165f..00000000000 --- a/docs-exp/installations.getid.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/installations](./installations.md) > [getId](./installations.getid.md) - -## getId() function - -Creates a Firebase Installation if there isn't one for the app and returns the Installation ID. - -Signature: - -```typescript -export declare function getId(installations: FirebaseInstallations): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| installations | [FirebaseInstallations](./installations-types.firebaseinstallations.md) | | - -Returns: - -Promise<string> - diff --git a/docs-exp/installations.getinstallations.md b/docs-exp/installations.getinstallations.md deleted file mode 100644 index cb95fa12b96..00000000000 --- a/docs-exp/installations.getinstallations.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/installations](./installations.md) > [getInstallations](./installations.getinstallations.md) - -## getInstallations() function - -Returns an instance of FirebaseInstallations associated with the given FirebaseApp instance. - -Signature: - -```typescript -export declare function getInstallations(app: FirebaseApp): FirebaseInstallations; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| app | [FirebaseApp](./app-types.firebaseapp.md) | | - -Returns: - -[FirebaseInstallations](./installations-types.firebaseinstallations.md) - diff --git a/docs-exp/installations.gettoken.md b/docs-exp/installations.gettoken.md deleted file mode 100644 index 978c8b07e10..00000000000 --- a/docs-exp/installations.gettoken.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [@firebase/installations](./installations.md) > [getToken](./installations.gettoken.md) - -## getToken() function - -Returns an Installation auth token, identifying the current Firebase Installation. - -Signature: - -```typescript -export declare function getToken(installations: FirebaseInstallations, forceRefresh?: boolean): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| installations | [FirebaseInstallations](./installations-types.firebaseinstallations.md) | | -| forceRefresh | boolean | | - -Returns: - -Promise<string> - diff --git a/docs-exp/installations.idchangecallbackfn.md b/docs-exp/installations.idchangecallbackfn.md deleted file mode 100644 index d11b38e6580..00000000000 --- a/docs-exp/installations.idchangecallbackfn.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/installations](./installations.md) > [IdChangeCallbackFn](./installations.idchangecallbackfn.md) - -## IdChangeCallbackFn type - -An user defined callback function that gets called when Installations ID changes. - -Signature: - -```typescript -export declare type IdChangeCallbackFn = (installationId: string) => void; -``` diff --git a/docs-exp/installations.idchangeunsubscribefn.md b/docs-exp/installations.idchangeunsubscribefn.md deleted file mode 100644 index 3faec71e5dc..00000000000 --- a/docs-exp/installations.idchangeunsubscribefn.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/installations](./installations.md) > [IdChangeUnsubscribeFn](./installations.idchangeunsubscribefn.md) - -## IdChangeUnsubscribeFn type - -Unsubscribe a callback function previously added via . - -Signature: - -```typescript -export declare type IdChangeUnsubscribeFn = () => void; -``` diff --git a/docs-exp/installations.md b/docs-exp/installations.md deleted file mode 100644 index ee79c9eb02e..00000000000 --- a/docs-exp/installations.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [@firebase/installations](./installations.md) - -## installations package - -## Functions - -| Function | Description | -| --- | --- | -| [deleteInstallations(installations)](./installations.deleteinstallations.md) | Deletes the Firebase Installation and all associated data. | -| [getId(installations)](./installations.getid.md) | Creates a Firebase Installation if there isn't one for the app and returns the Installation ID. | -| [getInstallations(app)](./installations.getinstallations.md) | Returns an instance of FirebaseInstallations associated with the given FirebaseApp instance. | -| [getToken(installations, forceRefresh)](./installations.gettoken.md) | Returns an Installation auth token, identifying the current Firebase Installation. | -| [onIdChange(installations, callback)](./installations.onidchange.md) | Sets a new callback that will get called when Installation ID changes. Returns an unsubscribe function that will remove the callback when called. | - -## Type Aliases - -| Type Alias | Description | -| --- | --- | -| [IdChangeCallbackFn](./installations.idchangecallbackfn.md) | An user defined callback function that gets called when Installations ID changes. | -| [IdChangeUnsubscribeFn](./installations.idchangeunsubscribefn.md) | Unsubscribe a callback function previously added via . | - diff --git a/docs-exp/installations.onidchange.md b/docs-exp/installations.onidchange.md deleted file mode 100644 index 53065297eb1..00000000000 --- a/docs-exp/installations.onidchange.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [@firebase/installations](./installations.md) > [onIdChange](./installations.onidchange.md) - -## onIdChange() function - -Sets a new callback that will get called when Installation ID changes. Returns an unsubscribe function that will remove the callback when called. - -Signature: - -```typescript -export declare function onIdChange(installations: FirebaseInstallations, callback: IdChangeCallbackFn): IdChangeUnsubscribeFn; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| installations | [FirebaseInstallations](./installations-types.firebaseinstallations.md) | | -| callback | [IdChangeCallbackFn](./installations.idchangecallbackfn.md) | | - -Returns: - -[IdChangeUnsubscribeFn](./installations.idchangeunsubscribefn.md) - diff --git a/docs-exp/performance-types.firebaseperformance.datacollectionenabled.md b/docs-exp/performance-types.firebaseperformance.datacollectionenabled.md deleted file mode 100644 index bf9e5a9814d..00000000000 --- a/docs-exp/performance-types.firebaseperformance.datacollectionenabled.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/performance-types](./performance-types.md) > [FirebasePerformance](./performance-types.firebaseperformance.md) > [dataCollectionEnabled](./performance-types.firebaseperformance.datacollectionenabled.md) - -## FirebasePerformance.dataCollectionEnabled property - -Controls the logging of custom traces. - -Signature: - -```typescript -dataCollectionEnabled: boolean; -``` diff --git a/docs-exp/performance-types.firebaseperformance.instrumentationenabled.md b/docs-exp/performance-types.firebaseperformance.instrumentationenabled.md deleted file mode 100644 index 5b1234028bf..00000000000 --- a/docs-exp/performance-types.firebaseperformance.instrumentationenabled.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/performance-types](./performance-types.md) > [FirebasePerformance](./performance-types.firebaseperformance.md) > [instrumentationEnabled](./performance-types.firebaseperformance.instrumentationenabled.md) - -## FirebasePerformance.instrumentationEnabled property - -Controls the logging of automatic traces and HTTP/S network monitoring. - -Signature: - -```typescript -instrumentationEnabled: boolean; -``` diff --git a/docs-exp/performance-types.firebaseperformance.md b/docs-exp/performance-types.firebaseperformance.md deleted file mode 100644 index 217dd71b115..00000000000 --- a/docs-exp/performance-types.firebaseperformance.md +++ /dev/null @@ -1,19 +0,0 @@ - - -[Home](./index.md) > [@firebase/performance-types](./performance-types.md) > [FirebasePerformance](./performance-types.firebaseperformance.md) - -## FirebasePerformance interface - -Signature: - -```typescript -export interface FirebasePerformance -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [dataCollectionEnabled](./performance-types.firebaseperformance.datacollectionenabled.md) | boolean | Controls the logging of custom traces. | -| [instrumentationEnabled](./performance-types.firebaseperformance.instrumentationenabled.md) | boolean | Controls the logging of automatic traces and HTTP/S network monitoring. | - diff --git a/docs-exp/performance-types.md b/docs-exp/performance-types.md deleted file mode 100644 index 0e570e39f94..00000000000 --- a/docs-exp/performance-types.md +++ /dev/null @@ -1,14 +0,0 @@ - - -[Home](./index.md) > [@firebase/performance-types](./performance-types.md) - -## performance-types package - -## Interfaces - -| Interface | Description | -| --- | --- | -| [FirebasePerformance](./performance-types.firebaseperformance.md) | | -| [PerformanceSettings](./performance-types.performancesettings.md) | | -| [PerformanceTrace](./performance-types.performancetrace.md) | | - diff --git a/docs-exp/performance-types.performancesettings.datacollectionenabled.md b/docs-exp/performance-types.performancesettings.datacollectionenabled.md deleted file mode 100644 index 3d9cc2cfa81..00000000000 --- a/docs-exp/performance-types.performancesettings.datacollectionenabled.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/performance-types](./performance-types.md) > [PerformanceSettings](./performance-types.performancesettings.md) > [dataCollectionEnabled](./performance-types.performancesettings.datacollectionenabled.md) - -## PerformanceSettings.dataCollectionEnabled property - -Whether to collect custom events. - -Signature: - -```typescript -dataCollectionEnabled?: boolean; -``` diff --git a/docs-exp/performance-types.performancesettings.instrumentationenabled.md b/docs-exp/performance-types.performancesettings.instrumentationenabled.md deleted file mode 100644 index 9e662d62719..00000000000 --- a/docs-exp/performance-types.performancesettings.instrumentationenabled.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/performance-types](./performance-types.md) > [PerformanceSettings](./performance-types.performancesettings.md) > [instrumentationEnabled](./performance-types.performancesettings.instrumentationenabled.md) - -## PerformanceSettings.instrumentationEnabled property - -Whether to collect out of the box events. - -Signature: - -```typescript -instrumentationEnabled?: boolean; -``` diff --git a/docs-exp/performance-types.performancesettings.md b/docs-exp/performance-types.performancesettings.md deleted file mode 100644 index 818c0ff0135..00000000000 --- a/docs-exp/performance-types.performancesettings.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [@firebase/performance-types](./performance-types.md) > [PerformanceSettings](./performance-types.performancesettings.md) - -## PerformanceSettings interface - - -Signature: - -```typescript -export interface PerformanceSettings -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [dataCollectionEnabled](./performance-types.performancesettings.datacollectionenabled.md) | boolean | Whether to collect custom events. | -| [instrumentationEnabled](./performance-types.performancesettings.instrumentationenabled.md) | boolean | Whether to collect out of the box events. | - diff --git a/docs-exp/performance-types.performancetrace.getattribute.md b/docs-exp/performance-types.performancetrace.getattribute.md deleted file mode 100644 index 446cf18bd89..00000000000 --- a/docs-exp/performance-types.performancetrace.getattribute.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/performance-types](./performance-types.md) > [PerformanceTrace](./performance-types.performancetrace.md) > [getAttribute](./performance-types.performancetrace.getattribute.md) - -## PerformanceTrace.getAttribute() method - -Retrieves the value which a custom attribute is set to. - -Signature: - -```typescript -getAttribute(attr: string): string | undefined; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| attr | string | Name of the custom attribute. | - -Returns: - -string \| undefined - diff --git a/docs-exp/performance-types.performancetrace.getattributes.md b/docs-exp/performance-types.performancetrace.getattributes.md deleted file mode 100644 index 881b4432146..00000000000 --- a/docs-exp/performance-types.performancetrace.getattributes.md +++ /dev/null @@ -1,17 +0,0 @@ - - -[Home](./index.md) > [@firebase/performance-types](./performance-types.md) > [PerformanceTrace](./performance-types.performancetrace.md) > [getAttributes](./performance-types.performancetrace.getattributes.md) - -## PerformanceTrace.getAttributes() method - -Returns a map of all custom attributes of a trace instance. - -Signature: - -```typescript -getAttributes(): { [key: string]: string }; -``` -Returns: - -{ \[key: string\]: string } - diff --git a/docs-exp/performance-types.performancetrace.getmetric.md b/docs-exp/performance-types.performancetrace.getmetric.md deleted file mode 100644 index 1637633b959..00000000000 --- a/docs-exp/performance-types.performancetrace.getmetric.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/performance-types](./performance-types.md) > [PerformanceTrace](./performance-types.performancetrace.md) > [getMetric](./performance-types.performancetrace.getmetric.md) - -## PerformanceTrace.getMetric() method - -Returns the value of the custom metric by that name. If a custom metric with that name does not exist will return zero. - -Signature: - -```typescript -getMetric(metricName: string): number; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| metricName | string | Name of the custom metric. | - -Returns: - -number - diff --git a/docs-exp/performance-types.performancetrace.incrementmetric.md b/docs-exp/performance-types.performancetrace.incrementmetric.md deleted file mode 100644 index 94644031e3b..00000000000 --- a/docs-exp/performance-types.performancetrace.incrementmetric.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [@firebase/performance-types](./performance-types.md) > [PerformanceTrace](./performance-types.performancetrace.md) > [incrementMetric](./performance-types.performancetrace.incrementmetric.md) - -## PerformanceTrace.incrementMetric() method - -Adds to the value of a custom metric. If a custom metric with the provided name does not exist, it creates one with that name and the value equal to the given number. The value will be floored down to an integer. - -Signature: - -```typescript -incrementMetric(metricName: string, num?: number): void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| metricName | string | The name of the custom metric. | -| num | number | The number to be added to the value of the custom metric. If not provided, it uses a default value of one. | - -Returns: - -void - diff --git a/docs-exp/performance-types.performancetrace.md b/docs-exp/performance-types.performancetrace.md deleted file mode 100644 index bdbfbc03ce8..00000000000 --- a/docs-exp/performance-types.performancetrace.md +++ /dev/null @@ -1,27 +0,0 @@ - - -[Home](./index.md) > [@firebase/performance-types](./performance-types.md) > [PerformanceTrace](./performance-types.performancetrace.md) - -## PerformanceTrace interface - -Signature: - -```typescript -export interface PerformanceTrace -``` - -## Methods - -| Method | Description | -| --- | --- | -| [getAttribute(attr)](./performance-types.performancetrace.getattribute.md) | Retrieves the value which a custom attribute is set to. | -| [getAttributes()](./performance-types.performancetrace.getattributes.md) | Returns a map of all custom attributes of a trace instance. | -| [getMetric(metricName)](./performance-types.performancetrace.getmetric.md) | Returns the value of the custom metric by that name. If a custom metric with that name does not exist will return zero. | -| [incrementMetric(metricName, num)](./performance-types.performancetrace.incrementmetric.md) | Adds to the value of a custom metric. If a custom metric with the provided name does not exist, it creates one with that name and the value equal to the given number. The value will be floored down to an integer. | -| [putAttribute(attr, value)](./performance-types.performancetrace.putattribute.md) | Set a custom attribute of a trace to a certain value. | -| [putMetric(metricName, num)](./performance-types.performancetrace.putmetric.md) | Sets the value of the specified custom metric to the given number regardless of whether a metric with that name already exists on the trace instance or not. The value will be floored down to an integer. | -| [record(startTime, duration, options)](./performance-types.performancetrace.record.md) | Records a trace from given parameters. This provides a direct way to use trace without a need to start/stop. This is useful for use cases in which the trace cannot directly be used (e.g. if the duration was captured before the Performance SDK was loaded). | -| [removeAttribute(attr)](./performance-types.performancetrace.removeattribute.md) | Removes the specified custom attribute from a trace instance. | -| [start()](./performance-types.performancetrace.start.md) | Starts the timing for the trace instance. | -| [stop()](./performance-types.performancetrace.stop.md) | Stops the timing of the trace instance and logs the data of the instance. | - diff --git a/docs-exp/performance-types.performancetrace.putattribute.md b/docs-exp/performance-types.performancetrace.putattribute.md deleted file mode 100644 index 1ca92e74305..00000000000 --- a/docs-exp/performance-types.performancetrace.putattribute.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [@firebase/performance-types](./performance-types.md) > [PerformanceTrace](./performance-types.performancetrace.md) > [putAttribute](./performance-types.performancetrace.putattribute.md) - -## PerformanceTrace.putAttribute() method - -Set a custom attribute of a trace to a certain value. - -Signature: - -```typescript -putAttribute(attr: string, value: string): void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| attr | string | Name of the custom attribute. | -| value | string | Value of the custom attribute. | - -Returns: - -void - diff --git a/docs-exp/performance-types.performancetrace.putmetric.md b/docs-exp/performance-types.performancetrace.putmetric.md deleted file mode 100644 index 382d5995fff..00000000000 --- a/docs-exp/performance-types.performancetrace.putmetric.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [@firebase/performance-types](./performance-types.md) > [PerformanceTrace](./performance-types.performancetrace.md) > [putMetric](./performance-types.performancetrace.putmetric.md) - -## PerformanceTrace.putMetric() method - -Sets the value of the specified custom metric to the given number regardless of whether a metric with that name already exists on the trace instance or not. The value will be floored down to an integer. - -Signature: - -```typescript -putMetric(metricName: string, num: number): void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| metricName | string | Name of the custom metric. | -| num | number | Value to of the custom metric. | - -Returns: - -void - diff --git a/docs-exp/performance-types.performancetrace.record.md b/docs-exp/performance-types.performancetrace.record.md deleted file mode 100644 index e0db9fceeb9..00000000000 --- a/docs-exp/performance-types.performancetrace.record.md +++ /dev/null @@ -1,33 +0,0 @@ - - -[Home](./index.md) > [@firebase/performance-types](./performance-types.md) > [PerformanceTrace](./performance-types.performancetrace.md) > [record](./performance-types.performancetrace.record.md) - -## PerformanceTrace.record() method - -Records a trace from given parameters. This provides a direct way to use trace without a need to start/stop. This is useful for use cases in which the trace cannot directly be used (e.g. if the duration was captured before the Performance SDK was loaded). - -Signature: - -```typescript -record( - startTime: number, - duration: number, - options?: { - metrics?: { [key: string]: number }; - attributes?: { [key: string]: string }; - } - ): void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| startTime | number | trace start time since epoch in millisec. | -| duration | number | The duraction of the trace in millisec. | -| options | { metrics?: { \[key: string\]: number }; attributes?: { \[key: string\]: string }; } | An object which can optionally hold maps of custom metrics and custom attributes. | - -Returns: - -void - diff --git a/docs-exp/performance-types.performancetrace.removeattribute.md b/docs-exp/performance-types.performancetrace.removeattribute.md deleted file mode 100644 index fe780253110..00000000000 --- a/docs-exp/performance-types.performancetrace.removeattribute.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [@firebase/performance-types](./performance-types.md) > [PerformanceTrace](./performance-types.performancetrace.md) > [removeAttribute](./performance-types.performancetrace.removeattribute.md) - -## PerformanceTrace.removeAttribute() method - -Removes the specified custom attribute from a trace instance. - -Signature: - -```typescript -removeAttribute(attr: string): void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| attr | string | Name of the custom attribute. | - -Returns: - -void - diff --git a/docs-exp/performance-types.performancetrace.start.md b/docs-exp/performance-types.performancetrace.start.md deleted file mode 100644 index ad3a01a29a3..00000000000 --- a/docs-exp/performance-types.performancetrace.start.md +++ /dev/null @@ -1,17 +0,0 @@ - - -[Home](./index.md) > [@firebase/performance-types](./performance-types.md) > [PerformanceTrace](./performance-types.performancetrace.md) > [start](./performance-types.performancetrace.start.md) - -## PerformanceTrace.start() method - -Starts the timing for the trace instance. - -Signature: - -```typescript -start(): void; -``` -Returns: - -void - diff --git a/docs-exp/performance-types.performancetrace.stop.md b/docs-exp/performance-types.performancetrace.stop.md deleted file mode 100644 index a4e2ed2f5d0..00000000000 --- a/docs-exp/performance-types.performancetrace.stop.md +++ /dev/null @@ -1,17 +0,0 @@ - - -[Home](./index.md) > [@firebase/performance-types](./performance-types.md) > [PerformanceTrace](./performance-types.performancetrace.md) > [stop](./performance-types.performancetrace.stop.md) - -## PerformanceTrace.stop() method - -Stops the timing of the trace instance and logs the data of the instance. - -Signature: - -```typescript -stop(): void; -``` -Returns: - -void - diff --git a/docs-exp/performance.getperformance.md b/docs-exp/performance.getperformance.md deleted file mode 100644 index a6d57622aa7..00000000000 --- a/docs-exp/performance.getperformance.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [@firebase/performance](./performance.md) > [getPerformance](./performance.getperformance.md) - -## getPerformance() function - -Returns a FirebasePerformance instance for the given app. - -Signature: - -```typescript -export declare function getPerformance(app: FirebaseApp, settings?: PerformanceSettings): FirebasePerformance; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| app | [FirebaseApp](./app-types.firebaseapp.md) | The FirebaseApp to use. | -| settings | [PerformanceSettings](./performance-types.performancesettings.md) | Optional settings for the Performance instance. | - -Returns: - -[FirebasePerformance](./performance-types.firebaseperformance.md) - diff --git a/docs-exp/performance.md b/docs-exp/performance.md deleted file mode 100644 index aa70a5ac4a5..00000000000 --- a/docs-exp/performance.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [@firebase/performance](./performance.md) - -## performance package - -## Functions - -| Function | Description | -| --- | --- | -| [getPerformance(app, settings)](./performance.getperformance.md) | Returns a FirebasePerformance instance for the given app. | -| [trace(performance, name)](./performance.trace.md) | Returns a new PerformanceTrace instance. | - diff --git a/docs-exp/performance.trace.md b/docs-exp/performance.trace.md deleted file mode 100644 index 7787886004a..00000000000 --- a/docs-exp/performance.trace.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [@firebase/performance](./performance.md) > [trace](./performance.trace.md) - -## trace() function - -Returns a new PerformanceTrace instance. - -Signature: - -```typescript -export declare function trace(performance: FirebasePerformance, name: string): PerformanceTrace; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| performance | [FirebasePerformance](./performance-types.firebaseperformance.md) | The FirebasePerformance instance to use. | -| name | string | The name of the trace. | - -Returns: - -[PerformanceTrace](./performance-types.performancetrace.md) - diff --git a/integration/compat-interop/analytics.test.ts b/integration/compat-interop/analytics.test.ts new file mode 100644 index 00000000000..637e533286d --- /dev/null +++ b/integration/compat-interop/analytics.test.ts @@ -0,0 +1,35 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getModularInstance } from '@firebase/util'; +import { expect } from 'chai'; +import { getAnalytics } from '@firebase/analytics-exp'; +import firebase from '@firebase/app-compat'; +import '@firebase/analytics-compat'; + +import { TEST_PROJECT_CONFIG } from './util'; + +firebase.initializeApp(TEST_PROJECT_CONFIG); + +const compatAnalytics = firebase.analytics(); +const modularAnalytics = getAnalytics(); + +describe('Analytics compat interop', () => { + it('Analytics compat instance references modular Analytics instance', () => { + expect(getModularInstance(compatAnalytics)).to.equal(modularAnalytics); + }); +}); diff --git a/integration/compat-interop/app.test.ts b/integration/compat-interop/app.test.ts new file mode 100644 index 00000000000..4a79c4b5bc3 --- /dev/null +++ b/integration/compat-interop/app.test.ts @@ -0,0 +1,50 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getModularInstance } from '@firebase/util'; +import { expect } from 'chai'; +import { getApp, getApps } from '@firebase/app-exp'; +import firebase from '@firebase/app-compat'; + +import { TEST_PROJECT_CONFIG } from './util'; +//TODO: add Storage, Firestore and Database tests once v8 is removed. Currently it's too difficult to set them up in integration tests. +describe('App compat interop', () => { + afterEach(() => { + const deletePromises = []; + for (const app of firebase.apps) { + deletePromises.push(app.delete()); + } + + return Promise.all(deletePromises); + }); + + it('App compat instance references modular App instance', () => { + const compatApp = firebase.initializeApp(TEST_PROJECT_CONFIG); + const modularApp = getApp(); + expect(getModularInstance(compatApp)).to.equal(modularApp); + }); + + it('deleting compat app deletes modular app', async () => { + const compatApp = firebase.initializeApp(TEST_PROJECT_CONFIG); + expect(firebase.apps.length).to.equal(1); + expect(getApps().length).to.equal(1); + + await compatApp.delete(); + expect(firebase.apps.length).to.equal(0); + expect(getApps().length).to.equal(0); + }); +}); diff --git a/integration/compat-interop/auth.test.ts b/integration/compat-interop/auth.test.ts new file mode 100644 index 00000000000..b15b368ed22 --- /dev/null +++ b/integration/compat-interop/auth.test.ts @@ -0,0 +1,49 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getModularInstance } from '@firebase/util'; +import { expect } from 'chai'; +import { getAuth, signOut } from '@firebase/auth-exp'; +import firebase from '@firebase/app-compat'; +import '@firebase/auth-compat'; + +import { TEST_PROJECT_CONFIG } from './util'; + +firebase.initializeApp(TEST_PROJECT_CONFIG); + +const compatAuth = firebase.auth(); +const modularAuth = getAuth(); + +describe('Auth compat interop', () => { + it('Auth compat instance references modular Auth instance', () => { + expect(getModularInstance(compatAuth)).to.equal(modularAuth); + }); + + it('Auth compat and modular Auth share the same user state', async () => { + expect(compatAuth.currentUser).to.equal(null); + expect(modularAuth.currentUser).to.equal(null); + const userCred = await compatAuth.signInAnonymously(); + expect(userCred.user?.uid).to.equal(modularAuth.currentUser?.uid); + expect(await userCred.user?.getIdToken()).to.equal( + await modularAuth.currentUser?.getIdToken() + ); + + await signOut(modularAuth); + expect(compatAuth.currentUser).to.equal(null); + expect(modularAuth.currentUser).to.equal(null); + }); +}); diff --git a/integration/compat-interop/functions.test.ts b/integration/compat-interop/functions.test.ts new file mode 100644 index 00000000000..327fd8bde94 --- /dev/null +++ b/integration/compat-interop/functions.test.ts @@ -0,0 +1,35 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getModularInstance } from '@firebase/util'; +import { expect } from 'chai'; +import { getFunctions } from '@firebase/functions-exp'; +import firebase from '@firebase/app-compat'; +import '@firebase/functions-compat'; + +import { TEST_PROJECT_CONFIG } from './util'; + +firebase.initializeApp(TEST_PROJECT_CONFIG); + +const compatFunction = firebase.functions(); +const modularFunctions = getFunctions(); + +describe('Functions compat interop', () => { + it('Functions compat instance references modular Functions instance', () => { + expect(getModularInstance(compatFunction)).to.equal(modularFunctions); + }); +}); diff --git a/integration/compat-interop/karma.conf.js b/integration/compat-interop/karma.conf.js new file mode 100644 index 00000000000..6dde3b61344 --- /dev/null +++ b/integration/compat-interop/karma.conf.js @@ -0,0 +1,36 @@ +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const karma = require('karma'); +const karmaBase = require('../../config/karma.base'); + +const files = ['*.test.*']; + +module.exports = function (config) { + const karmaConfig = Object.assign({}, karmaBase, { + // files to load into karma + files: files, + preprocessors: { '**/*.ts': ['webpack', 'sourcemap'] }, + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['mocha'] + }); + + config.set(karmaConfig); +}; + +module.exports.files = files; diff --git a/integration/compat-interop/messaging.test.ts b/integration/compat-interop/messaging.test.ts new file mode 100644 index 00000000000..88758d2e3e3 --- /dev/null +++ b/integration/compat-interop/messaging.test.ts @@ -0,0 +1,35 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getModularInstance } from '@firebase/util'; +import { expect } from 'chai'; +import { getMessaging } from '@firebase/messaging-exp'; +import firebase from '@firebase/app-compat'; +import '@firebase/messaging-compat'; + +import { TEST_PROJECT_CONFIG } from './util'; + +firebase.initializeApp(TEST_PROJECT_CONFIG); + +const compatMessaging = firebase.messaging(); +const modularMessaging = getMessaging(); + +describe('Messaging compat interop', () => { + it('Messaging compat instance references modular Messaging instance', () => { + expect(getModularInstance(compatMessaging)).to.equal(modularMessaging); + }); +}); diff --git a/integration/compat-interop/package.json b/integration/compat-interop/package.json new file mode 100644 index 00000000000..eb0f2fed166 --- /dev/null +++ b/integration/compat-interop/package.json @@ -0,0 +1,29 @@ +{ + "name": "firebase-compat-interop-test", + "private": true, + "version": "0.1.0", + "scripts": { + "test": "karma start --single-run", + "test:ci": "node ../../scripts/run_tests_in_ci.js -s test", + "test:debug": "karma start --browsers Chrome --auto-watch" + }, + "dependencies": { + "@firebase/app-exp": "0.0.900", + "@firebase/app-compat": "0.0.900", + "@firebase/analytics-exp": "0.0.900", + "@firebase/analytics-compat": "0.0.900", + "@firebase/auth-exp": "0.0.900", + "@firebase/auth-compat": "0.0.900", + "@firebase/functions-exp": "0.0.900", + "@firebase/functions-compat": "0.0.900", + "@firebase/messaging-exp": "0.0.900", + "@firebase/messaging-compat": "0.0.900", + "@firebase/performance-exp": "0.0.900", + "@firebase/performance-compat": "0.0.900", + "@firebase/remote-config-exp": "0.0.900", + "@firebase/remote-config-compat": "0.0.900" + }, + "devDependencies": { + "typescript": "4.2.2" + } +} \ No newline at end of file diff --git a/integration/compat-interop/performance.test.ts b/integration/compat-interop/performance.test.ts new file mode 100644 index 00000000000..6820b0cb368 --- /dev/null +++ b/integration/compat-interop/performance.test.ts @@ -0,0 +1,60 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getModularInstance } from '@firebase/util'; +import { expect } from 'chai'; +import { getPerformance } from '@firebase/performance-exp'; +import firebase from '@firebase/app-compat'; +import '@firebase/performance-compat'; + +import { TEST_PROJECT_CONFIG } from './util'; + +firebase.initializeApp(TEST_PROJECT_CONFIG); + +const compatPerf = firebase.performance(); +const modularPerf = getPerformance(); + +describe('Performance compat interop', () => { + it('Performance compat instance references modular Performance instance', () => { + expect(getModularInstance(compatPerf)).to.equal(modularPerf); + }); + + it('Performance compat and modular Performance instance share the same configuration', () => { + expect(compatPerf.dataCollectionEnabled).to.equal(true); + expect(compatPerf.instrumentationEnabled).to.equal(true); + expect(modularPerf.dataCollectionEnabled).to.equal(true); + expect(modularPerf.instrumentationEnabled).to.equal(true); + + // change settings on the compat instance + compatPerf.dataCollectionEnabled = false; + compatPerf.instrumentationEnabled = false; + + expect(compatPerf.dataCollectionEnabled).to.equal(false); + expect(compatPerf.instrumentationEnabled).to.equal(false); + expect(modularPerf.dataCollectionEnabled).to.equal(false); + expect(modularPerf.instrumentationEnabled).to.equal(false); + + // change settings on the modular instance + modularPerf.dataCollectionEnabled = true; + modularPerf.instrumentationEnabled = true; + + expect(compatPerf.dataCollectionEnabled).to.equal(true); + expect(compatPerf.instrumentationEnabled).to.equal(true); + expect(modularPerf.dataCollectionEnabled).to.equal(true); + expect(modularPerf.instrumentationEnabled).to.equal(true); + }); +}); diff --git a/integration/compat-interop/remote-config.test.ts b/integration/compat-interop/remote-config.test.ts new file mode 100644 index 00000000000..580832aee12 --- /dev/null +++ b/integration/compat-interop/remote-config.test.ts @@ -0,0 +1,35 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getModularInstance } from '@firebase/util'; +import { expect } from 'chai'; +import { getRemoteConfig } from '@firebase/remote-config-exp'; +import firebase from '@firebase/app-compat'; +import '@firebase/remote-config-compat'; + +import { TEST_PROJECT_CONFIG } from './util'; + +firebase.initializeApp(TEST_PROJECT_CONFIG); + +const compatRC = firebase.remoteConfig(); +const modularRC = getRemoteConfig(); + +describe('RC compat interop', () => { + it('RC compat instance references modular RC instance', () => { + expect(getModularInstance(compatRC)).to.equal(modularRC); + }); +}); diff --git a/integration/compat-interop/tsconfig.json b/integration/compat-interop/tsconfig.json new file mode 100644 index 00000000000..4da78214594 --- /dev/null +++ b/integration/compat-interop/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../config/tsconfig.base.json", + "compileOnSave": false, + "compilerOptions": { + "allowJs": true, + "declaration": false, + "module": "commonjs", + "moduleResolution": "node", + "noImplicitAny": true, + "outDir": "dist", + "target": "ES5", + "sourceMap": true, + "esModuleInterop": true + }, + "exclude": [ + "node_modules", + "dist" + ] +} diff --git a/integration/compat-interop/util.ts b/integration/compat-interop/util.ts new file mode 100644 index 00000000000..68734591c6e --- /dev/null +++ b/integration/compat-interop/util.ts @@ -0,0 +1,18 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const TEST_PROJECT_CONFIG = require('../../config/project.json'); diff --git a/integration/compat-typings/package.json b/integration/compat-typings/package.json new file mode 100644 index 00000000000..ec6f6257eb8 --- /dev/null +++ b/integration/compat-typings/package.json @@ -0,0 +1,15 @@ +{ + "name": "firebase-compat-typings-test", + "private": true, + "version": "0.1.0", + "scripts": { + "test": "tsc", + "test:ci": "node ../../scripts/run_tests_in_ci.js -s test" + }, + "dependencies": { + "firebase-exp": "file:../../packages-exp/firebase-exp" + }, + "devDependencies": { + "typescript": "4.2.2" + } +} \ No newline at end of file diff --git a/integration/compat-typings/tsconfig.json b/integration/compat-typings/tsconfig.json new file mode 100644 index 00000000000..e8b1681f407 --- /dev/null +++ b/integration/compat-typings/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../config/tsconfig.base.json", + "compileOnSave": false, + "compilerOptions": { + "outDir": "dist" + }, + "include": ["typings.ts"], + "exclude": [ + "dist" + ] +} diff --git a/integration/compat-typings/typings.ts b/integration/compat-typings/typings.ts new file mode 100644 index 00000000000..4bfa5152089 --- /dev/null +++ b/integration/compat-typings/typings.ts @@ -0,0 +1,85 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import firebase from 'firebase-exp/compat'; +import { FirebaseAuth, User } from '@firebase/auth-types'; +import { FirebaseAnalytics } from '@firebase/analytics-types'; +import { FirebaseApp } from '@firebase/app-compat'; +import { + FirebaseFirestore, + DocumentReference, + CollectionReference +} from '@firebase/firestore-types'; +import { FirebaseFunctions } from '@firebase/functions-types'; +import { FirebaseInstallations } from '@firebase/installations-types'; +// Get type directly from messaging package, messaging-compat does not implement +// the current messaging API. +import { MessagingCompat } from '../../packages-exp/messaging-compat/src/messaging-compat'; +import { FirebasePerformance } from '@firebase/performance-types'; +import { RemoteConfig } from '@firebase/remote-config-types'; +import { + FirebaseStorage, + Reference as StorageReference +} from '@firebase/storage-types'; +import { FirebaseDatabase, Reference, Query } from '@firebase/database-types'; + +/** + * Test namespaced types from compat/index.d.ts against the types used in + * implementation of the actual compat services. In almost all cases the services + * implement types found in the corresponding -types package. The only exceptions + * are + * - messaging, which has changed its public API in the compat version + * - app-compat, which defines its FirebaseApp type in its own package + */ + +const compatTypes: { + auth: FirebaseAuth; + analytics: FirebaseAnalytics; + app: FirebaseApp; + firestore: FirebaseFirestore; + functions: FirebaseFunctions; + installations?: FirebaseInstallations; + messaging: MessagingCompat; + performance: FirebasePerformance; + remoteConfig: RemoteConfig; + storage: FirebaseStorage; + storageReference: StorageReference; + firestoreDocumentReference: DocumentReference; + firestoreCollectionReference: CollectionReference; + authUser: User; + database: FirebaseDatabase; + databaseReference: Reference; + databaseQuery: Query; +} = { + auth: firebase.auth(), + analytics: firebase.analytics(), + app: firebase.initializeApp({}), + firestore: firebase.firestore(), + functions: firebase.functions(), + installations: {} as firebase.installations.Installations, + messaging: firebase.messaging(), + performance: firebase.performance(), + remoteConfig: firebase.remoteConfig(), + storage: firebase.storage(), + storageReference: {} as firebase.storage.Reference, + firestoreDocumentReference: {} as firebase.firestore.DocumentReference, + firestoreCollectionReference: {} as firebase.firestore.CollectionReference, + authUser: {} as firebase.User, + database: {} as firebase.database.Database, + databaseReference: {} as firebase.database.Reference, + databaseQuery: {} as firebase.database.Query +}; diff --git a/integration/firebase/package.json b/integration/firebase/package.json index b40ff02e341..efb62e3332f 100644 --- a/integration/firebase/package.json +++ b/integration/firebase/package.json @@ -7,22 +7,19 @@ "test:ci": "node ../../scripts/run_tests_in_ci.js -s test" }, "devDependencies": { - "firebase": "7.24.0", - "@babel/core": "7.11.6", - "@babel/preset-env": "7.11.5", - "@types/chai": "4.2.13", + "firebase": "8.7.1", + "@types/chai": "4.2.14", "@types/mocha": "7.0.2", - "chai": "4.2.0", + "chai": "4.3.4", "karma": "5.2.3", "karma-babel-preprocessor": "8.0.1", "karma-chrome-launcher": "3.1.0", - "karma-firefox-launcher": "1.3.0", + "karma-firefox-launcher": "2.1.0", "karma-mocha": "2.0.1", - "karma-sauce-launcher": "1.2.0", "karma-spec-reporter": "0.0.32", - "karma-typescript": "5.2.0", + "karma-typescript": "5.5.1", "mocha": "7.2.0", "npm-run-all": "4.1.5", - "typescript": "4.0.2" + "typescript": "4.2.2" } } diff --git a/integration/firebase/test/namespace.test.ts b/integration/firebase/test/namespace.test.ts index ef6dce22e4b..42d4eec6390 100644 --- a/integration/firebase/test/namespace.test.ts +++ b/integration/firebase/test/namespace.test.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import * as firebase from 'firebase'; +import firebase from 'firebase'; import * as namespaceDefinition from './namespaceDefinition.json'; import validateNamespace from './validator'; diff --git a/integration/firestore/firebase_export.ts b/integration/firestore/firebase_export.ts index 4eeb1c7bfa9..92d28a23709 100644 --- a/integration/firestore/firebase_export.ts +++ b/integration/firestore/firebase_export.ts @@ -17,6 +17,7 @@ import firebase from '@firebase/app'; import '@firebase/firestore'; +import '@firebase/firestore/bundle'; import { FirebaseApp } from '@firebase/app-types'; import { Settings, FirebaseFirestore } from '@firebase/firestore-types'; @@ -49,11 +50,22 @@ export function usesFunctionalApi(): false { return false; } -const Firestore = firebase.firestore.Firestore; +const Blob = firebase.firestore.Blob; +const DocumentReference = firebase.firestore.DocumentReference; const FieldPath = firebase.firestore.FieldPath; -const Timestamp = firebase.firestore.Timestamp; -const GeoPoint = firebase.firestore.GeoPoint; const FieldValue = firebase.firestore.FieldValue; -const Blob = firebase.firestore.Blob; +const Firestore = firebase.firestore.Firestore; +const GeoPoint = firebase.firestore.GeoPoint; +const QueryDocumentSnapshot = firebase.firestore.QueryDocumentSnapshot; +const Timestamp = firebase.firestore.Timestamp; -export { Firestore, FieldValue, FieldPath, Timestamp, Blob, GeoPoint }; +export { + Blob, + DocumentReference, + FieldPath, + FieldValue, + Firestore, + GeoPoint, + QueryDocumentSnapshot, + Timestamp +}; diff --git a/integration/firestore/firebase_export_memory.ts b/integration/firestore/firebase_export_memory.ts index 441c778ef5a..063113e7409 100644 --- a/integration/firestore/firebase_export_memory.ts +++ b/integration/firestore/firebase_export_memory.ts @@ -17,6 +17,7 @@ import firebase from '@firebase/app'; import '@firebase/firestore/memory'; +import '@firebase/firestore/memory-bundle'; import { FirebaseApp } from '@firebase/app-types'; import { Settings, FirebaseFirestore } from '@firebase/firestore-types'; @@ -49,11 +50,22 @@ export function usesFunctionalApi(): false { return false; } -const Firestore = firebase.firestore.Firestore; +const Blob = firebase.firestore.Blob; +const DocumentReference = firebase.firestore.DocumentReference; const FieldPath = firebase.firestore.FieldPath; -const Timestamp = firebase.firestore.Timestamp; -const GeoPoint = firebase.firestore.GeoPoint; const FieldValue = firebase.firestore.FieldValue; -const Blob = firebase.firestore.Blob; +const Firestore = firebase.firestore.Firestore; +const GeoPoint = firebase.firestore.GeoPoint; +const QueryDocumentSnapshot = firebase.firestore.QueryDocumentSnapshot; +const Timestamp = firebase.firestore.Timestamp; -export { Firestore, FieldValue, FieldPath, Timestamp, Blob, GeoPoint }; +export { + Blob, + DocumentReference, + FieldPath, + FieldValue, + Firestore, + GeoPoint, + QueryDocumentSnapshot, + Timestamp +}; diff --git a/integration/firestore/package.json b/integration/firestore/package.json index 77fbf5701c6..6d36837403d 100644 --- a/integration/firestore/package.json +++ b/integration/firestore/package.json @@ -9,26 +9,26 @@ "test": "yarn build:memory; karma start --single-run; yarn build:persistence; karma start --single-run;", "test:ci": "node ../../scripts/run_tests_in_ci.js -s test", "test:persistence": " yarn build:persistence; karma start --single-run", - "test:persistence:debug:": "yarn build:persistence; karma start --auto-watch --browsers Chrome", + "test:persistence:debug": "yarn build:persistence; karma start --auto-watch --browsers Chrome", "test:memory": "yarn build:memory; karma start --single-run", "test:memory:debug": "yarn build:memory; karma start --auto-watch --browsers Chrome" }, "devDependencies": { - "@firebase/app": "0.6.11", - "@firebase/firestore": "1.18.0", + "@firebase/app": "0.6.28", + "@firebase/firestore": "2.3.8", "@types/mocha": "7.0.2", "gulp": "4.0.2", "gulp-filter": "6.0.0", - "gulp-replace": "1.0.0", + "gulp-replace": "1.1.3", "karma": "5.2.3", "karma-chrome-launcher": "3.1.0", - "karma-firefox-launcher": "1.3.0", + "karma-firefox-launcher": "2.1.0", "karma-mocha": "2.0.1", "karma-spec-reporter": "0.0.32", "mocha": "7.2.0", - "ts-loader": "8.0.5", - "typescript": "4.0.2", - "webpack": "4.44.2", - "webpack-stream": "6.1.0" + "ts-loader": "8.3.0", + "typescript": "4.2.2", + "webpack": "4.46.0", + "webpack-stream": "6.1.1" } } diff --git a/integration/messaging/package.json b/integration/messaging/package.json index 68bf6dce297..252e38172d1 100644 --- a/integration/messaging/package.json +++ b/integration/messaging/package.json @@ -9,11 +9,11 @@ "test:manual": "mocha --exit" }, "devDependencies": { - "firebase": "7.24.0", - "chai": "4.2.0", - "chromedriver": "86.0.0", + "firebase": "8.7.1", + "chai": "4.3.4", + "chromedriver": "91.0.0", "express": "4.17.1", - "geckodriver": "1.20.0", + "geckodriver": "1.22.3", "mocha": "7.2.0", "node-fetch": "2.6.1", "selenium-assistant": "6.1.0" diff --git a/integration/messaging/test/test-deleteToken.js b/integration/messaging/test/test-deleteToken.js deleted file mode 100644 index cb6aa341088..00000000000 --- a/integration/messaging/test/test-deleteToken.js +++ /dev/null @@ -1,82 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const expect = require('chai').expect; -const testServer = require('./utils/test-server'); -const deleteToken = require('./utils/deleteToken'); -const clearAppForTest = require('./utils/clearAppForTest'); -const retrieveToken = require('./utils/retrieveToken'); -const seleniumAssistant = require('selenium-assistant'); -const createPermittedWebDriver = require('./utils/createPermittedWebDriver'); - -const TEST_SUITE_TIMEOUT_MS = 100000; -const TEST_DOMAIN = 'valid-vapid-key'; - -describe('Firebase Messaging Integration Tests > get and delete token', function () { - this.timeout(TEST_SUITE_TIMEOUT_MS); - this.retries(3); - let globalWebDriver; - - before(async function () { - await testServer.start(); - }); - - after(async function () { - await testServer.stop(); - }); - - const availableBrowsers = seleniumAssistant.getLocalBrowsers(); - availableBrowsers.forEach(assistantBrowser => { - // TODO: enable testing for firefox - if (assistantBrowser.getId() !== 'chrome') { - return; - } - - describe(`Testing browser: ${assistantBrowser.getPrettyName()} : ${TEST_DOMAIN}`, function () { - before(async function () { - // Use one webDriver per browser instead of one per test to speed up test. - globalWebDriver = createPermittedWebDriver( - /* browser= */ assistantBrowser.getId() - ); - - await globalWebDriver.get( - `${testServer.serverAddress}/${TEST_DOMAIN}/` - ); - }); - - after(async function () { - await seleniumAssistant.killWebDriver(globalWebDriver); - }); - - afterEach(async function () { - await clearAppForTest(globalWebDriver); - }); - - it(`Test app can delete a valid token`, async function () { - const token = await retrieveToken(globalWebDriver); - expect(token).to.exist; - try { - await deleteToken(globalWebDriver, token); - console.log('Token deletion succeed.'); - } catch (e) { - console.log('Error trying to delete FCM token: ', e); - fail(); - } - }); - }); - }); -}); diff --git a/integration/messaging/test/test-receive-background.js b/integration/messaging/test/test-receive-background.js new file mode 100644 index 00000000000..258bff32051 --- /dev/null +++ b/integration/messaging/test/test-receive-background.js @@ -0,0 +1,157 @@ +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const testServer = require('./utils/test-server'); +const sendMessage = require('./utils/sendMessage'); +const retrieveToken = require('./utils/retrieveToken'); +const seleniumAssistant = require('selenium-assistant'); +const getReceivedBackgroundMessages = require('./utils/getReceivedBackgroundMessages'); +const createPermittedWebDriver = require('./utils/createPermittedWebDriver'); +const checkSendResponse = require('./utils/checkSendResponse'); +const checkMessageReceived = require('./utils/checkMessageReceived'); +const openNewTab = require('./utils/openNewTab'); + +const TEST_DOMAINS = ['valid-vapid-key-modern-sw']; + +// 4 minutes. The fact that the flow includes making a request to the Send Service, +// storing/retrieving form indexedDb asynchronously makes these test units to have a execution time +// variance. Therefore, allowing these units to have a longer time to work is crucial. +const TIMEOUT_BACKGROUND_MESSAGE_TEST_UNIT_MILLISECONDS = 240000; + +// 1 minute. Wait for object store to be created and received message to be stored in idb. This +// waiting time MUST be longer than the wait time for adding to db in the sw. +const WAIT_TIME_BEFORE_RETRIEVING_BACKGROUND_MESSAGES_MILLISECONDS = 60000; + +const wait = ms => new Promise(res => setTimeout(res, ms)); + +// 50% of integration test run time is spent in testing receiving in background. Running these +// slower tests last so other tests can fail quickly if they do fail. +require('./test-token-delete'); +require('./test-token-update'); +require('./test-useValidManifest'); +require('./test-useDefaultServiceWorker'); +require('./test-receive-foreground'); + +describe('Firebase Messaging Integration Tests > Test Background Receive', function () { + this.retries(2); + let globalWebDriver; + + before(async function () { + await testServer.start(); + }); + + after(async function () { + await testServer.stop(); + }); + + // TODO: enable testing for firefox + seleniumAssistant.getLocalBrowsers().forEach(assistantBrowser => { + if (assistantBrowser.getId() !== 'chrome') { + return; + } + + TEST_DOMAINS.forEach(domain => { + describe(`Testing browser: ${assistantBrowser.getPrettyName()} : ${domain}`, function () { + before(async function () { + globalWebDriver = createPermittedWebDriver( + /* browser= */ assistantBrowser.getId() + ); + }); + + it('Background app can receive a {} empty message from sw', async function () { + this.timeout(TIMEOUT_BACKGROUND_MESSAGE_TEST_UNIT_MILLISECONDS); + + // Clearing the cache and db data by killing the previously instantiated driver. Note that + // ideally this call is placed inside the after/before hooks. However, Mocha forbids + // operations longer than 2s in hooks. Hence, this clearing call needs to be inside the + // test unit. + await seleniumAssistant.killWebDriver(globalWebDriver); + + globalWebDriver = createPermittedWebDriver( + /* browser= */ assistantBrowser.getId() + ); + + prepareBackgroundApp(globalWebDriver, domain); + + checkSendResponse( + await sendMessage({ + to: await retrieveToken(globalWebDriver) + }) + ); + + await wait( + WAIT_TIME_BEFORE_RETRIEVING_BACKGROUND_MESSAGES_MILLISECONDS + ); + + checkMessageReceived( + await getReceivedBackgroundMessages(globalWebDriver), + /* expectedNotificationPayload= */ null, + /* expectedDataPayload= */ null, + /* isLegacyPayload= */ false + ); + }); + + it('Background app can receive a {"data"} message frow sw', async function () { + this.timeout(TIMEOUT_BACKGROUND_MESSAGE_TEST_UNIT_MILLISECONDS); + + await seleniumAssistant.killWebDriver(globalWebDriver); + + globalWebDriver = createPermittedWebDriver( + /* browser= */ assistantBrowser.getId() + ); + + prepareBackgroundApp(globalWebDriver, domain); + + checkSendResponse( + await sendMessage({ + to: await retrieveToken(globalWebDriver), + data: getTestDataPayload() + }) + ); + + await wait( + WAIT_TIME_BEFORE_RETRIEVING_BACKGROUND_MESSAGES_MILLISECONDS + ); + + checkMessageReceived( + await getReceivedBackgroundMessages(globalWebDriver), + /* expectedNotificationPayload= */ null, + /* expectedDataPayload= */ getTestDataPayload() + ); + }); + }); + }); + }); +}); + +async function prepareBackgroundApp(globalWebDriver, domain) { + await globalWebDriver.get(`${testServer.serverAddress}/${domain}/`); + // TODO: remove the try/catch block once the underlying bug has been resolved. Shift window focus + // away from app window so that background messages can be received/processed + try { + await openNewTab(globalWebDriver); + } catch (err) { + // ChromeDriver seems to have an open bug which throws "JavascriptError: javascript error: + // circular reference". Nevertheless, a new tab can still be opened. Hence, just catch and + // continue here. + console.log('FCM (ignored on purpose): ' + err); + } +} + +function getTestDataPayload() { + return { hello: 'world' }; +} diff --git a/integration/messaging/test/test-receive-foreground.js b/integration/messaging/test/test-receive-foreground.js new file mode 100644 index 00000000000..8c23a07fae6 --- /dev/null +++ b/integration/messaging/test/test-receive-foreground.js @@ -0,0 +1,177 @@ +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const testServer = require('./utils/test-server'); +const sendMessage = require('./utils/sendMessage'); +const retrieveToken = require('./utils/retrieveToken'); +const seleniumAssistant = require('selenium-assistant'); +const checkSendResponse = require('./utils/checkSendResponse'); +const getReceivedForegroundMessages = require('./utils/getReceivedForegroundMessages'); +const checkMessageReceived = require('./utils/checkMessageReceived'); +const createPermittedWebDriver = require('./utils/createPermittedWebDriver'); + +// Only testing 'valid-vapid-key' because 'valid-vapid-key-modern-sw' has the same behavior +const TEST_DOMAINS = ['valid-vapid-key']; +const TIMEOUT_FOREGROUND_MESSAGE_TEST_UNIT_MILLISECONDS = 120000; + +// Getting and deleting token is the entry step of using FM SDK. Let it run first and fail quickly. +require('./test-token-delete'); + +describe('Firebase Messaging Integration Tests > Test Foreground Receive', function () { + this.retries(2); + let globalWebDriver; + + before(async function () { + await testServer.start(); + }); + + after(async function () { + await testServer.stop(); + }); + + // TODO: enable testing for firefox + seleniumAssistant.getLocalBrowsers().forEach(assistantBrowser => { + if (assistantBrowser.getId() !== 'chrome') { + return; + } + + TEST_DOMAINS.forEach(domain => { + describe(`Testing browser: ${assistantBrowser.getPrettyName()} : ${domain}`, function () { + before(async function () { + globalWebDriver = createPermittedWebDriver( + /* browser= */ assistantBrowser.getId() + ); + }); + }); + + it('Foreground app can receive a {} empty message in onMessage', async function () { + this.timeout(TIMEOUT_FOREGROUND_MESSAGE_TEST_UNIT_MILLISECONDS); + + await seleniumAssistant.killWebDriver(globalWebDriver); + + globalWebDriver = createPermittedWebDriver( + /* browser= */ assistantBrowser.getId() + ); + + await globalWebDriver.get(`${testServer.serverAddress}/${domain}/`); + + let token = await retrieveToken(globalWebDriver); + checkSendResponse( + await sendMessage({ + to: token + }) + ); + + await checkMessageReceived( + await getReceivedForegroundMessages(globalWebDriver), + /* expectedNotificationPayload= */ null, + /* expectedDataPayload= */ null + ); + }); + + it('Foreground app can receive a {"notification"} message in onMessage', async function () { + this.timeout(TIMEOUT_FOREGROUND_MESSAGE_TEST_UNIT_MILLISECONDS); + + await seleniumAssistant.killWebDriver(globalWebDriver); + + globalWebDriver = createPermittedWebDriver( + /* browser= */ assistantBrowser.getId() + ); + + await globalWebDriver.get(`${testServer.serverAddress}/${domain}/`); + + checkSendResponse( + await sendMessage({ + to: await retrieveToken(globalWebDriver), + notification: getTestNotificationPayload() + }) + ); + + await checkMessageReceived( + await getReceivedForegroundMessages(globalWebDriver), + /* expectedNotificationPayload= */ getTestNotificationPayload(), + /* expectedDataPayload= */ null + ); + }); + + it('Foreground app can receive a {"data"} message in onMessage', async function () { + this.timeout(TIMEOUT_FOREGROUND_MESSAGE_TEST_UNIT_MILLISECONDS); + + await seleniumAssistant.killWebDriver(globalWebDriver); + + globalWebDriver = createPermittedWebDriver( + /* browser= */ assistantBrowser.getId() + ); + + await globalWebDriver.get(`${testServer.serverAddress}/${domain}/`); + + checkSendResponse( + await sendMessage({ + to: await retrieveToken(globalWebDriver), + data: getTestDataPayload() + }) + ); + + await checkMessageReceived( + await getReceivedForegroundMessages(globalWebDriver), + /* expectedNotificationPayload= */ null, + /* expectedDataPayload= */ getTestDataPayload() + ); + }); + + it('Foreground app can receive a {"notification", "data"} message in onMessage', async function () { + this.timeout(TIMEOUT_FOREGROUND_MESSAGE_TEST_UNIT_MILLISECONDS); + + await seleniumAssistant.killWebDriver(globalWebDriver); + + globalWebDriver = createPermittedWebDriver( + /* browser= */ assistantBrowser.getId() + ); + + await globalWebDriver.get(`${testServer.serverAddress}/${domain}/`); + + checkSendResponse( + await sendMessage({ + to: await retrieveToken(globalWebDriver), + data: getTestDataPayload(), + notification: getTestNotificationPayload() + }) + ); + + await checkMessageReceived( + await getReceivedForegroundMessages(globalWebDriver), + /* expectedNotificationPayload= */ getTestNotificationPayload(), + /* expectedDataPayload= */ getTestDataPayload() + ); + }); + }); + }); +}); + +function getTestDataPayload() { + return { hello: 'world' }; +} + +function getTestNotificationPayload() { + return { + title: 'test title', + body: 'test body', + icon: '/test/icon.png', + click_action: '/', + tag: 'test-tag' + }; +} diff --git a/integration/messaging/test/test-send.js b/integration/messaging/test/test-send.js deleted file mode 100644 index 93ead480819..00000000000 --- a/integration/messaging/test/test-send.js +++ /dev/null @@ -1,302 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const expect = require('chai').expect; -const testServer = require('./utils/test-server'); -const sendMessage = require('./utils/sendMessage'); -const retrieveToken = require('./utils/retrieveToken'); -const seleniumAssistant = require('selenium-assistant'); -const getReceivedBackgroundMessages = require('./utils/getReceivedBackgroundMessages'); -const getReceivedForegroundMessages = require('./utils/getReceivedForegroundMessages'); -const openNewTab = require('./utils/openNewTab'); -const createPermittedWebDriver = require('./utils/createPermittedWebDriver'); - -const TEST_DOMAINS = ['valid-vapid-key', 'valid-vapid-key-modern-sw']; -const TEST_PROJECT_SENDER_ID = '750970317741'; -const DEFAULT_COLLAPSE_KEY_VALUE = 'do_not_collapse'; -const FIELD_FROM = 'from'; -const FIELD_COLLAPSE_KEY_LEGACY = 'collapse_key'; -const FIELD_COLLAPSE_KEY = 'collapseKey'; - -const FIELD_DATA = 'data'; -const FIELD_NOTIFICATION = 'notification'; - -// 4 minutes. The fact that the flow includes making a request to the Send Service, -// storing/retrieving form indexedDb asynchronously makes these test units to have a execution time -// variance. Therefore, allowing these units to have a longer time to work is crucial. -const TIMEOUT_BACKGROUND_MESSAGE_TEST_UNIT_MILLISECONDS = 240000; - -const TIMEOUT_FOREGROUND_MESSAGE_TEST_UNIT_MILLISECONDS = 120000; - -// 1 minute. Wait for object store to be created and received message to be stored in idb. This -// waiting time MUST be longer than the wait time for adding to db in the sw. -const WAIT_TIME_BEFORE_RETRIEVING_BACKGROUND_MESSAGES_MILLISECONDS = 60000; - -const wait = ms => new Promise(res => setTimeout(res, ms)); - -describe('Starting Integration Test > Sending and Receiving ', function () { - this.retries(3); - let globalWebDriver; - - before(async function () { - await testServer.start(); - }); - - after(async function () { - await testServer.stop(); - }); - - // TODO: enable testing for firefox - seleniumAssistant.getLocalBrowsers().forEach(assistantBrowser => { - if (assistantBrowser.getId() !== 'chrome') { - return; - } - - TEST_DOMAINS.forEach(domain => { - describe(`Testing browser: ${assistantBrowser.getPrettyName()} : ${domain}`, function () { - before(async function () { - globalWebDriver = createPermittedWebDriver( - /* browser= */ assistantBrowser.getId() - ); - }); - - it('Background app can receive a {} empty message from sw', async function () { - this.timeout(TIMEOUT_BACKGROUND_MESSAGE_TEST_UNIT_MILLISECONDS); - - // Clearing the cache and db data by killing the previously instantiated driver. Note that - // ideally this call is placed inside the after/before hooks. However, Mocha forbids - // operations longer than 2s in hooks. Hence, this clearing call needs to be inside the - // test unit. - await seleniumAssistant.killWebDriver(globalWebDriver); - - globalWebDriver = createPermittedWebDriver( - /* browser= */ assistantBrowser.getId() - ); - - prepareBackgroundApp(globalWebDriver, domain); - - checkSendResponse( - await sendMessage({ - to: await retrieveToken(globalWebDriver) - }) - ); - - await wait( - WAIT_TIME_BEFORE_RETRIEVING_BACKGROUND_MESSAGES_MILLISECONDS - ); - - checkMessageReceived( - await getReceivedBackgroundMessages(globalWebDriver), - /* expectedNotificationPayload= */ null, - /* expectedDataPayload= */ null, - /* isLegacyPayload= */ false - ); - }); - - it('Background app can receive a {"data"} message frow sw', async function () { - this.timeout(TIMEOUT_BACKGROUND_MESSAGE_TEST_UNIT_MILLISECONDS); - - await seleniumAssistant.killWebDriver(globalWebDriver); - - globalWebDriver = createPermittedWebDriver( - /* browser= */ assistantBrowser.getId() - ); - - prepareBackgroundApp(globalWebDriver, domain); - - checkSendResponse( - await sendMessage({ - to: await retrieveToken(globalWebDriver), - data: getTestDataPayload() - }) - ); - - await wait( - WAIT_TIME_BEFORE_RETRIEVING_BACKGROUND_MESSAGES_MILLISECONDS - ); - - checkMessageReceived( - await getReceivedBackgroundMessages(globalWebDriver), - /* expectedNotificationPayload= */ null, - /* expectedDataPayload= */ getTestDataPayload() - ); - }); - }); - - it('Foreground app can receive a {} empty message in onMessage', async function () { - this.timeout(TIMEOUT_FOREGROUND_MESSAGE_TEST_UNIT_MILLISECONDS); - - await seleniumAssistant.killWebDriver(globalWebDriver); - - globalWebDriver = createPermittedWebDriver( - /* browser= */ assistantBrowser.getId() - ); - - await globalWebDriver.get(`${testServer.serverAddress}/${domain}/`); - - let token = await retrieveToken(globalWebDriver); - checkSendResponse( - await sendMessage({ - to: token - }) - ); - - await checkMessageReceived( - await getReceivedForegroundMessages(globalWebDriver), - /* expectedNotificationPayload= */ null, - /* expectedDataPayload= */ null - ); - }); - - it('Foreground app can receive a {"notification"} message in onMessage', async function () { - this.timeout(TIMEOUT_FOREGROUND_MESSAGE_TEST_UNIT_MILLISECONDS); - - await seleniumAssistant.killWebDriver(globalWebDriver); - - globalWebDriver = createPermittedWebDriver( - /* browser= */ assistantBrowser.getId() - ); - - await globalWebDriver.get(`${testServer.serverAddress}/${domain}/`); - - checkSendResponse( - await sendMessage({ - to: await retrieveToken(globalWebDriver), - notification: getTestNotificationPayload() - }) - ); - - await checkMessageReceived( - await getReceivedForegroundMessages(globalWebDriver), - /* expectedNotificationPayload= */ getTestNotificationPayload(), - /* expectedDataPayload= */ null - ); - }); - - it('Foreground app can receive a {"data"} message in onMessage', async function () { - this.timeout(TIMEOUT_FOREGROUND_MESSAGE_TEST_UNIT_MILLISECONDS); - - await seleniumAssistant.killWebDriver(globalWebDriver); - - globalWebDriver = createPermittedWebDriver( - /* browser= */ assistantBrowser.getId() - ); - - await globalWebDriver.get(`${testServer.serverAddress}/${domain}/`); - - checkSendResponse( - await sendMessage({ - to: await retrieveToken(globalWebDriver), - data: getTestDataPayload() - }) - ); - - await checkMessageReceived( - await getReceivedForegroundMessages(globalWebDriver), - /* expectedNotificationPayload= */ null, - /* expectedDataPayload= */ getTestDataPayload() - ); - }); - - it('Foreground app can receive a {"notification", "data"} message in onMessage', async function () { - this.timeout(TIMEOUT_FOREGROUND_MESSAGE_TEST_UNIT_MILLISECONDS); - - await seleniumAssistant.killWebDriver(globalWebDriver); - - globalWebDriver = createPermittedWebDriver( - /* browser= */ assistantBrowser.getId() - ); - - await globalWebDriver.get(`${testServer.serverAddress}/${domain}/`); - - checkSendResponse( - await sendMessage({ - to: await retrieveToken(globalWebDriver), - data: getTestDataPayload(), - notification: getTestNotificationPayload() - }) - ); - - await checkMessageReceived( - await getReceivedForegroundMessages(globalWebDriver), - /* expectedNotificationPayload= */ getTestNotificationPayload(), - /* expectedDataPayload= */ getTestDataPayload() - ); - }); - }); - }); -}); - -function checkMessageReceived( - receivedMessages, - expectedNotificationPayload, - expectedDataPayload -) { - expect(receivedMessages).to.exist; - - const message = receivedMessages[0]; - - expect(message[FIELD_FROM]).to.equal(TEST_PROJECT_SENDER_ID); - const collapseKey = !!message[FIELD_COLLAPSE_KEY_LEGACY] - ? message[FIELD_COLLAPSE_KEY_LEGACY] - : message[FIELD_COLLAPSE_KEY]; - expect(collapseKey).to.equal(DEFAULT_COLLAPSE_KEY_VALUE); - - if (expectedNotificationPayload) { - expect(message[FIELD_NOTIFICATION]).to.deep.equal( - getTestNotificationPayload() - ); - } - - if (expectedDataPayload) { - expect(message[FIELD_DATA]).to.deep.equal(getTestDataPayload()); - } -} - -function checkSendResponse(response) { - expect(response).to.exist; - expect(response.success).to.equal(1); -} - -function getTestNotificationPayload() { - return { - title: 'test title', - body: 'test body', - icon: '/test/icon.png', - click_action: '/', - tag: 'test-tag' - }; -} - -function getTestDataPayload() { - return { hello: 'world' }; -} - -async function prepareBackgroundApp(globalWebDriver, domain) { - await globalWebDriver.get(`${testServer.serverAddress}/${domain}/`); - - // TODO: remove the try/catch block once the underlying bug has been resolved. Shift window focus - // away from app window so that background messages can be received/processed - try { - await openNewTab(globalWebDriver); - } catch (err) { - // ChromeDriver seems to have an open bug which throws "JavascriptError: javascript error: - // circular reference". Nevertheless, a new tab can still be opened. Hence, just catch and - // continue here. - console.log('FCM (ignored on purpose): ' + err); - } -} diff --git a/integration/messaging/test/test-token-delete.js b/integration/messaging/test/test-token-delete.js new file mode 100644 index 00000000000..d3f0fca908e --- /dev/null +++ b/integration/messaging/test/test-token-delete.js @@ -0,0 +1,82 @@ +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const expect = require('chai').expect; +const testServer = require('./utils/test-server'); +const deleteToken = require('./utils/deleteToken'); +const clearAppForTest = require('./utils/clearAppForTest'); +const retrieveToken = require('./utils/retrieveToken'); +const seleniumAssistant = require('selenium-assistant'); +const createPermittedWebDriver = require('./utils/createPermittedWebDriver'); + +const TEST_SUITE_TIMEOUT_MS = 20000; +const TEST_DOMAIN = 'valid-vapid-key'; + +describe('Firebase Messaging Integration Tests > get and delete token', function () { + this.timeout(TEST_SUITE_TIMEOUT_MS); + this.retries(2); + let globalWebDriver; + + before(async function () { + await testServer.start(); + }); + + after(async function () { + await testServer.stop(); + }); + + const availableBrowsers = seleniumAssistant.getLocalBrowsers(); + availableBrowsers.forEach(assistantBrowser => { + // TODO: enable testing for firefox + if (assistantBrowser.getId() !== 'chrome') { + return; + } + + describe(`Testing browser: ${assistantBrowser.getPrettyName()} : ${TEST_DOMAIN}`, function () { + before(async function () { + // Use one webDriver per browser instead of one per test to speed up test. + globalWebDriver = createPermittedWebDriver( + /* browser= */ assistantBrowser.getId() + ); + + await globalWebDriver.get( + `${testServer.serverAddress}/${TEST_DOMAIN}/` + ); + }); + + after(async function () { + await seleniumAssistant.killWebDriver(globalWebDriver); + }); + + afterEach(async function () { + await clearAppForTest(globalWebDriver); + }); + + it(`Test app can delete a valid token`, async function () { + const token = await retrieveToken(globalWebDriver); + expect(token).to.exist; + try { + await deleteToken(globalWebDriver, token); + console.log('Token deletion succeed.'); + } catch (e) { + console.log('Error trying to delete FCM token: ', e); + fail(); + } + }); + }); + }); +}); diff --git a/integration/messaging/test/test-token-update.js b/integration/messaging/test/test-token-update.js new file mode 100644 index 00000000000..d2fc33782c1 --- /dev/null +++ b/integration/messaging/test/test-token-update.js @@ -0,0 +1,88 @@ +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const seleniumAssistant = require('selenium-assistant'); +const expect = require('chai').expect; +const testServer = require('./utils/test-server'); +const retrieveToken = require('./utils/retrieveToken'); +const clearAppForTest = require('./utils/clearAppForTest'); +const createPermittedWebDriver = require('./utils/createPermittedWebDriver'); +const timeForward = require('./utils/forwardTime'); +const triggerGetToken = require('./utils/triggerGetToken'); +const getErrors = require('./utils/getErrors'); + +const TEST_SUITE_TIMEOUT_MS = 70000; +const TEST_DOMAIN = 'valid-vapid-key'; + +// Getting and deleting token is the entry step of using FM SDK. Let it run first and fail quickly. +require('./test-token-delete'); + +describe('Firebase Messaging Integration Tests > update a token', function () { + this.timeout(TEST_SUITE_TIMEOUT_MS); + this.retries(2); + + let globalWebDriver; + + before(async function () { + await testServer.start(); + }); + + after(async function () { + await testServer.stop(); + }); + + const availableBrowsers = seleniumAssistant.getLocalBrowsers(); + // TODO: enable testing for edge and firefox if applicable + availableBrowsers.forEach(assistantBrowser => { + if (assistantBrowser.getId() !== 'chrome') { + return; + } + + describe(`Testing browser: ${assistantBrowser.getPrettyName()} : ${TEST_DOMAIN}`, function () { + before(async function () { + // Use one webDriver per browser instead of one per test to speed up test. + globalWebDriver = createPermittedWebDriver( + /* browser= */ assistantBrowser.getId() + ); + await globalWebDriver.get( + `${testServer.serverAddress}/${TEST_DOMAIN}/` + ); + }); + + after(async function () { + await seleniumAssistant.killWebDriver(globalWebDriver); + }); + + afterEach(async function () { + await clearAppForTest(globalWebDriver); + }); + + it(`should update a token`, async function () { + const token = await retrieveToken(globalWebDriver); + expect(token).to.exist; + + // roll the clock forward > 7days + await timeForward(globalWebDriver); + const updatedToken = await triggerGetToken(globalWebDriver); + const errors = await getErrors(globalWebDriver); + expect(errors).to.exist; + expect(errors.length).to.equal(0); + expect(updatedToken).to.exist; + }); + }); + }); +}); diff --git a/integration/messaging/test/test-updateToken.js b/integration/messaging/test/test-updateToken.js deleted file mode 100644 index 2de606be2a9..00000000000 --- a/integration/messaging/test/test-updateToken.js +++ /dev/null @@ -1,85 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const seleniumAssistant = require('selenium-assistant'); -const expect = require('chai').expect; -const testServer = require('./utils/test-server'); -const retrieveToken = require('./utils/retrieveToken'); -const clearAppForTest = require('./utils/clearAppForTest'); -const createPermittedWebDriver = require('./utils/createPermittedWebDriver'); -const timeForward = require('./utils/forwardTime'); -const triggerGetToken = require('./utils/triggerGetToken'); -const getErrors = require('./utils/getErrors'); - -const TEST_SUITE_TIMEOUT_MS = 70000; -const TEST_DOMAIN = 'valid-vapid-key'; - -describe('Firebase Messaging Integration Tests > update a token', function () { - this.timeout(TEST_SUITE_TIMEOUT_MS); - this.retries(3); - - let globalWebDriver; - - before(async function () { - await testServer.start(); - }); - - after(async function () { - await testServer.stop(); - }); - - const availableBrowsers = seleniumAssistant.getLocalBrowsers(); - // TODO: enable testing for edge and firefox if applicable - availableBrowsers.forEach(assistantBrowser => { - if (assistantBrowser.getId() !== 'chrome') { - return; - } - - describe(`Testing browser: ${assistantBrowser.getPrettyName()} : ${TEST_DOMAIN}`, function () { - before(async function () { - // Use one webDriver per browser instead of one per test to speed up test. - globalWebDriver = createPermittedWebDriver( - /* browser= */ assistantBrowser.getId() - ); - await globalWebDriver.get( - `${testServer.serverAddress}/${TEST_DOMAIN}/` - ); - }); - - after(async function () { - await seleniumAssistant.killWebDriver(globalWebDriver); - }); - - afterEach(async function () { - await clearAppForTest(globalWebDriver); - }); - - it(`should update a token`, async function () { - const token = await retrieveToken(globalWebDriver); - expect(token).to.exist; - - // roll the clock forward > 7days - await timeForward(globalWebDriver); - const updatedToken = await triggerGetToken(globalWebDriver); - const errors = await getErrors(globalWebDriver); - expect(errors).to.exist; - expect(errors.length).to.equal(0); - expect(updatedToken).to.exist; - }); - }); - }); -}); diff --git a/integration/messaging/test/test-useDefaultServiceWorker.js b/integration/messaging/test/test-useDefaultServiceWorker.js index 0d29f22b0ab..0e3a903042f 100644 --- a/integration/messaging/test/test-useDefaultServiceWorker.js +++ b/integration/messaging/test/test-useDefaultServiceWorker.js @@ -24,11 +24,12 @@ const createPermittedWebDriver = require('./utils/createPermittedWebDriver'); const TEST_DOMAIN = 'default-sw'; const TEST_SUITE_TIMEOUT_MS = 70000; +// Getting and deleting token is the entry step of using FM SDK. Let it run first and fail quickly. +require('./test-token-delete'); + describe(`Firebase Messaging Integration Tests > Use 'firebase-messaging-sw.js' by default`, function () { this.timeout(TEST_SUITE_TIMEOUT_MS); - this.retries(3); - let globalWebDriver; before(async function () { diff --git a/integration/messaging/test/test-useValidManifest.js b/integration/messaging/test/test-useValidManifest.js index da2a8c3f15f..4c566fc9eb4 100644 --- a/integration/messaging/test/test-useValidManifest.js +++ b/integration/messaging/test/test-useValidManifest.js @@ -24,11 +24,12 @@ const createPermittedWebDriver = require('./utils/createPermittedWebDriver'); const TEST_DOMAIN = 'valid-manifest'; const TEST_SUITE_TIMEOUT_MS = 70000; +// Getting and deleting token is the entry step of using FM SDK. Let it run first and fail quickly. +require('./test-token-delete'); + describe(`Firebase Messaging Integration Tests > Use 'use valid manifest`, function () { this.timeout(TEST_SUITE_TIMEOUT_MS); - this.retries(3); - let globalWebDriver; before(async function () { diff --git a/integration/messaging/test/utils/checkMessageReceived.js b/integration/messaging/test/utils/checkMessageReceived.js new file mode 100644 index 00000000000..c407ae6b9b8 --- /dev/null +++ b/integration/messaging/test/utils/checkMessageReceived.js @@ -0,0 +1,66 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const expect = require('chai').expect; + +const TEST_PROJECT_SENDER_ID = '750970317741'; +const DEFAULT_COLLAPSE_KEY_VALUE = 'do_not_collapse'; +const FIELD_FROM = 'from'; +const FIELD_COLLAPSE_KEY_LEGACY = 'collapse_key'; +const FIELD_COLLAPSE_KEY = 'collapseKey'; + +const FIELD_DATA = 'data'; +const FIELD_NOTIFICATION = 'notification'; + +module.exports = ( + receivedMessages, + expectedNotificationPayload, + expectedDataPayload +) => { + expect(receivedMessages).to.exist; + + const message = receivedMessages[0]; + + expect(message[FIELD_FROM]).to.equal(TEST_PROJECT_SENDER_ID); + const collapseKey = !!message[FIELD_COLLAPSE_KEY_LEGACY] + ? message[FIELD_COLLAPSE_KEY_LEGACY] + : message[FIELD_COLLAPSE_KEY]; + expect(collapseKey).to.equal(DEFAULT_COLLAPSE_KEY_VALUE); + + if (expectedNotificationPayload) { + expect(message[FIELD_NOTIFICATION]).to.deep.equal( + getTestNotificationPayload() + ); + } + + if (expectedDataPayload) { + expect(message[FIELD_DATA]).to.deep.equal(getTestDataPayload()); + } +}; + +function getTestNotificationPayload() { + return { + title: 'test title', + body: 'test body', + icon: '/test/icon.png', + click_action: '/', + tag: 'test-tag' + }; +} + +function getTestDataPayload() { + return { hello: 'world' }; +} diff --git a/integration/messaging/test/utils/checkSendResponse.js b/integration/messaging/test/utils/checkSendResponse.js new file mode 100644 index 00000000000..5c43f78ef8a --- /dev/null +++ b/integration/messaging/test/utils/checkSendResponse.js @@ -0,0 +1,22 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const expect = require('chai').expect; + +module.exports = response => { + expect(response).to.exist; + expect(response.success).to.equal(1); +}; diff --git a/lerna.json b/lerna.json index 09a5ad04362..9192331da41 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,4 @@ { - "lerna": "3.10.5", "version": "independent", "npmClient": "yarn", "packages": [ diff --git a/package.json b/package.json index 41af396fd89..61f3030fa66 100644 --- a/package.json +++ b/package.json @@ -21,21 +21,21 @@ "performance" ], "scripts": { - "dev": "lerna run --parallel --scope @firebase/* --scope firebase --scope rxfire dev", - "build": "lerna run --scope @firebase/app-exp build:deps && lerna run --scope @firebase/* --scope firebase --scope rxfire --ignore @firebase/app-exp build", + "dev": "lerna run --parallel --scope @firebase/* --scope firebase dev", + "build": "lerna run --scope @firebase/app-exp build:deps && lerna run --scope @firebase/* --scope firebase --ignore @firebase/app-exp build", "build:exp": "lerna run --scope @firebase/*-exp --scope @firebase/*-compat --scope firebase-exp build", - "build:release": "lerna run --scope @firebase/app-exp build:deps && lerna run --scope @firebase/* --scope firebase --scope rxfire --ignore @firebase/*-exp --ignore @firebase/*-compat prepare", - "build:exp:release": "yarn --cwd packages/app build:deps && lerna run --scope @firebase/*-exp --scope @firebase/*-compat --scope firebase-exp prepare && yarn --cwd packages-exp/app-exp typings:public", + "build:release": "lerna run --scope @firebase/app-exp build:deps && lerna run --scope @firebase/* --scope firebase --ignore @firebase/*-exp --ignore @firebase/*-compat build", "build:changed": "ts-node-script scripts/ci-test/build_changed.ts", - "link:packages": "lerna exec --scope @firebase/* --scope firebase --scope rxfire -- yarn link", + "link:packages": "lerna exec --scope @firebase/* --scope firebase -- yarn link", "stage:packages": "./scripts/prepublish.sh", "repl": "node tools/repl.js", "release": "ts-node-script scripts/release/cli.ts", + "release:exp": "ts-node-script scripts/exp/release.ts", "pretest": "node tools/pretest.js", "test": "lerna run --concurrency 4 --stream test", "test:ci": "lerna run --concurrency 4 test:ci", - "test:release": "lerna run --concurrency 4 --ignore @firebase/*-exp --ignore firebase-exp test:ci", - "test:exp": "lerna run --concurrency 4 --scope @firebase/*-exp --scope firebase-exp --stream test", + "test:release": "lerna run --concurrency 4 --ignore @firebase/*-exp --ignore firebase-exp --ignore @firebase/*-compat test:ci", + "test:exp": "lerna run --concurrency 4 --scope @firebase/*-exp --scope firebase-exp --scope @firebase/*-compat --stream test", "pretest:coverage": "mkdirp coverage", "ci:coverage": "lcov-result-merger 'packages/**/lcov.info' 'lcov-all.info'", "test:coverage": "lcov-result-merger 'packages/**/lcov.info' | coveralls", @@ -46,13 +46,15 @@ "docgen:node": "node scripts/docgen/generate-docs.js --api node", "docgen": "yarn docgen:js; yarn docgen:node", "prettier": "prettier --config .prettierrc --write '**/*.{ts,js}'", - "lint": "lerna run --scope @firebase/* --scope rxfire lint", + "lint": "lerna run --scope @firebase/* lint", + "lint:fix": "lerna run --scope @firebase/* lint:fix", "size-report": "ts-node-script scripts/size_report/report_binary_size.ts", "modular-export-size-report": "ts-node-script scripts/size_report/report_modular_export_binary_size.ts", - "api-report": "lerna run --scope @firebase/*-exp api-report", + "api-report": "lerna run --scope @firebase/*-exp --scope @firebase/firestore --scope @firebase/storage --scope @firebase/storage-types --scope @firebase/database api-report", "docgen:exp": "ts-node-script scripts/exp/docgen.ts", "postinstall": "yarn --cwd repo-scripts/changelog-generator build", - "sa": "ts-node-script repo-scripts/size-analysis/analysis.ts" + "sa": "ts-node-script repo-scripts/size-analysis/cli.ts", + "api-documenter-devsite": "ts-node-script repo-scripts/api-documenter/src/start.ts" }, "repository": { "type": "git", @@ -65,62 +67,67 @@ "repo-scripts/*" ], "devDependencies": { - "@changesets/changelog-github": "0.2.7", - "@changesets/cli": "2.11.0", - "@microsoft/api-documenter": "7.9.10", - "@microsoft/api-extractor": "7.10.4", - "@types/chai": "4.2.13", + "@babel/core": "7.14.6", + "@babel/plugin-transform-modules-commonjs": "7.14.5", + "@babel/preset-env": "7.14.7", + "@changesets/changelog-github": "0.4.0", + "@changesets/cli": "2.16.0", + "@types/chai": "4.2.14", "@types/chai-as-promised": "7.1.3", "@types/child-process-promise": "2.2.1", "@types/clone": "2.1.0", + "@types/eslint": "7.2.10", "@types/inquirer": "7.3.1", "@types/listr": "0.14.2", "@types/long": "4.0.1", "@types/mocha": "7.0.2", - "@types/mz": "2.7.1", - "@types/node": "12.12.67", - "@types/sinon": "9.0.8", + "@types/mz": "2.7.3", + "@types/node": "12.20.15", + "@types/sinon": "9.0.10", "@types/sinon-chai": "3.2.5", "@types/tmp": "0.2.0", - "@types/yargs": "15.0.8", - "@typescript-eslint/eslint-plugin": "4.4.1", - "@typescript-eslint/eslint-plugin-tslint": "4.4.1", - "@typescript-eslint/parser": "4.4.1", - "babel-loader": "8.1.0", - "chai": "4.2.0", + "@types/yargs": "16.0.0", + "@types/js-yaml": "4.0.0", + "@typescript-eslint/eslint-plugin": "4.28.0", + "@typescript-eslint/eslint-plugin-tslint": "4.28.0", + "@typescript-eslint/parser": "4.28.0", + "api-documenter-me": "0.1.1", + "api-extractor-me": "0.1.2", + "babel-loader": "8.2.2", + "chai": "4.3.4", "chai-as-promised": "7.1.1", "chalk": "4.1.0", "child-process-promise": "2.2.1", "clone": "2.1.2", "coveralls": "3.1.0", "del": "6.0.0", - "dependency-graph": "0.9.0", - "eslint": "7.11.0", - "eslint-plugin-import": "2.22.1", + "dependency-graph": "0.11.0", + "eslint": "7.29.0", + "eslint-plugin-import": "2.23.4", + "eslint-plugin-unused-imports": "1.1.1", "express": "4.17.1", "find-free-port": "2.0.0", - "firebase-functions": "3.11.0", - "firebase-tools": "8.12.1", + "firebase-functions": "3.14.1", + "firebase-tools": "9.14.0", "git-rev-sync": "3.0.1", "glob": "7.1.6", "http-server": "0.12.3", - "husky": "4.3.0", - "indexeddbshim": "7.0.0", - "inquirer": "7.3.3", + "husky": "4.3.6", + "indexeddbshim": "7.1.0", + "inquirer": "8.1.1", "istanbul-instrumenter-loader": "3.0.1", - "js-yaml": "3.14.0", + "js-yaml": "4.1.0", "karma": "5.2.3", "karma-chrome-launcher": "3.1.0", "karma-cli": "2.0.0", "karma-coverage-istanbul-reporter": "2.1.1", - "karma-firefox-launcher": "1.3.0", + "karma-firefox-launcher": "2.1.0", "karma-mocha": "2.0.1", "karma-mocha-reporter": "2.2.5", "karma-safari-launcher": "1.0.0", - "karma-sauce-launcher": "1.2.0", "karma-sourcemap-loader": "0.3.8", "karma-spec-reporter": "0.0.32", - "karma-summary-reporter": "1.9.0", + "karma-summary-reporter": "2.0.0", "karma-webpack": "4.0.2", "lcov-result-merger": "3.1.0", "lerna": "3.22.1", @@ -134,24 +141,24 @@ "npm-run-all": "4.1.5", "npm-run-path": "4.0.1", "nyc": "15.1.0", - "ora": "5.1.0", - "prettier": "2.1.2", + "ora": "5.4.1", + "prettier": "2.3.1", "protractor": "5.4.2", "rxjs": "6.6.3", - "semver": "7.3.2", - "simple-git": "2.21.0", - "sinon": "9.2.0", - "sinon-chai": "3.5.0", - "source-map-loader": "1.1.1", - "terser": "5.3.5", - "ts-loader": "8.0.5", - "ts-node": "9.0.0", + "semver": "7.3.4", + "simple-git": "2.40.0", + "sinon": "9.2.2", + "sinon-chai": "3.7.0", + "source-map-loader": "1.1.3", + "terser": "5.7.0", + "ts-loader": "8.3.0", + "ts-node": "9.1.1", "tslint": "6.1.3", "typedoc": "0.16.11", - "typescript": "4.0.2", + "typescript": "4.2.2", "watch": "1.0.2", - "webpack": "4.44.2", - "yargs": "16.0.3" + "webpack": "4.46.0", + "yargs": "16.2.0" }, "husky": { "hooks": { diff --git a/packages-exp/analytics-compat/.eslintrc.js b/packages-exp/analytics-compat/.eslintrc.js new file mode 100644 index 00000000000..85388055d9d --- /dev/null +++ b/packages-exp/analytics-compat/.eslintrc.js @@ -0,0 +1,33 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const path = require('path'); + +module.exports = { + 'extends': '../../config/.eslintrc.js', + 'parserOptions': { + 'project': 'tsconfig.json', + 'tsconfigRootDir': __dirname + }, + rules: { + 'import/no-extraneous-dependencies': [ + 'error', + { + 'packageDir': [path.resolve(__dirname, '../../'), __dirname] + } + ] + } +}; diff --git a/packages-exp/analytics-compat/karma.conf.js b/packages-exp/analytics-compat/karma.conf.js new file mode 100644 index 00000000000..c6488ea06bd --- /dev/null +++ b/packages-exp/analytics-compat/karma.conf.js @@ -0,0 +1,32 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// eslint-disable-next-line @typescript-eslint/no-require-imports +const karmaBase = require('../../config/karma.base'); + +const files = [`**/*.test.ts`]; + +module.exports = function (config) { + config.set({ + ...karmaBase, + files, + preprocessors: { '**/*.ts': ['webpack', 'sourcemap'] }, + frameworks: ['mocha'] + }); +}; + +module.exports.files = files; diff --git a/packages-exp/analytics-compat/package.json b/packages-exp/analytics-compat/package.json new file mode 100644 index 00000000000..491348b5074 --- /dev/null +++ b/packages-exp/analytics-compat/package.json @@ -0,0 +1,60 @@ +{ + "name": "@firebase/analytics-compat", + "version": "0.0.900", + "description": "", + "private": true, + "author": "Firebase (https://firebase.google.com/)", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm2017.js", + "module": "dist/index.esm2017.js", + "files": [ + "dist" + ], + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-compat": "0.x" + }, + "devDependencies": { + "@firebase/app-compat": "0.0.900", + "rollup": "2.52.2", + "@rollup/plugin-json": "4.1.0", + "rollup-plugin-typescript2": "0.30.0", + "typescript": "4.2.2" + }, + "repository": { + "directory": "packages-exp/analytics-compat", + "type": "git", + "url": "https://github.com/firebase/firebase-js-sdk.git" + }, + "bugs": { + "url": "https://github.com/firebase/firebase-js-sdk/issues" + }, + "scripts": { + "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "build": "rollup -c", + "build:deps": "lerna run --scope @firebase/analytics-compat --include-dependencies build", + "build:release": "rollup -c rollup.config.release.js && yarn add-compat-overloads", + "dev": "rollup -c -w", + "test": "run-p lint test:browser", + "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:browser", + "test:browser": "karma start --single-run", + "test:browser:debug": "karma start --browsers=Chrome --auto-watch", + "add-compat-overloads": "ts-node-script ../../scripts/exp/create-overloads.ts -i ../analytics-exp/dist/analytics-exp-public.d.ts -o dist/src/index.d.ts -a -r Analytics:FirebaseAnalytics -r FirebaseApp:FirebaseAppCompat --moduleToEnhance @firebase/analytics" + }, + "typings": "dist/src/index.d.ts", + "dependencies": { + "@firebase/component": "0.5.4", + "@firebase/analytics-exp": "0.0.900", + "@firebase/analytics-types": "0.4.0", + "@firebase/util": "1.1.0", + "tslib": "^2.1.0" + }, + "nyc": { + "extension": [ + ".ts" + ], + "reportDir": "./coverage/node" + }, + "esm5": "dist/index.esm.js" +} \ No newline at end of file diff --git a/packages-exp/analytics-compat/rollup.config.js b/packages-exp/analytics-compat/rollup.config.js new file mode 100644 index 00000000000..9b89b7772af --- /dev/null +++ b/packages-exp/analytics-compat/rollup.config.js @@ -0,0 +1,58 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import json from '@rollup/plugin-json'; +import typescriptPlugin from 'rollup-plugin-typescript2'; +import typescript from 'typescript'; +import { es2017BuildsNoPlugin, es5BuildsNoPlugin } from './rollup.shared'; + +/** + * ES5 Builds + */ +const es5BuildPlugins = [ + typescriptPlugin({ + typescript + }), + json() +]; + +const es5Builds = es5BuildsNoPlugin.map(build => ({ + ...build, + plugins: es5BuildPlugins +})); + +/** + * ES2017 Builds + */ +const es2017BuildPlugins = [ + typescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017' + } + } + }), + json({ preferConst: true }) +]; + +const es2017Builds = es2017BuildsNoPlugin.map(build => ({ + ...build, + plugins: es2017BuildPlugins +})); + +export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/analytics-compat/rollup.config.release.js b/packages-exp/analytics-compat/rollup.config.release.js new file mode 100644 index 00000000000..aed49b162c9 --- /dev/null +++ b/packages-exp/analytics-compat/rollup.config.release.js @@ -0,0 +1,73 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import typescriptPlugin from 'rollup-plugin-typescript2'; +import typescript from 'typescript'; +import json from '@rollup/plugin-json'; +import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; +import { es2017BuildsNoPlugin, es5BuildsNoPlugin } from './rollup.shared'; + +/** + * ES5 Builds + */ +const es5BuildPlugins = [ + typescriptPlugin({ + typescript, + clean: true, + abortOnError: false, + transformers: [importPathTransformer] + }), + json() +]; + +const es5Builds = es5BuildsNoPlugin.map(build => ({ + ...build, + plugins: es5BuildPlugins, + treeshake: { + moduleSideEffects: id => id === '@firebase/installations' + } +})); + +/** + * ES2017 Builds + */ +const es2017BuildPlugins = [ + typescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017' + } + }, + abortOnError: false, + clean: true, + transformers: [importPathTransformer] + }), + json({ + preferConst: true + }) +]; + +const es2017Builds = es2017BuildsNoPlugin.map(build => ({ + ...build, + plugins: es2017BuildPlugins, + treeshake: { + moduleSideEffects: id => id === '@firebase/installations' + } +})); + +export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/analytics-compat/rollup.shared.js b/packages-exp/analytics-compat/rollup.shared.js new file mode 100644 index 00000000000..ff3cbd71e62 --- /dev/null +++ b/packages-exp/analytics-compat/rollup.shared.js @@ -0,0 +1,54 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import pkg from './package.json'; + +const deps = [ + ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), + '@firebase/analytics' +]; + +export const es5BuildsNoPlugin = [ + /** + * Browser Builds + */ + { + input: 'src/index.ts', + output: [ + { file: pkg.main, format: 'cjs', sourcemap: true }, + { file: pkg.esm5, format: 'es', sourcemap: true } + ], + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + } +]; + +/** + * ES2017 Builds + */ +export const es2017BuildsNoPlugin = [ + { + /** + * Browser Build + */ + input: 'src/index.ts', + output: { + file: pkg.browser, + format: 'es', + sourcemap: true + }, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + } +]; diff --git a/packages-exp/analytics-compat/src/constants.ts b/packages-exp/analytics-compat/src/constants.ts new file mode 100644 index 00000000000..89944e29afe --- /dev/null +++ b/packages-exp/analytics-compat/src/constants.ts @@ -0,0 +1,56 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Officially recommended event names for gtag.js + * Any other string is also allowed. + */ +export enum EventName { + ADD_SHIPPING_INFO = 'add_shipping_info', + ADD_PAYMENT_INFO = 'add_payment_info', + ADD_TO_CART = 'add_to_cart', + ADD_TO_WISHLIST = 'add_to_wishlist', + BEGIN_CHECKOUT = 'begin_checkout', + /** + * @deprecated + * This event name is deprecated and is unsupported in updated + * Enhanced Ecommerce reports. + */ + CHECKOUT_PROGRESS = 'checkout_progress', + EXCEPTION = 'exception', + GENERATE_LEAD = 'generate_lead', + LOGIN = 'login', + PAGE_VIEW = 'page_view', + PURCHASE = 'purchase', + REFUND = 'refund', + REMOVE_FROM_CART = 'remove_from_cart', + SCREEN_VIEW = 'screen_view', + SEARCH = 'search', + SELECT_CONTENT = 'select_content', + SELECT_ITEM = 'select_item', + SELECT_PROMOTION = 'select_promotion', + /** @deprecated */ + SET_CHECKOUT_OPTION = 'set_checkout_option', + SHARE = 'share', + SIGN_UP = 'sign_up', + TIMING_COMPLETE = 'timing_complete', + VIEW_CART = 'view_cart', + VIEW_ITEM = 'view_item', + VIEW_ITEM_LIST = 'view_item_list', + VIEW_PROMOTION = 'view_promotion', + VIEW_SEARCH_RESULTS = 'view_search_results' +} diff --git a/packages-exp/analytics-compat/src/index.ts b/packages-exp/analytics-compat/src/index.ts new file mode 100644 index 00000000000..eeb46e88f8e --- /dev/null +++ b/packages-exp/analytics-compat/src/index.ts @@ -0,0 +1,83 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import firebase, { + _FirebaseNamespace, + FirebaseApp +} from '@firebase/app-compat'; +import { FirebaseAnalytics } from '@firebase/analytics-types'; +import { name, version } from '../package.json'; +import { AnalyticsService } from './service'; +import { + Component, + ComponentContainer, + ComponentType, + InstanceFactory +} from '@firebase/component'; +import { + settings as settingsExp, + isSupported as isSupportedExp +} from '@firebase/analytics-exp'; +import { EventName } from './constants'; + +declare module '@firebase/component' { + interface NameServiceMapping { + 'analytics-compat': AnalyticsService; + } +} + +const factory: InstanceFactory<'analytics-compat'> = ( + container: ComponentContainer +) => { + // Dependencies + const app = container.getProvider('app-compat').getImmediate(); + const analyticsServiceExp = container + .getProvider('analytics-exp') + .getImmediate(); + + return new AnalyticsService(app as FirebaseApp, analyticsServiceExp); +}; + +export function registerAnalytics(): void { + const namespaceExports = { + Analytics: AnalyticsService, + settings: settingsExp, + isSupported: isSupportedExp, + // We removed this enum in exp so need to re-create it here for compat. + EventName + }; + (firebase as _FirebaseNamespace).INTERNAL.registerComponent( + new Component('analytics-compat', factory, ComponentType.PUBLIC) + .setServiceProps(namespaceExports) + .setMultipleInstances(true) + ); +} + +registerAnalytics(); +firebase.registerVersion(name, version); + +/** + * Define extension behavior of `registerAnalytics` + */ +declare module '@firebase/app-compat' { + interface FirebaseNamespace { + analytics(app?: FirebaseApp): FirebaseAnalytics; + } + interface FirebaseApp { + analytics(): FirebaseAnalytics; + } +} diff --git a/packages-exp/analytics-compat/src/service.test.ts b/packages-exp/analytics-compat/src/service.test.ts new file mode 100644 index 00000000000..1817a0be314 --- /dev/null +++ b/packages-exp/analytics-compat/src/service.test.ts @@ -0,0 +1,162 @@ +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { expect, use } from 'chai'; +import { AnalyticsService } from './service'; +import { firebase, FirebaseApp } from '@firebase/app-compat'; +import * as analyticsExp from '@firebase/analytics-exp'; +import { stub, match, SinonStub } from 'sinon'; +import * as sinonChai from 'sinon-chai'; + +use(sinonChai); + +function createTestService(app: FirebaseApp): AnalyticsService { + return new AnalyticsService(app, analyticsExp.getAnalytics(app)); +} + +describe('Firebase Analytics > Service', () => { + let app: FirebaseApp; + let service: AnalyticsService; + let logEventStub: SinonStub = stub(); + let setUserIdStub: SinonStub = stub(); + let setCurrentScreenStub: SinonStub = stub(); + let setUserPropertiesStub: SinonStub = stub(); + let setAnalyticsCollectionEnabledStub: SinonStub = stub(); + + before(() => { + logEventStub = stub(analyticsExp, 'logEvent'); + setUserIdStub = stub(analyticsExp, 'setUserId'); + setCurrentScreenStub = stub(analyticsExp, 'setCurrentScreen'); + setUserPropertiesStub = stub(analyticsExp, 'setUserProperties'); + setAnalyticsCollectionEnabledStub = stub( + analyticsExp, + 'setAnalyticsCollectionEnabled' + ); + }); + + beforeEach(() => { + app = firebase.initializeApp({ + apiKey: '456_LETTERS_AND_1234NUMBERS', + appId: '123lettersand:numbers', + projectId: 'my-project', + messagingSenderId: 'messaging-sender-id' + }); + }); + + afterEach(async () => { + await app.delete(); + }); + + after(() => { + logEventStub.restore(); + setUserIdStub.restore(); + }); + + it('logEvent() calls modular logEvent() with only event name', () => { + service = createTestService(app); + service.logEvent('begin_checkout'); + expect(logEventStub).to.be.calledWith(match.any, 'begin_checkout'); + logEventStub.resetHistory(); + }); + + it('logEvent() calls modular logEvent() with 2 args', () => { + service = createTestService(app); + service.logEvent('begin_checkout', { 'currency': 'USD' }); + expect(logEventStub).to.be.calledWith(match.any, 'begin_checkout', { + 'currency': 'USD' + }); + logEventStub.resetHistory(); + }); + + it('logEvent() calls modular logEvent() with all args', () => { + service = createTestService(app); + service.logEvent('begin_checkout', { 'currency': 'USD' }, { global: true }); + expect(logEventStub).to.be.calledWith( + match.any, + 'begin_checkout', + { 'currency': 'USD' }, + { global: true } + ); + logEventStub.resetHistory(); + }); + + it('setUserId() calls modular setUserId()', () => { + service = createTestService(app); + service.setUserId('user123'); + expect(setUserIdStub).to.be.calledWith(match.any, 'user123'); + setUserIdStub.resetHistory(); + }); + + it('setUserId() calls modular setUserId() with options if provided', () => { + service = createTestService(app); + service.setUserId('user123', { global: true }); + expect(setUserIdStub).to.be.calledWith(match.any, 'user123', { + global: true + }); + setUserIdStub.resetHistory(); + }); + + it('setCurrentScreen() calls modular setCurrentScreen()', () => { + service = createTestService(app); + service.setCurrentScreen('some_screen'); + expect(setCurrentScreenStub).to.be.calledWith(match.any, 'some_screen'); + setCurrentScreenStub.resetHistory(); + }); + + it('setCurrentScreen() calls modular setCurrentScreen() with options if provided', () => { + service = createTestService(app); + service.setCurrentScreen('some_screen', { global: true }); + expect(setCurrentScreenStub).to.be.calledWith(match.any, 'some_screen', { + global: true + }); + setCurrentScreenStub.resetHistory(); + }); + + it('setUserProperties() calls modular setUserProperties()', () => { + service = createTestService(app); + service.setUserProperties({ 'my_custom_property': 'abc' }); + expect(setUserPropertiesStub).to.be.calledWith(match.any, { + 'my_custom_property': 'abc' + }); + setUserPropertiesStub.resetHistory(); + }); + + it('setUserProperties() calls modular setUserProperties() with options if provided', () => { + service = createTestService(app); + service.setUserProperties( + { 'my_custom_property': 'abc' }, + { global: true } + ); + expect(setUserPropertiesStub).to.be.calledWith( + match.any, + { 'my_custom_property': 'abc' }, + { + global: true + } + ); + setCurrentScreenStub.resetHistory(); + }); + + it('setAnalyticsCollectionEnabled() calls modular setAnalyticsCollectionEnabled()', () => { + service = createTestService(app); + service.setAnalyticsCollectionEnabled(false); + expect(setAnalyticsCollectionEnabledStub).to.be.calledWith( + match.any, + false + ); + setAnalyticsCollectionEnabledStub.resetHistory(); + }); +}); diff --git a/packages-exp/analytics-compat/src/service.ts b/packages-exp/analytics-compat/src/service.ts new file mode 100644 index 00000000000..283c4478683 --- /dev/null +++ b/packages-exp/analytics-compat/src/service.ts @@ -0,0 +1,66 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + AnalyticsCallOptions, + CustomParams, + EventParams, + FirebaseAnalytics +} from '@firebase/analytics-types'; +import { + Analytics as AnalyticsServiceExp, + logEvent as logEventExp, + setAnalyticsCollectionEnabled as setAnalyticsCollectionEnabledExp, + setCurrentScreen as setCurrentScreenExp, + setUserId as setUserIdExp, + setUserProperties as setUserPropertiesExp +} from '@firebase/analytics-exp'; +import { _FirebaseService, FirebaseApp } from '@firebase/app-compat'; + +export class AnalyticsService implements FirebaseAnalytics, _FirebaseService { + constructor( + public app: FirebaseApp, + readonly _delegate: AnalyticsServiceExp + ) {} + + logEvent( + eventName: string, + eventParams?: EventParams | CustomParams, + options?: AnalyticsCallOptions + ): void { + logEventExp(this._delegate, eventName as '', eventParams, options); + } + + setCurrentScreen(screenName: string, options?: AnalyticsCallOptions): void { + setCurrentScreenExp(this._delegate, screenName, options); + } + + setUserId(id: string, options?: AnalyticsCallOptions): void { + setUserIdExp(this._delegate, id, options); + } + + setUserProperties( + properties: CustomParams, + options?: AnalyticsCallOptions + ): void { + setUserPropertiesExp(this._delegate, properties, options); + } + + setAnalyticsCollectionEnabled(enabled: boolean): void { + setAnalyticsCollectionEnabledExp(this._delegate, enabled); + } +} diff --git a/packages-exp/analytics-compat/tsconfig.json b/packages-exp/analytics-compat/tsconfig.json new file mode 100644 index 00000000000..a06ed9a374c --- /dev/null +++ b/packages-exp/analytics-compat/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../config/tsconfig.base.json", + "compilerOptions": { + "outDir": "dist" + }, + "exclude": [ + "dist/**/*" + ] +} \ No newline at end of file diff --git a/packages-exp/analytics-exp/.eslintrc.js b/packages-exp/analytics-exp/.eslintrc.js new file mode 100644 index 00000000000..85388055d9d --- /dev/null +++ b/packages-exp/analytics-exp/.eslintrc.js @@ -0,0 +1,33 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const path = require('path'); + +module.exports = { + 'extends': '../../config/.eslintrc.js', + 'parserOptions': { + 'project': 'tsconfig.json', + 'tsconfigRootDir': __dirname + }, + rules: { + 'import/no-extraneous-dependencies': [ + 'error', + { + 'packageDir': [path.resolve(__dirname, '../../'), __dirname] + } + ] + } +}; diff --git a/packages-exp/analytics-exp/README.md b/packages-exp/analytics-exp/README.md new file mode 100644 index 00000000000..45dee7ef81e --- /dev/null +++ b/packages-exp/analytics-exp/README.md @@ -0,0 +1,5 @@ +# @firebase/analytics + +This is the Firebase Analytics component of the Firebase JS SDK. + +**This package is not intended for direct usage, and should only be used via the officially supported [firebase](https://www.npmjs.com/package/firebase) package.** diff --git a/packages-exp/analytics-exp/api-extractor.json b/packages-exp/analytics-exp/api-extractor.json new file mode 100644 index 00000000000..8a3c6cb251e --- /dev/null +++ b/packages-exp/analytics-exp/api-extractor.json @@ -0,0 +1,10 @@ +{ + "extends": "../../config/api-extractor.json", + // Point it to your entry point d.ts file. + "mainEntryPointFilePath": "/dist/src/index.d.ts", + "dtsRollup": { + "enabled": true, + "untrimmedFilePath": "/dist/.d.ts", + "publicTrimmedFilePath": "/dist/-public.d.ts" + } +} \ No newline at end of file diff --git a/packages-exp/analytics-exp/karma.conf.js b/packages-exp/analytics-exp/karma.conf.js new file mode 100644 index 00000000000..c6488ea06bd --- /dev/null +++ b/packages-exp/analytics-exp/karma.conf.js @@ -0,0 +1,32 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// eslint-disable-next-line @typescript-eslint/no-require-imports +const karmaBase = require('../../config/karma.base'); + +const files = [`**/*.test.ts`]; + +module.exports = function (config) { + config.set({ + ...karmaBase, + files, + preprocessors: { '**/*.ts': ['webpack', 'sourcemap'] }, + frameworks: ['mocha'] + }); +}; + +module.exports.files = files; diff --git a/packages-exp/analytics-exp/karma.integration.conf.js b/packages-exp/analytics-exp/karma.integration.conf.js new file mode 100644 index 00000000000..94215252451 --- /dev/null +++ b/packages-exp/analytics-exp/karma.integration.conf.js @@ -0,0 +1,32 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// eslint-disable-next-line @typescript-eslint/no-require-imports +const karmaBase = require('../../config/karma.base'); + +const files = [`./testing/integration-tests/integration.ts`]; + +module.exports = function (config) { + config.set({ + ...karmaBase, + files, + preprocessors: { '**/*.ts': ['webpack', 'sourcemap'] }, + frameworks: ['mocha'] + }); +}; + +module.exports.files = files; diff --git a/packages-exp/analytics-exp/package.json b/packages-exp/analytics-exp/package.json new file mode 100644 index 00000000000..9957326fa40 --- /dev/null +++ b/packages-exp/analytics-exp/package.json @@ -0,0 +1,67 @@ +{ + "name": "@firebase/analytics-exp", + "version": "0.0.900", + "private": true, + "description": "A analytics package for new firebase packages", + "author": "Firebase (https://firebase.google.com/)", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm2017.js", + "module": "dist/index.esm2017.js", + "files": [ + "dist" + ], + "scripts": { + "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "build": "rollup -c && yarn api-report", + "build:release": "rollup -c rollup.config.release.js && yarn api-report && yarn typings:public", + "build:deps": "lerna run --scope @firebase/analytics-exp --include-dependencies build", + "dev": "rollup -c -w", + "test": "run-p lint test:all", + "test:all": "run-p test:browser test:integration", + "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:all", + "test:browser": "karma start --single-run --nocache", + "test:integration": "karma start ./karma.integration.conf.js --single-run --nocache", + "api-report": "api-extractor run --local --verbose", + "predoc": "node ../../scripts/exp/remove-exp.js temp", + "doc": "api-documenter markdown --input temp --output docs", + "build:doc": "yarn build && yarn doc", + "typings:public": "node ../../scripts/exp/use_typings.js ./dist/analytics-exp-public.d.ts" + }, + "peerDependencies": { + "@firebase/app-exp": "0.x" + }, + "dependencies": { + "@firebase/installations-exp": "0.0.900", + "@firebase/logger": "0.2.6", + "@firebase/util": "1.1.0", + "@firebase/component": "0.5.4", + "tslib": "^2.1.0" + }, + "license": "Apache-2.0", + "devDependencies": { + "@firebase/app-exp": "0.0.900", + "rollup": "2.52.2", + "@rollup/plugin-commonjs": "17.1.0", + "@rollup/plugin-json": "4.1.0", + "@rollup/plugin-node-resolve": "11.2.0", + "rollup-plugin-typescript2": "0.30.0", + "typescript": "4.2.2" + }, + "repository": { + "directory": "packages/analytics", + "type": "git", + "url": "https://github.com/firebase/firebase-js-sdk.git" + }, + "bugs": { + "url": "https://github.com/firebase/firebase-js-sdk/issues" + }, + "typings": "dist/src/index.d.ts", + "nyc": { + "extension": [ + ".ts" + ], + "reportDir": "./coverage/node" + }, + "esm5": "dist/index.esm.js" +} \ No newline at end of file diff --git a/packages-exp/analytics-exp/rollup.config.js b/packages-exp/analytics-exp/rollup.config.js new file mode 100644 index 00000000000..184071d3577 --- /dev/null +++ b/packages-exp/analytics-exp/rollup.config.js @@ -0,0 +1,58 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import json from '@rollup/plugin-json'; +import typescriptPlugin from 'rollup-plugin-typescript2'; +import typescript from 'typescript'; +import { es2017BuildsNoPlugin, es5BuildsNoPlugin } from './rollup.shared'; + +/** + * ES5 Builds + */ +const es5BuildPlugins = [ + typescriptPlugin({ + typescript + }), + json() +]; + +const es5Builds = es5BuildsNoPlugin.map(build => ({ + ...build, + plugins: es5BuildPlugins +})); + +/** + * ES2017 Builds + */ +const es2017BuildPlugins = [ + typescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017' + } + } + }), + json({ preferConst: true }) +]; + +const es2017Builds = es2017BuildsNoPlugin.map(build => ({ + ...build, + plugins: es2017BuildPlugins +})); + +export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/analytics-exp/rollup.config.release.js b/packages-exp/analytics-exp/rollup.config.release.js new file mode 100644 index 00000000000..c7f9f69c381 --- /dev/null +++ b/packages-exp/analytics-exp/rollup.config.release.js @@ -0,0 +1,73 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import typescriptPlugin from 'rollup-plugin-typescript2'; +import typescript from 'typescript'; +import json from '@rollup/plugin-json'; +import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; +import { es2017BuildsNoPlugin, es5BuildsNoPlugin } from './rollup.shared'; + +/** + * ES5 Builds + */ +const es5BuildPlugins = [ + typescriptPlugin({ + typescript, + clean: true, + abortOnError: false, + transformers: [importPathTransformer] + }), + json() +]; + +const es5Builds = es5BuildsNoPlugin.map(build => ({ + ...build, + plugins: es5BuildPlugins, + treeshake: { + moduleSideEffects: id => id === '@firebase/installations' + } +})); + +/** + * ES2017 Builds + */ +const es2017BuildPlugins = [ + typescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017' + } + }, + abortOnError: false, + clean: true, + transformers: [importPathTransformer] + }), + json({ + preferConst: true + }) +]; + +const es2017Builds = es2017BuildsNoPlugin.map(build => ({ + ...build, + plugins: es2017BuildPlugins, + treeshake: { + moduleSideEffects: id => id === '@firebase/installations' + } +})); + +export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/analytics-exp/rollup.shared.js b/packages-exp/analytics-exp/rollup.shared.js new file mode 100644 index 00000000000..397949ded6b --- /dev/null +++ b/packages-exp/analytics-exp/rollup.shared.js @@ -0,0 +1,54 @@ +/** + * @license + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import pkg from './package.json'; + +const deps = [ + ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), + '@firebase/app' +]; + +export const es5BuildsNoPlugin = [ + /** + * Browser Builds + */ + { + input: 'src/index.ts', + output: [ + { file: pkg.main, format: 'cjs', sourcemap: true }, + { file: pkg.esm5, format: 'es', sourcemap: true } + ], + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + } +]; + +/** + * ES2017 Builds + */ +export const es2017BuildsNoPlugin = [ + { + /** + * Browser Build + */ + input: 'src/index.ts', + output: { + file: pkg.browser, + format: 'es', + sourcemap: true + }, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + } +]; diff --git a/packages-exp/analytics-exp/src/api.ts b/packages-exp/analytics-exp/src/api.ts new file mode 100644 index 00000000000..c0ff7f337fc --- /dev/null +++ b/packages-exp/analytics-exp/src/api.ts @@ -0,0 +1,716 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable camelcase */ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { _getProvider, FirebaseApp, getApp } from '@firebase/app-exp'; +import { + Analytics, + AnalyticsCallOptions, + AnalyticsOptions, + CustomParams, + EventNameString, + EventParams +} from './public-types'; +import { Provider } from '@firebase/component'; +import { + isIndexedDBAvailable, + validateIndexedDBOpenable, + areCookiesEnabled, + isBrowserExtension, + getModularInstance +} from '@firebase/util'; +import { ANALYTICS_TYPE } from './constants'; +import { + AnalyticsService, + initializationPromisesMap, + wrappedGtagFunction +} from './factory'; +import { logger } from './logger'; +import { + logEvent as internalLogEvent, + setCurrentScreen as internalSetCurrentScreen, + setUserId as internalSetUserId, + setUserProperties as internalSetUserProperties, + setAnalyticsCollectionEnabled as internalSetAnalyticsCollectionEnabled +} from './functions'; +import { ERROR_FACTORY, AnalyticsError } from './errors'; + +export { settings } from './factory'; + +declare module '@firebase/component' { + interface NameServiceMapping { + [ANALYTICS_TYPE]: AnalyticsService; + } +} + +/** + * Returns a Firebase Analytics instance for the given app. + * + * @public + * + * @param app - The FirebaseApp to use. + */ +export function getAnalytics(app: FirebaseApp = getApp()): Analytics { + app = getModularInstance(app); + // Dependencies + const analyticsProvider: Provider<'analytics-exp'> = _getProvider( + app, + ANALYTICS_TYPE + ); + + if (analyticsProvider.isInitialized()) { + return analyticsProvider.getImmediate(); + } + + return initializeAnalytics(app); +} + +/** + * Returns a Firebase Analytics instance for the given app. + * + * @public + * + * @param app - The FirebaseApp to use. + */ +export function initializeAnalytics( + app: FirebaseApp, + options: AnalyticsOptions = {} +): Analytics { + // Dependencies + const analyticsProvider: Provider<'analytics-exp'> = _getProvider( + app, + ANALYTICS_TYPE + ); + if (analyticsProvider.isInitialized()) { + throw ERROR_FACTORY.create(AnalyticsError.ALREADY_INITIALIZED); + } + const analyticsInstance = analyticsProvider.initialize({ options }); + return analyticsInstance; +} + +/** + * This is a public static method provided to users that wraps four different checks: + * + * 1. Check if it's not a browser extension environment. + * 2. Check if cookies are enabled in current browser. + * 3. Check if IndexedDB is supported by the browser environment. + * 4. Check if the current browser context is valid for using IndexedDB.open(). + * + * @public + * + */ +export async function isSupported(): Promise { + if (isBrowserExtension()) { + return false; + } + if (!areCookiesEnabled()) { + return false; + } + if (!isIndexedDBAvailable()) { + return false; + } + + try { + const isDBOpenable: boolean = await validateIndexedDBOpenable(); + return isDBOpenable; + } catch (error) { + return false; + } +} + +/** + * Use gtag 'config' command to set 'screen_name'. + * + * @public + * + * @param analyticsInstance - Firebase Analytics instance. + * @param screenName - Screen name to set. + */ +export function setCurrentScreen( + analyticsInstance: Analytics, + screenName: string, + options?: AnalyticsCallOptions +): void { + analyticsInstance = getModularInstance(analyticsInstance); + internalSetCurrentScreen( + wrappedGtagFunction, + initializationPromisesMap[analyticsInstance.app.options.appId!], + screenName, + options + ).catch(e => logger.error(e)); +} + +/** + * Use gtag 'config' command to set 'user_id'. + * + * @public + * + * @param analyticsInstance - Firebase Analytics instance. + * @param id - User ID to set. + */ +export function setUserId( + analyticsInstance: Analytics, + id: string, + options?: AnalyticsCallOptions +): void { + analyticsInstance = getModularInstance(analyticsInstance); + internalSetUserId( + wrappedGtagFunction, + initializationPromisesMap[analyticsInstance.app.options.appId!], + id, + options + ).catch(e => logger.error(e)); +} + +/** + * Use gtag 'config' command to set all params specified. + * + * @public + */ +export function setUserProperties( + analyticsInstance: Analytics, + properties: CustomParams, + options?: AnalyticsCallOptions +): void { + analyticsInstance = getModularInstance(analyticsInstance); + internalSetUserProperties( + wrappedGtagFunction, + initializationPromisesMap[analyticsInstance.app.options.appId!], + properties, + options + ).catch(e => logger.error(e)); +} + +/** + * Sets whether analytics collection is enabled for this app on this device. + * window['ga-disable-analyticsId'] = true; + * + * @public + * + * @param analyticsInstance - Firebase Analytics instance. + * @param enabled - If true, enables collection, if false, disables it. + */ +export function setAnalyticsCollectionEnabled( + analyticsInstance: Analytics, + enabled: boolean +): void { + analyticsInstance = getModularInstance(analyticsInstance); + internalSetAnalyticsCollectionEnabled( + initializationPromisesMap[analyticsInstance.app.options.appId!], + enabled + ).catch(e => logger.error(e)); +} +/** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * @public + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * | the gtag.js reference documentation}. + */ +export function logEvent( + analyticsInstance: Analytics, + eventName: 'add_payment_info', + eventParams?: { + coupon?: EventParams['coupon']; + currency?: EventParams['currency']; + items?: EventParams['items']; + payment_type?: EventParams['payment_type']; + value?: EventParams['value']; + [key: string]: any; + }, + options?: AnalyticsCallOptions +): void; + +/** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * @public + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * | the gtag.js reference documentation}. + */ +export function logEvent( + analyticsInstance: Analytics, + eventName: 'add_shipping_info', + eventParams?: { + coupon?: EventParams['coupon']; + currency?: EventParams['currency']; + items?: EventParams['items']; + shipping_tier?: EventParams['shipping_tier']; + value?: EventParams['value']; + [key: string]: any; + }, + options?: AnalyticsCallOptions +): void; + +/** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * @public + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * | the gtag.js reference documentation}. + */ +export function logEvent( + analyticsInstance: Analytics, + eventName: 'add_to_cart' | 'add_to_wishlist' | 'remove_from_cart', + eventParams?: { + currency?: EventParams['currency']; + value?: EventParams['value']; + items?: EventParams['items']; + [key: string]: any; + }, + options?: AnalyticsCallOptions +): void; + +/** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * @public + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * | the gtag.js reference documentation}. + */ +export function logEvent( + analyticsInstance: Analytics, + eventName: 'begin_checkout', + eventParams?: { + currency?: EventParams['currency']; + coupon?: EventParams['coupon']; + value?: EventParams['value']; + items?: EventParams['items']; + [key: string]: any; + }, + options?: AnalyticsCallOptions +): void; + +/** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * @public + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * | the gtag.js reference documentation}. + */ +export function logEvent( + analyticsInstance: Analytics, + eventName: 'checkout_progress', + eventParams?: { + currency?: EventParams['currency']; + coupon?: EventParams['coupon']; + value?: EventParams['value']; + items?: EventParams['items']; + checkout_step?: EventParams['checkout_step']; + checkout_option?: EventParams['checkout_option']; + [key: string]: any; + }, + options?: AnalyticsCallOptions +): void; + +/** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * @public + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * | the gtag.js reference documentation}. + */ +export function logEvent( + analyticsInstance: Analytics, + eventName: 'exception', + eventParams?: { + description?: EventParams['description']; + fatal?: EventParams['fatal']; + [key: string]: any; + }, + options?: AnalyticsCallOptions +): void; + +/** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * @public + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * | the gtag.js reference documentation}. + */ +export function logEvent( + analyticsInstance: Analytics, + eventName: 'generate_lead', + eventParams?: { + value?: EventParams['value']; + currency?: EventParams['currency']; + transaction_id?: EventParams['transaction_id']; + [key: string]: any; + }, + options?: AnalyticsCallOptions +): void; + +/** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * @public + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * | the gtag.js reference documentation}. + */ +export function logEvent( + analyticsInstance: Analytics, + eventName: 'login', + eventParams?: { + method?: EventParams['method']; + [key: string]: any; + }, + options?: AnalyticsCallOptions +): void; + +/** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * @public + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * | the gtag.js reference documentation}. + */ +export function logEvent( + analyticsInstance: Analytics, + eventName: 'page_view', + eventParams?: { + page_title?: string; + page_location?: string; + page_path?: string; + [key: string]: any; + }, + options?: AnalyticsCallOptions +): void; + +/** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * @public + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * | the gtag.js reference documentation}. + */ +export function logEvent( + analyticsInstance: Analytics, + eventName: 'purchase' | 'refund', + eventParams?: { + value?: EventParams['value']; + currency?: EventParams['currency']; + transaction_id: EventParams['transaction_id']; + tax?: EventParams['tax']; + shipping?: EventParams['shipping']; + items?: EventParams['items']; + coupon?: EventParams['coupon']; + affiliation?: EventParams['affiliation']; + [key: string]: any; + }, + options?: AnalyticsCallOptions +): void; + +/** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * @public + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * | the gtag.js reference documentation}. + */ +export function logEvent( + analyticsInstance: Analytics, + eventName: 'screen_view', + eventParams?: { + app_name: string; + screen_name: EventParams['screen_name']; + app_id?: string; + app_version?: string; + app_installer_id?: string; + [key: string]: any; + }, + options?: AnalyticsCallOptions +): void; + +/** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * @public + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * | the gtag.js reference documentation}. + */ +export function logEvent( + analyticsInstance: Analytics, + eventName: 'search' | 'view_search_results', + eventParams?: { + search_term?: EventParams['search_term']; + [key: string]: any; + }, + options?: AnalyticsCallOptions +): void; + +/** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * @public + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * | the gtag.js reference documentation}. + */ +export function logEvent( + analyticsInstance: Analytics, + eventName: 'select_content', + eventParams?: { + items?: EventParams['items']; + promotions?: EventParams['promotions']; + content_type?: EventParams['content_type']; + content_id?: EventParams['content_id']; + [key: string]: any; + }, + options?: AnalyticsCallOptions +): void; + +/** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * @public + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * | the gtag.js reference documentation}. + */ +export function logEvent( + analyticsInstance: Analytics, + eventName: 'select_item', + eventParams?: { + items?: EventParams['items']; + item_list_name?: EventParams['item_list_name']; + item_list_id?: EventParams['item_list_id']; + [key: string]: any; + }, + options?: AnalyticsCallOptions +): void; + +/** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * @public + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * | the gtag.js reference documentation}. + */ +export function logEvent( + analyticsInstance: Analytics, + eventName: 'select_promotion' | 'view_promotion', + eventParams?: { + items?: EventParams['items']; + promotion_id?: EventParams['promotion_id']; + promotion_name?: EventParams['promotion_name']; + [key: string]: any; + }, + options?: AnalyticsCallOptions +): void; + +/** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * @public + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * | the gtag.js reference documentation}. + */ +export function logEvent( + analyticsInstance: Analytics, + eventName: 'set_checkout_option', + eventParams?: { + checkout_step?: EventParams['checkout_step']; + checkout_option?: EventParams['checkout_option']; + [key: string]: any; + }, + options?: AnalyticsCallOptions +): void; + +/** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * @public + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * | the gtag.js reference documentation}. + */ +export function logEvent( + analyticsInstance: Analytics, + eventName: 'share', + eventParams?: { + method?: EventParams['method']; + content_type?: EventParams['content_type']; + content_id?: EventParams['content_id']; + [key: string]: any; + }, + options?: AnalyticsCallOptions +): void; + +/** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * @public + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * | the gtag.js reference documentation}. + */ +export function logEvent( + analyticsInstance: Analytics, + eventName: 'sign_up', + eventParams?: { + method?: EventParams['method']; + [key: string]: any; + }, + options?: AnalyticsCallOptions +): void; + +/** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * @public + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * | the gtag.js reference documentation}. + */ +export function logEvent( + analyticsInstance: Analytics, + eventName: 'timing_complete', + eventParams?: { + name: string; + value: number; + event_category?: string; + event_label?: string; + [key: string]: any; + }, + options?: AnalyticsCallOptions +): void; + +/** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * @public + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * | the gtag.js reference documentation}. + */ +export function logEvent( + analyticsInstance: Analytics, + eventName: 'view_cart' | 'view_item', + eventParams?: { + currency?: EventParams['currency']; + items?: EventParams['items']; + value?: EventParams['value']; + [key: string]: any; + }, + options?: AnalyticsCallOptions +): void; + +/** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * @public + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * | the gtag.js reference documentation}. + */ +export function logEvent( + analyticsInstance: Analytics, + eventName: 'view_item_list', + eventParams?: { + items?: EventParams['items']; + item_list_name?: EventParams['item_list_name']; + item_list_id?: EventParams['item_list_id']; + [key: string]: any; + }, + options?: AnalyticsCallOptions +): void; + +/** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * @public + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * | the gtag.js reference documentation}. + */ +export function logEvent( + analyticsInstance: Analytics, + eventName: CustomEventName, + eventParams?: { [key: string]: any }, + options?: AnalyticsCallOptions +): void; + +/** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * List of official event parameters can be found in the gtag.js + * reference documentation: + * {@link https://developers.google.com/gtagjs/reference/event + * | the gtag.js reference documentation}. + * + * @public + */ +export function logEvent( + analyticsInstance: Analytics, + eventName: string, + eventParams?: EventParams, + options?: AnalyticsCallOptions +): void { + analyticsInstance = getModularInstance(analyticsInstance); + internalLogEvent( + wrappedGtagFunction, + initializationPromisesMap[analyticsInstance.app.options.appId!], + eventName, + eventParams, + options + ).catch(e => logger.error(e)); +} + +/** + * Any custom event name string not in the standard list of recommended + * event names. + * @public + */ +export type CustomEventName = T extends EventNameString ? never : T; diff --git a/packages-exp/analytics-exp/src/constants.ts b/packages-exp/analytics-exp/src/constants.ts new file mode 100644 index 00000000000..1fdfdd5e523 --- /dev/null +++ b/packages-exp/analytics-exp/src/constants.ts @@ -0,0 +1,38 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Type constant for Firebase Analytics. + */ +export const ANALYTICS_TYPE = 'analytics-exp'; + +// Key to attach FID to in gtag params. +export const GA_FID_KEY = 'firebase_id'; +export const ORIGIN_KEY = 'origin'; + +export const FETCH_TIMEOUT_MILLIS = 60 * 1000; + +export const DYNAMIC_CONFIG_URL = + 'https://firebase.googleapis.com/v1alpha/projects/-/apps/{app-id}/webConfig'; + +export const GTAG_URL = 'https://www.googletagmanager.com/gtag/js'; + +export const enum GtagCommand { + EVENT = 'event', + SET = 'set', + CONFIG = 'config' +} diff --git a/packages-exp/analytics-exp/src/errors.ts b/packages-exp/analytics-exp/src/errors.ts new file mode 100644 index 00000000000..b500f88d013 --- /dev/null +++ b/packages-exp/analytics-exp/src/errors.ts @@ -0,0 +1,85 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ErrorFactory, ErrorMap } from '@firebase/util'; + +export const enum AnalyticsError { + ALREADY_EXISTS = 'already-exists', + ALREADY_INITIALIZED = 'already-initialized', + ALREADY_INITIALIZED_SETTINGS = 'already-initialized-settings', + INTEROP_COMPONENT_REG_FAILED = 'interop-component-reg-failed', + INVALID_ANALYTICS_CONTEXT = 'invalid-analytics-context', + INDEXEDDB_UNAVAILABLE = 'indexeddb-unavailable', + FETCH_THROTTLE = 'fetch-throttle', + CONFIG_FETCH_FAILED = 'config-fetch-failed', + NO_API_KEY = 'no-api-key', + NO_APP_ID = 'no-app-id' +} + +const ERRORS: ErrorMap = { + [AnalyticsError.ALREADY_EXISTS]: + 'A Firebase Analytics instance with the appId {$id} ' + + ' already exists. ' + + 'Only one Firebase Analytics instance can be created for each appId.', + [AnalyticsError.ALREADY_INITIALIZED]: + 'Firebase Analytics has already been initialized. ' + + 'initializeAnalytics() must only be called once. getAnalytics() can be used ' + + 'to get a reference to the already-intialized instance.', + [AnalyticsError.ALREADY_INITIALIZED_SETTINGS]: + 'Firebase Analytics has already been initialized.' + + 'settings() must be called before initializing any Analytics instance' + + 'or it will have no effect.', + [AnalyticsError.INTEROP_COMPONENT_REG_FAILED]: + 'Firebase Analytics Interop Component failed to instantiate: {$reason}', + [AnalyticsError.INVALID_ANALYTICS_CONTEXT]: + 'Firebase Analytics is not supported in this environment. ' + + 'Wrap initialization of analytics in analytics.isSupported() ' + + 'to prevent initialization in unsupported environments. Details: {$errorInfo}', + [AnalyticsError.INDEXEDDB_UNAVAILABLE]: + 'IndexedDB unavailable or restricted in this environment. ' + + 'Wrap initialization of analytics in analytics.isSupported() ' + + 'to prevent initialization in unsupported environments. Details: {$errorInfo}', + [AnalyticsError.FETCH_THROTTLE]: + 'The config fetch request timed out while in an exponential backoff state.' + + ' Unix timestamp in milliseconds when fetch request throttling ends: {$throttleEndTimeMillis}.', + [AnalyticsError.CONFIG_FETCH_FAILED]: + 'Dynamic config fetch failed: [{$httpStatus}] {$responseMessage}', + [AnalyticsError.NO_API_KEY]: + 'The "apiKey" field is empty in the local Firebase config. Firebase Analytics requires this field to' + + 'contain a valid API key.', + [AnalyticsError.NO_APP_ID]: + 'The "appId" field is empty in the local Firebase config. Firebase Analytics requires this field to' + + 'contain a valid app ID.' +}; + +interface ErrorParams { + [AnalyticsError.ALREADY_EXISTS]: { id: string }; + [AnalyticsError.INTEROP_COMPONENT_REG_FAILED]: { reason: Error }; + [AnalyticsError.FETCH_THROTTLE]: { throttleEndTimeMillis: number }; + [AnalyticsError.CONFIG_FETCH_FAILED]: { + httpStatus: number; + responseMessage: string; + }; + [AnalyticsError.INVALID_ANALYTICS_CONTEXT]: { errorInfo: string }; + [AnalyticsError.INDEXEDDB_UNAVAILABLE]: { errorInfo: string }; +} + +export const ERROR_FACTORY = new ErrorFactory( + 'analytics', + 'Analytics', + ERRORS +); diff --git a/packages-exp/analytics-exp/src/factory.ts b/packages-exp/analytics-exp/src/factory.ts new file mode 100644 index 00000000000..e43535a3558 --- /dev/null +++ b/packages-exp/analytics-exp/src/factory.ts @@ -0,0 +1,237 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SettingsOptions, Analytics, AnalyticsOptions } from './public-types'; +import { Gtag, DynamicConfig, MinimalDynamicConfig } from './types'; +import { getOrCreateDataLayer, wrapOrCreateGtag } from './helpers'; +import { AnalyticsError, ERROR_FACTORY } from './errors'; +import { _FirebaseInstallationsInternal } from '@firebase/installations-exp'; +import { areCookiesEnabled, isBrowserExtension } from '@firebase/util'; +import { initializeAnalytics } from './initialize-analytics'; +import { logger } from './logger'; +import { FirebaseApp, _FirebaseService } from '@firebase/app-exp'; + +/** + * Analytics Service class. + */ +export class AnalyticsService implements Analytics, _FirebaseService { + constructor(public app: FirebaseApp) {} + _delete(): Promise { + delete initializationPromisesMap[this.app.options.appId!]; + return Promise.resolve(); + } +} + +/** + * Maps appId to full initialization promise. Wrapped gtag calls must wait on + * all or some of these, depending on the call's `send_to` param and the status + * of the dynamic config fetches (see below). + */ +export let initializationPromisesMap: { + [appId: string]: Promise; // Promise contains measurement ID string. +} = {}; + +/** + * List of dynamic config fetch promises. In certain cases, wrapped gtag calls + * wait on all these to be complete in order to determine if it can selectively + * wait for only certain initialization (FID) promises or if it must wait for all. + */ +let dynamicConfigPromisesList: Array< + Promise +> = []; + +/** + * Maps fetched measurementIds to appId. Populated when the app's dynamic config + * fetch completes. If already populated, gtag config calls can use this to + * selectively wait for only this app's initialization promise (FID) instead of all + * initialization promises. + */ +const measurementIdToAppId: { [measurementId: string]: string } = {}; + +/** + * Name for window global data layer array used by GA: defaults to 'dataLayer'. + */ +let dataLayerName: string = 'dataLayer'; + +/** + * Name for window global gtag function used by GA: defaults to 'gtag'. + */ +let gtagName: string = 'gtag'; + +/** + * Reproduction of standard gtag function or reference to existing + * gtag function on window object. + */ +let gtagCoreFunction: Gtag; + +/** + * Wrapper around gtag function that ensures FID is sent with all + * relevant event and config calls. + */ +export let wrappedGtagFunction: Gtag; + +/** + * Flag to ensure page initialization steps (creation or wrapping of + * dataLayer and gtag script) are only run once per page load. + */ +let globalInitDone: boolean = false; + +/** + * For testing + * @internal + */ +export function resetGlobalVars( + newGlobalInitDone = false, + newInitializationPromisesMap = {}, + newDynamicPromises = [] +): void { + globalInitDone = newGlobalInitDone; + initializationPromisesMap = newInitializationPromisesMap; + dynamicConfigPromisesList = newDynamicPromises; + dataLayerName = 'dataLayer'; + gtagName = 'gtag'; +} + +/** + * For testing + * @internal + */ +export function getGlobalVars(): { + initializationPromisesMap: { [appId: string]: Promise }; + dynamicConfigPromisesList: Array< + Promise + >; +} { + return { + initializationPromisesMap, + dynamicConfigPromisesList + }; +} + +/** + * Configures Firebase Analytics to use custom `gtag` or `dataLayer` names. + * Intended to be used if `gtag.js` script has been installed on + * this page independently of Firebase Analytics, and is using non-default + * names for either the `gtag` function or for `dataLayer`. + * Must be called before calling `getAnalytics()` or it won't + * have any effect. + * + * @public + * + * @param options - Custom gtag and dataLayer names. + */ +export function settings(options: SettingsOptions): void { + if (globalInitDone) { + throw ERROR_FACTORY.create(AnalyticsError.ALREADY_INITIALIZED); + } + if (options.dataLayerName) { + dataLayerName = options.dataLayerName; + } + if (options.gtagName) { + gtagName = options.gtagName; + } +} + +/** + * Returns true if no environment mismatch is found. + * If environment mismatches are found, throws an INVALID_ANALYTICS_CONTEXT + * error that also lists details for each mismatch found. + */ +function warnOnBrowserContextMismatch(): void { + const mismatchedEnvMessages = []; + if (isBrowserExtension()) { + mismatchedEnvMessages.push('This is a browser extension environment.'); + } + if (!areCookiesEnabled()) { + mismatchedEnvMessages.push('Cookies are not available.'); + } + if (mismatchedEnvMessages.length > 0) { + const details = mismatchedEnvMessages + .map((message, index) => `(${index + 1}) ${message}`) + .join(' '); + const err = ERROR_FACTORY.create(AnalyticsError.INVALID_ANALYTICS_CONTEXT, { + errorInfo: details + }); + logger.warn(err.message); + } +} + +/** + * Analytics instance factory. + * @internal + */ +export function factory( + app: FirebaseApp, + installations: _FirebaseInstallationsInternal, + options?: AnalyticsOptions +): AnalyticsService { + warnOnBrowserContextMismatch(); + const appId = app.options.appId; + if (!appId) { + throw ERROR_FACTORY.create(AnalyticsError.NO_APP_ID); + } + if (!app.options.apiKey) { + if (app.options.measurementId) { + logger.warn( + `The "apiKey" field is empty in the local Firebase config. This is needed to fetch the latest` + + ` measurement ID for this Firebase app. Falling back to the measurement ID ${app.options.measurementId}` + + ` provided in the "measurementId" field in the local Firebase config.` + ); + } else { + throw ERROR_FACTORY.create(AnalyticsError.NO_API_KEY); + } + } + if (initializationPromisesMap[appId] != null) { + throw ERROR_FACTORY.create(AnalyticsError.ALREADY_EXISTS, { + id: appId + }); + } + + if (!globalInitDone) { + // Steps here should only be done once per page: creation or wrapping + // of dataLayer and global gtag function. + + getOrCreateDataLayer(dataLayerName); + + const { wrappedGtag, gtagCore } = wrapOrCreateGtag( + initializationPromisesMap, + dynamicConfigPromisesList, + measurementIdToAppId, + dataLayerName, + gtagName + ); + wrappedGtagFunction = wrappedGtag; + gtagCoreFunction = gtagCore; + + globalInitDone = true; + } + // Async but non-blocking. + // This map reflects the completion state of all promises for each appId. + initializationPromisesMap[appId] = initializeAnalytics( + app, + dynamicConfigPromisesList, + measurementIdToAppId, + installations, + gtagCoreFunction, + dataLayerName, + options + ); + + const analyticsInstance: AnalyticsService = new AnalyticsService(app); + + return analyticsInstance; +} diff --git a/packages-exp/analytics-exp/src/functions.test.ts b/packages-exp/analytics-exp/src/functions.test.ts new file mode 100644 index 00000000000..bf4f08dbd95 --- /dev/null +++ b/packages-exp/analytics-exp/src/functions.test.ts @@ -0,0 +1,173 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from 'chai'; +import { SinonStub, stub } from 'sinon'; +import '../testing/setup'; +import { + setCurrentScreen, + logEvent, + setUserId, + setUserProperties, + setAnalyticsCollectionEnabled +} from './functions'; +import { GtagCommand } from './constants'; + +const fakeMeasurementId = 'abcd-efgh-ijkl'; +const fakeInitializationPromise = Promise.resolve(fakeMeasurementId); + +describe('FirebaseAnalytics methods', () => { + const gtagStub: SinonStub = stub(); + + afterEach(() => { + gtagStub.reset(); + }); + + it('logEvent() calls gtag function correctly', async () => { + await logEvent(gtagStub, fakeInitializationPromise, 'add_to_cart', { + currency: 'USD' + }); + + expect(gtagStub).to.have.been.calledWith(GtagCommand.EVENT, 'add_to_cart', { + 'send_to': fakeMeasurementId, + currency: 'USD' + }); + }); + + it('logEvent() with no event params calls gtag function correctly', async () => { + await logEvent(gtagStub, fakeInitializationPromise, 'view_item'); + + expect(gtagStub).to.have.been.calledWith(GtagCommand.EVENT, 'view_item', { + 'send_to': fakeMeasurementId + }); + }); + + it('logEvent() globally calls gtag function correctly', async () => { + await logEvent( + gtagStub, + fakeInitializationPromise, + 'add_to_cart', + { + currency: 'USD' + }, + { global: true } + ); + + expect(gtagStub).to.have.been.calledWith(GtagCommand.EVENT, 'add_to_cart', { + currency: 'USD' + }); + }); + + it('logEvent() with no event params globally calls gtag function correctly', async () => { + await logEvent( + gtagStub, + fakeInitializationPromise, + 'add_to_cart', + undefined, + { + global: true + } + ); + + expect(gtagStub).to.have.been.calledWith( + GtagCommand.EVENT, + 'add_to_cart', + undefined + ); + }); + + it('setCurrentScreen() calls gtag correctly (instance)', async () => { + await setCurrentScreen(gtagStub, fakeInitializationPromise, 'home'); + expect(gtagStub).to.have.been.calledWith( + GtagCommand.CONFIG, + fakeMeasurementId, + { + 'screen_name': 'home', + update: true + } + ); + }); + + it('setCurrentScreen() calls gtag correctly (global)', async () => { + await setCurrentScreen(gtagStub, fakeInitializationPromise, 'home', { + global: true + }); + expect(gtagStub).to.be.calledWith(GtagCommand.SET, { + 'screen_name': 'home' + }); + }); + + it('setUserId() calls gtag correctly (instance)', async () => { + await setUserId(gtagStub, fakeInitializationPromise, 'user123'); + expect(gtagStub).to.have.been.calledWith( + GtagCommand.CONFIG, + fakeMeasurementId, + { + 'user_id': 'user123', + update: true + } + ); + }); + + it('setUserId() calls gtag correctly (global)', async () => { + await setUserId(gtagStub, fakeInitializationPromise, 'user123', { + global: true + }); + expect(gtagStub).to.be.calledWith(GtagCommand.SET, { + 'user_id': 'user123' + }); + }); + + it('setUserProperties() calls gtag correctly (instance)', async () => { + await setUserProperties(gtagStub, fakeInitializationPromise, { + 'currency': 'USD', + 'language': 'en' + }); + expect(gtagStub).to.have.been.calledWith( + GtagCommand.CONFIG, + fakeMeasurementId, + { + 'user_properties': { + 'currency': 'USD', + 'language': 'en' + }, + update: true + } + ); + }); + + it('setUserProperties() calls gtag correctly (global)', async () => { + await setUserProperties( + gtagStub, + fakeInitializationPromise, + { 'currency': 'USD', 'language': 'en' }, + { global: true } + ); + expect(gtagStub).to.be.calledWith(GtagCommand.SET, { + 'user_properties.currency': 'USD', + 'user_properties.language': 'en' + }); + }); + + it('setAnalyticsCollectionEnabled() calls gtag correctly', async () => { + await setAnalyticsCollectionEnabled(fakeInitializationPromise, true); + expect(window[`ga-disable-${fakeMeasurementId}`]).to.be.false; + await setAnalyticsCollectionEnabled(fakeInitializationPromise, false); + expect(window[`ga-disable-${fakeMeasurementId}`]).to.be.true; + delete window[`ga-disable-${fakeMeasurementId}`]; + }); +}); diff --git a/packages-exp/analytics-exp/src/functions.ts b/packages-exp/analytics-exp/src/functions.ts new file mode 100644 index 00000000000..15c397f5799 --- /dev/null +++ b/packages-exp/analytics-exp/src/functions.ts @@ -0,0 +1,141 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + AnalyticsCallOptions, + CustomParams, + ControlParams, + EventParams +} from './public-types'; +import { Gtag } from './types'; +import { GtagCommand } from './constants'; +/** + * Logs an analytics event through the Firebase SDK. + * + * @param gtagFunction Wrapped gtag function that waits for fid to be set before sending an event + * @param eventName Google Analytics event name, choose from standard list or use a custom string. + * @param eventParams Analytics event parameters. + */ +export async function logEvent( + gtagFunction: Gtag, + initializationPromise: Promise, + eventName: string, + eventParams?: EventParams, + options?: AnalyticsCallOptions +): Promise { + if (options && options.global) { + gtagFunction(GtagCommand.EVENT, eventName, eventParams); + return; + } else { + const measurementId = await initializationPromise; + const params: EventParams | ControlParams = { + ...eventParams, + 'send_to': measurementId + }; + gtagFunction(GtagCommand.EVENT, eventName, params); + } +} + +/** + * Set screen_name parameter for this Google Analytics ID. + * + * @param gtagFunction Wrapped gtag function that waits for fid to be set before sending an event + * @param screenName Screen name string to set. + */ +export async function setCurrentScreen( + gtagFunction: Gtag, + initializationPromise: Promise, + screenName: string | null, + options?: AnalyticsCallOptions +): Promise { + if (options && options.global) { + gtagFunction(GtagCommand.SET, { 'screen_name': screenName }); + return Promise.resolve(); + } else { + const measurementId = await initializationPromise; + gtagFunction(GtagCommand.CONFIG, measurementId, { + update: true, + 'screen_name': screenName + }); + } +} + +/** + * Set user_id parameter for this Google Analytics ID. + * + * @param gtagFunction Wrapped gtag function that waits for fid to be set before sending an event + * @param id User ID string to set + */ +export async function setUserId( + gtagFunction: Gtag, + initializationPromise: Promise, + id: string | null, + options?: AnalyticsCallOptions +): Promise { + if (options && options.global) { + gtagFunction(GtagCommand.SET, { 'user_id': id }); + return Promise.resolve(); + } else { + const measurementId = await initializationPromise; + gtagFunction(GtagCommand.CONFIG, measurementId, { + update: true, + 'user_id': id + }); + } +} + +/** + * Set all other user properties other than user_id and screen_name. + * + * @param gtagFunction Wrapped gtag function that waits for fid to be set before sending an event + * @param properties Map of user properties to set + */ +export async function setUserProperties( + gtagFunction: Gtag, + initializationPromise: Promise, + properties: CustomParams, + options?: AnalyticsCallOptions +): Promise { + if (options && options.global) { + const flatProperties: { [key: string]: unknown } = {}; + for (const key of Object.keys(properties)) { + // use dot notation for merge behavior in gtag.js + flatProperties[`user_properties.${key}`] = properties[key]; + } + gtagFunction(GtagCommand.SET, flatProperties); + return Promise.resolve(); + } else { + const measurementId = await initializationPromise; + gtagFunction(GtagCommand.CONFIG, measurementId, { + update: true, + 'user_properties': properties + }); + } +} + +/** + * Set whether collection is enabled for this ID. + * + * @param enabled If true, collection is enabled for this ID. + */ +export async function setAnalyticsCollectionEnabled( + initializationPromise: Promise, + enabled: boolean +): Promise { + const measurementId = await initializationPromise; + window[`ga-disable-${measurementId}`] = !enabled; +} diff --git a/packages-exp/analytics-exp/src/get-config.test.ts b/packages-exp/analytics-exp/src/get-config.test.ts new file mode 100644 index 00000000000..84aafaa412d --- /dev/null +++ b/packages-exp/analytics-exp/src/get-config.test.ts @@ -0,0 +1,259 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from 'chai'; +import { SinonStub, stub, useFakeTimers, restore } from 'sinon'; +import '../testing/setup'; +import { + fetchDynamicConfig, + fetchDynamicConfigWithRetry, + AppFields, + LONG_RETRY_FACTOR +} from './get-config'; +import { DYNAMIC_CONFIG_URL } from './constants'; +import { getFakeApp } from '../testing/get-fake-firebase-services'; +import { DynamicConfig, MinimalDynamicConfig } from './types'; +import { AnalyticsError } from './errors'; + +const fakeMeasurementId = 'abcd-efgh-ijkl'; +const fakeAppId = 'abcdefgh12345:23405'; +const fakeAppParams = { appId: fakeAppId, apiKey: 'AAbbCCdd12345' }; +const fakeUrl = DYNAMIC_CONFIG_URL.replace('{app-id}', fakeAppId); +const successObject = { measurementId: fakeMeasurementId, appId: fakeAppId }; +let fetchStub: SinonStub; + +function stubFetch(status: number, body: { [key: string]: any }): void { + fetchStub = stub(window, 'fetch'); + const mockResponse = new window.Response(JSON.stringify(body), { + status + }); + fetchStub.returns(Promise.resolve(mockResponse)); +} + +describe('Dynamic Config Fetch Functions', () => { + afterEach(restore); + describe('fetchDynamicConfig() - no retry', () => { + it('successfully request and receives dynamic config JSON data', async () => { + stubFetch(200, successObject); + const config: DynamicConfig = await fetchDynamicConfig(fakeAppParams); + expect(fetchStub.args[0][0]).to.equal(fakeUrl); + expect(fetchStub.args[0][1].headers.get('x-goog-api-key')).to.equal( + fakeAppParams.apiKey + ); + expect(config.appId).to.equal(fakeAppId); + expect(config.measurementId).to.equal(fakeMeasurementId); + }); + it('throws error on failed response', async () => { + stubFetch(500, { + error: { + /* no message */ + } + }); + const app = getFakeApp(fakeAppParams); + await expect( + fetchDynamicConfig(app.options as AppFields) + ).to.be.rejectedWith(AnalyticsError.CONFIG_FETCH_FAILED); + }); + it('throws error on failed response, includes server error message if provided', async () => { + stubFetch(500, { error: { message: 'Oops' } }); + const app = getFakeApp(fakeAppParams); + await expect( + fetchDynamicConfig(app.options as AppFields) + ).to.be.rejectedWith( + new RegExp(`Oops.+${AnalyticsError.CONFIG_FETCH_FAILED}`) + ); + }); + }); + describe('fetchDynamicConfigWithRetry()', () => { + it('successfully request and receives dynamic config JSON data', async () => { + stubFetch(200, successObject); + const app = getFakeApp(fakeAppParams); + const config: + | DynamicConfig + | MinimalDynamicConfig = await fetchDynamicConfigWithRetry(app); + expect(fetchStub.args[0][0]).to.equal(fakeUrl); + expect(fetchStub.args[0][1].headers.get('x-goog-api-key')).to.equal( + fakeAppParams.apiKey + ); + expect(config.appId).to.equal(fakeAppId); + expect(config.measurementId).to.equal(fakeMeasurementId); + }); + it('throws error on non-retriable failed response', async () => { + stubFetch(404, { + error: { + /* no message */ + } + }); + const app = getFakeApp(fakeAppParams); + await expect(fetchDynamicConfigWithRetry(app)).to.be.rejectedWith( + AnalyticsError.CONFIG_FETCH_FAILED + ); + }); + it('warns on non-retriable failed response if local measurementId available', async () => { + stubFetch(404, { + error: { + /* no message */ + } + }); + const consoleStub = stub(console, 'warn'); + const app = getFakeApp({ + ...fakeAppParams, + measurementId: fakeMeasurementId + }); + await fetchDynamicConfigWithRetry(app); + expect(consoleStub.args[0][1]).to.include(fakeMeasurementId); + consoleStub.restore(); + }); + it('retries on retriable error until success', async () => { + // Configures Date.now() to advance clock from zero in 20ms increments, enabling + // tests to assert a known throttle end time and allow setTimeout to work. + const clock = useFakeTimers({ shouldAdvanceTime: true }); + + // Ensures backoff is always zero, which simplifies reasoning about timer. + const powSpy = stub(Math, 'pow').returns(0); + const randomSpy = stub(Math, 'random').returns(0.5); + const fakeRetryData = { + throttleMetadata: {}, + getThrottleMetadata: stub(), + setThrottleMetadata: stub(), + deleteThrottleMetadata: stub(), + intervalMillis: 5 + }; + + // Returns responses with each of 4 retriable statuses, then a success response. + const retriableStatuses = [429, 500, 503, 504]; + fetchStub = stub(window, 'fetch'); + retriableStatuses.forEach((status, index) => { + const failResponse = new window.Response(JSON.stringify({}), { + status + }); + fetchStub.onCall(index).resolves(failResponse); + }); + const successResponse = new window.Response( + JSON.stringify(successObject), + { + status: 200 + } + ); + fetchStub.onCall(retriableStatuses.length).resolves(successResponse); + + const app = getFakeApp(fakeAppParams); + const config: + | DynamicConfig + | MinimalDynamicConfig = await fetchDynamicConfigWithRetry( + app, + fakeRetryData + ); + + // Verify retryData.setThrottleMetadata() was called on each retry. + for (let i = 0; i < retriableStatuses.length; i++) { + retriableStatuses[i]; + expect(fakeRetryData.setThrottleMetadata.args[i][1]).to.deep.equal({ + backoffCount: i + 1, + throttleEndTimeMillis: (i + 1) * 20 + }); + } + + expect(fetchStub.args[0][0]).to.equal(fakeUrl); + expect(fetchStub.args[0][1].headers.get('x-goog-api-key')).to.equal( + fakeAppParams.apiKey + ); + expect(config.appId).to.equal(fakeAppId); + expect(config.measurementId).to.equal(fakeMeasurementId); + + powSpy.restore(); + randomSpy.restore(); + clock.restore(); + }); + it('retries on retriable error until aborted by timeout', async () => { + const fakeRetryData = { + throttleMetadata: {}, + getThrottleMetadata: stub(), + setThrottleMetadata: stub(), + deleteThrottleMetadata: stub(), + intervalMillis: 10 + }; + + // Always returns retriable server error. + stubFetch(500, {}); + + const app = getFakeApp(fakeAppParams); + // Set fetch timeout to 50 ms. + const fetchPromise = fetchDynamicConfigWithRetry(app, fakeRetryData, 50); + await expect(fetchPromise).to.be.rejectedWith( + AnalyticsError.FETCH_THROTTLE + ); + // Should be enough time for at least 2 retries, including fuzzing. + expect(fakeRetryData.setThrottleMetadata.callCount).to.be.greaterThan(1); + }); + it('retries on 503 error until aborted by timeout', async () => { + const fakeRetryData = { + throttleMetadata: {}, + getThrottleMetadata: stub(), + setThrottleMetadata: stub(), + deleteThrottleMetadata: stub(), + intervalMillis: 10 + }; + + // Always returns retriable server error. + stubFetch(503, {}); + + const app = getFakeApp(fakeAppParams); + // Set fetch timeout to 50 ms. + const fetchPromise = fetchDynamicConfigWithRetry(app, fakeRetryData, 50); + await expect(fetchPromise).to.be.rejectedWith( + AnalyticsError.FETCH_THROTTLE + ); + const retryTime1 = + fakeRetryData.setThrottleMetadata.args[0][1].throttleEndTimeMillis; + const retryTime2 = + fakeRetryData.setThrottleMetadata.args[1][1].throttleEndTimeMillis; + expect(fakeRetryData.setThrottleMetadata).to.be.called; + // Interval between first and second retry should be greater than lowest fuzzable + // value of LONG_RETRY_FACTOR. + expect(retryTime2 - retryTime1).to.be.at.least( + Math.floor(LONG_RETRY_FACTOR / 2) * fakeRetryData.intervalMillis + ); + }); + it( + 'retries on retriable error until aborted by timeout,' + + ' then uses local measurementId if available', + async () => { + const fakeRetryData = { + throttleMetadata: {}, + getThrottleMetadata: stub(), + setThrottleMetadata: stub(), + deleteThrottleMetadata: stub(), + intervalMillis: 10 + }; + + // Always returns retriable server error. + stubFetch(500, {}); + const consoleStub = stub(console, 'warn'); + + const app = getFakeApp({ + ...fakeAppParams, + measurementId: fakeMeasurementId + }); + // Set fetch timeout to 50 ms. + await fetchDynamicConfigWithRetry(app, fakeRetryData, 50); + expect(consoleStub.args[0][1]).to.include(fakeMeasurementId); + consoleStub.restore(); + } + ); + }); +}); diff --git a/packages-exp/analytics-exp/src/get-config.ts b/packages-exp/analytics-exp/src/get-config.ts new file mode 100644 index 00000000000..0b03867c857 --- /dev/null +++ b/packages-exp/analytics-exp/src/get-config.ts @@ -0,0 +1,320 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Most logic is copied from packages/remote-config/src/client/retrying_client.ts + */ + +import { FirebaseApp } from '@firebase/app-exp'; +import { DynamicConfig, ThrottleMetadata, MinimalDynamicConfig } from './types'; +import { FirebaseError, calculateBackoffMillis } from '@firebase/util'; +import { AnalyticsError, ERROR_FACTORY } from './errors'; +import { DYNAMIC_CONFIG_URL, FETCH_TIMEOUT_MILLIS } from './constants'; +import { logger } from './logger'; + +// App config fields needed by analytics. +export interface AppFields { + appId: string; + apiKey: string; + measurementId?: string; +} + +/** + * Backoff factor for 503 errors, which we want to be conservative about + * to avoid overloading servers. Each retry interval will be + * BASE_INTERVAL_MILLIS * LONG_RETRY_FACTOR ^ retryCount, so the second one + * will be ~30 seconds (with fuzzing). + */ +export const LONG_RETRY_FACTOR = 30; + +/** + * Base wait interval to multiplied by backoffFactor^backoffCount. + */ +const BASE_INTERVAL_MILLIS = 1000; + +/** + * Stubbable retry data storage class. + */ +class RetryData { + constructor( + public throttleMetadata: { [appId: string]: ThrottleMetadata } = {}, + public intervalMillis: number = BASE_INTERVAL_MILLIS + ) {} + + getThrottleMetadata(appId: string): ThrottleMetadata { + return this.throttleMetadata[appId]; + } + + setThrottleMetadata(appId: string, metadata: ThrottleMetadata): void { + this.throttleMetadata[appId] = metadata; + } + + deleteThrottleMetadata(appId: string): void { + delete this.throttleMetadata[appId]; + } +} + +const defaultRetryData = new RetryData(); + +/** + * Set GET request headers. + * @param apiKey App API key. + */ +function getHeaders(apiKey: string): Headers { + return new Headers({ + Accept: 'application/json', + 'x-goog-api-key': apiKey + }); +} + +/** + * Fetches dynamic config from backend. + * @param app Firebase app to fetch config for. + */ +export async function fetchDynamicConfig( + appFields: AppFields +): Promise { + const { appId, apiKey } = appFields; + const request: RequestInit = { + method: 'GET', + headers: getHeaders(apiKey) + }; + const appUrl = DYNAMIC_CONFIG_URL.replace('{app-id}', appId); + const response = await fetch(appUrl, request); + if (response.status !== 200 && response.status !== 304) { + let errorMessage = ''; + try { + // Try to get any error message text from server response. + const jsonResponse = (await response.json()) as { + error?: { message?: string }; + }; + if (jsonResponse.error?.message) { + errorMessage = jsonResponse.error.message; + } + } catch (_ignored) {} + throw ERROR_FACTORY.create(AnalyticsError.CONFIG_FETCH_FAILED, { + httpStatus: response.status, + responseMessage: errorMessage + }); + } + return response.json(); +} + +/** + * Fetches dynamic config from backend, retrying if failed. + * @param app Firebase app to fetch config for. + */ +export async function fetchDynamicConfigWithRetry( + app: FirebaseApp, + // retryData and timeoutMillis are parameterized to allow passing a different value for testing. + retryData: RetryData = defaultRetryData, + timeoutMillis?: number +): Promise { + const { appId, apiKey, measurementId } = app.options; + + if (!appId) { + throw ERROR_FACTORY.create(AnalyticsError.NO_APP_ID); + } + + if (!apiKey) { + if (measurementId) { + return { + measurementId, + appId + }; + } + throw ERROR_FACTORY.create(AnalyticsError.NO_API_KEY); + } + + const throttleMetadata: ThrottleMetadata = retryData.getThrottleMetadata( + appId + ) || { + backoffCount: 0, + throttleEndTimeMillis: Date.now() + }; + + const signal = new AnalyticsAbortSignal(); + + setTimeout( + async () => { + // Note a very low delay, eg < 10ms, can elapse before listeners are initialized. + signal.abort(); + }, + timeoutMillis !== undefined ? timeoutMillis : FETCH_TIMEOUT_MILLIS + ); + + return attemptFetchDynamicConfigWithRetry( + { appId, apiKey, measurementId }, + throttleMetadata, + signal, + retryData + ); +} + +/** + * Runs one retry attempt. + * @param appFields Necessary app config fields. + * @param throttleMetadata Ongoing metadata to determine throttling times. + * @param signal Abort signal. + */ +async function attemptFetchDynamicConfigWithRetry( + appFields: AppFields, + { throttleEndTimeMillis, backoffCount }: ThrottleMetadata, + signal: AnalyticsAbortSignal, + retryData: RetryData = defaultRetryData // for testing +): Promise { + const { appId, measurementId } = appFields; + // Starts with a (potentially zero) timeout to support resumption from stored state. + // Ensures the throttle end time is honored if the last attempt timed out. + // Note the SDK will never make a request if the fetch timeout expires at this point. + try { + await setAbortableTimeout(signal, throttleEndTimeMillis); + } catch (e) { + if (measurementId) { + logger.warn( + `Timed out fetching this Firebase app's measurement ID from the server.` + + ` Falling back to the measurement ID ${measurementId}` + + ` provided in the "measurementId" field in the local Firebase config. [${e.message}]` + ); + return { appId, measurementId }; + } + throw e; + } + + try { + const response = await fetchDynamicConfig(appFields); + + // Note the SDK only clears throttle state if response is success or non-retriable. + retryData.deleteThrottleMetadata(appId); + + return response; + } catch (e) { + if (!isRetriableError(e)) { + retryData.deleteThrottleMetadata(appId); + if (measurementId) { + logger.warn( + `Failed to fetch this Firebase app's measurement ID from the server.` + + ` Falling back to the measurement ID ${measurementId}` + + ` provided in the "measurementId" field in the local Firebase config. [${e.message}]` + ); + return { appId, measurementId }; + } else { + throw e; + } + } + + const backoffMillis = + Number(e.customData.httpStatus) === 503 + ? calculateBackoffMillis( + backoffCount, + retryData.intervalMillis, + LONG_RETRY_FACTOR + ) + : calculateBackoffMillis(backoffCount, retryData.intervalMillis); + + // Increments backoff state. + const throttleMetadata = { + throttleEndTimeMillis: Date.now() + backoffMillis, + backoffCount: backoffCount + 1 + }; + + // Persists state. + retryData.setThrottleMetadata(appId, throttleMetadata); + logger.debug(`Calling attemptFetch again in ${backoffMillis} millis`); + + return attemptFetchDynamicConfigWithRetry( + appFields, + throttleMetadata, + signal, + retryData + ); + } +} + +/** + * Supports waiting on a backoff by: + * + *
    + *
  • Promisifying setTimeout, so we can set a timeout in our Promise chain
  • + *
  • Listening on a signal bus for abort events, just like the Fetch API
  • + *
  • Failing in the same way the Fetch API fails, so timing out a live request and a throttled + * request appear the same.
  • + *
+ * + *

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

AbortController's AbortSignal conveniently decouples fetch timeout logic from other aspects + * of networking, such as retries. Firebase doesn't use AbortController enough to justify a + * polyfill recommendation, like we do with the Fetch API, but this minimal shim can easily be + * swapped out if/when we do. + */ +export class AnalyticsAbortSignal { + listeners: Array<() => void> = []; + addEventListener(listener: () => void): void { + this.listeners.push(listener); + } + abort(): void { + this.listeners.forEach(listener => listener()); + } +} diff --git a/packages-exp/analytics-exp/src/helpers.test.ts b/packages-exp/analytics-exp/src/helpers.test.ts new file mode 100644 index 00000000000..79614f9edf4 --- /dev/null +++ b/packages-exp/analytics-exp/src/helpers.test.ts @@ -0,0 +1,499 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from 'chai'; +import { SinonStub, stub } from 'sinon'; +import '../testing/setup'; +import { DataLayer, Gtag, DynamicConfig } from './types'; +import { + getOrCreateDataLayer, + insertScriptTag, + wrapOrCreateGtag, + findGtagScriptOnPage, + promiseAllSettled +} from './helpers'; +import { GtagCommand } from './constants'; +import { Deferred } from '@firebase/util'; + +const fakeMeasurementId = 'abcd-efgh-ijkl'; +const fakeAppId = 'my-test-app-1234'; +const fakeDynamicConfig: DynamicConfig = { + projectId: '---', + appId: fakeAppId, + databaseURL: '---', + storageBucket: '---', + locationId: '---', + apiKey: '---', + authDomain: '---', + messagingSenderId: '---', + measurementId: fakeMeasurementId +}; +const fakeDynamicConfigPromises = [Promise.resolve(fakeDynamicConfig)]; + +describe('Gtag wrapping functions', () => { + it('getOrCreateDataLayer is able to create a new data layer if none exists', () => { + delete window['dataLayer']; + expect(getOrCreateDataLayer('dataLayer')).to.deep.equal([]); + }); + + it('getOrCreateDataLayer is able to correctly identify an existing data layer', () => { + const existingDataLayer = (window['dataLayer'] = []); + expect(getOrCreateDataLayer('dataLayer')).to.equal(existingDataLayer); + }); + + it('insertScriptIfNeeded inserts script tag', () => { + expect(findGtagScriptOnPage()).to.be.null; + insertScriptTag('customDataLayerName', fakeMeasurementId); + const scriptTag = findGtagScriptOnPage(); + expect(scriptTag).to.not.be.null; + expect(scriptTag!.src).to.contain(`l=customDataLayerName`); + expect(scriptTag!.src).to.contain(`id=${fakeMeasurementId}`); + }); + + describe('wrapOrCreateGtag() when user has not previously inserted a gtag script tag on this page', () => { + afterEach(() => { + delete window['gtag']; + delete window['dataLayer']; + }); + + it('wrapOrCreateGtag creates new gtag function if needed', () => { + expect(window['gtag']).to.not.exist; + wrapOrCreateGtag({}, fakeDynamicConfigPromises, {}, 'dataLayer', 'gtag'); + expect(window['gtag']).to.exist; + }); + + it('new window.gtag function waits for all initialization promises before sending group events', async () => { + const initPromise1 = new Deferred(); + const initPromise2 = new Deferred(); + wrapOrCreateGtag( + { + [fakeAppId]: initPromise1.promise, + otherId: initPromise2.promise + }, + fakeDynamicConfigPromises, + {}, + 'dataLayer', + 'gtag' + ); + window['dataLayer'] = []; + (window['gtag'] as Gtag)(GtagCommand.EVENT, 'purchase', { + 'transaction_id': 'abcd123', + 'send_to': 'some_group' + }); + expect((window['dataLayer'] as DataLayer).length).to.equal(0); + + initPromise1.resolve(fakeMeasurementId); // Resolves first initialization promise. + expect((window['dataLayer'] as DataLayer).length).to.equal(0); + + initPromise2.resolve('other-measurement-id'); // Resolves second initialization promise. + await Promise.all([initPromise1, initPromise2]); // Wait for resolution of Promise.all() + await promiseAllSettled(fakeDynamicConfigPromises); + + expect((window['dataLayer'] as DataLayer).length).to.equal(1); + }); + + it( + 'new window.gtag function waits for all initialization promises before sending ' + + 'event with at least one unknown send_to ID', + async () => { + const initPromise1 = new Deferred(); + const initPromise2 = new Deferred(); + wrapOrCreateGtag( + { + [fakeAppId]: initPromise1.promise, + otherId: initPromise2.promise + }, + fakeDynamicConfigPromises, + { [fakeMeasurementId]: fakeAppId }, + 'dataLayer', + 'gtag' + ); + window['dataLayer'] = []; + (window['gtag'] as Gtag)(GtagCommand.EVENT, 'purchase', { + 'transaction_id': 'abcd123', + 'send_to': [fakeMeasurementId, 'some_group'] + }); + expect((window['dataLayer'] as DataLayer).length).to.equal(0); + + initPromise1.resolve(); // Resolves first initialization promise. + expect((window['dataLayer'] as DataLayer).length).to.equal(0); + + initPromise2.resolve(); // Resolves second initialization promise. + await Promise.all([initPromise1, initPromise2]); // Wait for resolution of Promise.all() + await promiseAllSettled(fakeDynamicConfigPromises); + + expect((window['dataLayer'] as DataLayer).length).to.equal(1); + } + ); + + it( + 'new window.gtag function waits for all initialization promises before sending ' + + 'events with no send_to field', + async () => { + const initPromise1 = new Deferred(); + const initPromise2 = new Deferred(); + wrapOrCreateGtag( + { + [fakeAppId]: initPromise1.promise, + otherId: initPromise2.promise + }, + fakeDynamicConfigPromises, + { [fakeMeasurementId]: fakeAppId }, + 'dataLayer', + 'gtag' + ); + window['dataLayer'] = []; + (window['gtag'] as Gtag)(GtagCommand.EVENT, 'purchase', { + 'transaction_id': 'abcd123' + }); + expect((window['dataLayer'] as DataLayer).length).to.equal(0); + + initPromise1.resolve(); // Resolves first initialization promise. + expect((window['dataLayer'] as DataLayer).length).to.equal(0); + + initPromise2.resolve(); // Resolves second initialization promise. + await Promise.all([initPromise1, initPromise2]); // Wait for resolution of Promise.all() + + expect((window['dataLayer'] as DataLayer).length).to.equal(1); + } + ); + + it( + 'new window.gtag function only waits for firebase initialization promise ' + + 'before sending event only targeted to Firebase instance GA ID', + async () => { + const initPromise1 = new Deferred(); + const initPromise2 = new Deferred(); + wrapOrCreateGtag( + { + [fakeAppId]: initPromise1.promise, + otherId: initPromise2.promise + }, + fakeDynamicConfigPromises, + { [fakeMeasurementId]: fakeAppId }, + 'dataLayer', + 'gtag' + ); + window['dataLayer'] = []; + (window['gtag'] as Gtag)(GtagCommand.EVENT, 'purchase', { + 'transaction_id': 'abcd123', + 'send_to': fakeMeasurementId + }); + expect((window['dataLayer'] as DataLayer).length).to.equal(0); + + initPromise1.resolve(); // Resolves first initialization promise. + await promiseAllSettled(fakeDynamicConfigPromises); + await Promise.all([initPromise1]); // Wait for resolution of Promise.all() + + expect((window['dataLayer'] as DataLayer).length).to.equal(1); + } + ); + + it('new window.gtag function does not wait before sending events if there are no pending initialization promises', async () => { + wrapOrCreateGtag({}, fakeDynamicConfigPromises, {}, 'dataLayer', 'gtag'); + window['dataLayer'] = []; + (window['gtag'] as Gtag)(GtagCommand.EVENT, 'purchase', { + 'transaction_id': 'abcd123' + }); + await Promise.all([]); // Promise.all() always runs before event call, even if empty. + expect((window['dataLayer'] as DataLayer).length).to.equal(1); + }); + + it('new window.gtag function does not wait when sending "set" calls', async () => { + wrapOrCreateGtag( + { [fakeAppId]: Promise.resolve(fakeMeasurementId) }, + fakeDynamicConfigPromises, + {}, + 'dataLayer', + 'gtag' + ); + window['dataLayer'] = []; + (window['gtag'] as Gtag)(GtagCommand.SET, { 'language': 'en' }); + expect((window['dataLayer'] as DataLayer).length).to.equal(1); + }); + + it('new window.gtag function waits for initialization promise when sending "config" calls', async () => { + const initPromise1 = new Deferred(); + wrapOrCreateGtag( + { [fakeAppId]: initPromise1.promise }, + fakeDynamicConfigPromises, + {}, + 'dataLayer', + 'gtag' + ); + window['dataLayer'] = []; + (window['gtag'] as Gtag)(GtagCommand.CONFIG, fakeMeasurementId, { + 'language': 'en' + }); + expect((window['dataLayer'] as DataLayer).length).to.equal(0); + + initPromise1.resolve(fakeMeasurementId); + await promiseAllSettled(fakeDynamicConfigPromises); // Resolves dynamic config fetches. + expect((window['dataLayer'] as DataLayer).length).to.equal(0); + + await Promise.all([initPromise1]); // Wait for resolution of Promise.all() + + expect((window['dataLayer'] as DataLayer).length).to.equal(1); + }); + + it('new window.gtag function does not wait when sending "config" calls if there are no pending initialization promises', async () => { + wrapOrCreateGtag({}, fakeDynamicConfigPromises, {}, 'dataLayer', 'gtag'); + window['dataLayer'] = []; + (window['gtag'] as Gtag)(GtagCommand.CONFIG, fakeMeasurementId, { + 'transaction_id': 'abcd123' + }); + await promiseAllSettled(fakeDynamicConfigPromises); + await Promise.resolve(); // Config call is always chained onto initialization promise list, even if empty. + expect((window['dataLayer'] as DataLayer).length).to.equal(1); + }); + }); + + describe('wrapOrCreateGtag() when user has previously inserted gtag script tag on this page', () => { + const existingGtagStub: SinonStub = stub(); + + beforeEach(() => { + window['gtag'] = existingGtagStub; + }); + + afterEach(() => { + existingGtagStub.reset(); + }); + + it('new window.gtag function waits for all initialization promises before sending group events', async () => { + const initPromise1 = new Deferred(); + const initPromise2 = new Deferred(); + wrapOrCreateGtag( + { + [fakeAppId]: initPromise1.promise, + otherId: initPromise2.promise + }, + fakeDynamicConfigPromises, + { [fakeMeasurementId]: fakeAppId }, + 'dataLayer', + 'gtag' + ); + (window['gtag'] as Gtag)(GtagCommand.EVENT, 'purchase', { + 'transaction_id': 'abcd123', + 'send_to': 'some_group' + }); + expect(existingGtagStub).to.not.be.called; + + initPromise1.resolve(); // Resolves first initialization promise. + expect(existingGtagStub).to.not.be.called; + + initPromise2.resolve(); // Resolves second initialization promise. + await promiseAllSettled(fakeDynamicConfigPromises); // Resolves dynamic config fetches. + expect(existingGtagStub).to.not.be.called; + + await Promise.all([initPromise1, initPromise2]); // Wait for resolution of Promise.all() + + expect(existingGtagStub).to.be.calledWith(GtagCommand.EVENT, 'purchase', { + 'send_to': 'some_group', + 'transaction_id': 'abcd123' + }); + }); + + it( + 'new window.gtag function waits for all initialization promises before sending ' + + 'event with at least one unknown send_to ID', + async () => { + const initPromise1 = new Deferred(); + const initPromise2 = new Deferred(); + wrapOrCreateGtag( + { + [fakeAppId]: initPromise1.promise, + otherId: initPromise2.promise + }, + fakeDynamicConfigPromises, + { [fakeMeasurementId]: fakeAppId }, + 'dataLayer', + 'gtag' + ); + (window['gtag'] as Gtag)(GtagCommand.EVENT, 'purchase', { + 'transaction_id': 'abcd123', + 'send_to': [fakeMeasurementId, 'some_group'] + }); + expect(existingGtagStub).to.not.be.called; + + initPromise1.resolve(); // Resolves first initialization promise. + expect(existingGtagStub).to.not.be.called; + + initPromise2.resolve(); // Resolves second initialization promise. + await promiseAllSettled(fakeDynamicConfigPromises); // Resolves dynamic config fetches. + expect(existingGtagStub).to.not.be.called; + + await Promise.all([initPromise1, initPromise2]); // Wait for resolution of Promise.all() + + expect(existingGtagStub).to.be.calledWith( + GtagCommand.EVENT, + 'purchase', + { + 'send_to': [fakeMeasurementId, 'some_group'], + 'transaction_id': 'abcd123' + } + ); + } + ); + + it( + 'new window.gtag function waits for all initialization promises before sending ' + + 'events with no send_to field', + async () => { + const initPromise1 = new Deferred(); + const initPromise2 = new Deferred(); + wrapOrCreateGtag( + { + [fakeAppId]: initPromise1.promise, + otherId: initPromise2.promise + }, + fakeDynamicConfigPromises, + { [fakeMeasurementId]: fakeAppId }, + 'dataLayer', + 'gtag' + ); + (window['gtag'] as Gtag)(GtagCommand.EVENT, 'purchase', { + 'transaction_id': 'abcd123' + }); + expect(existingGtagStub).to.not.be.called; + + initPromise1.resolve(); // Resolves first initialization promise. + expect(existingGtagStub).to.not.be.called; + + initPromise2.resolve(); // Resolves second initialization promise. + + await Promise.all([initPromise1, initPromise2]); // Wait for resolution of Promise.all() + + expect(existingGtagStub).to.be.calledWith( + GtagCommand.EVENT, + 'purchase', + { 'transaction_id': 'abcd123' } + ); + } + ); + + it( + 'new window.gtag function only waits for firebase initialization promise ' + + 'before sending event only targeted to Firebase instance GA ID', + async () => { + const initPromise1 = new Deferred(); + const initPromise2 = new Deferred(); + wrapOrCreateGtag( + { + [fakeAppId]: initPromise1.promise, + otherId: initPromise2.promise + }, + fakeDynamicConfigPromises, + { [fakeMeasurementId]: fakeAppId }, + 'dataLayer', + 'gtag' + ); + (window['gtag'] as Gtag)(GtagCommand.EVENT, 'purchase', { + 'transaction_id': 'abcd123', + 'send_to': fakeMeasurementId + }); + expect(existingGtagStub).to.not.be.called; + + initPromise1.resolve(); // Resolves first initialization promise. + await promiseAllSettled(fakeDynamicConfigPromises); // Resolves dynamic config fetches. + expect(existingGtagStub).to.not.be.called; + + await Promise.all([initPromise1]); // Wait for resolution of Promise.all() + + expect(existingGtagStub).to.be.calledWith( + GtagCommand.EVENT, + 'purchase', + { 'send_to': fakeMeasurementId, 'transaction_id': 'abcd123' } + ); + } + ); + + it('wrapped window.gtag function does not wait if there are no pending initialization promises', async () => { + wrapOrCreateGtag({}, fakeDynamicConfigPromises, {}, 'dataLayer', 'gtag'); + window['dataLayer'] = []; + (window['gtag'] as Gtag)(GtagCommand.EVENT, 'purchase', { + 'transaction_id': 'abcd321' + }); + await Promise.all([]); // Promise.all() always runs before event call, even if empty. + expect(existingGtagStub).to.be.calledWith(GtagCommand.EVENT, 'purchase', { + 'transaction_id': 'abcd321' + }); + }); + + it('wrapped window.gtag function does not wait when sending "set" calls', async () => { + wrapOrCreateGtag( + { [fakeAppId]: Promise.resolve(fakeMeasurementId) }, + fakeDynamicConfigPromises, + {}, + 'dataLayer', + 'gtag' + ); + window['dataLayer'] = []; + (window['gtag'] as Gtag)(GtagCommand.SET, { 'language': 'en' }); + expect(existingGtagStub).to.be.calledWith(GtagCommand.SET, { + 'language': 'en' + }); + }); + + it('new window.gtag function waits for initialization promise when sending "config" calls', async () => { + const initPromise1 = new Deferred(); + wrapOrCreateGtag( + { [fakeAppId]: initPromise1.promise }, + fakeDynamicConfigPromises, + {}, + 'dataLayer', + 'gtag' + ); + window['dataLayer'] = []; + (window['gtag'] as Gtag)(GtagCommand.CONFIG, fakeMeasurementId, { + 'language': 'en' + }); + expect(existingGtagStub).to.not.be.called; + + initPromise1.resolve(fakeMeasurementId); + await promiseAllSettled(fakeDynamicConfigPromises); // Resolves dynamic config fetches. + expect(existingGtagStub).to.not.be.called; + + await Promise.all([initPromise1]); // Wait for resolution of Promise.all() + + expect(existingGtagStub).to.be.calledWith( + GtagCommand.CONFIG, + fakeMeasurementId, + { + 'language': 'en' + } + ); + }); + + it('new window.gtag function does not wait when sending "config" calls if there are no pending initialization promises', async () => { + wrapOrCreateGtag({}, fakeDynamicConfigPromises, {}, 'dataLayer', 'gtag'); + window['dataLayer'] = []; + (window['gtag'] as Gtag)(GtagCommand.CONFIG, fakeMeasurementId, { + 'transaction_id': 'abcd123' + }); + await promiseAllSettled(fakeDynamicConfigPromises); // Resolves dynamic config fetches. + expect(existingGtagStub).to.not.be.called; + await Promise.resolve(); // Config call is always chained onto initialization promise list, even if empty. + expect(existingGtagStub).to.be.calledWith( + GtagCommand.CONFIG, + fakeMeasurementId, + { + 'transaction_id': 'abcd123' + } + ); + }); + }); +}); diff --git a/packages-exp/analytics-exp/src/helpers.ts b/packages-exp/analytics-exp/src/helpers.ts new file mode 100644 index 00000000000..d8171f9c0f5 --- /dev/null +++ b/packages-exp/analytics-exp/src/helpers.ts @@ -0,0 +1,320 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { CustomParams, ControlParams, EventParams } from './public-types'; +import { DynamicConfig, DataLayer, Gtag, MinimalDynamicConfig } from './types'; +import { GtagCommand, GTAG_URL } from './constants'; +import { logger } from './logger'; + +/** + * Makeshift polyfill for Promise.allSettled(). Resolves when all promises + * have either resolved or rejected. + * + * @param promises Array of promises to wait for. + */ +export function promiseAllSettled( + promises: Array> +): Promise { + return Promise.all(promises.map(promise => promise.catch(e => e))); +} + +/** + * Inserts gtag script tag into the page to asynchronously download gtag. + * @param dataLayerName Name of datalayer (most often the default, "_dataLayer"). + */ +export function insertScriptTag( + dataLayerName: string, + measurementId: string +): void { + const script = document.createElement('script'); + // We are not providing an analyticsId in the URL because it would trigger a `page_view` + // without fid. We will initialize ga-id using gtag (config) command together with fid. + script.src = `${GTAG_URL}?l=${dataLayerName}&id=${measurementId}`; + script.async = true; + document.head.appendChild(script); +} + +/** + * Get reference to, or create, global datalayer. + * @param dataLayerName Name of datalayer (most often the default, "_dataLayer"). + */ +export function getOrCreateDataLayer(dataLayerName: string): DataLayer { + // Check for existing dataLayer and create if needed. + let dataLayer: DataLayer = []; + if (Array.isArray(window[dataLayerName])) { + dataLayer = window[dataLayerName] as DataLayer; + } else { + window[dataLayerName] = dataLayer; + } + return dataLayer; +} + +/** + * Wrapped gtag logic when gtag is called with 'config' command. + * + * @param gtagCore Basic gtag function that just appends to dataLayer. + * @param initializationPromisesMap Map of appIds to their initialization promises. + * @param dynamicConfigPromisesList Array of dynamic config fetch promises. + * @param measurementIdToAppId Map of GA measurementIDs to corresponding Firebase appId. + * @param measurementId GA Measurement ID to set config for. + * @param gtagParams Gtag config params to set. + */ +async function gtagOnConfig( + gtagCore: Gtag, + initializationPromisesMap: { [appId: string]: Promise }, + dynamicConfigPromisesList: Array< + Promise + >, + measurementIdToAppId: { [measurementId: string]: string }, + measurementId: string, + gtagParams?: ControlParams & EventParams & CustomParams +): Promise { + // If config is already fetched, we know the appId and can use it to look up what FID promise we + /// are waiting for, and wait only on that one. + const correspondingAppId = measurementIdToAppId[measurementId as string]; + try { + if (correspondingAppId) { + await initializationPromisesMap[correspondingAppId]; + } else { + // If config is not fetched yet, wait for all configs (we don't know which one we need) and + // find the appId (if any) corresponding to this measurementId. If there is one, wait on + // that appId's initialization promise. If there is none, promise resolves and gtag + // call goes through. + const dynamicConfigResults = await promiseAllSettled( + dynamicConfigPromisesList + ); + const foundConfig = dynamicConfigResults.find( + config => config.measurementId === measurementId + ); + if (foundConfig) { + await initializationPromisesMap[foundConfig.appId]; + } + } + } catch (e) { + logger.error(e); + } + gtagCore(GtagCommand.CONFIG, measurementId, gtagParams); +} + +/** + * Wrapped gtag logic when gtag is called with 'event' command. + * + * @param gtagCore Basic gtag function that just appends to dataLayer. + * @param initializationPromisesMap Map of appIds to their initialization promises. + * @param dynamicConfigPromisesList Array of dynamic config fetch promises. + * @param measurementId GA Measurement ID to log event to. + * @param gtagParams Params to log with this event. + */ +async function gtagOnEvent( + gtagCore: Gtag, + initializationPromisesMap: { [appId: string]: Promise }, + dynamicConfigPromisesList: Array< + Promise + >, + measurementId: string, + gtagParams?: ControlParams & EventParams & CustomParams +): Promise { + try { + let initializationPromisesToWaitFor: Array> = []; + + // If there's a 'send_to' param, check if any ID specified matches + // an initializeIds() promise we are waiting for. + if (gtagParams && gtagParams['send_to']) { + let gaSendToList: string | string[] = gtagParams['send_to']; + // Make it an array if is isn't, so it can be dealt with the same way. + if (!Array.isArray(gaSendToList)) { + gaSendToList = [gaSendToList]; + } + // Checking 'send_to' fields requires having all measurement ID results back from + // the dynamic config fetch. + const dynamicConfigResults = await promiseAllSettled( + dynamicConfigPromisesList + ); + for (const sendToId of gaSendToList) { + // Any fetched dynamic measurement ID that matches this 'send_to' ID + const foundConfig = dynamicConfigResults.find( + config => config.measurementId === sendToId + ); + const initializationPromise = + foundConfig && initializationPromisesMap[foundConfig.appId]; + if (initializationPromise) { + initializationPromisesToWaitFor.push(initializationPromise); + } else { + // Found an item in 'send_to' that is not associated + // directly with an FID, possibly a group. Empty this array, + // exit the loop early, and let it get populated below. + initializationPromisesToWaitFor = []; + break; + } + } + } + + // This will be unpopulated if there was no 'send_to' field , or + // if not all entries in the 'send_to' field could be mapped to + // a FID. In these cases, wait on all pending initialization promises. + if (initializationPromisesToWaitFor.length === 0) { + initializationPromisesToWaitFor = Object.values( + initializationPromisesMap + ); + } + + // Run core gtag function with args after all relevant initialization + // promises have been resolved. + await Promise.all(initializationPromisesToWaitFor); + // Workaround for http://b/141370449 - third argument cannot be undefined. + gtagCore(GtagCommand.EVENT, measurementId, gtagParams || {}); + } catch (e) { + logger.error(e); + } +} + +/** + * Wraps a standard gtag function with extra code to wait for completion of + * relevant initialization promises before sending requests. + * + * @param gtagCore Basic gtag function that just appends to dataLayer. + * @param initializationPromisesMap Map of appIds to their initialization promises. + * @param dynamicConfigPromisesList Array of dynamic config fetch promises. + * @param measurementIdToAppId Map of GA measurementIDs to corresponding Firebase appId. + */ +function wrapGtag( + gtagCore: Gtag, + /** + * Allows wrapped gtag calls to wait on whichever intialization promises are required, + * depending on the contents of the gtag params' `send_to` field, if any. + */ + initializationPromisesMap: { [appId: string]: Promise }, + /** + * Wrapped gtag calls sometimes require all dynamic config fetches to have returned + * before determining what initialization promises (which include FIDs) to wait for. + */ + dynamicConfigPromisesList: Array< + Promise + >, + /** + * Wrapped gtag config calls can narrow down which initialization promise (with FID) + * to wait for if the measurementId is already fetched, by getting the corresponding appId, + * which is the key for the initialization promises map. + */ + measurementIdToAppId: { [measurementId: string]: string } +): Gtag { + /** + * Wrapper around gtag that ensures FID is sent with gtag calls. + * @param command Gtag command type. + * @param idOrNameOrParams Measurement ID if command is EVENT/CONFIG, params if command is SET. + * @param gtagParams Params if event is EVENT/CONFIG. + */ + async function gtagWrapper( + command: 'config' | 'set' | 'event', + idOrNameOrParams: string | ControlParams, + gtagParams?: ControlParams & EventParams & CustomParams + ): Promise { + try { + // If event, check that relevant initialization promises have completed. + if (command === GtagCommand.EVENT) { + // If EVENT, second arg must be measurementId. + await gtagOnEvent( + gtagCore, + initializationPromisesMap, + dynamicConfigPromisesList, + idOrNameOrParams as string, + gtagParams + ); + } else if (command === GtagCommand.CONFIG) { + // If CONFIG, second arg must be measurementId. + await gtagOnConfig( + gtagCore, + initializationPromisesMap, + dynamicConfigPromisesList, + measurementIdToAppId, + idOrNameOrParams as string, + gtagParams + ); + } else { + // If SET, second arg must be params. + gtagCore(GtagCommand.SET, idOrNameOrParams as CustomParams); + } + } catch (e) { + logger.error(e); + } + } + return gtagWrapper as Gtag; +} + +/** + * Creates global gtag function or wraps existing one if found. + * This wrapped function attaches Firebase instance ID (FID) to gtag 'config' and + * 'event' calls that belong to the GAID associated with this Firebase instance. + * + * @param initializationPromisesMap Map of appIds to their initialization promises. + * @param dynamicConfigPromisesList Array of dynamic config fetch promises. + * @param measurementIdToAppId Map of GA measurementIDs to corresponding Firebase appId. + * @param dataLayerName Name of global GA datalayer array. + * @param gtagFunctionName Name of global gtag function ("gtag" if not user-specified). + */ +export function wrapOrCreateGtag( + initializationPromisesMap: { [appId: string]: Promise }, + dynamicConfigPromisesList: Array< + Promise + >, + measurementIdToAppId: { [measurementId: string]: string }, + dataLayerName: string, + gtagFunctionName: string +): { + gtagCore: Gtag; + wrappedGtag: Gtag; +} { + // Create a basic core gtag function + let gtagCore: Gtag = function (..._args: unknown[]) { + // Must push IArguments object, not an array. + (window[dataLayerName] as DataLayer).push(arguments); + }; + + // Replace it with existing one if found + if ( + window[gtagFunctionName] && + typeof window[gtagFunctionName] === 'function' + ) { + // @ts-ignore + gtagCore = window[gtagFunctionName]; + } + + window[gtagFunctionName] = wrapGtag( + gtagCore, + initializationPromisesMap, + dynamicConfigPromisesList, + measurementIdToAppId + ); + + return { + gtagCore, + wrappedGtag: window[gtagFunctionName] as Gtag + }; +} + +/** + * Returns first script tag in DOM matching our gtag url pattern. + */ +export function findGtagScriptOnPage(): HTMLScriptElement | null { + const scriptTags = window.document.getElementsByTagName('script'); + for (const tag of Object.values(scriptTags)) { + if (tag.src && tag.src.includes(GTAG_URL)) { + return tag; + } + } + return null; +} diff --git a/packages-exp/analytics-exp/src/index.test.ts b/packages-exp/analytics-exp/src/index.test.ts new file mode 100644 index 00000000000..1438d95e31b --- /dev/null +++ b/packages-exp/analytics-exp/src/index.test.ts @@ -0,0 +1,369 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from 'chai'; +import { SinonStub, stub, useFakeTimers } from 'sinon'; +import '../testing/setup'; +import { settings } from './index'; +import { + getFakeApp, + getFakeInstallations +} from '../testing/get-fake-firebase-services'; +import { FirebaseApp } from '@firebase/app-exp'; +import { GtagCommand } from './constants'; +import { findGtagScriptOnPage } from './helpers'; +import { removeGtagScript } from '../testing/gtag-script-util'; +import { Deferred } from '@firebase/util'; +import { AnalyticsError } from './errors'; +import { logEvent } from './api'; +import { + AnalyticsService, + getGlobalVars, + resetGlobalVars, + factory as analyticsFactory +} from './factory'; +import { _FirebaseInstallationsInternal } from '@firebase/installations-exp'; + +let analyticsInstance: AnalyticsService = {} as AnalyticsService; +const fakeMeasurementId = 'abcd-efgh'; +const fakeAppParams = { appId: 'abcdefgh12345:23405', apiKey: 'AAbbCCdd12345' }; +let fetchStub: SinonStub = stub(); +const customGtagName = 'customGtag'; +const customDataLayerName = 'customDataLayer'; +let clock: sinon.SinonFakeTimers; +let fakeInstallations: _FirebaseInstallationsInternal; + +// Fake indexedDB.open() request +let fakeRequest = { + onsuccess: () => {}, + result: { + close: () => {} + } +}; +let idbOpenStub = stub(); + +function stubFetch(status: number, body: object): void { + fetchStub = stub(window, 'fetch'); + const mockResponse = new Response(JSON.stringify(body), { + status + }); + fetchStub.returns(Promise.resolve(mockResponse)); +} + +// Stub indexedDB.open() because sinon's clock does not know +// how to wait for the real indexedDB callbacks to resolve. +function stubIdbOpen(): void { + (fakeRequest = { + onsuccess: () => {}, + result: { + close: () => {} + } + }), + (idbOpenStub = stub(indexedDB, 'open').returns(fakeRequest as any)); +} + +describe('FirebaseAnalytics instance tests', () => { + describe('Initialization', () => { + beforeEach(() => { + clock = useFakeTimers(); + resetGlobalVars(); + fakeInstallations = getFakeInstallations(); + }); + afterEach(() => { + clock.restore(); + }); + + it('Throws if no appId in config', () => { + const app = getFakeApp({ apiKey: fakeAppParams.apiKey }); + expect(() => analyticsFactory(app, fakeInstallations)).to.throw( + AnalyticsError.NO_APP_ID + ); + }); + it('Throws if no apiKey or measurementId in config', () => { + const app = getFakeApp({ appId: fakeAppParams.appId }); + expect(() => analyticsFactory(app, fakeInstallations)).to.throw( + AnalyticsError.NO_API_KEY + ); + }); + it('Warns if config has no apiKey but does have a measurementId', async () => { + // Since this is a warning and doesn't block the rest of initialization + // all the async stuff needs to be stubbed and cleaned up. + const warnStub = stub(console, 'warn'); + const docStub = stub(document, 'createElement'); + stubFetch(200, { measurementId: fakeMeasurementId }); + const app = getFakeApp({ + appId: fakeAppParams.appId, + measurementId: fakeMeasurementId + }); + stubIdbOpen(); + analyticsFactory(app, fakeInstallations); + // Successfully resolves fake IDB open request. + fakeRequest.onsuccess(); + // Lets async IDB validation process complete. + // await clock.runAllAsync(); + expect(warnStub.args[0][1]).to.include( + `Falling back to the measurement ID ${fakeMeasurementId}` + ); + warnStub.restore(); + docStub.restore(); + fetchStub.restore(); + idbOpenStub.restore(); + delete window['gtag']; + delete window['dataLayer']; + }); + it('Throws if creating an instance with already-used appId', () => { + const app = getFakeApp(fakeAppParams); + resetGlobalVars(false, { [fakeAppParams.appId]: Promise.resolve() }); + expect(() => analyticsFactory(app, fakeInstallations)).to.throw( + AnalyticsError.ALREADY_EXISTS + ); + }); + }); + describe('Standard app, page already has user gtag script', () => { + let app: FirebaseApp = {} as FirebaseApp; + let fidDeferred: Deferred; + const gtagStub: SinonStub = stub(); + before(async () => { + clock = useFakeTimers(); + resetGlobalVars(); + app = getFakeApp(fakeAppParams); + fidDeferred = new Deferred(); + fakeInstallations = getFakeInstallations('fid-1234', () => + fidDeferred.resolve() + ); + window['gtag'] = gtagStub; + window['dataLayer'] = []; + stubFetch(200, { measurementId: fakeMeasurementId }); + stubIdbOpen(); + analyticsInstance = analyticsFactory(app, fakeInstallations); + // Successfully resolves fake IDB open request. + fakeRequest.onsuccess(); + }); + after(() => { + delete window['gtag']; + delete window['dataLayer']; + removeGtagScript(); + fetchStub.restore(); + clock.restore(); + idbOpenStub.restore(); + }); + it('Contains reference to parent app', () => { + expect(analyticsInstance.app).to.equal(app); + }); + it('Calls gtag correctly on logEvent (instance)', async () => { + logEvent(analyticsInstance, 'add_payment_info', { + currency: 'USD' + }); + // Clear promise chain started by logEvent. + await clock.runAllAsync(); + expect(gtagStub).to.have.been.calledWith('js'); + expect(gtagStub).to.have.been.calledWith( + GtagCommand.CONFIG, + fakeMeasurementId, + { + 'firebase_id': 'fid-1234', + origin: 'firebase', + update: true + } + ); + expect(gtagStub).to.have.been.calledWith( + GtagCommand.EVENT, + 'add_payment_info', + { + 'send_to': 'abcd-efgh', + currency: 'USD' + } + ); + }); + }); + + describe('Standard app, mismatched environment', () => { + let app: FirebaseApp = {} as FirebaseApp; + const gtagStub: SinonStub = stub(); + let fidDeferred: Deferred; + let warnStub: SinonStub; + let cookieStub: SinonStub; + beforeEach(() => { + clock = useFakeTimers(); + resetGlobalVars(); + app = getFakeApp(fakeAppParams); + fidDeferred = new Deferred(); + fakeInstallations = getFakeInstallations('fid-1234', () => + fidDeferred.resolve() + ); + window['gtag'] = gtagStub; + window['dataLayer'] = []; + stubFetch(200, { measurementId: fakeMeasurementId }); + warnStub = stub(console, 'warn'); + stubIdbOpen(); + }); + afterEach(() => { + delete window['gtag']; + delete window['dataLayer']; + removeGtagScript(); + fetchStub.restore(); + clock.restore(); + warnStub.restore(); + idbOpenStub.restore(); + gtagStub.resetHistory(); + }); + it('Warns on initialization if cookies not available', async () => { + cookieStub = stub(navigator, 'cookieEnabled').value(false); + analyticsInstance = analyticsFactory(app, fakeInstallations); + // Successfully resolves fake IDB open request. + fakeRequest.onsuccess(); + expect(warnStub.args[0][1]).to.include( + AnalyticsError.INVALID_ANALYTICS_CONTEXT + ); + expect(warnStub.args[0][1]).to.include('Cookies'); + cookieStub.restore(); + }); + it('Warns on initialization if in browser extension', async () => { + window.chrome = { runtime: { id: 'blah' } }; + analyticsInstance = analyticsFactory(app, fakeInstallations); + // Successfully resolves fake IDB open request. + fakeRequest.onsuccess(); + expect(warnStub.args[0][1]).to.include( + AnalyticsError.INVALID_ANALYTICS_CONTEXT + ); + expect(warnStub.args[0][1]).to.include('browser extension'); + window.chrome = undefined; + }); + it('Warns on logEvent if indexedDB API not available', async () => { + const idbStub = stub(window, 'indexedDB').value(undefined); + analyticsInstance = analyticsFactory(app, fakeInstallations); + logEvent(analyticsInstance, 'add_payment_info', { + currency: 'USD' + }); + // Clear promise chain started by logEvent. + await clock.runAllAsync(); + // gtag config call omits FID + expect(gtagStub).to.be.calledWith('config', 'abcd-efgh', { + update: true, + origin: 'firebase' + }); + expect(warnStub.args[0][1]).to.include( + AnalyticsError.INDEXEDDB_UNAVAILABLE + ); + expect(warnStub.args[0][1]).to.include('IndexedDB is not available'); + idbStub.restore(); + }); + it('Warns on logEvent if indexedDB.open() not allowed', async () => { + idbOpenStub.restore(); + idbOpenStub = stub(indexedDB, 'open').throws('idb open error test'); + analyticsInstance = analyticsFactory(app, fakeInstallations); + logEvent(analyticsInstance, 'add_payment_info', { + currency: 'USD' + }); + // Clear promise chain started by logEvent. + await clock.runAllAsync(); + // gtag config call omits FID + expect(gtagStub).to.be.calledWith('config', 'abcd-efgh', { + update: true, + origin: 'firebase' + }); + expect(warnStub.args[0][1]).to.include( + AnalyticsError.INDEXEDDB_UNAVAILABLE + ); + expect(warnStub.args[0][1]).to.include('idb open error test'); + }); + }); + + describe('Page has user gtag script with custom gtag and dataLayer names', () => { + let app: FirebaseApp = {} as FirebaseApp; + let fidDeferred: Deferred; + const gtagStub: SinonStub = stub(); + before(() => { + clock = useFakeTimers(); + resetGlobalVars(); + app = getFakeApp(fakeAppParams); + fidDeferred = new Deferred(); + fakeInstallations = getFakeInstallations('fid-1234', () => + fidDeferred.resolve() + ); + window[customGtagName] = gtagStub; + window[customDataLayerName] = []; + settings({ + dataLayerName: customDataLayerName, + gtagName: customGtagName + }); + stubIdbOpen(); + stubFetch(200, { measurementId: fakeMeasurementId }); + analyticsInstance = analyticsFactory(app, fakeInstallations); + // Successfully resolves fake IDB open request. + fakeRequest.onsuccess(); + }); + after(() => { + delete window[customGtagName]; + delete window[customDataLayerName]; + removeGtagScript(); + fetchStub.restore(); + clock.restore(); + idbOpenStub.restore(); + }); + it('Calls gtag correctly on logEvent (instance)', async () => { + logEvent(analyticsInstance, 'add_payment_info', { + currency: 'USD' + }); + // Clear promise chain started by logEvent. + await clock.runAllAsync(); + expect(gtagStub).to.have.been.calledWith('js'); + expect(gtagStub).to.have.been.calledWith( + GtagCommand.CONFIG, + fakeMeasurementId, + { + 'firebase_id': 'fid-1234', + origin: 'firebase', + update: true + } + ); + expect(gtagStub).to.have.been.calledWith( + GtagCommand.EVENT, + 'add_payment_info', + { + 'send_to': 'abcd-efgh', + currency: 'USD' + } + ); + }); + }); + + describe('Page has no existing gtag script or dataLayer', () => { + it('Adds the script tag to the page', async () => { + resetGlobalVars(); + const app = getFakeApp(fakeAppParams); + fakeInstallations = getFakeInstallations(); + stubFetch(200, {}); + stubIdbOpen(); + analyticsInstance = analyticsFactory(app, fakeInstallations); + + const { initializationPromisesMap } = getGlobalVars(); + // Successfully resolves fake IDB open request. + fakeRequest.onsuccess(); + await initializationPromisesMap[fakeAppParams.appId]; + expect(findGtagScriptOnPage()).to.not.be.null; + expect(typeof window['gtag']).to.equal('function'); + expect(Array.isArray(window['dataLayer'])).to.be.true; + + delete window['gtag']; + delete window['dataLayer']; + removeGtagScript(); + fetchStub.restore(); + idbOpenStub.restore(); + }); + }); +}); diff --git a/packages-exp/analytics-exp/src/index.ts b/packages-exp/analytics-exp/src/index.ts new file mode 100644 index 00000000000..d4050799802 --- /dev/null +++ b/packages-exp/analytics-exp/src/index.ts @@ -0,0 +1,92 @@ +/** + * Firebase Analytics + * + * @packageDocumentation + */ + +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { registerVersion, _registerComponent } from '@firebase/app-exp'; +import { FirebaseAnalyticsInternal } from '@firebase/analytics-interop-types'; +import { factory } from './factory'; +import { ANALYTICS_TYPE } from './constants'; +import { + Component, + ComponentType, + ComponentContainer, + InstanceFactoryOptions +} from '@firebase/component'; +import { ERROR_FACTORY, AnalyticsError } from './errors'; +import { logEvent } from './api'; +import { name, version } from '../package.json'; +import { AnalyticsCallOptions } from './public-types'; +import '@firebase/installations-exp'; + +declare global { + interface Window { + [key: string]: unknown; + } +} + +function registerAnalytics(): void { + _registerComponent( + new Component( + ANALYTICS_TYPE, + (container, { options: analyticsOptions }: InstanceFactoryOptions) => { + // getImmediate for FirebaseApp will always succeed + const app = container.getProvider('app-exp').getImmediate(); + const installations = container + .getProvider('installations-exp-internal') + .getImmediate(); + + return factory(app, installations, analyticsOptions); + }, + ComponentType.PUBLIC + ) + ); + + _registerComponent( + new Component('analytics-internal', internalFactory, ComponentType.PRIVATE) + ); + + registerVersion(name, version); + + function internalFactory( + container: ComponentContainer + ): FirebaseAnalyticsInternal { + try { + const analytics = container.getProvider(ANALYTICS_TYPE).getImmediate(); + return { + logEvent: ( + eventName: string, + eventParams?: { [key: string]: unknown }, + options?: AnalyticsCallOptions + ) => logEvent(analytics, eventName, eventParams, options) + }; + } catch (e) { + throw ERROR_FACTORY.create(AnalyticsError.INTEROP_COMPONENT_REG_FAILED, { + reason: e + }); + } + } +} + +registerAnalytics(); + +export * from './api'; +export * from './public-types'; diff --git a/packages-exp/analytics-exp/src/initialize-analytics.test.ts b/packages-exp/analytics-exp/src/initialize-analytics.test.ts new file mode 100644 index 00000000000..93d2bc8d4b9 --- /dev/null +++ b/packages-exp/analytics-exp/src/initialize-analytics.test.ts @@ -0,0 +1,142 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from 'chai'; +import { SinonStub, stub } from 'sinon'; +import '../testing/setup'; +import { initializeAnalytics } from './initialize-analytics'; +import { + getFakeApp, + getFakeInstallations +} from '../testing/get-fake-firebase-services'; +import { GtagCommand } from './constants'; +import { DynamicConfig } from './types'; +import { FirebaseApp } from '@firebase/app-exp'; +import { Deferred } from '@firebase/util'; +import { _FirebaseInstallationsInternal } from '@firebase/installations-exp'; +import { removeGtagScript } from '../testing/gtag-script-util'; + +const fakeMeasurementId = 'abcd-efgh-ijkl'; +const fakeFid = 'fid-1234-zyxw'; +const fakeAppId = 'abcdefgh12345:23405'; +const fakeAppParams = { appId: fakeAppId, apiKey: 'AAbbCCdd12345' }; +let fetchStub: SinonStub; +let fakeInstallations: _FirebaseInstallationsInternal; + +function stubFetch(): void { + fetchStub = stub(window, 'fetch'); + const mockResponse = new window.Response( + JSON.stringify({ measurementId: fakeMeasurementId, appId: fakeAppId }), + { + status: 200 + } + ); + fetchStub.returns(Promise.resolve(mockResponse)); +} + +describe('initializeAnalytics()', () => { + const gtagStub: SinonStub = stub(); + const dynamicPromisesList: Array> = []; + const measurementIdToAppId: { [key: string]: string } = {}; + let app: FirebaseApp; + let fidDeferred: Deferred; + beforeEach(() => { + fidDeferred = new Deferred(); + app = getFakeApp(fakeAppParams); + fakeInstallations = getFakeInstallations(fakeFid, fidDeferred.resolve); + }); + afterEach(() => { + fetchStub.restore(); + removeGtagScript(); + }); + it('gets FID and measurement ID and calls gtag config with them', async () => { + stubFetch(); + await initializeAnalytics( + app, + dynamicPromisesList, + measurementIdToAppId, + fakeInstallations, + gtagStub, + 'dataLayer' + ); + expect(gtagStub).to.be.calledWith(GtagCommand.CONFIG, fakeMeasurementId, { + 'firebase_id': fakeFid, + 'origin': 'firebase', + update: true + }); + }); + it('calls gtag config with options if provided', async () => { + stubFetch(); + await initializeAnalytics( + app, + dynamicPromisesList, + measurementIdToAppId, + fakeInstallations, + gtagStub, + 'dataLayer', + { config: { 'send_page_view': false } } + ); + expect(gtagStub).to.be.calledWith(GtagCommand.CONFIG, fakeMeasurementId, { + 'firebase_id': fakeFid, + 'origin': 'firebase', + update: true, + 'send_page_view': false + }); + }); + it('puts dynamic fetch promise into dynamic promises list', async () => { + stubFetch(); + await initializeAnalytics( + app, + dynamicPromisesList, + measurementIdToAppId, + fakeInstallations, + gtagStub, + 'dataLayer' + ); + const dynamicPromiseResult = await dynamicPromisesList[0]; + expect(dynamicPromiseResult.measurementId).to.equal(fakeMeasurementId); + expect(dynamicPromiseResult.appId).to.equal(fakeAppId); + }); + it('puts dynamically fetched measurementId into lookup table', async () => { + stubFetch(); + await initializeAnalytics( + app, + dynamicPromisesList, + measurementIdToAppId, + fakeInstallations, + gtagStub, + 'dataLayer' + ); + expect(measurementIdToAppId[fakeMeasurementId]).to.equal(fakeAppId); + }); + it('warns on local/fetched measurement ID mismatch', async () => { + stubFetch(); + const consoleStub = stub(console, 'warn'); + await initializeAnalytics( + getFakeApp({ ...fakeAppParams, measurementId: 'old-measurement-id' }), + dynamicPromisesList, + measurementIdToAppId, + fakeInstallations, + gtagStub, + 'dataLayer' + ); + expect(consoleStub.args[0][1]).to.include(fakeMeasurementId); + expect(consoleStub.args[0][1]).to.include('old-measurement-id'); + expect(consoleStub.args[0][1]).to.include('does not match'); + consoleStub.restore(); + }); +}); diff --git a/packages-exp/analytics-exp/src/initialize-analytics.ts b/packages-exp/analytics-exp/src/initialize-analytics.ts new file mode 100644 index 00000000000..3eab2f3da9b --- /dev/null +++ b/packages-exp/analytics-exp/src/initialize-analytics.ts @@ -0,0 +1,144 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DynamicConfig, Gtag, MinimalDynamicConfig } from './types'; +import { GtagCommand, GA_FID_KEY, ORIGIN_KEY } from './constants'; +import { _FirebaseInstallationsInternal } from '@firebase/installations-exp'; +import { fetchDynamicConfigWithRetry } from './get-config'; +import { logger } from './logger'; +import { FirebaseApp } from '@firebase/app-exp'; +import { + isIndexedDBAvailable, + validateIndexedDBOpenable +} from '@firebase/util'; +import { ERROR_FACTORY, AnalyticsError } from './errors'; +import { findGtagScriptOnPage, insertScriptTag } from './helpers'; +import { AnalyticsOptions } from './public-types'; + +async function validateIndexedDB(): Promise { + if (!isIndexedDBAvailable()) { + logger.warn( + ERROR_FACTORY.create(AnalyticsError.INDEXEDDB_UNAVAILABLE, { + errorInfo: 'IndexedDB is not available in this environment.' + }).message + ); + return false; + } else { + try { + await validateIndexedDBOpenable(); + } catch (e) { + logger.warn( + ERROR_FACTORY.create(AnalyticsError.INDEXEDDB_UNAVAILABLE, { + errorInfo: e + }).message + ); + return false; + } + } + return true; +} + +/** + * Initialize the analytics instance in gtag.js by calling config command with fid. + * + * NOTE: We combine analytics initialization and setting fid together because we want fid to be + * part of the `page_view` event that's sent during the initialization + * @param app Firebase app + * @param gtagCore The gtag function that's not wrapped. + * @param dynamicConfigPromisesList Array of all dynamic config promises. + * @param measurementIdToAppId Maps measurementID to appID. + * @param installations _FirebaseInstallationsInternal instance. + * + * @returns Measurement ID. + */ +export async function initializeAnalytics( + app: FirebaseApp, + dynamicConfigPromisesList: Array< + Promise + >, + measurementIdToAppId: { [key: string]: string }, + installations: _FirebaseInstallationsInternal, + gtagCore: Gtag, + dataLayerName: string, + options?: AnalyticsOptions +): Promise { + const dynamicConfigPromise = fetchDynamicConfigWithRetry(app); + // Once fetched, map measurementIds to appId, for ease of lookup in wrapped gtag function. + dynamicConfigPromise + .then(config => { + measurementIdToAppId[config.measurementId] = config.appId; + if ( + app.options.measurementId && + config.measurementId !== app.options.measurementId + ) { + logger.warn( + `The measurement ID in the local Firebase config (${app.options.measurementId})` + + ` does not match the measurement ID fetched from the server (${config.measurementId}).` + + ` To ensure analytics events are always sent to the correct Analytics property,` + + ` update the` + + ` measurement ID field in the local config or remove it from the local config.` + ); + } + }) + .catch(e => logger.error(e)); + // Add to list to track state of all dynamic config promises. + dynamicConfigPromisesList.push(dynamicConfigPromise); + + const fidPromise: Promise = validateIndexedDB().then( + envIsValid => { + if (envIsValid) { + return installations.getId(); + } else { + return undefined; + } + } + ); + + const [dynamicConfig, fid] = await Promise.all([ + dynamicConfigPromise, + fidPromise + ]); + + // Detect if user has already put the gtag +

Compatibility layer tests

+
diff --git a/packages-exp/auth-compat-exp/test/integration/webdriver/static/index.js b/packages-exp/auth-compat-exp/test/integration/webdriver/static/index.js new file mode 100644 index 00000000000..f2670f3c167 --- /dev/null +++ b/packages-exp/auth-compat-exp/test/integration/webdriver/static/index.js @@ -0,0 +1,72 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as redirect from './redirect'; +import firebase from '@firebase/app-compat'; +import '@firebase/auth-compat'; +import * as anonymous from './anonymous'; +import * as core from './core'; +import * as popup from './popup'; +import * as email from './email'; +import * as persistence from './persistence'; +import * as ui from './ui'; +import { loadScript } from './lazy_load'; + +window.core = core; +window.anonymous = anonymous; +window.redirect = redirect; +window.popup = popup; +window.email = email; +window.persistence = persistence; +window.ui = ui; + +window.compat = null; +window.legacyAuth = null; + +// The config and emulator URL are injected by the test. The test framework +// calls this function after that injection. +window.startAuth = async () => { + // Make sure we haven't confused our firebase with the old firebase + if (!firebase.SDK_VERSION.startsWith('0.9')) { + throw new Error( + 'Using legacy SDK version instead of compat version ' + + firebase.SDK_VERSION + ); + } + firebase.initializeApp(firebaseConfig); + firebase.auth().useEmulator(emulatorUrl); + window.compat = firebase; +}; + +window.startLegacySDK = async persistence => { + await loadScript('https://www.gstatic.com/firebasejs/8.3.0/firebase-app.js'); + await loadScript('https://www.gstatic.com/firebasejs/8.3.0/firebase-auth.js'); + + window.firebase.initializeApp(firebaseConfig); + // Make sure the firebase variable here is the legacy SDK + if (window.firebase.SDK_VERSION !== '8.3.0') { + reject( + new Error( + 'Not using correct legacy version; using ' + window.firebase.SDK_VERSION + ) + ); + } + const legacyAuth = window.firebase.auth(); + legacyAuth.useEmulator(emulatorUrl); + legacyAuth.setPersistence(persistence.toLowerCase()); + window.legacyAuth = legacyAuth; +}; diff --git a/packages-exp/auth-compat-exp/test/integration/webdriver/static/lazy_load.js b/packages-exp/auth-compat-exp/test/integration/webdriver/static/lazy_load.js new file mode 100644 index 00000000000..d6dc71ddee3 --- /dev/null +++ b/packages-exp/auth-compat-exp/test/integration/webdriver/static/lazy_load.js @@ -0,0 +1,38 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export function loadScript(url) { + return new Promise((resolve, reject) => { + const script = document.createElement('script'); + script.src = url; + script.onerror = reject; + script.onload = resolve; + document.head.appendChild(script); + }); +} + +export function loadCss(url) { + return new Promise((resolve, reject) => { + const link = document.createElement('link'); + link.type = 'text/css'; + link.rel = 'stylesheet'; + link.href = url; + link.onerror = reject; + link.onload = resolve; + document.head.appendChild(link); + }); +} diff --git a/packages-exp/auth-compat-exp/test/integration/webdriver/static/logged_in.html b/packages-exp/auth-compat-exp/test/integration/webdriver/static/logged_in.html new file mode 100644 index 00000000000..b3828463846 --- /dev/null +++ b/packages-exp/auth-compat-exp/test/integration/webdriver/static/logged_in.html @@ -0,0 +1,3 @@ + +User logged in + diff --git a/packages-exp/auth-compat-exp/test/integration/webdriver/static/persistence.js b/packages-exp/auth-compat-exp/test/integration/webdriver/static/persistence.js new file mode 100644 index 00000000000..8b974a4974e --- /dev/null +++ b/packages-exp/auth-compat-exp/test/integration/webdriver/static/persistence.js @@ -0,0 +1,143 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const INDEXED_DB_NAME = 'firebaseLocalStorageDb'; + +// Save these variables for test utils use below, since some tests may delete them. +const indexedDB = window.indexedDB; +const localStorage = window.localStorage; +const sessionStorage = window.sessionStorage; + +export async function clearPersistence() { + sessionStorage.clear(); + localStorage.clear(); + // HACK: Deleting databases in Firefox sometimes take a few seconds. Let's just return early. + return withTimeout( + 1000, + dbPromise(indexedDB.deleteDatabase(INDEXED_DB_NAME)) + ).catch(e => { + console.error(e); + return; + }); +} + +export async function localStorageSnap() { + return dumpStorage(localStorage); +} +export async function localStorageSet(dict) { + setInStorage(localStorage, dict); +} +export async function sessionStorageSnap() { + return dumpStorage(sessionStorage); +} +export async function sessionStorageSet(dict) { + setInStorage(sessionStorage, dict); +} + +const DB_OBJECTSTORE_NAME = 'firebaseLocalStorage'; + +export async function indexedDBSnap() { + const db = await dbPromise(indexedDB.open(INDEXED_DB_NAME)); + let entries; + try { + const store = db + .transaction([DB_OBJECTSTORE_NAME], 'readonly') + .objectStore(DB_OBJECTSTORE_NAME); + entries = await dbPromise(store.getAll()); + } catch { + // May throw if DB_OBJECTSTORE_NAME is never created -- this is normal. + return {}; + } + const result = {}; + for (const { fbase_key: key, value } of entries) { + result[key] = value; + } + return result; +} + +export async function setPersistenceMemory() { + return compat.auth().setPersistence('none'); +} + +export async function setPersistenceSession() { + return compat.auth().setPersistence('session'); +} + +export async function setPersistenceLocalStorage() { + return compat.auth().setPersistence('local'); +} + +export async function setPersistenceIndexedDB() { + return compat.auth().setPersistence('local'); +} + +// Mock functions for testing edge cases +export async function makeIndexedDBReadonly() { + IDBObjectStore.prototype.add = IDBObjectStore.prototype.put = () => { + return { + error: 'add/put is disabled for test purposes', + readyState: 'done', + addEventListener(event, listener) { + if (event === 'error') { + void Promise.resolve({}).then(listener); + } + } + }; + }; +} + +function dumpStorage(storage) { + const result = {}; + for (let i = 0; i < storage.length; i++) { + const key = storage.key(i); + result[key] = JSON.parse(storage.getItem(key)); + } + return result; +} + +function setInStorage(storage, dict) { + for (const [key, value] of Object.entries(dict)) { + if (value === undefined) { + storage.removeItem(key); + } else { + storage.setItem(key, JSON.stringify(value)); + } + } +} + +function dbPromise(dbRequest) { + return new Promise((resolve, reject) => { + dbRequest.addEventListener('success', () => { + resolve(dbRequest.result); + }); + dbRequest.addEventListener('error', () => { + reject(dbRequest.error); + }); + dbRequest.addEventListener('blocked', () => { + reject(dbRequest.error || 'blocked'); + }); + }); +} + +function withTimeout(ms, promise) { + return Promise.race([ + new Promise((_, reject) => + setTimeout(() => reject(new Error('operation timed out')), ms) + ), + promise + ]); +} diff --git a/packages-exp/auth-compat-exp/test/integration/webdriver/static/popup.js b/packages-exp/auth-compat-exp/test/integration/webdriver/static/popup.js new file mode 100644 index 00000000000..54daaaee185 --- /dev/null +++ b/packages-exp/auth-compat-exp/test/integration/webdriver/static/popup.js @@ -0,0 +1,91 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// These functions are a little funky: WebDriver relies on callbacks to +// pass data back to the main Node process. Because of that setup, we can't +// return the popup tasks as pending promises as they won't resolve until +// the WebDriver is allowed to do other stuff. Instead, we'll store the +// promises in variables and provide a way to retrieve them later, unblocking +// the WebDriver process. +let popupPromise = null; +let popupCred = null; +let errorCred = null; + +export function idpPopup(optProvider) { + const provider = optProvider + ? new compat.auth.OAuthProvider(optProvider) + : new compat.auth.GoogleAuthProvider(); + popupPromise = compat.auth().signInWithPopup(provider); +} + +export function idpReauthPopup() { + popupPromise = compat + .auth() + .currentUser.reauthenticateWithPopup(new compat.auth.GoogleAuthProvider()); +} + +export function idpLinkPopup() { + popupPromise = compat + .auth() + .currentUser.linkWithPopup(new compat.auth.GoogleAuthProvider()); +} + +export function popupResult() { + return popupPromise; +} + +export async function generateCredentialFromResult() { + const result = await popupPromise; + popupCred = result.credential; + return popupCred; +} + +export async function signInWithPopupCredential() { + return compat.auth().signInWithCredential(popupCred); +} + +export async function linkWithErrorCredential() { + await compat.auth().currentUser.linkWithCredential(errorCred); +} + +// These below are not technically popup functions but they're helpers for +// the popup tests. + +export function createFakeGoogleUser(email) { + return compat + .auth() + .signInWithCredential( + compat.auth.GoogleAuthProvider.credential( + `{"sub": "__${email}__", "email": "${email}", "email_verified": true}` + ) + ); +} + +export async function tryToSignInUnverified(email) { + try { + await compat + .auth() + .signInWithCredential( + compat.auth.FacebookAuthProvider.credential( + `{"sub": "$$${email}$$", "email": "${email}", "email_verified": false}` + ) + ); + } catch (e) { + errorCred = e.credential; + throw e; + } +} diff --git a/packages-exp/auth-compat-exp/test/integration/webdriver/static/redirect.js b/packages-exp/auth-compat-exp/test/integration/webdriver/static/redirect.js new file mode 100644 index 00000000000..4335b0b86c6 --- /dev/null +++ b/packages-exp/auth-compat-exp/test/integration/webdriver/static/redirect.js @@ -0,0 +1,86 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +let redirectCred = null; +let errorCred = null; + +export function idpRedirect(optProvider) { + const provider = optProvider + ? new compat.auth.OAuthProvider(optProvider) + : new compat.auth.GoogleAuthProvider(); + compat.auth().signInWithRedirect(provider); +} + +export function idpReauthRedirect() { + compat + .auth() + .currentUser.reauthenticateWithRedirect( + new compat.auth.GoogleAuthProvider() + ); +} + +export function idpLinkRedirect() { + compat + .auth() + .currentUser.linkWithRedirect(new compat.auth.GoogleAuthProvider()); +} + +export function redirectResult() { + return compat.auth().getRedirectResult(); +} + +export async function generateCredentialFromRedirectResultAndStore() { + const result = await compat.auth().getRedirectResult(); + redirectCred = result.credential; + return redirectCred; +} + +export async function signInWithRedirectCredential() { + return compat.auth().signInWithCredential(redirectCred); +} + +export async function linkWithErrorCredential() { + await compat.auth().currentUser.linkWithCredential(errorCred); +} + +// These below are not technically redirect functions but they're helpers for +// the redirect tests. + +export function createFakeGoogleUser(email) { + return compat + .auth() + .signInWithCredential( + compat.auth.GoogleAuthProvider.credential( + `{"sub": "__${email}__", "email": "${email}", "email_verified": true}` + ) + ); +} + +export async function tryToSignInUnverified(email) { + try { + await compat + .auth() + .signInWithCredential( + compat.auth.FacebookAuthProvider.credential( + `{"sub": "$$${email}$$", "email": "${email}", "email_verified": false}` + ) + ); + } catch (e) { + errorCred = e.credential; + throw e; + } +} diff --git a/packages-exp/auth-compat-exp/test/integration/webdriver/static/rollup.config.js b/packages-exp/auth-compat-exp/test/integration/webdriver/static/rollup.config.js new file mode 100644 index 00000000000..d48a1208ff1 --- /dev/null +++ b/packages-exp/auth-compat-exp/test/integration/webdriver/static/rollup.config.js @@ -0,0 +1,28 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { nodeResolve } from '@rollup/plugin-node-resolve'; + +// This is run from the auth-exp package.json +export default { + input: ['test/integration/webdriver/static/index.js'], + output: { + file: 'test/integration/webdriver/static/dist/bundle.js', + format: 'esm' + }, + plugins: [nodeResolve()] +}; diff --git a/packages-exp/auth-compat-exp/test/integration/webdriver/static/ui.js b/packages-exp/auth-compat-exp/test/integration/webdriver/static/ui.js new file mode 100644 index 00000000000..9d2a3416f11 --- /dev/null +++ b/packages-exp/auth-compat-exp/test/integration/webdriver/static/ui.js @@ -0,0 +1,51 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { loadCss, loadScript } from './lazy_load'; + +export async function loadUiCode() { + await loadScript( + 'https://www.gstatic.com/firebasejs/ui/4.8.0/firebase-ui-auth.js' + ); + await loadCss( + 'https://www.gstatic.com/firebasejs/ui/4.8.0/firebase-ui-auth.css' + ); +} + +export async function startUi(signInFlow = 'redirect') { + // Hacky hack hack + window.firebase = compat; + + const uiConfig = { + signInSuccessUrl: '/logged_in.html', + signInFlow, + signInOptions: [ + compat.auth.GoogleAuthProvider.PROVIDER_ID, + compat.auth.FacebookAuthProvider.PROVIDER_ID, + compat.auth.TwitterAuthProvider.PROVIDER_ID, + compat.auth.GithubAuthProvider.PROVIDER_ID, + compat.auth.EmailAuthProvider.PROVIDER_ID, + compat.auth.PhoneAuthProvider.PROVIDER_ID, + firebaseui.auth.AnonymousAuthProvider.PROVIDER_ID + ] + }; + + // Initialize the FirebaseUI Widget using Firebase. + const ui = new firebaseui.auth.AuthUI(compat.auth()); + // The start method will wait until the DOM is loaded. + ui.start('#ui-root', uiConfig); +} diff --git a/packages-exp/auth-exp/.eslintrc.js b/packages-exp/auth-exp/.eslintrc.js index 674937d0c30..6cfe90711fc 100644 --- a/packages-exp/auth-exp/.eslintrc.js +++ b/packages-exp/auth-exp/.eslintrc.js @@ -17,7 +17,7 @@ module.exports = { extends: '../../config/.eslintrc.js', - ignorePatterns: ['demo/'], + ignorePatterns: ['demo/', 'scripts/'], parserOptions: { project: 'tsconfig.json', // to make vscode-eslint work with monorepo diff --git a/packages-exp/auth-exp/README.md b/packages-exp/auth-exp/README.md index 6b08dc72029..6d4cb154b94 100644 --- a/packages-exp/auth-exp/README.md +++ b/packages-exp/auth-exp/README.md @@ -2,4 +2,55 @@ This is the Firebase Authentication component of the Firebase JS SDK. -**This package is not intended for direct usage, and should only be used via the officially supported [firebase](https://www.npmjs.com/package/firebase) package.** \ No newline at end of file +**This package is not intended for direct usage, and should only be used via the officially supported [firebase](https://www.npmjs.com/package/firebase) package.** + +## Testing + +The modular Auth SDK has both unit tests and integration tests, along with a +host of npm scripts to run these tests. The most important commands are: + +| Command | Description | +| ------- | ----------- | +| `yarn test` | This will run lint, unit tests, and integration tests against the live environment| +| `yarn test:` | Runs all browser tests, unit and integration | +| `yarn test::unit` | Runs only \ unit tests | +| `yarn test::unit:debug` | Runs \ unit tests, auto-watching for file system changes | +| `yarn test::integration` | Runs only integration tests against the live environment | +| `yarn test::integration:local` | Runs all headless \ integration tests against the emulator (more below) | + +Where \ is "browser" or "node". There are also cordova tests, but they +are not broken into such granular details. Check out `package.json` for more. + +### Integration testing with the emulator + +To test against the emulator, set up the Auth emulator +([instructions](https://firebase.google.com/docs/emulator-suite/connect_and_prototype)). +The easiest way to run these tests is to use the `firebase emulators:exec` +command +([documentation](https://firebase.google.com/docs/emulator-suite/install_and_configure#startup)). +You can also manually start the emulator separately, and then point the tests +to it by setting the `GCLOUD_PROJECT` and `FIREBASE_AUTH_EMULATOR_HOST` +environmental variables. In addition to the commands listed above, the below +commands also run various tests: + + * `yarn test:integration:local` — Executes Node and browser emulator + integration tests, as well as the Selenium WebDriver tests + + * `yarn test:webdriver` — Executes only the Selenium WebDriver + integration tests + +For example, to run all integration and WebDriver tests against the emulator, +you would simply execute the following command: + +```sh +firebase emulators:exec --project foo-bar --only auth "yarn test:integration:local" +``` + +### Selenium Webdriver tests + +These tests assume that you have both Firefox and Chrome installed on your +computer and in your `$PATH`. The tests will error out if this is not the case. +The WebDriver tests talk to the emulator, but unlike the headless integration +tests, these run in a browser robot environment; the assertions themselves run +in Node. When you run these tests a small Express server will be started to +serve the static files the browser robot uses. diff --git a/packages-exp/auth-exp/api-extractor.json b/packages-exp/auth-exp/api-extractor.json index 620d10a071c..0be9d1870b4 100644 --- a/packages-exp/auth-exp/api-extractor.json +++ b/packages-exp/auth-exp/api-extractor.json @@ -1,5 +1,10 @@ { "extends": "../../config/api-extractor.json", // Point it to your entry point d.ts file. - "mainEntryPointFilePath": "/dist/index.d.ts" + "mainEntryPointFilePath": "/dist/esm5/index.d.ts", + "dtsRollup": { + "enabled": true, + "untrimmedFilePath": "/dist/.d.ts", + "publicTrimmedFilePath": "/dist/-public.d.ts" + } } \ No newline at end of file diff --git a/packages-exp/auth-exp/cordova/demo/.gitignore b/packages-exp/auth-exp/cordova/demo/.gitignore new file mode 100644 index 00000000000..24a9d84d538 --- /dev/null +++ b/packages-exp/auth-exp/cordova/demo/.gitignore @@ -0,0 +1,5 @@ +platforms/ +plugins/ +ul_web_hooks/ +src/config.js +config.xml \ No newline at end of file diff --git a/packages-exp/auth-exp/cordova/demo/README.md b/packages-exp/auth-exp/cordova/demo/README.md new file mode 100644 index 00000000000..cf72e9ec6d3 --- /dev/null +++ b/packages-exp/auth-exp/cordova/demo/README.md @@ -0,0 +1,119 @@ +# Cordova Auth Demo App +This package contains a demo of the various Firebase Auth features bundled in +an Apache Cordova app. + +## Dev Setup + +Follow [this guide](https://cordova.apache.org/docs/en/10.x/guide/cli/) to run +set up Cordova CLI. tl;dr: + +```bash +npm install -g cordova +cordova requirements +``` + +### Preparing the deps + +At this point you should have a basic environment set up that can support +Cordova. Now you'll need to update some config files to make everything work. +First, read through steps 1 - 5, 7 in the +[Firebase Auth Cordova docs page](https://firebase.google.com/docs/auth/web/cordova) +so that you get a sense of the values you need to add / adjust. + +Make a copy of `sample-config.xml` to `config.xml`, and replace the values +outlined in curly braces. Notably, you'll need the package name / bundle ID +of a registered app in the Firebase Console. You'll also need the Auth Domain +that comes from the Web config. + +Next, you'll need to make a copy of `src/sample-config.js` to `src/config.js`, +and you'll need to supply a Firebase JS SDK configuration in the form of that +file. + +Once all this is done, you can get the Cordova setup ready in this specific +project: + +```bash +cordova prepare +``` + +Work through any issues until no more errors are printed. + +## Building and running the demo + +The app consists of a bundled script, `www/dist/bundle.js`. This script is built +from the `./src` directory. To change it, modify the source code in +`src` and then rebuild the bundle: + +```bash +# Build the deps the first time, and subsequent times if changing the core SDK +npm run build:deps +npm run build:js +``` + +### Android + +You can now build and test the app on Android Emulator: + +```bash +cordova build android +cordova emulate android + +# Or +cordova run android +``` + +TODO: Any tips or gotchas? + +### iOS + +```bash +cordova build ios +cordova emulate ios +``` + +Please ignore the command-line output mentioning `logPath` -- that file +[will not actually exist](https://github.com/ios-control/ios-sim/issues/167) and +won't contain JavaScript console logs. The Simulator app itself does not +expose console logs either (System Log won't help). + +The recommended way around this is to use the remote debugger in Safari. Launch +the Safari app on the same MacBook, and then go to Safari menu > Preferences > +Advanced and check the "Show Develop menu in menu bar" option. Then, you should +be able to see the Simulator under the Safari `Developer` menu and choose the +app web view (Hello World > Hello World). This only works when the Simulator has +already started and is running the Cordova app. This gives you full access to +console logs AND uncaught errors in the JavaScript code. + +WARNING: This may not catch any JavaScript errors during initialization (or +before the debugger was opened). If nothing seems working, try clicking the +Reload Page button in the top-left corner of the inspector, which will reload +the whole web view and hopefully catch the error this time. + +#### Xcode + +If you really want to, you can also start the Simulator via Xcode. Note that +this will only give you access to console log but WON'T show JavaScript errors +-- for which you still need the Safari remote debugger. + +```bash +cordova build ios +open ./platforms/ios/HelloWorld.xcworkspace/ +``` + +Select/add a Simulator through the nav bar and click on "Run" to start it. You +can then find console logs in the corresponding Xcode panel (not in the +Simulator window itself). + +If you go this route, +[DO NOT edit files in Xcode IDE](https://cordova.apache.org/docs/en/10.x/guide/platforms/ios/index.html#open-a-project-within-xcode). +Instead, edit files in the `www` folder and run `cordova build ios` to copy the +changes over (and over). + +### Notes + +You may need to update the cordova-universal-links-plugin `manifestWriter.js` +to point to the correct Android manifest. For example: + +```js +var pathToManifest = path.join(cordovaContext.opts.projectRoot, 'platforms', 'android', 'app', 'src', 'main', 'AndroidManifest.xml'); +``` diff --git a/packages-exp/auth-exp/cordova/demo/package.json b/packages-exp/auth-exp/cordova/demo/package.json new file mode 100644 index 00000000000..93ae5a6d1d1 --- /dev/null +++ b/packages-exp/auth-exp/cordova/demo/package.json @@ -0,0 +1,52 @@ +{ + "name": "cordova-auth-demo", + "displayName": "Cordova Auth Demo", + "version": "1.0.0", + "scripts": { + "build:js": "rollup -c", + "build:deps": "lerna run --scope @firebase/'{app-exp,auth-exp/cordova}' --include-dependencies build" + }, + "keywords": [ + "ecosystem:cordova" + ], + "devDependencies": { + "cordova-android": "^9.1.0", + "cordova-ios": "^6.2.0", + "cordova-plugin-whitelist": "^1.3.4", + "cordova-universal-links-plugin": "^1.2.1", + "rollup": "^2.52.2" + }, + "cordova": { + "plugins": { + "cordova-plugin-whitelist": {}, + "cordova-plugin-buildinfo": {}, + "cordova-universal-links-plugin": {}, + "cordova-plugin-browsertab": {}, + "cordova-plugin-inappbrowser": {}, + "cordova-plugin-customurlscheme": { + "URL_SCHEME": "com.example.hello", + "ANDROID_SCHEME": " ", + "ANDROID_HOST": " ", + "ANDROID_PATHPREFIX": "/" + } + }, + "platforms": [ + "ios", + "android" + ] + }, + "dependencies": { + "@firebase/app-exp": "0.0.900", + "@firebase/auth-exp": "0.0.900", + "@firebase/logger": "0.2.6", + "@firebase/util": "1.1.0", + "cordova-plugin-browsertab": "0.2.0", + "cordova-plugin-buildinfo": "4.0.0", + "cordova-plugin-compat": "1.2.0", + "cordova-plugin-customurlscheme": "5.0.2", + "cordova-plugin-inappbrowser": "4.1.0", + "cordova-universal-links-plugin-fix": "^1.2.1", + "lerna": "^4.0.0", + "tslib": "^2.1.0" + } +} diff --git a/packages-exp/auth-exp/cordova/demo/rollup.config.js b/packages-exp/auth-exp/cordova/demo/rollup.config.js new file mode 100644 index 00000000000..252e2e6b622 --- /dev/null +++ b/packages-exp/auth-exp/cordova/demo/rollup.config.js @@ -0,0 +1,45 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import resolve from '@rollup/plugin-node-resolve'; +import strip from '@rollup/plugin-strip'; + +/** + * Common plugins for all builds + */ +const commonPlugins = [ + strip({ + functions: ['debugAssert.*'] + }) +]; + +const es5Builds = [ + /** + * Browser Builds + */ + { + input: 'src/index.js', + output: [{ file: 'www/dist/bundle.js', format: 'esm', sourcemap: true }], + plugins: [ + ...commonPlugins, + resolve({ + mainFields: ['module', 'main'] + }) + ] + } +]; + +export default [...es5Builds]; diff --git a/packages-exp/auth-exp/cordova/demo/sample-config.xml b/packages-exp/auth-exp/cordova/demo/sample-config.xml new file mode 100644 index 00000000000..a22a6cd84ea --- /dev/null +++ b/packages-exp/auth-exp/cordova/demo/sample-config.xml @@ -0,0 +1,16 @@ + + + Cordova Auth Demo + + + + + + + + + + + + + diff --git a/packages-exp/auth-exp/cordova/demo/src/index.js b/packages-exp/auth-exp/cordova/demo/src/index.js new file mode 100644 index 00000000000..c8bc760c31d --- /dev/null +++ b/packages-exp/auth-exp/cordova/demo/src/index.js @@ -0,0 +1,1568 @@ +/* eslint-disable import/no-extraneous-dependencies */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview This code is for the most part copied over from the packages/auth/demo + * package. + */ + +import { initializeApp } from '@firebase/app-exp'; +import { + applyActionCode, + browserLocalPersistence, + browserSessionPersistence, + confirmPasswordReset, + createUserWithEmailAndPassword, + EmailAuthProvider, + fetchSignInMethodsForEmail, + indexedDBLocalPersistence, + initializeAuth, + getAuth, + inMemoryPersistence, + isSignInWithEmailLink, + linkWithCredential, + multiFactor, + reauthenticateWithCredential, + sendEmailVerification, + sendPasswordResetEmail, + sendSignInLinkToEmail, + signInAnonymously, + signInWithCredential, + signInWithCustomToken, + signInWithEmailAndPassword, + unlink, + updateEmail, + updatePassword, + updateProfile, + verifyPasswordResetCode, + getMultiFactorResolver, + OAuthProvider, + GoogleAuthProvider, + FacebookAuthProvider, + TwitterAuthProvider, + GithubAuthProvider, + signInWithRedirect, + linkWithRedirect, + reauthenticateWithRedirect, + getRedirectResult, + cordovaPopupRedirectResolver +} from '@firebase/auth-exp/dist/cordova'; + +import { config } from './config'; +import { + alertError, + alertNotImplemented, + alertSuccess, + clearLogs, + log, + logAtLevel_ +} from './logging'; + +let app = null; +let auth = null; +let currentTab = null; +let lastUser = null; +let applicationVerifier = null; +let multiFactorErrorResolver = null; +let selectedMultiFactorHint = null; +let recaptchaSize = 'normal'; +let webWorker = null; + +// The corresponding Font Awesome icons for each provider. +const providersIcons = { + 'google.com': 'fa-google', + 'facebook.com': 'fa-facebook-official', + 'twitter.com': 'fa-twitter-square', + 'github.com': 'fa-github', + 'yahoo.com': 'fa-yahoo', + 'phone': 'fa-phone' +}; + +/** + * Returns the active user (i.e. currentUser or lastUser). + * @return {!firebase.User} + */ +function activeUser() { + const type = $('input[name=toggle-user-selection]:checked').val(); + if (type === 'lastUser') { + return lastUser; + } else { + return auth.currentUser; + } +} + +/** + * Refreshes the current user data in the UI, displaying a user info box if + * a user is signed in, or removing it. + */ +function refreshUserData() { + if (activeUser()) { + const user = activeUser(); + $('.profile').show(); + $('body').addClass('user-info-displayed'); + $('div.profile-email,span.profile-email').text(user.email || 'No Email'); + $('div.profile-phone,span.profile-phone').text( + user.phoneNumber || 'No Phone' + ); + $('div.profile-uid,span.profile-uid').text(user.uid); + $('div.profile-name,span.profile-name').text(user.displayName || 'No Name'); + $('input.profile-name').val(user.displayName); + $('input.photo-url').val(user.photoURL); + if (user.photoURL != null) { + let photoURL = user.photoURL; + // Append size to the photo URL for Google hosted images to avoid requesting + // the image with its original resolution (using more bandwidth than needed) + // when it is going to be presented in smaller size. + if ( + photoURL.indexOf('googleusercontent.com') !== -1 || + photoURL.indexOf('ggpht.com') !== -1 + ) { + photoURL = photoURL + '?sz=' + $('img.profile-image').height(); + } + $('img.profile-image').attr('src', photoURL).show(); + } else { + $('img.profile-image').hide(); + } + $('.profile-email-verified').toggle(user.emailVerified); + $('.profile-email-not-verified').toggle(!user.emailVerified); + $('.profile-anonymous').toggle(user.isAnonymous); + // Display/Hide providers icons. + $('.profile-providers').empty(); + if (user['providerData'] && user['providerData'].length) { + const providersCount = user['providerData'].length; + for (let i = 0; i < providersCount; i++) { + addProviderIcon(user['providerData'][i]['providerId']); + } + } + // Show enrolled second factors if available for the active user. + showMultiFactorStatus(user); + // Change color. + if (user === auth.currentUser) { + $('#user-info').removeClass('last-user'); + $('#user-info').addClass('current-user'); + } else { + $('#user-info').removeClass('current-user'); + $('#user-info').addClass('last-user'); + } + } else { + $('.profile').slideUp(); + $('body').removeClass('user-info-displayed'); + $('input.profile-data').val(''); + } +} + +/** + * Sets last signed in user and updates UI. + * @param {?firebase.User} user The last signed in user. + */ +function setLastUser(user) { + lastUser = user; + if (user) { + // Displays the toggle. + $('#toggle-user').show(); + $('#toggle-user-placeholder').hide(); + } else { + $('#toggle-user').hide(); + $('#toggle-user-placeholder').show(); + } +} + +/** + * Add a provider icon to the profile info. + * @param {string} providerId The providerId of the provider. + */ +function addProviderIcon(providerId) { + const pElt = $('') + .addClass('fa ' + providersIcons[providerId]) + .attr('title', providerId) + .data({ + 'toggle': 'tooltip', + 'placement': 'bottom' + }); + $('.profile-providers').append(pElt); + pElt.tooltip(); +} + +/** + * Updates the active user's multi-factor enrollment status. + * @param {!firebase.User} activeUser The corresponding user. + */ +function showMultiFactorStatus(activeUser) { + mfaUser = multiFactor(activeUser); + const enrolledFactors = (mfaUser && mfaUser.enrolledFactors) || []; + const $listGroup = $('#user-info .dropdown-menu.enrolled-second-factors'); + // Hide the drop down menu initially. + $listGroup.empty().parent().hide(); + if (enrolledFactors.length) { + // If enrolled factors are available, show the drop down menu. + $listGroup.parent().show(); + // Populate the enrolled factors. + showMultiFactors( + $listGroup, + enrolledFactors, + // On row click, do nothing. This is needed to prevent the drop down + // menu from closing. + e => { + e.preventDefault(); + e.stopPropagation(); + }, + // On delete click unenroll the selected factor. + function (e) { + e.preventDefault(); + // Get the corresponding second factor index. + const index = parseInt($(this).attr('data-index'), 10); + // Get the second factor info. + const info = enrolledFactors[index]; + // Get the display name. If not available, use uid. + const label = info && (info.displayName || info.uid); + if (label) { + $('#enrolled-factors-drop-down').removeClass('open'); + mfaUser.unenroll(info).then(() => { + refreshUserData(); + alertSuccess('Multi-factor successfully unenrolled.'); + }, onAuthError); + } + } + ); + } +} + +/** + * Updates the UI when the user is successfully authenticated. + * @param {!firebase.User} user User authenticated. + */ +function onAuthSuccess(user) { + console.log(user); + alertSuccess('User authenticated, id: ' + user.uid); + refreshUserData(); +} + +/** + * Displays an error message when the authentication failed. + * @param {!Error} error Error message to display. + */ +function onAuthError(error) { + logAtLevel_(error, 'error'); + if (error.code === 'auth/multi-factor-auth-required') { + // Handle second factor sign-in. + handleMultiFactorSignIn(getMultiFactorResolver(auth, error)); + } else { + alertError('Error: ' + error.code); + } +} + +/** + * Changes the UI when the user has been signed out. + */ +function signOut() { + log('User successfully signed out.'); + alertSuccess('User successfully signed out.'); + refreshUserData(); +} + +/** + * Saves the new language code provided in the language code input field. + */ +function onSetLanguageCode() { + const languageCode = $('#language-code').val() || null; + try { + auth.languageCode = languageCode; + alertSuccess('Language code changed to "' + languageCode + '".'); + } catch (error) { + alertError('Error: ' + error.code); + } +} + +/** + * Switches Auth instance language to device language. + */ +function onUseDeviceLanguage() { + auth.useDeviceLanguage(); + $('#language-code').val(auth.languageCode); + alertSuccess('Using device language "' + auth.languageCode + '".'); +} + +/** + * Changes the Auth state persistence to the specified one. + */ +function onSetPersistence() { + const type = $('#persistence-type').val(); + let persistence; + switch (type) { + case 'local': + persistence = browserLocalPersistence; + break; + case 'session': + persistence = browserSessionPersistence; + break; + case 'indexedDB': + persistence = indexedDBLocalPersistence; + break; + case 'none': + persistence = inMemoryPersistence; + break; + default: + alertError('Unexpected persistence type: ' + type); + } + try { + auth.setPersistence(persistence).then( + () => { + log('Persistence state change to "' + type + '".'); + alertSuccess('Persistence state change to "' + type + '".'); + }, + error => { + alertError('Error: ' + error.code); + } + ); + } catch (error) { + alertError('Error: ' + error.code); + } +} + +/** + * Signs up a new user with an email and a password. + */ +function onSignUp() { + const email = $('#signup-email').val(); + const password = $('#signup-password').val(); + createUserWithEmailAndPassword(auth, email, password).then( + onAuthUserCredentialSuccess, + onAuthError + ); +} + +/** + * Signs in a user with an email and a password. + */ +function onSignInWithEmailAndPassword() { + const email = $('#signin-email').val(); + const password = $('#signin-password').val(); + signInWithEmailAndPassword(auth, email, password).then( + onAuthUserCredentialSuccess, + onAuthError + ); +} + +/** + * Signs in a user with an email link. + */ +function onSignInWithEmailLink() { + const email = $('#sign-in-with-email-link-email').val(); + const link = $('#sign-in-with-email-link-link').val() || undefined; + if (isSignInWithEmailLink(auth, link)) { + signInWithEmailLink(auth, email, link).then(onAuthSuccess, onAuthError); + } else { + alertError('Sign in link is invalid'); + } +} + +/** + * Links a user with an email link. + */ +function onLinkWithEmailLink() { + const email = $('#link-with-email-link-email').val(); + const link = $('#link-with-email-link-link').val() || undefined; + const credential = EmailAuthProvider.credentialWithLink(email, link); + linkWithCredential(activeUser(), credential).then( + onAuthUserCredentialSuccess, + onAuthError + ); +} + +/** + * Re-authenticate a user with email link credential. + */ +function onReauthenticateWithEmailLink() { + const email = $('#link-with-email-link-email').val(); + const link = $('#link-with-email-link-link').val() || undefined; + const credential = EmailAuthProvider.credentialWithLink(email, link); + reauthenticateWithCredential(activeUser(), credential).then(result => { + logAdditionalUserInfo(result); + refreshUserData(); + alertSuccess('User reauthenticated!'); + }, onAuthError); +} + +/** + * Signs in with a custom token. + * @param {DOMEvent} _event HTML DOM event returned by the listener. + */ +function onSignInWithCustomToken(_event) { + // The token can be directly specified on the html element. + const token = $('#user-custom-token').val(); + + signInWithCustomToken(auth, token).then( + onAuthUserCredentialSuccess, + onAuthError + ); +} + +/** + * Signs in anonymously. + */ +function onSignInAnonymously() { + signInAnonymously(auth).then(onAuthUserCredentialSuccess, onAuthError); +} + +/** + * Signs in with a generic IdP credential. + */ +function onSignInWithGenericIdPCredential() { + alertNotImplemented(); + // var providerId = $('#signin-generic-idp-provider-id').val(); + // var idToken = $('#signin-generic-idp-id-token').val() || undefined; + // var rawNonce = $('#signin-generic-idp-raw-nonce').val() || undefined; + // var accessToken = $('#signin-generic-idp-access-token').val() || undefined; + // var provider = new OAuthProvider(providerId); + // signInWithCredential( + // auth, + // provider.credential({ + // idToken: idToken, + // accessToken: accessToken, + // rawNonce: rawNonce, + // })).then(onAuthUserCredentialSuccess, onAuthError); +} + +/** @return {!Object} The Action Code Settings object. */ +function getActionCodeSettings() { + const actionCodeSettings = {}; + const url = $('#continueUrl').val(); + const apn = $('#apn').val(); + const amv = $('#amv').val(); + const ibi = $('#ibi').val(); + const installApp = $('input[name=install-app]:checked').val() === 'Yes'; + const handleCodeInApp = + $('input[name=handle-in-app]:checked').val() === 'Yes'; + if (url || apn || ibi) { + actionCodeSettings['url'] = url; + if (apn) { + actionCodeSettings['android'] = { + 'packageName': apn, + 'installApp': !!installApp, + 'minimumVersion': amv || undefined + }; + } + if (ibi) { + actionCodeSettings['iOS'] = { + 'bundleId': ibi + }; + } + actionCodeSettings['handleCodeInApp'] = handleCodeInApp; + } + return actionCodeSettings; +} + +/** Reset action code settings form. */ +function onActionCodeSettingsReset() { + $('#continueUrl').val(''); + $('#apn').val(''); + $('#amv').val(''); + $('#ibi').val(''); +} + +/** + * Changes the user's email. + */ +function onChangeEmail() { + const email = $('#changed-email').val(); + updateEmail(activeUser(), email).then(() => { + refreshUserData(); + alertSuccess('Email changed!'); + }, onAuthError); +} + +/** + * Changes the user's password. + */ +function onChangePassword() { + const password = $('#changed-password').val(); + updatePassword(activeUser(), password).then(() => { + refreshUserData(); + alertSuccess('Password changed!'); + }, onAuthError); +} + +/** + * Changes the user's password. + */ +function onUpdateProfile() { + const displayName = $('#display-name').val(); + const photoURL = $('#photo-url').val(); + updateProfile(activeUser(), { + displayName, + photoURL + }).then(() => { + refreshUserData(); + alertSuccess('Profile updated!'); + }, onAuthError); +} + +/** + * Sends sign in with email link to the user. + */ +function onSendSignInLinkToEmail() { + const email = $('#sign-in-with-email-link-email').val(); + sendSignInLinkToEmail(auth, email, getActionCodeSettings()).then(() => { + alertSuccess('Email sent!'); + }, onAuthError); +} + +/** + * Sends sign in with email link to the user and pass in current url. + */ +function onSendSignInLinkToEmailCurrentUrl() { + const email = $('#sign-in-with-email-link-email').val(); + const actionCodeSettings = { + 'url': window.location.href, + 'handleCodeInApp': true + }; + + sendSignInLinkToEmail(auth, email, actionCodeSettings).then(() => { + if ('localStorage' in window && window['localStorage'] !== null) { + window.localStorage.setItem( + 'emailForSignIn', + // Save the email and the timestamp. + JSON.stringify({ + email, + timestamp: new Date().getTime() + }) + ); + } + alertSuccess('Email sent!'); + }, onAuthError); +} + +/** + * Sends email link to link the user. + */ +function onSendLinkEmailLink() { + const email = $('#link-with-email-link-email').val(); + sendSignInLinkToEmail(auth, email, getActionCodeSettings()).then(() => { + alertSuccess('Email sent!'); + }, onAuthError); +} + +/** + * Sends password reset email to the user. + */ +function onSendPasswordResetEmail() { + const email = $('#password-reset-email').val(); + sendPasswordResetEmail(auth, email, getActionCodeSettings()).then(() => { + alertSuccess('Email sent!'); + }, onAuthError); +} + +/** + * Verifies the password reset code entered by the user. + */ +function onVerifyPasswordResetCode() { + const code = $('#password-reset-code').val(); + verifyPasswordResetCode(auth, code).then(() => { + alertSuccess('Password reset code is valid!'); + }, onAuthError); +} + +/** + * Confirms the password reset with the code and password supplied by the user. + */ +function onConfirmPasswordReset() { + const code = $('#password-reset-code').val(); + const password = $('#password-reset-password').val(); + confirmPasswordReset(auth, code, password).then(() => { + alertSuccess('Password has been changed!'); + }, onAuthError); +} + +/** + * Gets the list of possible sign in methods for the given email address. + */ +function onFetchSignInMethodsForEmail() { + const email = $('#fetch-sign-in-methods-email').val(); + fetchSignInMethodsForEmail(auth, email).then(signInMethods => { + log('Sign in methods for ' + email + ' :'); + log(signInMethods); + if (signInMethods.length === 0) { + alertSuccess('Sign In Methods for ' + email + ': N/A'); + } else { + alertSuccess( + 'Sign In Methods for ' + email + ': ' + signInMethods.join(', ') + ); + } + }, onAuthError); +} + +/** + * Fetches and logs the user's providers data. + */ +function onGetProviderData() { + log('Providers data:'); + log(activeUser()['providerData']); +} + +/** + * Links a signed in user with an email and password account. + */ +function onLinkWithEmailAndPassword() { + const email = $('#link-email').val(); + const password = $('#link-password').val(); + linkWithCredential( + activeUser(), + EmailAuthProvider.credential(email, password) + ).then(onAuthUserCredentialSuccess, onAuthError); +} + +/** + * Links with a generic IdP credential. + */ +function onLinkWithGenericIdPCredential() { + alertNotImplemented(); + // var providerId = $('#link-generic-idp-provider-id').val(); + // var idToken = $('#link-generic-idp-id-token').val() || undefined; + // var rawNonce = $('#link-generic-idp-raw-nonce').val() || undefined; + // var accessToken = $('#link-generic-idp-access-token').val() || undefined; + // var provider = new OAuthProvider(providerId); + // activeUser().linkWithCredential( + // provider.credential({ + // idToken: idToken, + // accessToken: accessToken, + // rawNonce: rawNonce, + // })).then(onAuthUserCredentialSuccess, onAuthError); +} + +/** + * Unlinks the specified provider. + */ +function onUnlinkProvider() { + const providerId = $('#unlinked-provider-id').val(); + unlink(activeUser(), providerId).then(_user => { + alertSuccess('Provider unlinked from user.'); + refreshUserData(); + }, onAuthError); +} + +/** + * Sends email verification to the user. + */ +function onSendEmailVerification() { + sendEmailVerification(activeUser(), getActionCodeSettings()).then(() => { + alertSuccess('Email verification sent!'); + }, onAuthError); +} + +/** + * Confirms the email verification code given. + */ +function onApplyActionCode() { + var code = $('#email-verification-code').val(); + applyActionCode(auth, code).then(function () { + alertSuccess('Email successfully verified!'); + refreshUserData(); + }, onAuthError); +} + +/** + * Gets or refreshes the ID token. + * @param {boolean} forceRefresh Whether to force the refresh of the token + * or not. + */ +function getIdToken(forceRefresh) { + if (activeUser() == null) { + alertError('No user logged in.'); + return; + } + if (activeUser().getIdToken) { + activeUser() + .getIdToken(forceRefresh) + .then(alertSuccess, () => { + log('No token'); + }); + } else { + activeUser() + .getToken(forceRefresh) + .then(alertSuccess, () => { + log('No token'); + }); + } +} + +/** + * Gets or refreshes the ID token result. + * @param {boolean} forceRefresh Whether to force the refresh of the token + * or not + */ +function getIdTokenResult(forceRefresh) { + if (activeUser() == null) { + alertError('No user logged in.'); + return; + } + activeUser() + .getIdTokenResult(forceRefresh) + .then(idTokenResult => { + alertSuccess(JSON.stringify(idTokenResult)); + }, onAuthError); +} + +/** + * Triggers the retrieval of the ID token result. + */ +function onGetIdTokenResult() { + getIdTokenResult(false); +} + +/** + * Triggers the refresh of the ID token result. + */ +function onRefreshTokenResult() { + getIdTokenResult(true); +} + +/** + * Triggers the retrieval of the ID token. + */ +function onGetIdToken() { + getIdToken(false); +} + +/** + * Triggers the refresh of the ID token. + */ +function onRefreshToken() { + getIdToken(true); +} + +/** + * Signs out the user. + */ +function onSignOut() { + setLastUser(auth.currentUser); + auth.signOut().then(signOut, onAuthError); +} + +/** + * Handles multi-factor sign-in completion. + * @param {!MultiFactorResolver} resolver The multi-factor error + * resolver. + */ +function handleMultiFactorSignIn(resolver) { + // Save multi-factor error resolver. + multiFactorErrorResolver = resolver; + // Populate 2nd factor options from resolver. + const $listGroup = $('#multiFactorModal div.enrolled-second-factors'); + // Populate the list of 2nd factors in the list group specified. + showMultiFactors( + $listGroup, + multiFactorErrorResolver.hints, + // On row click, select the corresponding second factor to complete + // sign-in with. + function (e) { + e.preventDefault(); + // Remove all other active entries. + $listGroup.find('a').removeClass('active'); + // Mark current entry as active. + $(this).addClass('active'); + // Select current factor. + onSelectMultiFactorHint(parseInt($(this).attr('data-index'), 10)); + }, + // Do not show delete option + null + ); + // Hide phone form (other second factor types could be supported). + $('#multi-factor-phone').addClass('hidden'); + // Show second factor recovery dialog. + $('#multiFactorModal').modal(); +} + +/** + * Displays the list of multi-factors in the provided list group. + * @param {!jQuery} $listGroup The list group where the enrolled + * factors will be displayed. + * @param {!Array} multiFactorInfo The list of + * multi-factors to display. + * @param {?function(!jQuery.Event)} onClick The click handler when a second + * factor is clicked. + * @param {?function(!jQuery.Event)} onDelete The click handler when a second + * factor is delete. If not provided, no delete button is shown. + */ +function showMultiFactors($listGroup, multiFactorInfo, onClick, onDelete) { + // Append entry to list. + $listGroup.empty(); + $.each(multiFactorInfo, i => { + // Append entry to list. + const info = multiFactorInfo[i]; + const displayName = info.displayName || 'N/A'; + const $a = $('') + .addClass('list-group-item') + .addClass('list-group-item-action') + // Set index on entry. + .attr('data-index', i) + .appendTo($listGroup); + $a.append($('

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

').text(displayName)); + if (info.phoneNumber) { + $a.append($('').text(info.phoneNumber)); + } + // Check if a delete button is to be displayed. + if (onDelete) { + const $deleteBtn = $( + '' + + '' + + '' + ); + // Append delete button to row. + $a.append($deleteBtn); + // Add delete button click handler. + $a.find('button.delete-factor').click(onDelete); + } + // On entry click. + if (onClick) { + $a.click(onClick); + } + }); +} + +/** + * Handles the user selection of second factor to complete sign-in with. + * @param {number} index The selected multi-factor hint index. + */ +function onSelectMultiFactorHint(index) { + // Hide all forms for handling each type of second factors. + // Currently only phone is supported. + $('#multi-factor-phone').addClass('hidden'); + if ( + !multiFactorErrorResolver || + typeof multiFactorErrorResolver.hints[index] === 'undefined' + ) { + return; + } + + if (multiFactorErrorResolver.hints[index].factorId === 'phone') { + // Save selected second factor. + selectedMultiFactorHint = multiFactorErrorResolver.hints[index]; + // Show options for phone 2nd factor. + // Get reCAPTCHA ready. + clearApplicationVerifier(); + makeApplicationVerifier('send-2fa-phone-code'); + // Show sign-in with phone second factor menu. + $('#multi-factor-phone').removeClass('hidden'); + // Clear all input. + $('#multi-factor-sign-in-verification-id').val(''); + $('#multi-factor-sign-in-verification-code').val(''); + } else { + // 2nd factor not found or not supported by app. + alertError('Selected 2nd factor is not supported!'); + } +} + +/** + * Adds a new row to insert an OAuth custom parameter key/value pair. + * @param {!jQuery.Event} _event The jQuery event object. + */ +function onPopupRedirectAddCustomParam(_event) { + // Form container. + let html = '

'; + // OAuth parameter key input. + html += + ''; + // OAuth parameter value input. + html += + ''; + // Button to remove current key/value pair. + html += ''; + html += ''; + // Create jQuery node. + const $node = $(html); + // Add button click event listener to remove item. + $node.find('button').on('click', function (e) { + // Remove button click event listener. + $(this).off('click'); + // Get row container and remove it. + $(this).closest('form.customParamItem').remove(); + e.preventDefault(); + }); + // Append constructed row to parameter list container. + $('#popup-redirect-custom-parameters').append($node); +} + +/** + * Performs the corresponding popup/redirect action for a generic provider. + */ +function onPopupRedirectGenericProviderClick() { + var providerId = $('#popup-redirect-generic-providerid').val(); + var provider = new OAuthProvider(providerId); + signInWithPopupRedirect(provider); +} + +/** + * Performs the corresponding popup/redirect action for a SAML provider. + */ +function onPopupRedirectSamlProviderClick() { + alertNotImplemented(); + // var providerId = $('#popup-redirect-saml-providerid').val(); + // var provider = new SAMLAuthProvider(providerId); + // signInWithPopupRedirect(provider); +} + +/** + * Performs the corresponding popup/redirect action based on user's selection. + * @param {!jQuery.Event} _event The jQuery event object. + */ +function onPopupRedirectProviderClick(_event) { + const providerId = $(event.currentTarget).data('provider'); + let provider = null; + switch (providerId) { + case 'google.com': + provider = new GoogleAuthProvider(); + break; + case 'facebook.com': + provider = new FacebookAuthProvider(); + break; + case 'github.com': + provider = new GithubAuthProvider(); + break; + case 'twitter.com': + provider = new TwitterAuthProvider(); + break; + default: + return; + } + signInWithPopupRedirect(provider); +} + +/** + * Performs a popup/redirect action based on a given provider and the user's + * selections. + * @param {!AuthProvider} provider The provider with which to + * sign in. + */ +function signInWithPopupRedirect(provider) { + let action = $('input[name=popup-redirect-action]:checked').val(); + let type = $('input[name=popup-redirect-type]:checked').val(); + let method = null; + let inst = null; + + switch (action) { + case 'link': + if (!activeUser()) { + alertError('No user logged in.'); + return; + } + inst = activeUser(); + method = type === 'popup' ? alertNotImplemented() : linkWithRedirect; + break; + case 'reauthenticate': + if (!activeUser()) { + alertError('No user logged in.'); + return; + } + inst = activeUser(); + method = + type === 'popup' ? alertNotImplemented() : reauthenticateWithRedirect; + break; + default: + inst = auth; + method = type === 'popup' ? alertNotImplemented() : signInWithRedirect; + } + + // Get custom OAuth parameters. + const customParameters = {}; + // For each entry. + $('form.customParamItem').each(function (_index) { + // Get parameter key. + const key = $(this).find('input.customParamKey').val(); + // Get parameter value. + const value = $(this).find('input.customParamValue').val(); + // Save to list if valid. + if (key && value) { + customParameters[key] = value; + } + }); + console.log('customParameters: ', customParameters); + // For older jscore versions that do not support this. + if (provider.setCustomParameters) { + // Set custom parameters on current provider. + provider.setCustomParameters(customParameters); + } + + // Add scopes for providers who do have scopes available (i.e. not Twitter). + if (provider.addScope) { + // String.prototype.trim not available in IE8. + const scopes = $.trim($('#scopes').val()).split(/\s*,\s*/); + for (let i = 0; i < scopes.length; i++) { + provider.addScope(scopes[i]); + } + } + console.log('Provider:'); + console.log(provider); + method(inst, provider, cordovaPopupRedirectResolver) + .then(() => getRedirectResult(auth)) + .then(response => { + console.log('Popup response:'); + console.log(response); + alertSuccess(action + ' with ' + provider['providerId'] + ' successful!'); + logAdditionalUserInfo(response); + onAuthSuccess(activeUser()); + }, onAuthError); +} + +/** + * Displays user credential result. + * @param {!UserCredential} result The UserCredential result + * object. + */ +function onAuthUserCredentialSuccess(result) { + onAuthSuccess(result.user); + logAdditionalUserInfo(result); +} + +/** + * Displays redirect result. + */ +function onGetRedirectResult() { + getRedirectResult(auth, cordovaPopupRedirectResolver).then(function ( + response + ) { + log('Redirect results:'); + if (response.credential) { + log('Credential:'); + log(response.credential); + } else { + log('No credential'); + } + if (response.user) { + log("User's id:"); + log(response.user.uid); + } else { + log('No user'); + } + logAdditionalUserInfo(response); + console.log(response); + }, + onAuthError); +} + +/** + * Logs additional user info returned by a sign-in event, if available. + * @param {!Object} response + */ +function logAdditionalUserInfo(response) { + if (response?.additionalUserInfo) { + if (response.additionalUserInfo.username) { + log( + response.additionalUserInfo['providerId'] + + ' username: ' + + response.additionalUserInfo.username + ); + } + if (response.additionalUserInfo.profile) { + log(response.additionalUserInfo['providerId'] + ' profile information:'); + log(JSON.stringify(response.additionalUserInfo.profile, null, 2)); + } + if (typeof response.additionalUserInfo.isNewUser !== 'undefined') { + log( + response.additionalUserInfo['providerId'] + + ' isNewUser: ' + + response.additionalUserInfo.isNewUser + ); + } + if (response.credential) { + log('credential: ' + JSON.stringify(response.credential.toJSON())); + } + } +} + +/** + * Deletes the user account. + */ +function onDelete() { + activeUser() + ['delete']() + .then(() => { + log('User successfully deleted.'); + alertSuccess('User successfully deleted.'); + refreshUserData(); + }, onAuthError); +} + +/** + * Gets a specific query parameter from the current URL. + * @param {string} name Name of the parameter. + * @return {string} The query parameter requested. + */ +function getParameterByName(name) { + const url = window.location.href; + name = name.replace(/[\[\]]/g, '\\$&'); + const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'); + const results = regex.exec(url); + if (!results) { + return null; + } + if (!results[2]) { + return ''; + } + return decodeURIComponent(results[2].replace(/\+/g, ' ')); +} + +/** + * Detects if an action code is passed in the URL, and populates accordingly + * the input field for the confirm email verification process. + */ +function populateActionCodes() { + let emailForSignIn = null; + let signInTime = 0; + if ('localStorage' in window && window['localStorage'] !== null) { + try { + // Try to parse as JSON first using new storage format. + const emailForSignInData = JSON.parse( + window.localStorage.getItem('emailForSignIn') + ); + emailForSignIn = emailForSignInData['email'] || null; + signInTime = emailForSignInData['timestamp'] || 0; + } catch (e) { + // JSON parsing failed. This means the email is stored in the old string + // format. + emailForSignIn = window.localStorage.getItem('emailForSignIn'); + } + if (emailForSignIn) { + // Clear old codes. Old format codes should be cleared immediately. + if (new Date().getTime() - signInTime >= 1 * 24 * 3600 * 1000) { + // Remove email from storage. + window.localStorage.removeItem('emailForSignIn'); + } + } + } + const actionCode = getParameterByName('oobCode'); + if (actionCode != null) { + const mode = getParameterByName('mode'); + if (mode === 'verifyEmail') { + $('#email-verification-code').val(actionCode); + } else if (mode === 'resetPassword') { + $('#password-reset-code').val(actionCode); + } else if (mode === 'signIn') { + if (emailForSignIn) { + $('#sign-in-with-email-link-email').val(emailForSignIn); + $('#sign-in-with-email-link-link').val(window.location.href); + onSignInWithEmailLink(); + // Remove email from storage as the code is only usable once. + window.localStorage.removeItem('emailForSignIn'); + } + } else { + $('#email-verification-code').val(actionCode); + $('#password-reset-code').val(actionCode); + } + } +} + +/** + * Provides basic Database checks for authenticated and unauthenticated access. + * The Database node being tested has the following rule: + * "users": { + * "$user_id": { + * ".read": "$user_id === auth.uid", + * ".write": "$user_id === auth.uid" + * } + * } + * This applies when Real-time database service is available. + */ +function checkDatabaseAuthAccess() { + const randomString = Math.floor(Math.random() * 10000000).toString(); + let dbRef; + let dbPath; + let errMessage; + // Run this check only when Database module is available. + if ( + typeof firebase !== 'undefined' && + typeof firebase.database !== 'undefined' + ) { + if (lastUser && !auth.currentUser) { + dbPath = 'users/' + lastUser.uid; + // After sign out, confirm read/write access to users/$user_id blocked. + dbRef = firebase.database().ref(dbPath); + dbRef + .set({ + 'test': randomString + }) + .then(() => { + alertError( + 'Error: Unauthenticated write to Database node ' + + dbPath + + ' unexpectedly succeeded!' + ); + }) + .catch(error => { + errMessage = error.message.toLowerCase(); + // Permission denied error should be thrown. + if (errMessage.indexOf('permission_denied') === -1) { + alertError('Error: ' + error.code); + return; + } + dbRef + .once('value') + .then(() => { + alertError( + 'Error: Unauthenticated read to Database node ' + + dbPath + + ' unexpectedly succeeded!' + ); + }) + .catch(error => { + errMessage = error.message.toLowerCase(); + // Permission denied error should be thrown. + if (errMessage.indexOf('permission_denied') === -1) { + alertError('Error: ' + error.code); + return; + } + log( + 'Unauthenticated read/write to Database node ' + + dbPath + + ' failed as expected!' + ); + }); + }); + } else if (auth.currentUser) { + dbPath = 'users/' + auth.currentUser.uid; + // Confirm read/write access to users/$user_id allowed. + dbRef = firebase.database().ref(dbPath); + dbRef + .set({ + 'test': randomString + }) + .then(() => { + return dbRef.once('value'); + }) + .then(snapshot => { + if (snapshot.val().test === randomString) { + // read/write successful. + log( + 'Authenticated read/write to Database node ' + + dbPath + + ' succeeded!' + ); + } else { + throw new Error( + 'Authenticated read/write to Database node ' + dbPath + ' failed!' + ); + } + // Clean up: clear that node's content. + return dbRef.remove(); + }) + .catch(error => { + alertError('Error: ' + error.code); + }); + } + } +} + +/** Copy current user of auth to tempAuth. */ +function onCopyActiveUser() { + tempAuth.updateCurrentUser(activeUser()).then( + () => { + alertSuccess('Copied active user to temp Auth'); + }, + error => { + alertError('Error: ' + error.code); + } + ); +} + +/** Copy last user to auth. */ +function onCopyLastUser() { + // If last user is null, NULL_USER error will be thrown. + auth.updateCurrentUser(lastUser).then( + () => { + alertSuccess('Copied last user to Auth'); + }, + error => { + alertError('Error: ' + error.code); + } + ); +} + +/** Applies selected auth settings change. */ +function onApplyAuthSettingsChange() { + try { + auth.settings.appVerificationDisabledForTesting = + $('input[name=enable-app-verification]:checked').val() === 'No'; + alertSuccess('Auth settings changed'); + } catch (error) { + alertError('Error: ' + error.code); + } +} + +/** + * Initiates the application by setting event listeners on the various buttons. + */ +function initApp() { + log('Initializing app...'); + app = initializeApp(config); + auth = getAuth(app); + + tempApp = initializeApp( + { + apiKey: config.apiKey, + authDomain: config.authDomain + }, + `${auth.name}-temp` + ); + tempAuth = initializeAuth(tempApp, { + persistence: inMemoryPersistence, + popupRedirectResolver: cordovaPopupRedirectResolver + }); + + // Listen to reCAPTCHA config togglers. + initRecaptchaToggle(size => { + clearApplicationVerifier(); + recaptchaSize = size; + }); + + // The action code for email verification or password reset + // can be passed in the url address as a parameter, and for convenience + // this preloads the input field. + populateActionCodes(); + + // Allows to login the user if previously logged in. + if (auth.onIdTokenChanged) { + auth.onIdTokenChanged(user => { + refreshUserData(); + if (user) { + user.getIdTokenResult(false).then( + idTokenResult => { + log(JSON.stringify(idTokenResult)); + }, + () => { + log('No token.'); + } + ); + } else { + log('No user logged in.'); + } + }); + } + + if (auth.onAuthStateChanged) { + auth.onAuthStateChanged(user => { + if (user) { + log('user state change detected: ' + user.uid); + } else { + log('user state change detected: no user'); + } + // Check Database Auth access. + checkDatabaseAuthAccess(); + }); + } + + if (tempAuth.onAuthStateChanged) { + tempAuth.onAuthStateChanged(user => { + if (user) { + log('user state change on temp Auth detect: ' + JSON.stringify(user)); + alertSuccess('user state change on temp Auth detect: ' + user.uid); + } + }); + } + + /** + * @fileoverview Utilities for Auth test app features. + */ + + /** + * Initializes the widget for toggling reCAPTCHA size. + * @param {function(string):void} callback The callback to call when the + * size toggler is changed, which takes in the new reCAPTCHA size. + */ + function initRecaptchaToggle(callback) { + // Listen to recaptcha config togglers. + const $recaptchaConfigTogglers = $('.toggleRecaptcha'); + $recaptchaConfigTogglers.click(function (e) { + // Remove currently active option. + $recaptchaConfigTogglers.removeClass('active'); + // Set currently selected option. + $(this).addClass('active'); + // Get the current reCAPTCHA setting label. + const size = $(e.target).text().toLowerCase(); + callback(size); + }); + } + + // Install servicerWorker if supported. + if ('serviceWorker' in navigator) { + navigator.serviceWorker + .register('/service-worker.js') + .then(reg => { + // Registration worked. + console.log('Registration succeeded. Scope is ' + reg.scope); + }) + .catch(error => { + // Registration failed. + console.log('Registration failed with ' + error.message); + }); + } + + if (window.Worker) { + webWorker = new Worker('/web-worker.js'); + /** + * Handles the incoming message from the web worker. + * @param {!Object} e The message event received. + */ + webWorker.onmessage = function (e) { + console.log('User data passed through web worker: ', e.data); + switch (e.data.type) { + case 'GET_USER_INFO': + alertSuccess( + 'User data passed through web worker: ' + JSON.stringify(e.data) + ); + break; + case 'RUN_TESTS': + if (e.data.status === 'success') { + alertSuccess('Web worker tests ran successfully!'); + } else { + alertError('Error: ' + JSON.stringify(e.data.error)); + } + break; + default: + return; + } + }; + } + + /** + * Asks the web worker, if supported in current browser, to return the user info + * corresponding to the currentUser as seen within the worker. + */ + function onGetCurrentUserDataFromWebWorker() { + if (webWorker) { + webWorker.postMessage({ type: 'GET_USER_INFO' }); + } else { + alertError( + 'Error: Web workers are not supported in the current browser!' + ); + } + } + + // We check for redirect result to refresh user's data. + getRedirectResult(auth, cordovaPopupRedirectResolver).then(function ( + response + ) { + refreshUserData(); + logAdditionalUserInfo(response); + }, + onAuthError); + + // Bootstrap tooltips. + $('[data-toggle="tooltip"]').tooltip(); + + // Auto submit the choose library type form. + $('#library-form').on('change', 'input.library-option', () => { + $('#library-form').submit(); + }); + + // To clear the logs in the page. + $('.clear-logs').click(clearLogs); + + // Disables JS forms. + $('form.no-submit').on('submit', () => { + return false; + }); + + // Keeps track of the current tab opened. + $('#tab-menu a').click(event => { + currentTab = $(event.currentTarget).attr('href'); + }); + + // Toggles user. + $('input[name=toggle-user-selection]').change(refreshUserData); + + // Actions listeners. + $('#sign-up-with-email-and-password').click(onSignUp); + $('#sign-in-with-email-and-password').click(onSignInWithEmailAndPassword); + $('.sign-in-with-custom-token').click(onSignInWithCustomToken); + $('#sign-in-anonymously').click(onSignInAnonymously); + $('#sign-in-with-generic-idp-credential').click( + onSignInWithGenericIdPCredential + ); + $('#sign-in-with-email-link').click(onSignInWithEmailLink); + $('#link-with-email-link').click(onLinkWithEmailLink); + $('#reauth-with-email-link').click(onReauthenticateWithEmailLink); + + $('#change-email').click(onChangeEmail); + $('#change-password').click(onChangePassword); + $('#update-profile').click(onUpdateProfile); + + $('#send-sign-in-link-to-email').click(onSendSignInLinkToEmail); + $('#send-sign-in-link-to-email-current-url').click( + onSendSignInLinkToEmailCurrentUrl + ); + $('#send-link-email-link').click(onSendLinkEmailLink); + + $('#send-password-reset-email').click(onSendPasswordResetEmail); + $('#verify-password-reset-code').click(onVerifyPasswordResetCode); + $('#confirm-password-reset').click(onConfirmPasswordReset); + + $('#get-provider-data').click(onGetProviderData); + $('#link-with-email-and-password').click(onLinkWithEmailAndPassword); + $('#link-with-generic-idp-credential').click(onLinkWithGenericIdPCredential); + $('#unlink-provider').click(onUnlinkProvider); + + $('#send-email-verification').click(onSendEmailVerification); + $('#confirm-email-verification').click(onApplyActionCode); + $('#get-token-result').click(onGetIdTokenResult); + $('#refresh-token-result').click(onRefreshTokenResult); + $('#get-token').click(onGetIdToken); + $('#refresh-token').click(onRefreshToken); + $('#get-token-worker').click(onGetCurrentUserDataFromWebWorker); + $('#sign-out').click(onSignOut); + + $('.popup-redirect-provider').click(onPopupRedirectProviderClick); + $('#popup-redirect-generic').click(onPopupRedirectGenericProviderClick); + $('#popup-redirect-get-redirect-result').click(onGetRedirectResult); + $('#popup-redirect-add-custom-parameter').click( + onPopupRedirectAddCustomParam + ); + $('#popup-redirect-saml').click(onPopupRedirectSamlProviderClick); + + $('#action-code-settings-reset').click(onActionCodeSettingsReset); + + $('#delete').click(onDelete); + + $('#set-persistence').click(onSetPersistence); + + $('#set-language-code').click(onSetLanguageCode); + $('#use-device-language').click(onUseDeviceLanguage); + + $('#fetch-sign-in-methods-for-email').click(onFetchSignInMethodsForEmail); + + $('#copy-active-user').click(onCopyActiveUser); + $('#copy-last-user').click(onCopyLastUser); + + $('#apply-auth-settings-change').click(onApplyAuthSettingsChange); +} + +document.addEventListener( + 'deviceready', + function () { + initApp(); + }, + false +); diff --git a/packages-exp/auth-exp/cordova/demo/src/logging.js b/packages-exp/auth-exp/cordova/demo/src/logging.js new file mode 100644 index 00000000000..dae567befe0 --- /dev/null +++ b/packages-exp/auth-exp/cordova/demo/src/logging.js @@ -0,0 +1,121 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Fix for IE8 when developer's console is not opened. +if (!window.console) { + window.console = { + log() {}, + error() {} + }; +} + +/** + * Logs the message in the console and on the log window in the app + * using the level given. + * @param {?Object} message Object or message to log. + * @param {string} level The level of log (log, error, debug). + * @private + */ +export function logAtLevel_(message, level) { + if (message != null) { + const messageDiv = $('
'); + messageDiv.addClass(level); + if (typeof message === 'object') { + messageDiv.text(JSON.stringify(message, null, ' ')); + } else { + messageDiv.text(message); + } + $('.logs').append(messageDiv); + } + console[level](message); +} + +/** + * Logs info level. + * @param {string} message Object or message to log. + */ +export function log(message) { + logAtLevel_(message, 'log'); +} + +/** + * Clear the logs. + */ +export function clearLogs() { + $('.logs').text(''); +} + +/** + * Displays for a few seconds a box with a specific message and then fades + * it out. + * @param {string} message Small message to display. + * @param {string} cssClass The class(s) to give the alert box. + * @private + */ +function alertMessage_(message, cssClass) { + const alertBox = $('
') + .addClass(cssClass) + .css('display', 'none') + .text(message); + // When modals are visible, display the alert in the modal layer above the + // grey background. + const visibleModal = $('.modal.in'); + if (visibleModal.size() > 0) { + // Check first if the model has an overlaying-alert. If not, append the + // overlaying-alert container. + if (visibleModal.find('.overlaying-alert').size() === 0) { + const $overlayingAlert = $( + '
' + ); + visibleModal.append($overlayingAlert); + } + visibleModal.find('.overlaying-alert').prepend(alertBox); + } else { + $('#alert-messages').prepend(alertBox); + } + alertBox.fadeIn({ + complete() { + setTimeout(() => { + alertBox.slideUp(400, () => { + // On completion, remove the alert element from the DOM. + alertBox.remove(); + }); + }, 3000); + } + }); +} + +/** + * Alerts a small success message in a overlaying alert box. + * @param {string} message Small message to display. + */ +export function alertSuccess(message) { + alertMessage_(message, 'alert alert-success'); +} + +/** + * Alerts a small error message in a overlaying alert box. + * @param {string} message Small message to display. + */ +export function alertError(message) { + alertMessage_(message, 'alert alert-danger'); +} + +export function alertNotImplemented() { + alertError('Method not yet implemented in the new SDK'); +} diff --git a/packages-exp/auth-exp/cordova/demo/src/sample-config.js b/packages-exp/auth-exp/cordova/demo/src/sample-config.js new file mode 100644 index 00000000000..871a1674d5e --- /dev/null +++ b/packages-exp/auth-exp/cordova/demo/src/sample-config.js @@ -0,0 +1,23 @@ +/* + * @license + * Copyright 2017 Google LLC All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const config = { + apiKey: 'YOUR_API_KEY', + authDomain: 'your-app.firebaseapp.com', + databaseURL: 'https://your-app.firebaseio.com', + projectId: 'your-app', + storageBucket: 'your-app.appspot.com', + messagingSenderId: 'MESSAGING_SENDER_ID' +}; diff --git a/packages-exp/auth-exp/cordova/demo/www/index.html b/packages-exp/auth-exp/cordova/demo/www/index.html new file mode 100644 index 00000000000..a7199342fa0 --- /dev/null +++ b/packages-exp/auth-exp/cordova/demo/www/index.html @@ -0,0 +1,664 @@ + + + + + Headless App + + + + + + + + + + + +
+ +
+ + + + + +
+
+ + +
+
+ + +
+
+ + + [anonymous] / + uid: + +
+
+ + / + + + + + + + +
+ + + +
+
+
+
+ + +
+ +
+
+
+ + + +
+
+ + +
Auth State Persistence
+
+ + +
+ + +
Language code
+
+ + + +
+ + +
Sign Up
+
+ + + +
+ + + +
Sign In
+
+ + + +
+
+ + +
+ +
+ + + + + +
+ + +
Sign In with Email Link
+
+ + + +
+
+ + +
+ + +
Password Reset
+
+ + +
+
+ + + + +
+ +
Fetch Sign In Methods
+
+ + +
+ + +
Update Current User
+
+ +
+
+ +
+
+
+ +
Update Profile
+
+ + +
+
+ + +
+
+ + + +
+ + + +
Linking/Unlinking
+ +
+ + + +
+
+ + + + + +
+ +
+ + + + + +
+ +
+ + +
+ + +
Enroll Second Factor
+ + + +
Other Actions
+ +
+ + +
+ + + + + + + +
Delete account
+ +
+ +
+
Web
+
+ +
+
Android
+
+
+ +
+ + +
+ +
+
+
iOS
+
+
+ +
+
+
+
+ + +
+ +
+
+
+

+              
+            
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages-exp/auth-exp/cordova/demo/www/style.css b/packages-exp/auth-exp/cordova/demo/www/style.css new file mode 100644 index 00000000000..c9f10255d00 --- /dev/null +++ b/packages-exp/auth-exp/cordova/demo/www/style.css @@ -0,0 +1,207 @@ +/** + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + body { + padding-top: 70px; +} +body.user-info-displayed { + padding-top: 120px; +} + +@media (min-width: 768px) { + body { + padding-bottom: 100px; + } +} +@media (max-width: 767px) { + body { + padding-bottom: 15px; + } +} + +#tab-menu { + margin-bottom: 15px; +} + +#user-info { + display: none; + padding: 10px 15px; + position: fixed; + top: 50px; + width: 100%; + z-index: 1000; +} + +#toggle-user-placeholder { + margin-bottom: 15px; +} + +#user-info.current-user, +#toggle-user-placeholder .current-user, +#toggle-user label.active.current-user { + background-color: #d6e9c6; + border: 1px solid #dff0d8; + color: #3c763d; +} + +#user-info.last-user, +#toggle-user label.active.last-user { + background-color: #d9edf7; + border: 1px solid #bce8f1; + color: #31708f; +} + +#recaptcha-container { + border: 5px solid red; + bottom: 0; + position: fixed; + right: 0; + z-index: 1500; +} + +#recaptcha-container:empty { + border: none; +} + +.logs { + color: #555; + font-family: 'Courier New', Courier; + font-size: 0.9em; + word-wrap: break-word; +} + +.logs > .error { + color: #d9534f; +} + +/* Margin top for small screens when the logs are below the buttons */ +@media (max-width: 767px) { + .logs { + margin-top: 20px; + } +} + +.overlaying-alert { + bottom: 15px; + pointer-events: none; + position: fixed; + width: 100%; + word-wrap: break-word; + z-index: 1010; +} + +.actions { + margin-bottom: 15px; +} + +.group { + border-top: 1px solid #555; + color: #555; + font-size: 0.9em; + font-weight: bold; + letter-spacing: 0.2em; + margin-bottom: 15px; + padding: 2px 0 0 10px; +} + +button + .group, +div + .group, +.btn-block + .group, +.form + .group { + margin-top: 30px; +} + +.form { + text-align: center; +} + +.form-bordered { + border: 1px solid #CCC; + border-radius: 9px; + padding: 5px; +} + +button + .form, +input + .form, +.form + .form { + margin: 15px 0; +} + +.form + button, +.form-control + .btn-block, +.form-control + .form-control { + margin-top: 5px; +} + +/* Bootstrap .hidden adds the !important which invalides jQuery .show() */ +.hidden, +.hide, +.profile, +.overlaying-alert > .alert, +#toggle-user { + display: none; +} + +.profile { + line-height: 30px; +} + +@media (max-width: 767px) { + .profile { + /* Use a smaller line height for small screens so user information doesn't + take up half the screen. */ + line-height: normal; + } +} + +.profile-uid { + font-family: 'Courier New', Courier; +} + +.profile-image { + float: left; + height: 30px; + margin-right: 10px; +} + +.profile-email-not-verified { + color: #d9534f; +} + +.profile-providers { + color: #333; +} + +.profile-providers > i { + margin: 0 5px; +} + +.radio-block { + margin-bottom: 15px; + width: 100%; +} + +.radio-block > label { + width: 50%; +} + +/** Overrides default drop down menu styles for enrolled factors display. */ +.enrolled-second-factors { + left: initial; + min-width: 400px; + right: 0px; + width: 100%; +} diff --git a/packages-exp/auth-exp/cordova/package.json b/packages-exp/auth-exp/cordova/package.json new file mode 100644 index 00000000000..64d433f737e --- /dev/null +++ b/packages-exp/auth-exp/cordova/package.json @@ -0,0 +1,6 @@ +{ + "name": "@firebase/auth-exp/cordova", + "description": "A Cordova-specific build of the Firebase Auth JS SDK", + "browser": "../dist/cordova/index.js", + "typings": "../dist/cordova/index.cordova.d.ts" +} \ No newline at end of file diff --git a/packages-exp/auth-exp/demo/.firebaserc b/packages-exp/auth-exp/demo/.firebaserc deleted file mode 100644 index 7f7f6d262c3..00000000000 --- a/packages-exp/auth-exp/demo/.firebaserc +++ /dev/null @@ -1,6 +0,0 @@ -{ - "projects": { - "default": "fir-auth-next-demo", - "demo": "fir-auth-next-demo" - } -} \ No newline at end of file diff --git a/packages-exp/auth-exp/demo/.gitignore b/packages-exp/auth-exp/demo/.gitignore index dcb0c27aff2..798f66cee47 100644 --- a/packages-exp/auth-exp/demo/.gitignore +++ b/packages-exp/auth-exp/demo/.gitignore @@ -1,5 +1,6 @@ src/config.js .firebaserc +.firebase public/service-worker.* public/web-worker.* public/index.js* \ No newline at end of file diff --git a/packages-exp/auth-exp/demo/functions/package.json b/packages-exp/auth-exp/demo/functions/package.json index 7a86ca4d8dc..c825a2e5203 100644 --- a/packages-exp/auth-exp/demo/functions/package.json +++ b/packages-exp/auth-exp/demo/functions/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "firebase-admin": "8.13.0", - "firebase-functions": "3.11.0" + "firebase-functions": "3.14.1" }, "private": true, "engines": { diff --git a/packages-exp/auth-exp/demo/functions/yarn.lock b/packages-exp/auth-exp/demo/functions/yarn.lock index efca8972cc7..d0de26b87a5 100644 --- a/packages-exp/auth-exp/demo/functions/yarn.lock +++ b/packages-exp/auth-exp/demo/functions/yarn.lock @@ -132,9 +132,9 @@ semver "^6.2.0" "@grpc/proto-loader@^0.5.1": - version "0.5.5" - resolved "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.5.tgz#6725e7a1827bdf8e92e29fbf4e9ef0203c0906a9" - integrity sha512-WwN9jVNdHRQoOBo9FDH7qU+mgfjPc8GygPYms3M+y3fbQLfnCe/Kv/E01t7JRgnrsOHH8euvSbed3mIalXhwqQ== + version "0.5.6" + resolved "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.6.tgz#1dea4b8a6412b05e2d58514d507137b63a52a98d" + integrity sha512-DT14xgw3PSzPxwS13auTEwxhMMOoz33DPUKNtmYK/QYbBSpLXJy78FGGs5yVoxVobEqPm4iW9MOIoz0A3bLTRQ== dependencies: lodash.camelcase "^4.3.0" protobufjs "^6.8.6" @@ -206,16 +206,16 @@ "@types/node" "*" "@types/connect@*": - version "3.4.33" - resolved "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz#31610c901eca573b8713c3330abc6e6b9f588546" - integrity sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A== + version "3.4.34" + resolved "https://registry.npmjs.org/@types/connect/-/connect-3.4.34.tgz#170a40223a6d666006d93ca128af2beb1d9b1901" + integrity sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ== dependencies: "@types/node" "*" "@types/express-serve-static-core@*": - version "4.17.13" - resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.13.tgz#d9af025e925fc8b089be37423b8d1eac781be084" - integrity sha512-RgDi5a4nuzam073lRGKTUIaL3eF2+H7LJvJ8eUnCI0wA6SNjXc44DCmWNiTLs/AZ7QlsFWZiw/gTG3nSQGL0fA== + version "4.17.18" + resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.18.tgz#8371e260f40e0e1ca0c116a9afcd9426fa094c40" + integrity sha512-m4JTwx5RUBNZvky/JJ8swEJPKFd8si08pPF2PfizYjGZOKr/svUWPcoUmLow6MmPzhasphB7gSTINY67xn3JNA== dependencies: "@types/node" "*" "@types/qs" "*" @@ -242,30 +242,30 @@ resolved "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w== -"@types/mime@*": - version "2.0.3" - resolved "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz#c893b73721db73699943bfc3653b1deb7faa4a3a" - integrity sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q== +"@types/mime@^1": + version "1.3.2" + resolved "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" + integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== "@types/node@*": - version "14.11.2" - resolved "https://registry.npmjs.org/@types/node/-/node-14.11.2.tgz#2de1ed6670439387da1c9f549a2ade2b0a799256" - integrity sha512-jiE3QIxJ8JLNcb1Ps6rDbysDhN4xa8DJJvuC9prr6w+1tIh+QAbYyNF3tyiZNLDBIuBCf4KEcV2UvQm/V60xfA== + version "14.14.33" + resolved "https://registry.npmjs.org/@types/node/-/node-14.14.33.tgz#9e4f8c64345522e4e8ce77b334a8aaa64e2b6c78" + integrity sha512-oJqcTrgPUF29oUP8AsUqbXGJNuPutsetaa9kTQAQce5Lx5dTYWV02ScBiT/k1BX/Z7pKeqedmvp39Wu4zR7N7g== "@types/node@^13.7.0": - version "13.13.21" - resolved "https://registry.npmjs.org/@types/node/-/node-13.13.21.tgz#e48d3c2e266253405cf404c8654d1bcf0d333e5c" - integrity sha512-tlFWakSzBITITJSxHV4hg4KvrhR/7h3xbJdSFbYJBVzKubrASbnnIFuSgolUh7qKGo/ZeJPKUfbZ0WS6Jp14DQ== + version "13.13.46" + resolved "https://registry.npmjs.org/@types/node/-/node-13.13.46.tgz#5471e176f3fa15e018dea7992072bf8ca208a3a6" + integrity sha512-dqpbzK/KDsOlEt+oyB3rv+u1IxlLFziZu/Z0adfRKoelkr+sTd6QcgiQC+HWq/vkYkHwG5ot2LxgV05aAjnhcg== "@types/node@^8.10.59": - version "8.10.64" - resolved "https://registry.npmjs.org/@types/node/-/node-8.10.64.tgz#0dddc4c53ca4819a32b7478232d8b446ca90e1c6" - integrity sha512-/EwBIb+imu8Qi/A3NF9sJ9iuKo7yV+pryqjmeRqaU0C4wBAOhas5mdvoYeJ5PCKrh6thRSJHdoasFqh3BQGILA== + version "8.10.66" + resolved "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz#dd035d409df322acc83dff62a602f12a5783bbb3" + integrity sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw== "@types/qs@*": - version "6.9.5" - resolved "https://registry.npmjs.org/@types/qs/-/qs-6.9.5.tgz#434711bdd49eb5ee69d90c1d67c354a9a8ecb18b" - integrity sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ== + version "6.9.6" + resolved "https://registry.npmjs.org/@types/qs/-/qs-6.9.6.tgz#df9c3c8b31a247ec315e6996566be3171df4b3b1" + integrity sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA== "@types/range-parser@*": version "1.2.3" @@ -273,12 +273,12 @@ integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== "@types/serve-static@*": - version "1.13.5" - resolved "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.5.tgz#3d25d941a18415d3ab092def846e135a08bbcf53" - integrity sha512-6M64P58N+OXjU432WoLLBQxbA0LRGBCRm7aAGQJ+SMC1IMl0dgRVi9EFfoDcS2a7Xogygk/eGN94CfwU9UF7UQ== + version "1.13.9" + resolved "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.9.tgz#aacf28a85a05ee29a11fb7c3ead935ac56f33e4e" + integrity sha512-ZFqF6qa48XsPdjXV5Gsz0Zqmux2PerNd3a/ktL45mHpa19cuMi/cL8tcxdAx497yRh+QtYPuofjT9oWw9P7nkA== dependencies: - "@types/express-serve-static-core" "*" - "@types/mime" "*" + "@types/mime" "^1" + "@types/node" "*" abort-controller@^3.0.0: version "3.0.0" @@ -296,9 +296,9 @@ accepts@~1.3.7: negotiator "0.6.2" agent-base@6: - version "6.0.1" - resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz#808007e4e5867decb0ab6ab2f928fbdb5a596db4" - integrity sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg== + version "6.0.2" + resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== dependencies: debug "4" @@ -317,7 +317,7 @@ arrify@^2.0.0: resolved "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== -available-typed-arrays@^1.0.0, available-typed-arrays@^1.0.2: +available-typed-arrays@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz#6b098ca9d8039079ee3f77f7b783c4480ba513f5" integrity sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ== @@ -325,9 +325,9 @@ available-typed-arrays@^1.0.0, available-typed-arrays@^1.0.2: array-filter "^1.0.0" base64-js@^1.3.0: - version "1.3.1" - resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" - integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== + version "1.5.1" + resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== bignumber.js@^9.0.0: version "9.0.1" @@ -365,6 +365,14 @@ bytes@3.1.0: resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + compressible@^2.0.12: version "2.0.18" resolved "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" @@ -447,28 +455,29 @@ debug@2.6.9: ms "2.0.0" debug@4, debug@^4.1.1: - version "4.2.0" - resolved "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1" - integrity sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg== + version "4.3.1" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== dependencies: ms "2.1.2" deep-equal@^2.0.0: - version "2.0.3" - resolved "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.3.tgz#cad1c15277ad78a5c01c49c2dee0f54de8a6a7b0" - integrity sha512-Spqdl4H+ky45I9ByyJtXteOm9CaIrPmnIPmOhrkKGNYWeDgCvJ8jNYVCTjChxW4FqGuZnLHADc8EKRMX6+CgvA== + version "2.0.5" + resolved "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.5.tgz#55cd2fe326d83f9cbf7261ef0e060b3f724c5cb9" + integrity sha512-nPiRgmbAtm1a3JsnLCf6/SLfXcjyN5v8L1TXzdCmHrXJ4hx+gW/w1YCcn7z8gJtSiDArZCgYtbao3QqLm/N1Sw== dependencies: - es-abstract "^1.17.5" - es-get-iterator "^1.1.0" + call-bind "^1.0.0" + es-get-iterator "^1.1.1" + get-intrinsic "^1.0.1" is-arguments "^1.0.4" is-date-object "^1.0.2" - is-regex "^1.0.5" + is-regex "^1.1.1" isarray "^2.0.5" - object-is "^1.1.2" + object-is "^1.1.4" object-keys "^1.1.1" - object.assign "^4.1.0" + object.assign "^4.1.2" regexp.prototype.flags "^1.3.0" - side-channel "^1.0.2" + side-channel "^1.0.3" which-boxed-primitive "^1.0.1" which-collection "^1.0.1" which-typed-array "^1.1.2" @@ -553,51 +562,39 @@ ent@^2.2.0: resolved "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d" integrity sha1-6WQhkyWiHQX0RGai9obtbOX13R0= -es-abstract@^1.17.0-next.1, es-abstract@^1.17.4, es-abstract@^1.17.5: - version "1.17.7" - resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c" - integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g== - dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.2.2" - is-regex "^1.1.1" - object-inspect "^1.8.0" - object-keys "^1.1.1" - object.assign "^4.1.1" - string.prototype.trimend "^1.0.1" - string.prototype.trimstart "^1.0.1" - -es-abstract@^1.18.0-next.0, es-abstract@^1.18.0-next.1: - version "1.18.0-next.1" - resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68" - integrity sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA== +es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2: + version "1.18.0" + resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz#ab80b359eecb7ede4c298000390bc5ac3ec7b5a4" + integrity sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw== dependencies: + call-bind "^1.0.2" es-to-primitive "^1.2.1" function-bind "^1.1.1" + get-intrinsic "^1.1.1" has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.2.2" - is-negative-zero "^2.0.0" - is-regex "^1.1.1" - object-inspect "^1.8.0" + has-symbols "^1.0.2" + is-callable "^1.2.3" + is-negative-zero "^2.0.1" + is-regex "^1.1.2" + is-string "^1.0.5" + object-inspect "^1.9.0" object-keys "^1.1.1" - object.assign "^4.1.1" - string.prototype.trimend "^1.0.1" - string.prototype.trimstart "^1.0.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.4" + string.prototype.trimstart "^1.0.4" + unbox-primitive "^1.0.0" -es-get-iterator@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.0.tgz#bb98ad9d6d63b31aacdc8f89d5d0ee57bcb5b4c8" - integrity sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ== +es-get-iterator@^1.1.1: + version "1.1.2" + resolved "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz#9234c54aba713486d7ebde0220864af5e2b283f7" + integrity sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ== dependencies: - es-abstract "^1.17.4" + call-bind "^1.0.2" + get-intrinsic "^1.1.0" has-symbols "^1.0.1" - is-arguments "^1.0.4" - is-map "^2.0.1" - is-set "^2.0.1" + is-arguments "^1.1.0" + is-map "^2.0.2" + is-set "^2.0.2" is-string "^1.0.5" isarray "^2.0.5" @@ -705,10 +702,10 @@ firebase-admin@8.13.0: "@google-cloud/firestore" "^3.0.0" "@google-cloud/storage" "^4.1.2" -firebase-functions@3.11.0: - version "3.11.0" - resolved "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.11.0.tgz#92f5a6af6a10641da6dc9b41b29974658b621a7b" - integrity sha512-i1uMhZ/M6i5SCI3ulKo7EWX0/LD+I5o6N+sk0HbOWfzyWfOl0iJTvQkR3BVDcjrlhPVC4xG1bDTLxd+DTkLqaw== +firebase-functions@3.14.1: + version "3.14.1" + resolved "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.14.1.tgz#3ac5bc70989365874f41d06bca3b42a233dd6039" + integrity sha512-hL/qm+i5i1qKYmAFMlQ4mwRngDkP+3YT3F4E4Nd5Hj2QKeawBdZiMGgEt6zqTx08Zq04vHiSnSM0z75UJRSg6Q== dependencies: "@types/express" "4.17.3" cors "^2.8.5" @@ -782,6 +779,15 @@ gcs-resumable-upload@^2.2.4: pumpify "^2.0.0" stream-events "^1.0.4" +get-intrinsic@^1.0.1, get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + google-auth-library@^5.0.0, google-auth-library@^5.5.0: version "5.10.1" resolved "https://registry.npmjs.org/google-auth-library/-/google-auth-library-5.10.1.tgz#504ec75487ad140e68dd577c21affa363c87ddff" @@ -826,9 +832,9 @@ google-p12-pem@^2.0.0: node-forge "^0.9.0" graceful-fs@^4.1.2: - version "4.2.4" - resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" - integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== + version "4.2.6" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" + integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== gtoken@^4.1.0: version "4.1.4" @@ -840,10 +846,15 @@ gtoken@^4.1.0: jws "^4.0.0" mime "^2.2.0" -has-symbols@^1.0.1: +has-bigints@^1.0.0: version "1.0.1" - resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" - integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== + resolved "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" + integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== + +has-symbols@^1.0.0, has-symbols@^1.0.1, has-symbols@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== has@^1.0.3: version "1.0.3" @@ -880,9 +891,9 @@ http-errors@~1.7.2: toidentifier "1.0.0" http-parser-js@>=0.5.1: - version "0.5.2" - resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.2.tgz#da2e31d237b393aae72ace43882dd7e270a8ff77" - integrity sha512-opCO9ASqg5Wy2FNo7A0sxy71yGbbkJJXLdgMK04Tcypw9jr2MgWbyubb0+WdmDmGnFflO7fRbqbaihh/ENDlRQ== + version "0.5.3" + resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz#01d2709c79d41698bb01d4decc5e9da4e4a033d9" + integrity sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg== http-proxy-agent@^4.0.0: version "4.0.1" @@ -928,42 +939,46 @@ ipaddr.js@1.9.1: resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== -is-arguments@^1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3" - integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA== - -is-bigint@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.0.tgz#73da8c33208d00f130e9b5e15d23eac9215601c4" - integrity sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g== +is-arguments@^1.0.4, is-arguments@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz#62353031dfbee07ceb34656a6bde59efecae8dd9" + integrity sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg== + dependencies: + call-bind "^1.0.0" -is-boolean-object@^1.0.0: +is-bigint@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.1.tgz#10edc0900dd127697a92f6f9807c7617d68ac48e" - integrity sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ== + resolved "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.1.tgz#6923051dfcbc764278540b9ce0e6b3213aa5ebc2" + integrity sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg== + +is-boolean-object@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.0.tgz#e2aaad3a3a8fca34c28f6eee135b156ed2587ff0" + integrity sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA== + dependencies: + call-bind "^1.0.0" -is-callable@^1.1.4, is-callable@^1.2.2: - version "1.2.2" - resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" - integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== +is-callable@^1.1.4, is-callable@^1.2.3: + version "1.2.3" + resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e" + integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ== is-date-object@^1.0.1, is-date-object@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== -is-map@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz#520dafc4307bb8ebc33b813de5ce7c9400d644a1" - integrity sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw== +is-map@^2.0.1, is-map@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" + integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== -is-negative-zero@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461" - integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE= +is-negative-zero@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" + integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== -is-number-object@^1.0.3: +is-number-object@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw== @@ -973,17 +988,18 @@ is-obj@^2.0.0: resolved "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== -is-regex@^1.0.5, is-regex@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" - integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg== +is-regex@^1.1.1, is-regex@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz#81c8ebde4db142f2cf1c53fc86d6a45788266251" + integrity sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg== dependencies: + call-bind "^1.0.2" has-symbols "^1.0.1" -is-set@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz#d1604afdab1724986d30091575f54945da7e5f43" - integrity sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA== +is-set@^2.0.1, is-set@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" + integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== is-stream-ended@^0.1.4: version "0.1.4" @@ -995,12 +1011,12 @@ is-stream@^2.0.0: resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== -is-string@^1.0.4, is-string@^1.0.5: +is-string@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== -is-symbol@^1.0.2: +is-symbol@^1.0.2, is-symbol@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== @@ -1008,12 +1024,13 @@ is-symbol@^1.0.2: has-symbols "^1.0.1" is-typed-array@^1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.3.tgz#a4ff5a5e672e1a55f99c7f54e59597af5c1df04d" - integrity sha512-BSYUBOK/HJibQ30wWkWold5txYwMUXQct9YHAQJr8fSwvZoiglcqB0pd7vEN23+Tsi9IUEjztdOSzl4qLVYGTQ== + version "1.1.5" + resolved "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.5.tgz#f32e6e096455e329eb7b423862456aa213f0eb4e" + integrity sha512-S+GRDgJlR3PyEbsX/Fobd9cqpZBuvUS+8asRqYDMLCb2qMzt1oz5m5oxQCxOgUDxiWsOVNi4yaF+/uvdlHlYug== dependencies: - available-typed-arrays "^1.0.0" - es-abstract "^1.17.4" + available-typed-arrays "^1.0.2" + call-bind "^1.0.2" + es-abstract "^1.18.0-next.2" foreach "^2.0.5" has-symbols "^1.0.1" @@ -1150,9 +1167,9 @@ lodash.once@^4.0.0: integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= lodash@^4.17.14: - version "4.17.20" - resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" - integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== + version "4.17.21" + resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== long@^4.0.0: version "4.0.0" @@ -1188,22 +1205,17 @@ methods@~1.1.2: resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= -mime-db@1.44.0: - version "1.44.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" - integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== - -"mime-db@>= 1.43.0 < 2": - version "1.45.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea" - integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w== +mime-db@1.46.0, "mime-db@>= 1.43.0 < 2": + version "1.46.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.46.0.tgz#6267748a7f799594de3cbc8cde91def349661cee" + integrity sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ== mime-types@^2.0.8, mime-types@~2.1.24: - version "2.1.27" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" - integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== + version "2.1.29" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.29.tgz#1d4ab77da64b91f5f72489df29236563754bb1b2" + integrity sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ== dependencies: - mime-db "1.44.0" + mime-db "1.46.0" mime@1.6.0: version "1.6.0" @@ -1211,9 +1223,9 @@ mime@1.6.0: integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== mime@^2.2.0: - version "2.4.6" - resolved "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1" - integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA== + version "2.5.2" + resolved "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" + integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg== mimic-fn@^2.1.0: version "2.1.0" @@ -1230,11 +1242,16 @@ ms@2.1.1: resolved "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== -ms@2.1.2, ms@^2.1.1: +ms@2.1.2: version "2.1.2" resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + negotiator@0.6.2: version "0.6.2" resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" @@ -1260,31 +1277,31 @@ object-assign@^4: resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= -object-inspect@^1.8.0: - version "1.8.0" - resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" - integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== +object-inspect@^1.9.0: + version "1.9.0" + resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a" + integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw== -object-is@^1.1.2: - version "1.1.3" - resolved "https://registry.npmjs.org/object-is/-/object-is-1.1.3.tgz#2e3b9e65560137455ee3bd62aec4d90a2ea1cc81" - integrity sha512-teyqLvFWzLkq5B9ki8FVWA902UER2qkxmdA4nLf+wjOLAWgxzCWZNCxpDq9MvE8MmhWNr+I8w3BN49Vx36Y6Xg== +object-is@^1.1.4: + version "1.1.5" + resolved "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" + integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.18.0-next.1" object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object.assign@^4.1.0, object.assign@^4.1.1: - version "4.1.1" - resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz#303867a666cdd41936ecdedfb1f8f3e32a478cdd" - integrity sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA== +object.assign@^4.1.2: + version "4.1.2" + resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== dependencies: + call-bind "^1.0.0" define-properties "^1.1.3" - es-abstract "^1.18.0-next.0" has-symbols "^1.0.1" object-keys "^1.1.1" @@ -1337,9 +1354,9 @@ process-nextick-args@~2.0.0: integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== protobufjs@^6.8.6, protobufjs@^6.8.9: - version "6.10.1" - resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.1.tgz#e6a484dd8f04b29629e9053344e3970cccf13cd2" - integrity sha512-pb8kTchL+1Ceg4lFd5XUpK8PdWacbvV5SK2ULH2ebrYtl4GjJmS24m6CKME67jzV53tbJxHlnNOSqQHbTsR9JQ== + version "6.10.2" + resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.2.tgz#b9cb6bd8ec8f87514592ba3fdfd28e93f33a469b" + integrity sha512-27yj+04uF6ya9l+qfpH187aqEzfCF4+Uit0I9ZBQVqK09hk/SQzKa2MUqUpXaVa7LOFRg1TSSr3lVxGOk6c0SQ== dependencies: "@protobufjs/aspromise" "^1.1.2" "@protobufjs/base64" "^1.1.2" @@ -1423,12 +1440,12 @@ readable-stream@^2.0.0: util-deprecate "~1.0.1" regexp.prototype.flags@^1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz#7aba89b3c13a64509dabcf3ca8d9fbb9bdf5cb75" - integrity sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ== + version "1.3.1" + resolved "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" + integrity sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" retry-request@^4.0.0: version "4.1.3" @@ -1496,13 +1513,14 @@ setprototypeof@1.1.1: resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== -side-channel@^1.0.2: - version "1.0.3" - resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.3.tgz#cdc46b057550bbab63706210838df5d4c19519c3" - integrity sha512-A6+ByhlLkksFoUepsGxfj5x1gTSrs+OydsRptUxeNCabQpCFUvcwIczgOigI8vhY/OJCnPnyE9rGiwgvr9cS1g== +side-channel@^1.0.3: + version "1.0.4" + resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== dependencies: - es-abstract "^1.18.0-next.0" - object-inspect "^1.8.0" + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" signal-exit@^3.0.2: version "3.0.3" @@ -1536,21 +1554,21 @@ streamsearch@0.1.2: resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= -string.prototype.trimend@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" - integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== +string.prototype.trimend@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" + integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.5" -string.prototype.trimstart@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" - integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== +string.prototype.trimstart@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" + integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.5" string_decoder@^1.1.1: version "1.3.0" @@ -1596,9 +1614,9 @@ toidentifier@1.0.0: integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== tslib@^1.11.1: - version "1.13.0" - resolved "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" - integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== + version "1.14.1" + resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== type-is@~1.6.17, type-is@~1.6.18: version "1.6.18" @@ -1620,6 +1638,16 @@ typedarray@^0.0.6: resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +unbox-primitive@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.0.tgz#eeacbc4affa28e9b3d36b5eaeccc50b3251b1d3f" + integrity sha512-P/51NX+JXyxK/aigg1/ZgyccdAxm5K1+n8+tvqSntjOivPt19gvm1VC49RWYetsiub8WViUchdxl/KWHHB0kzA== + dependencies: + function-bind "^1.1.1" + has-bigints "^1.0.0" + has-symbols "^1.0.0" + which-boxed-primitive "^1.0.1" + unique-string@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" @@ -1672,15 +1700,15 @@ websocket-extensions@>=0.1.1: integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== which-boxed-primitive@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz#cbe8f838ebe91ba2471bb69e9edbda67ab5a5ec1" - integrity sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ== + version "1.0.2" + resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== dependencies: - is-bigint "^1.0.0" - is-boolean-object "^1.0.0" - is-number-object "^1.0.3" - is-string "^1.0.4" - is-symbol "^1.0.2" + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" which-collection@^1.0.1: version "1.0.1" @@ -1693,12 +1721,13 @@ which-collection@^1.0.1: is-weakset "^2.0.1" which-typed-array@^1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.2.tgz#e5f98e56bda93e3dac196b01d47c1156679c00b2" - integrity sha512-KT6okrd1tE6JdZAy3o2VhMoYPh3+J6EMZLyrxBQsZflI1QCZIxMrIYLkosd8Twf+YfknVIHmYQPgJt238p8dnQ== + version "1.1.4" + resolved "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.4.tgz#8fcb7d3ee5adf2d771066fba7cf37e32fe8711ff" + integrity sha512-49E0SpUe90cjpoc7BOJwyPHRqSAd12c10Qm2amdEZrJPCY2NDxaW01zHITrem+rnETY3dwrbH3UUrUwagfCYDA== dependencies: available-typed-arrays "^1.0.2" - es-abstract "^1.17.5" + call-bind "^1.0.0" + es-abstract "^1.18.0-next.1" foreach "^2.0.5" function-bind "^1.1.1" has-symbols "^1.0.1" diff --git a/packages-exp/auth-exp/demo/package.json b/packages-exp/auth-exp/demo/package.json index 7521dccb76c..2b7221915ad 100644 --- a/packages-exp/auth-exp/demo/package.json +++ b/packages-exp/auth-exp/demo/package.json @@ -16,24 +16,22 @@ "dev": "rollup -c -w" }, "dependencies": { - "@firebase/app-exp": "0.0.800", - "@firebase/app-types-exp": "0.0.800", - "@firebase/auth-exp": "0.0.800", - "@firebase/auth-types-exp": "0.0.800", + "@firebase/app-exp": "0.0.900", + "@firebase/auth-exp": "0.0.900", "@firebase/logger": "0.2.6", - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" + "@firebase/util": "1.1.0", + "tslib": "^2.1.0" }, "license": "Apache-2.0", "devDependencies": { - "@rollup/plugin-strip": "2.0.0", - "rollup": "2.29.0", + "@rollup/plugin-strip": "2.0.1", + "rollup": "2.52.2", "@rollup/plugin-json": "4.1.0", "rollup-plugin-replace": "2.2.0", "rollup-plugin-terser": "6.1.0", - "rollup-plugin-typescript2": "0.27.3", + "rollup-plugin-typescript2": "0.30.0", "rollup-plugin-uglify": "6.0.4", - "@rollup/plugin-node-resolve": "9.0.0", + "@rollup/plugin-node-resolve": "11.2.0", "lerna": "3.22.1" }, "repository": { diff --git a/packages-exp/auth-exp/demo/src/worker/service-worker.ts b/packages-exp/auth-exp/demo/src/worker/service-worker.ts index bff551fee60..d6316465619 100644 --- a/packages-exp/auth-exp/demo/src/worker/service-worker.ts +++ b/packages-exp/auth-exp/demo/src/worker/service-worker.ts @@ -21,8 +21,7 @@ * mode. */ import { initializeApp } from '@firebase/app-exp'; -import { getAuth } from '@firebase/auth-exp'; -import { User } from '@firebase/auth-types-exp'; +import { getAuth, User } from '@firebase/auth-exp'; import { config } from '../config'; @@ -92,66 +91,70 @@ self.addEventListener('install', (event: ExtendableEvent) => { }); // As this is a test app, let's only return cached data when offline. -self.addEventListener('fetch', async (event: FetchEvent) => { - // Try to fetch the resource first after checking for the ID token. - const idToken = await getIdToken(); - let req = event.request; - // For same origin https requests, append idToken to header. - if ( - self.location.origin === getOriginFromUrl(event.request.url) && - (self.location.protocol === 'https:' || - self.location.hostname === 'localhost') && - idToken - ) { - // Clone headers as request headers are immutable. - const headers = new Headers(); - req.headers.forEach((value, key) => { - headers.append(key, value); - }); - // Add ID token to header. We can't add to Authentication header as it - // will break HTTP basic authentication. - headers.append('x-id-token', idToken); - try { - req = new Request(req.url, { - method: req.method, - headers, - mode: 'same-origin', - credentials: req.credentials, - cache: req.cache, - redirect: req.redirect, - referrer: req.referrer, - body: req.body - }); - } catch (e) { - // This will fail for CORS requests. We just continue with the - // fetch caching logic below and do not pass the ID token. - } - } - let response: Response; - try { - response = await fetch(req); - } catch (e) { - // For fetch errors, attempt to retrieve the resource from cache. - try { - response = (await caches.match(event.request.clone()))!; - } catch (error) { - // If error getting resource from cache, do nothing. - console.log(error); - return; - } - } - // Check if we received a valid response. - // If not, just funnel the error response. - if (!response || response.status !== 200 || response.type !== 'basic') { - return response; - } - // If response is valid, clone it and save it to the cache. - const responseToCache = response.clone(); - // Save response to cache. - const cache = await caches.open(CACHE_NAME); - await cache.put(event.request, responseToCache); - // After caching, return response. - return response; +self.addEventListener('fetch', (event: FetchEvent) => { + event.respondWith( + (async function () { + const idToken = await getIdToken(); + // Try to fetch the resource first after checking for the ID token. + let req = event.request; + // For same origin https requests, append idToken to header. + if ( + self.location.origin === getOriginFromUrl(event.request.url) && + (self.location.protocol === 'https:' || + self.location.hostname === 'localhost') && + idToken + ) { + // Clone headers as request headers are immutable. + const headers = new Headers(); + req.headers.forEach((value, key) => { + headers.append(key, value); + }); + // Add ID token to header. We can't add to Authentication header as it + // will break HTTP basic authentication. + headers.append('x-id-token', idToken); + try { + req = new Request(req.url, { + method: req.method, + headers, + mode: 'same-origin', + credentials: req.credentials, + cache: req.cache, + redirect: req.redirect, + referrer: req.referrer, + body: req.body + }); + } catch (e) { + // This will fail for CORS requests. We just continue with the + // fetch caching logic below and do not pass the ID token. + } + } + return fetch(req) + .then(response => { + // Check if we received a valid response. + // If not, just funnel the error response. + if (response.status !== 200 || response.type !== 'basic') { + return response; + } + // If response is valid, clone it and save it to the cache. + var responseToCache = response.clone(); + // Save response to cache. + caches.open(CACHE_NAME).then(function (cache) { + cache.put(event.request, responseToCache); + }); + // After caching, return response. + return response; + }) + .catch(() => { + // For fetch errors, attempt to retrieve the resource from cache. + return caches.match(event.request.clone()) as Promise; + }) + .catch(error => { + // If error getting resource from cache, do nothing. + console.log(error); + throw error; + }); + })() + ); }); self.addEventListener('activate', (event: ExtendableEvent) => { diff --git a/packages-exp/auth-exp/demo/src/worker/web-worker.ts b/packages-exp/auth-exp/demo/src/worker/web-worker.ts index af6186b942b..39a089e7d56 100644 --- a/packages-exp/auth-exp/demo/src/worker/web-worker.ts +++ b/packages-exp/auth-exp/demo/src/worker/web-worker.ts @@ -23,9 +23,9 @@ import { signInAnonymously, signInWithCredential, signInWithEmailAndPassword, - updateProfile + updateProfile, + User } from '@firebase/auth-exp'; -import { OAuthCredential, User } from '@firebase/auth-types-exp'; import { config } from '../config'; diff --git a/packages-exp/auth-exp/index.cordova.ts b/packages-exp/auth-exp/index.cordova.ts new file mode 100644 index 00000000000..e4306beb226 --- /dev/null +++ b/packages-exp/auth-exp/index.cordova.ts @@ -0,0 +1,64 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This is the file that people using Cordova will actually import. You + * should only include this file if you have something specific about your + * implementation that mandates having a separate entrypoint. Otherwise you can + * just use index.ts + */ + +import { FirebaseApp, getApp, _getProvider } from '@firebase/app-exp'; +import { Auth } from './src/model/public_types'; +import { indexedDBLocalPersistence } from './src/platform_browser/persistence/indexed_db'; + +import { initializeAuth } from './src'; +import { registerAuth } from './src/core/auth/register'; +import { ClientPlatform } from './src/core/util/version'; + +// Core functionality shared by all clients +export * from './src'; + +// Cordova also supports indexedDB / browserSession / browserLocal +export { indexedDBLocalPersistence } from './src/platform_browser/persistence/indexed_db'; +export { browserLocalPersistence } from './src/platform_browser/persistence/local_storage'; +export { browserSessionPersistence } from './src/platform_browser/persistence/session_storage'; +export { getRedirectResult } from './src/platform_browser/strategies/redirect'; + +export { cordovaPopupRedirectResolver } from './src/platform_cordova/popup_redirect/popup_redirect'; +export { + signInWithRedirect, + reauthenticateWithRedirect, + linkWithRedirect +} from './src/platform_cordova/strategies/redirect'; + +import { cordovaPopupRedirectResolver } from './src/platform_cordova/popup_redirect/popup_redirect'; + +export function getAuth(app: FirebaseApp = getApp()): Auth { + const provider = _getProvider(app, 'auth-exp'); + + if (provider.isInitialized()) { + return provider.getImmediate(); + } + + return initializeAuth(app, { + persistence: indexedDBLocalPersistence, + popupRedirectResolver: cordovaPopupRedirectResolver + }); +} + +registerAuth(ClientPlatform.CORDOVA); diff --git a/packages-exp/auth-exp/index.doc.ts b/packages-exp/auth-exp/index.doc.ts new file mode 100644 index 00000000000..a89d97aa7db --- /dev/null +++ b/packages-exp/auth-exp/index.doc.ts @@ -0,0 +1,25 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Internal index that pulls in all the different platforms, for documentation +// generation purposes only. + +// Core (Browser) index +export * from './index'; + +export { cordovaPopupRedirectResolver } from './index.cordova'; +export { reactNativeLocalPersistence } from './index.rn'; diff --git a/packages-exp/auth-exp/index.node.ts b/packages-exp/auth-exp/index.node.ts index 110233ef91e..33056956354 100644 --- a/packages-exp/auth-exp/index.node.ts +++ b/packages-exp/auth-exp/index.node.ts @@ -24,8 +24,8 @@ import * as fetchImpl from 'node-fetch'; -import { FirebaseApp } from '@firebase/app-types-exp'; -import { Auth } from '@firebase/auth-types-exp'; +import { FirebaseApp, getApp, _getProvider } from '@firebase/app-exp'; +import { Auth } from './src/model/public_types'; import { initializeAuth } from './src'; import { registerAuth } from './src/core/auth/register'; @@ -34,15 +34,28 @@ import { ClientPlatform } from './src/core/util/version'; // Initialize the fetch polyfill, the types are slightly off so just cast and hope for the best FetchProvider.initialize( - (fetchImpl.default as unknown) as typeof fetch, - (fetchImpl.Headers as unknown) as typeof Headers, - (fetchImpl.Response as unknown) as typeof Response + fetchImpl.default as unknown as typeof fetch, + fetchImpl.Headers as unknown as typeof Headers, + fetchImpl.Response as unknown as typeof Response ); // Core functionality shared by all clients export * from './src'; +export { + FactorId, + ProviderId, + SignInMethod, + OperationType, + ActionCodeOperation +} from './src/model/enum_maps'; + +export function getAuth(app: FirebaseApp = getApp()): Auth { + const provider = _getProvider(app, 'auth-exp'); + + if (provider.isInitialized()) { + return provider.getImmediate(); + } -export function getAuth(app?: FirebaseApp): Auth { return initializeAuth(app); } diff --git a/packages-exp/auth-exp/index.rn.ts b/packages-exp/auth-exp/index.rn.ts index 06d444b2be4..64c80112616 100644 --- a/packages-exp/auth-exp/index.rn.ts +++ b/packages-exp/auth-exp/index.rn.ts @@ -24,8 +24,8 @@ import { AsyncStorage } from 'react-native'; -import { FirebaseApp } from '@firebase/app-types-exp'; -import { Auth } from '@firebase/auth-types-exp'; +import { FirebaseApp, getApp, _getProvider } from '@firebase/app-exp'; +import { Auth } from './src/model/public_types'; import { initializeAuth } from './src'; import { registerAuth } from './src/core/auth/register'; @@ -35,11 +35,22 @@ import { getReactNativePersistence } from './src/platform_react_native/persisten // Core functionality shared by all clients export * from './src'; -export const reactNativeLocalPersistence = getReactNativePersistence( - AsyncStorage -); +/** + * An implementation of {@link Persistence} of type 'LOCAL' for use in React + * Native environments. + * + * @public + */ +export const reactNativeLocalPersistence = + getReactNativePersistence(AsyncStorage); + +export function getAuth(app: FirebaseApp = getApp()): Auth { + const provider = _getProvider(app, 'auth-exp'); + + if (provider.isInitialized()) { + return provider.getImmediate(); + } -export function getAuth(app?: FirebaseApp): Auth { return initializeAuth(app, { persistence: reactNativeLocalPersistence }); diff --git a/packages-exp/auth-exp/index.ts b/packages-exp/auth-exp/index.ts index 884a24ee132..b9d74996b7f 100644 --- a/packages-exp/auth-exp/index.ts +++ b/packages-exp/auth-exp/index.ts @@ -1,3 +1,9 @@ +/** + * Firebase Authentication + * + * @packageDocumentation + */ + /** * @license * Copyright 2017 Google LLC @@ -15,8 +21,7 @@ * limitations under the License. */ -import { FirebaseApp } from '@firebase/app-types-exp'; -import { Auth } from '@firebase/auth-types-exp'; +import { FirebaseApp, getApp, _getProvider } from '@firebase/app-exp'; import { initializeAuth } from './src'; import { registerAuth } from './src/core/auth/register'; @@ -24,6 +29,58 @@ import { ClientPlatform } from './src/core/util/version'; import { browserLocalPersistence } from './src/platform_browser/persistence/local_storage'; import { indexedDBLocalPersistence } from './src/platform_browser/persistence/indexed_db'; import { browserPopupRedirectResolver } from './src/platform_browser/popup_redirect'; +import { Auth } from './src/model/public_types'; + +// Public types +export { + // Interfaces + ActionCodeInfo, + ActionCodeSettings, + AdditionalUserInfo, + ApplicationVerifier, + Auth, + AuthError, + AuthErrorMap, + AuthProvider, + AuthSettings, + Config, + ConfirmationResult, + IdTokenResult, + MultiFactorAssertion, + MultiFactorError, + MultiFactorInfo, + MultiFactorResolver, + MultiFactorSession, + MultiFactorUser, + ParsedToken, + Persistence, + PhoneMultiFactorAssertion, + PhoneMultiFactorEnrollInfoOptions, + PhoneMultiFactorSignInInfoOptions, + PhoneSingleFactorInfoOptions, + PopupRedirectResolver, + ReactNativeAsyncStorage, + User, + UserCredential, + UserInfo, + UserMetadata, + UserProfile, + PhoneInfoOptions, + Dependencies, + NextOrObserver, + ErrorFn, + CompleteFn, + Unsubscribe +} from './src/model/public_types'; + +// Helper maps (not used internally) +export { + FactorId, + ProviderId, + SignInMethod, + OperationType, + ActionCodeOperation +} from './src/model/enum_maps'; // Core functionality shared by all clients export * from './src'; @@ -63,7 +120,20 @@ export { browserPopupRedirectResolver } from './src/platform_browser/popup_redir // MFA export { PhoneMultiFactorGenerator } from './src/platform_browser/mfa/assertions/phone'; -export function getAuth(app?: FirebaseApp): Auth { +/** + * Initializes an Auth instance with platform specific default dependencies. + * + * @param app - The Firebase App. + * + * @public + */ +export function getAuth(app: FirebaseApp = getApp()): Auth { + const provider = _getProvider(app, 'auth-exp'); + + if (provider.isInitialized()) { + return provider.getImmediate(); + } + return initializeAuth(app, { popupRedirectResolver: browserPopupRedirectResolver, persistence: [indexedDBLocalPersistence, browserLocalPersistence] diff --git a/packages-exp/auth-exp/index.webworker.ts b/packages-exp/auth-exp/index.webworker.ts index 4c68fada1dd..2c746bbd7bc 100644 --- a/packages-exp/auth-exp/index.webworker.ts +++ b/packages-exp/auth-exp/index.webworker.ts @@ -15,13 +15,13 @@ * limitations under the License. */ -import { _getProvider, getApp } from '@firebase/app-exp'; -import { Auth } from '@firebase/auth-types-exp'; +import { _getProvider, FirebaseApp, getApp } from '@firebase/app-exp'; +import { Auth } from './src/model/public_types'; import { AuthImpl } from './src/core/auth/auth_impl'; import { _initializeAuthInstance } from './src/core/auth/initialize'; -import { _AUTH_COMPONENT_NAME, registerAuth } from './src/core/auth/register'; -import { Persistence } from './src/core/persistence'; +import { _ComponentName, registerAuth } from './src/core/auth/register'; +import { PersistenceInternal } from './src/core/persistence'; import { _getInstance } from './src/core/util/instantiator'; import { ClientPlatform } from './src/core/util/version'; import { indexedDBLocalPersistence } from './src/platform_browser/persistence/indexed_db'; @@ -34,18 +34,18 @@ export { indexedDBLocalPersistence } from './src/platform_browser/persistence/in registerAuth(ClientPlatform.WORKER); -export function getAuth(app = getApp()): Auth { +export function getAuth(app: FirebaseApp = getApp()): Auth { // Unlike the other environments, we need to explicitly check if indexedDb is // available. That means doing the whole rigamarole const auth = _getProvider( app, - _AUTH_COMPONENT_NAME + _ComponentName.AUTH ).getImmediate() as AuthImpl; // This promise is intended to float; auth initialization happens in the // background, meanwhile the auth object may be used by the app. // eslint-disable-next-line @typescript-eslint/no-floating-promises - _getInstance(indexedDBLocalPersistence) + _getInstance(indexedDBLocalPersistence) ._isAvailable() .then(avail => { const deps = avail ? { persistence: indexedDBLocalPersistence } : {}; diff --git a/packages-exp/auth-exp/internal/index.ts b/packages-exp/auth-exp/internal/index.ts index 4c794bf3c8c..d8da272c895 100644 --- a/packages-exp/auth-exp/internal/index.ts +++ b/packages-exp/auth-exp/internal/index.ts @@ -15,30 +15,43 @@ * limitations under the License. */ +import { _castAuth } from '../src/core/auth/auth_impl'; +import { Auth } from '../src/model/public_types'; + /** * This interface is intended only for use by @firebase/auth-compat-exp, do not use directly */ export * from '../index'; -import { assert } from '../src/core/util/assert'; - export { SignInWithIdpResponse } from '../src/api/authentication/idp'; export { AuthErrorCode } from '../src/core/errors'; -export { Persistence } from '../src/core/persistence'; +export { PersistenceInternal } from '../src/core/persistence'; +export { _persistenceKeyName } from '../src/core/persistence/persistence_user_manager'; export { UserImpl } from '../src/core/user/user_impl'; export { _getInstance } from '../src/core/util/instantiator'; -export { UserCredential, UserParameters } from '../src/model/user'; -export { registerAuth } from '../src/core/auth/register'; export { - DEFAULT_API_HOST, - DEFAULT_API_SCHEME, - DEFAULT_TOKEN_API_HOST, - AuthImpl -} from '../src/core/auth/auth_impl'; + PopupRedirectResolverInternal, + EventManager, + AuthEventType +} from '../src/model/popup_redirect'; +export { UserCredentialInternal, UserParameters } from '../src/model/user'; +export { AuthInternal, ConfigInternal } from '../src/model/auth'; +export { DefaultConfig, AuthImpl, _castAuth } from '../src/core/auth/auth_impl'; export { ClientPlatform, _getClientVersion } from '../src/core/util/version'; export { _generateEventId } from '../src/core/util/event_id'; +export { TaggedWithTokenResponse } from '../src/model/id_token'; +export { _fail, _assert } from '../src/core/util/assert'; +export { AuthPopup } from '../src/platform_browser/util/popup'; +export { _getRedirectResult } from '../src/platform_browser/strategies/redirect'; +export { cordovaPopupRedirectResolver } from '../src/platform_cordova/popup_redirect/popup_redirect'; +export { FetchProvider } from '../src/core/util/fetch_provider'; +export { SAMLAuthCredential } from '../src/core/credentials/saml'; -export { fail } from '../src/core/util/assert'; -export const assertFn: typeof assert = assert; +// This function should only be called by frameworks (e.g. FirebaseUI-web) to log their usage. +// It is not intended for direct use by developer apps. NO jsdoc here to intentionally leave it out +// of autogenerated documentation pages to reduce accidental misuse. +export function addFrameworkForLogging(auth: Auth, framework: string): void { + _castAuth(auth)._logFramework(framework); +} diff --git a/packages-exp/auth-exp/internal/package.json b/packages-exp/auth-exp/internal/package.json index eaaf217e240..4c6482ee076 100644 --- a/packages-exp/auth-exp/internal/package.json +++ b/packages-exp/auth-exp/internal/package.json @@ -2,9 +2,9 @@ "name": "@firebase/auth-exp/internal", "description": "An internal version of the Auth SDK for use in the compatibility layer", "main": "../dist/node/internal.js", - "module": "../dist/esm5/internal.js", - "browser": "../dist/esm5/internal.js", - "esm2017": "../dist/esm2017/internal.js", + "module": "../dist/esm2017/internal.js", + "browser": "../dist/esm2017/internal.js", + "esm5": "../dist/esm5/internal.js", "typings": "../dist/esm5/internal/index.d.ts", "private": true } diff --git a/packages-exp/auth-exp/karma.conf.js b/packages-exp/auth-exp/karma.conf.js index ac7f41abfe4..ecd43cf50b9 100644 --- a/packages-exp/auth-exp/karma.conf.js +++ b/packages-exp/auth-exp/karma.conf.js @@ -24,7 +24,9 @@ module.exports = function (config) { files: getTestFiles(argv), // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks: ['mocha'] + frameworks: ['mocha'], + + client: Object.assign({}, karmaBase.client, getClientConfig(argv)) }); config.set(karmaConfig); @@ -34,7 +36,11 @@ function getTestFiles(argv) { if (argv.unit) { return ['src/**/*.test.ts', 'test/helpers/**/*.test.ts']; } else if (argv.integration) { - return ['test/integration/**/*.test.ts']; + return argv.local + ? ['test/integration/flows/*.test.ts'] + : ['test/integration/flows/*!(local).test.ts']; + } else if (argv.cordova) { + return ['src/platform_cordova/**/*.test.ts']; } else { // For the catch-all yarn:test, ignore the phone integration test return [ @@ -46,4 +52,28 @@ function getTestFiles(argv) { } } +function getClientConfig(argv) { + if (!argv.local) { + return {}; + } + + if (!process.env.GCLOUD_PROJECT || !process.env.FIREBASE_AUTH_EMULATOR_HOST) { + console.error( + 'Local testing against emulator requested, but ' + + 'GCLOUD_PROJECT and FIREBASE_AUTH_EMULATOR_HOST env variables ' + + 'are missing' + ); + process.exit(1); + } + + return { + authAppConfig: { + apiKey: 'local-api-key', + projectId: process.env.GCLOUD_PROJECT, + authDomain: 'local-auth-domain' + }, + authEmulatorHost: process.env.FIREBASE_AUTH_EMULATOR_HOST + }; +} + module.exports.files = getTestFiles(argv); diff --git a/packages-exp/auth-exp/package.json b/packages-exp/auth-exp/package.json index a0fd713d1ab..77dd0d5a8da 100644 --- a/packages-exp/auth-exp/package.json +++ b/packages-exp/auth-exp/package.json @@ -1,61 +1,72 @@ { "name": "@firebase/auth-exp", - "version": "0.0.800", + "version": "0.0.900", "private": true, "description": "The Firebase Authenticaton component of the Firebase JS SDK.", "author": "Firebase (https://firebase.google.com/)", "main": "dist/node/index.js", "react-native": "dist/rn/index.js", - "browser": "dist/esm5/index.js", - "module": "dist/esm5/index.js", - "esm2017": "dist/esm2017/index.js", + "browser": "dist/esm2017/index.js", + "cordova": "dist/cordova/index.esm5.js", + "module": "dist/esm2017/index.js", "webworker": "dist/index.webworker.esm5.js", "files": [ - "dist" + "dist", + "cordova/package.json", + "internal/package.json", + "react-native/package.json" ], "scripts": { "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", "build": "rollup -c && yarn api-report", "build:deps": "lerna run --scope @firebase/auth-exp --include-dependencies build", - "build:release": "rollup -c rollup.config.release.js", + "build:release": "rollup -c rollup.config.release.js && yarn api-report && yarn typings:public", + "build:scripts": "tsc -moduleResolution node --module commonjs scripts/*.ts && ls scripts/*.js | xargs -I % sh -c 'terser % -o %'", "dev": "rollup -c -w", "test": "run-p lint test:all", "test:all": "run-p test:browser test:node", "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:all", + "test:integration:local": "run-s test:node:integration:local test:browser:integration:local test:webdriver", "test:browser": "karma start --single-run", "test:browser:unit": "karma start --single-run --unit", "test:browser:integration": "karma start --single-run --integration", + "test:browser:integration:local": "karma start --single-run --integration --local --auto-watch", "test:browser:debug": "karma start --auto-watch", "test:browser:unit:debug": "karma start --auto-watch --unit", - "test:node": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha 'src/!(platform_browser|platform_react_native)/**/*.test.ts' --file index.node.ts --config ../../config/mocharc.node.js", - "prepare": "rollup -c rollup.config.release.js", + "test:cordova": "karma start --single-run --cordova", + "test:cordova:debug": "karma start --auto-watch --cordova", + "test:node": "run-s test:node:unit test:node:integration", + "test:node:unit": "node ./scripts/run-node-tests.js", + "test:node:integration": "node ./scripts/run-node-tests.js --integration", + "test:node:integration:local": "node ./scripts/run-node-tests.js --integration --local", + "test:webdriver": "rollup -c test/integration/webdriver/static/rollup.config.js && node ./scripts/run-node-tests.js --webdriver", "api-report": "api-extractor run --local --verbose", "predoc": "node ../../scripts/exp/remove-exp.js temp", - "doc": "api-documenter markdown --input temp --input ../auth-types-exp/temp --output docs", - "build:doc": "yarn build && yarn doc" + "doc": "api-documenter markdown --input temp --output docs", + "build:doc": "yarn build && yarn doc", + "typings:public": "node ../../scripts/exp/use_typings.js ./dist/auth-exp-public.d.ts" }, "peerDependencies": { - "@firebase/app-exp": "0.x", - "@firebase/app-types-exp": "0.x" + "@firebase/app-exp": "0.x" }, "dependencies": { + "@firebase/component": "0.5.4", "@firebase/logger": "0.2.6", - "@firebase/util": "0.3.2", - "@firebase/component": "0.1.19", - "@firebase/auth-types-exp": "0.0.800", + "@firebase/util": "1.1.0", "node-fetch": "2.6.1", - "tslib": "^1.11.1" + "selenium-webdriver": "4.0.0-beta.1", + "tslib": "^2.1.0" }, "license": "Apache-2.0", "devDependencies": { - "@firebase/app-exp": "0.0.800", - "rollup": "2.29.0", + "@firebase/app-exp": "0.0.900", "@rollup/plugin-json": "4.1.0", + "rollup": "2.52.2", "rollup-plugin-sourcemaps": "0.6.3", - "rollup-plugin-typescript2": "0.27.3", - "@rollup/plugin-strip": "2.0.0", - "typescript": "4.0.2" + "rollup-plugin-typescript2": "0.30.0", + "@rollup/plugin-strip": "2.0.1", + "typescript": "4.2.2" }, "repository": { "directory": "packages-exp/auth-exp", @@ -65,11 +76,12 @@ "bugs": { "url": "https://github.com/firebase/firebase-js-sdk/issues" }, - "typings": "dist/esm5/index.d.ts", + "typings": "dist/auth-exp.d.ts", "nyc": { "extension": [ ".ts" ], "reportDir": "./coverage/node" - } -} + }, + "esm5": "dist/esm5/index.js" +} \ No newline at end of file diff --git a/packages-exp/auth-exp/react-native/package.json b/packages-exp/auth-exp/react-native/package.json new file mode 100644 index 00000000000..f820f9bfc32 --- /dev/null +++ b/packages-exp/auth-exp/react-native/package.json @@ -0,0 +1,6 @@ +{ + "name": "@firebase/auth-exp/react-native", + "description": "A React Native-specific build of the Firebase Auth JS SDK", + "browser": "../dist/rn/index.js", + "typings": "../dist/rn/index.rn.d.ts" +} \ No newline at end of file diff --git a/packages-exp/auth-exp/rollup.config.shared.js b/packages-exp/auth-exp/rollup.config.shared.js index eaeb293dad7..eb127bb0e0f 100644 --- a/packages-exp/auth-exp/rollup.config.shared.js +++ b/packages-exp/auth-exp/rollup.config.shared.js @@ -22,9 +22,10 @@ import typescript from 'typescript'; import pkg from './package.json'; import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; -const deps = Object.keys( - Object.assign({}, pkg.peerDependencies, pkg.dependencies) -); +const deps = [ + ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), + '@firebase/app' +]; /** * Common plugins for all builds @@ -92,6 +93,19 @@ export function getConfig({ isReleaseBuild }) { plugins: es5BuildPlugins, external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) }, + /** + * Cordova Builds + */ + { + input: { + index: 'index.cordova.ts', + internal: 'internal/index.ts' + }, + output: [{ dir: 'dist/cordova', format: 'es', sourcemap: true }], + plugins: es5BuildPlugins, + external: id => + [...deps, 'cordova'].some(dep => id === dep || id.startsWith(`${dep}/`)) + }, /** * React Native Builds */ diff --git a/packages-exp/auth-exp/scripts/run-node-tests.js b/packages-exp/auth-exp/scripts/run-node-tests.js new file mode 100644 index 00000000000..198e99f89b6 --- /dev/null +++ b/packages-exp/auth-exp/scripts/run-node-tests.js @@ -0,0 +1,84 @@ +'use strict'; +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ var __spreadArrays = + (this && this.__spreadArrays) || + function () { + for (var s = 0, i = 0, il = arguments.length; i < il; i++) + s += arguments[i].length; + for (var r = Array(s), k = 0, i = 0; i < il; i++) + for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) + r[k] = a[j]; + return r; + }; +exports.__esModule = true; +var path_1 = require('path'); +var child_process_promise_1 = require('child-process-promise'); +var yargs = require('yargs'); +var argv = yargs.options({ + local: { type: 'boolean' }, + integration: { type: 'boolean' }, + webdriver: { type: 'boolean' } +}).argv; +var nyc = path_1.resolve(__dirname, '../../../node_modules/.bin/nyc'); +var mocha = path_1.resolve(__dirname, '../../../node_modules/.bin/mocha'); +process.env.TS_NODE_COMPILER_OPTIONS = '{"module":"commonjs", "target": "es6"}'; +var testConfig = [ + 'src/!(platform_browser|platform_react_native|platform_cordova)/**/*.test.ts', + '--file', + 'index.node.ts' +]; +if (argv.integration) { + testConfig = ['test/integration/flows/{email,anonymous}.test.ts']; + if (argv.local) { + testConfig.push('test/integration/flows/*.local.test.ts'); + } +} else if (argv.webdriver) { + testConfig = ['test/integration/webdriver/**.test.ts', '--delay']; +} +var args = __spreadArrays(['--reporter', 'lcovonly', mocha], testConfig, [ + '--config', + '../../config/mocharc.node.js' +]); +if (argv.local) { + if (!process.env.GCLOUD_PROJECT || !process.env.FIREBASE_AUTH_EMULATOR_HOST) { + console.error( + 'Local testing against emulator requested, but ' + + 'GCLOUD_PROJECT and FIREBASE_AUTH_EMULATOR_HOST env variables ' + + 'are missing' + ); + process.exit(1); + } +} +args = args.concat(argv._); +var spawned = child_process_promise_1.spawn(nyc, args, { + stdio: 'inherit', + cwd: process.cwd() +}); +var childProcess = spawned.childProcess; +spawned['catch'](function () { + childProcess.kill(); + process.exit(1); +}); +process.once('exit', function () { + return childProcess.kill(); +}); +process.once('SIGINT', function () { + return childProcess.kill('SIGINT'); +}); +process.once('SIGTERM', function () { + return childProcess.kill('SIGTERM'); +}); diff --git a/packages-exp/auth-exp/scripts/run-node-tests.ts b/packages-exp/auth-exp/scripts/run-node-tests.ts new file mode 100644 index 00000000000..0738a4cd299 --- /dev/null +++ b/packages-exp/auth-exp/scripts/run-node-tests.ts @@ -0,0 +1,91 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { resolve } from 'path'; + +import { spawn } from 'child-process-promise'; +import * as yargs from 'yargs'; + +const argv = yargs.options({ + local: { + type: 'boolean' + }, + integration: { + type: 'boolean' + }, + webdriver: { + type: 'boolean' + } +}).argv; + +const nyc = resolve(__dirname, '../../../node_modules/.bin/nyc'); +const mocha = resolve(__dirname, '../../../node_modules/.bin/mocha'); + +process.env.TS_NODE_COMPILER_OPTIONS = '{"module":"commonjs", "target": "es6"}'; + +let testConfig = [ + 'src/!(platform_browser|platform_react_native|platform_cordova)/**/*.test.ts', + '--file', + 'index.node.ts' +]; + +if (argv.integration) { + testConfig = ['test/integration/flows/{email,anonymous}.test.ts']; + if (argv.local) { + testConfig.push('test/integration/flows/*.local.test.ts'); + } +} else if (argv.webdriver) { + testConfig = ['test/integration/webdriver/*.test.ts', '--delay']; +} + +let args = [ + '--reporter', + 'lcovonly', + mocha, + ...testConfig, + '--config', + '../../config/mocharc.node.js' +]; + +// Make sure that the environment variables are present for local test +if (argv.local) { + if (!process.env.GCLOUD_PROJECT || !process.env.FIREBASE_AUTH_EMULATOR_HOST) { + console.error( + 'Local testing against emulator requested, but ' + + 'GCLOUD_PROJECT and FIREBASE_AUTH_EMULATOR_HOST env variables ' + + 'are missing' + ); + process.exit(1); + } +} + +args = args.concat(argv._ as string[]); + +const spawned = spawn(nyc, args, { + stdio: 'inherit', + cwd: process.cwd() +}); + +const childProcess = spawned.childProcess; +spawned.catch(() => { + childProcess.kill(); + process.exit(1); +}); + +process.once('exit', () => childProcess.kill()); +process.once('SIGINT', () => childProcess.kill('SIGINT')); +process.once('SIGTERM', () => childProcess.kill('SIGTERM')); diff --git a/packages-exp/auth-exp/src/api/account_management/account.test.ts b/packages-exp/auth-exp/src/api/account_management/account.test.ts index 7f1d4ba65a6..ff1cf27c4e3 100644 --- a/packages-exp/auth-exp/src/api/account_management/account.test.ts +++ b/packages-exp/auth-exp/src/api/account_management/account.test.ts @@ -18,7 +18,7 @@ import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; -import { ProviderId } from '@firebase/auth-types-exp'; +import { ProviderId } from '../../model/public_types'; import { FirebaseError } from '@firebase/util'; import { Endpoint, HttpHeader } from '../'; diff --git a/packages-exp/auth-exp/src/api/account_management/account.ts b/packages-exp/auth-exp/src/api/account_management/account.ts index 004423c4bc0..100e50ec010 100644 --- a/packages-exp/auth-exp/src/api/account_management/account.ts +++ b/packages-exp/auth-exp/src/api/account_management/account.ts @@ -15,9 +15,9 @@ * limitations under the License. */ -import { Endpoint, HttpMethod, _performApiRequest } from '../'; +import { Endpoint, HttpMethod, _performApiRequest } from '../index'; import { MfaEnrollment } from './mfa'; -import { Auth } from '@firebase/auth-types-exp'; +import { Auth } from '../../model/public_types'; export interface DeleteAccountRequest { idToken: string; diff --git a/packages-exp/auth-exp/src/api/account_management/email_and_password.test.ts b/packages-exp/auth-exp/src/api/account_management/email_and_password.test.ts index 9a0c4065b92..a77fdc5905a 100644 --- a/packages-exp/auth-exp/src/api/account_management/email_and_password.test.ts +++ b/packages-exp/auth-exp/src/api/account_management/email_and_password.test.ts @@ -53,9 +53,10 @@ describe('api/account_management/resetPassword', () => { email: 'test@foo.com' }); + auth.tenantId = 'tenant-id'; const response = await resetPassword(auth, request); expect(response.email).to.eq('test@foo.com'); - expect(mock.calls[0].request).to.eql(request); + expect(mock.calls[0].request).to.eql({ ...request, tenantId: 'tenant-id' }); expect(mock.calls[0].method).to.eq('POST'); expect(mock.calls[0].headers!.get(HttpHeader.CONTENT_TYPE)).to.eq( 'application/json' @@ -166,9 +167,10 @@ describe('api/account_management/applyActionCode', () => { it('should POST to the correct endpoint', async () => { const mock = mockEndpoint(Endpoint.SET_ACCOUNT_INFO, {}); + auth.tenantId = 'tenant-id'; const response = await applyActionCode(auth, request); expect(response).to.be.empty; - expect(mock.calls[0].request).to.eql(request); + expect(mock.calls[0].request).to.eql({ ...request, tenantId: 'tenant-id' }); expect(mock.calls[0].method).to.eq('POST'); expect(mock.calls[0].headers!.get(HttpHeader.CONTENT_TYPE)).to.eq( 'application/json' diff --git a/packages-exp/auth-exp/src/api/account_management/email_and_password.ts b/packages-exp/auth-exp/src/api/account_management/email_and_password.ts index 0c917f7afa4..7d6f4ab84c8 100644 --- a/packages-exp/auth-exp/src/api/account_management/email_and_password.ts +++ b/packages-exp/auth-exp/src/api/account_management/email_and_password.ts @@ -15,21 +15,27 @@ * limitations under the License. */ -import { Operation, Auth } from '@firebase/auth-types-exp'; +import { ActionCodeOperation, Auth } from '../../model/public_types'; -import { Endpoint, HttpMethod, _performApiRequest } from '..'; +import { + Endpoint, + HttpMethod, + _addTidIfNecessary, + _performApiRequest +} from '../index'; import { IdTokenResponse } from '../../model/id_token'; import { MfaEnrollment } from './mfa'; export interface ResetPasswordRequest { oobCode: string; newPassword?: string; + tenantId?: string; } export interface ResetPasswordResponse { email: string; newEmail?: string; - requestType?: Operation; + requestType?: ActionCodeOperation; mfaInfo?: MfaEnrollment; } @@ -41,7 +47,7 @@ export async function resetPassword( auth, HttpMethod.POST, Endpoint.RESET_PASSWORD, - request + _addTidIfNecessary(auth, request) ); } export interface UpdateEmailPasswordRequest { @@ -65,6 +71,7 @@ export async function updateEmailPassword( export interface ApplyActionCodeRequest { oobCode: string; + tenantId?: string; } export interface ApplyActionCodeResponse {} @@ -77,6 +84,6 @@ export async function applyActionCode( auth, HttpMethod.POST, Endpoint.SET_ACCOUNT_INFO, - request + _addTidIfNecessary(auth, request) ); } diff --git a/packages-exp/auth-exp/src/api/account_management/mfa.ts b/packages-exp/auth-exp/src/api/account_management/mfa.ts index 7ab83f2aefb..f2e8e2008cb 100644 --- a/packages-exp/auth-exp/src/api/account_management/mfa.ts +++ b/packages-exp/auth-exp/src/api/account_management/mfa.ts @@ -15,10 +15,10 @@ * limitations under the License. */ -import { Endpoint, HttpMethod, _performApiRequest } from '..'; +import { Endpoint, HttpMethod, _performApiRequest } from '../index'; import { SignInWithPhoneNumberRequest } from '../authentication/sms'; import { FinalizeMfaResponse } from '../authentication/mfa'; -import { Auth } from '../../model/auth'; +import { AuthInternal } from '../../model/auth'; /** * MFA Info as returned by the API @@ -57,7 +57,7 @@ export interface StartPhoneMfaEnrollmentResponse { } export function startEnrollPhoneMfa( - auth: Auth, + auth: AuthInternal, request: Omit ): Promise { return _performApiRequest< @@ -80,7 +80,7 @@ export interface FinalizePhoneMfaEnrollmentResponse extends FinalizeMfaResponse {} export function finalizeEnrollPhoneMfa( - auth: Auth, + auth: AuthInternal, request: Omit ): Promise { return _performApiRequest< @@ -101,7 +101,7 @@ export interface WithdrawMfaRequest { export interface WithdrawMfaResponse extends FinalizeMfaResponse {} export function withdrawMfa( - auth: Auth, + auth: AuthInternal, request: Omit ): Promise { return _performApiRequest( diff --git a/packages-exp/auth-exp/src/api/account_management/profile.test.ts b/packages-exp/auth-exp/src/api/account_management/profile.test.ts index dddeb07e9dd..085b5ac4d8a 100644 --- a/packages-exp/auth-exp/src/api/account_management/profile.test.ts +++ b/packages-exp/auth-exp/src/api/account_management/profile.test.ts @@ -33,7 +33,8 @@ describe('api/account_management/updateProfile', () => { const request = { idToken: 'my-token', email: 'test@foo.com', - password: 'my-password' + password: 'my-password', + returnSecureToken: true }; let auth: TestAuth; diff --git a/packages-exp/auth-exp/src/api/account_management/profile.ts b/packages-exp/auth-exp/src/api/account_management/profile.ts index 3441177a40c..d206d7ed1c6 100644 --- a/packages-exp/auth-exp/src/api/account_management/profile.ts +++ b/packages-exp/auth-exp/src/api/account_management/profile.ts @@ -15,14 +15,15 @@ * limitations under the License. */ -import { Endpoint, HttpMethod, _performApiRequest } from '..'; +import { Endpoint, HttpMethod, _performApiRequest } from '../index'; import { IdTokenResponse } from '../../model/id_token'; -import { Auth } from '@firebase/auth-types-exp'; +import { Auth } from '../../model/public_types'; export interface UpdateProfileRequest { idToken: string; displayName?: string | null; photoUrl?: string | null; + returnSecureToken: boolean; } export interface UpdateProfileResponse extends IdTokenResponse { diff --git a/packages-exp/auth-exp/src/api/authentication/create_auth_uri.test.ts b/packages-exp/auth-exp/src/api/authentication/create_auth_uri.test.ts index 4d39c371be7..b4cc9bc416f 100644 --- a/packages-exp/auth-exp/src/api/authentication/create_auth_uri.test.ts +++ b/packages-exp/auth-exp/src/api/authentication/create_auth_uri.test.ts @@ -49,9 +49,10 @@ describe('api/authentication/createAuthUri', () => { signinMethods: ['email'] }); + auth.tenantId = 'tenant-id'; const response = await createAuthUri(auth, request); expect(response.signinMethods).to.include('email'); - expect(mock.calls[0].request).to.eql(request); + expect(mock.calls[0].request).to.eql({ ...request, tenantId: 'tenant-id' }); expect(mock.calls[0].method).to.eq('POST'); expect(mock.calls[0].headers!.get(HttpHeader.CONTENT_TYPE)).to.eq( 'application/json' diff --git a/packages-exp/auth-exp/src/api/authentication/create_auth_uri.ts b/packages-exp/auth-exp/src/api/authentication/create_auth_uri.ts index 35ecb100896..a1830501cb2 100644 --- a/packages-exp/auth-exp/src/api/authentication/create_auth_uri.ts +++ b/packages-exp/auth-exp/src/api/authentication/create_auth_uri.ts @@ -15,12 +15,18 @@ * limitations under the License. */ -import { Endpoint, HttpMethod, _performApiRequest } from '..'; -import { Auth } from '@firebase/auth-types-exp'; +import { + Endpoint, + HttpMethod, + _addTidIfNecessary, + _performApiRequest +} from '../index'; +import { Auth } from '../../model/public_types'; export interface CreateAuthUriRequest { identifier: string; continueUri: string; + tenantId?: string; } export interface CreateAuthUriResponse { @@ -35,6 +41,6 @@ export async function createAuthUri( auth, HttpMethod.POST, Endpoint.CREATE_AUTH_URI, - request + _addTidIfNecessary(auth, request) ); } diff --git a/packages-exp/auth-exp/src/api/authentication/custom_token.test.ts b/packages-exp/auth-exp/src/api/authentication/custom_token.test.ts index b1cd9d0b7fc..8e003376ab6 100644 --- a/packages-exp/auth-exp/src/api/authentication/custom_token.test.ts +++ b/packages-exp/auth-exp/src/api/authentication/custom_token.test.ts @@ -18,7 +18,7 @@ import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; -import { ProviderId } from '@firebase/auth-types-exp'; +import { ProviderId } from '../../model/public_types'; import { FirebaseError } from '@firebase/util'; import { Endpoint, HttpHeader } from '../'; @@ -32,7 +32,8 @@ use(chaiAsPromised); describe('api/authentication/signInWithCustomToken', () => { const request = { - token: 'my-token' + token: 'my-token', + returnSecureToken: true }; let auth: TestAuth; @@ -52,12 +53,13 @@ describe('api/authentication/signInWithCustomToken', () => { localId: '1234' }); + auth.tenantId = 'tenant-id'; const response = await signInWithCustomToken(auth, request); expect(response.providerId).to.eq(ProviderId.CUSTOM); expect(response.idToken).to.eq('id-token'); expect(response.expiresIn).to.eq('1000'); expect(response.localId).to.eq('1234'); - expect(mock.calls[0].request).to.eql(request); + expect(mock.calls[0].request).to.eql({ ...request, tenantId: 'tenant-id' }); expect(mock.calls[0].method).to.eq('POST'); expect(mock.calls[0].headers!.get(HttpHeader.CONTENT_TYPE)).to.eq( 'application/json' diff --git a/packages-exp/auth-exp/src/api/authentication/custom_token.ts b/packages-exp/auth-exp/src/api/authentication/custom_token.ts index d58a4e4ab8b..6be72172f24 100644 --- a/packages-exp/auth-exp/src/api/authentication/custom_token.ts +++ b/packages-exp/auth-exp/src/api/authentication/custom_token.ts @@ -15,12 +15,19 @@ * limitations under the License. */ -import { Endpoint, HttpMethod, _performSignInRequest } from '..'; +import { + Endpoint, + HttpMethod, + _addTidIfNecessary, + _performSignInRequest +} from '../index'; import { IdTokenResponse } from '../../model/id_token'; -import { Auth } from '@firebase/auth-types-exp'; +import { Auth } from '../../model/public_types'; export interface SignInWithCustomTokenRequest { token: string; + returnSecureToken: boolean; + tenantId?: string; } export interface SignInWithCustomTokenResponse extends IdTokenResponse {} @@ -32,5 +39,10 @@ export async function signInWithCustomToken( return _performSignInRequest< SignInWithCustomTokenRequest, SignInWithCustomTokenResponse - >(auth, HttpMethod.POST, Endpoint.SIGN_IN_WITH_CUSTOM_TOKEN, request); + >( + auth, + HttpMethod.POST, + Endpoint.SIGN_IN_WITH_CUSTOM_TOKEN, + _addTidIfNecessary(auth, request) + ); } diff --git a/packages-exp/auth-exp/src/api/authentication/email_and_password.test.ts b/packages-exp/auth-exp/src/api/authentication/email_and_password.test.ts index f052f22e48d..5506c89c5cd 100644 --- a/packages-exp/auth-exp/src/api/authentication/email_and_password.test.ts +++ b/packages-exp/auth-exp/src/api/authentication/email_and_password.test.ts @@ -18,7 +18,7 @@ import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; -import { Operation } from '@firebase/auth-types-exp'; +import { ActionCodeOperation } from '../../model/public_types'; import { FirebaseError } from '@firebase/util'; import { Endpoint, HttpHeader } from '../'; @@ -62,10 +62,11 @@ describe('api/authentication/signInWithPassword', () => { email: 'test@foo.com' }); + auth.tenantId = 'tenant-id'; const response = await signInWithPassword(auth, request); expect(response.displayName).to.eq('my-name'); expect(response.email).to.eq('test@foo.com'); - expect(mock.calls[0].request).to.eql(request); + expect(mock.calls[0].request).to.eql({ ...request, tenantId: 'tenant-id' }); expect(mock.calls[0].method).to.eq('POST'); expect(mock.calls[0].headers!.get(HttpHeader.CONTENT_TYPE)).to.eq( 'application/json' @@ -102,7 +103,7 @@ describe('api/authentication/signInWithPassword', () => { describe('api/authentication/sendEmailVerification', () => { const request: VerifyEmailRequest = { - requestType: Operation.VERIFY_EMAIL, + requestType: ActionCodeOperation.VERIFY_EMAIL, idToken: 'my-token' }; @@ -120,9 +121,10 @@ describe('api/authentication/sendEmailVerification', () => { email: 'test@foo.com' }); + auth.tenantId = 'tenant-id'; const response = await sendEmailVerification(auth, request); expect(response.email).to.eq('test@foo.com'); - expect(mock.calls[0].request).to.eql(request); + expect(mock.calls[0].request).to.eql({ ...request, tenantId: 'tenant-id' }); expect(mock.calls[0].method).to.eq('POST'); expect(mock.calls[0].headers!.get(HttpHeader.CONTENT_TYPE)).to.eq( 'application/json' @@ -159,7 +161,7 @@ describe('api/authentication/sendEmailVerification', () => { describe('api/authentication/sendPasswordResetEmail', () => { const request: PasswordResetRequest = { - requestType: Operation.PASSWORD_RESET, + requestType: ActionCodeOperation.PASSWORD_RESET, email: 'test@foo.com' }; @@ -177,9 +179,10 @@ describe('api/authentication/sendPasswordResetEmail', () => { email: 'test@foo.com' }); + auth.tenantId = 'tenant-id'; const response = await sendPasswordResetEmail(auth, request); expect(response.email).to.eq('test@foo.com'); - expect(mock.calls[0].request).to.eql(request); + expect(mock.calls[0].request).to.eql({ ...request, tenantId: 'tenant-id' }); expect(mock.calls[0].method).to.eq('POST'); expect(mock.calls[0].headers!.get(HttpHeader.CONTENT_TYPE)).to.eq( 'application/json' @@ -216,7 +219,7 @@ describe('api/authentication/sendPasswordResetEmail', () => { describe('api/authentication/sendSignInLinkToEmail', () => { const request: EmailSignInRequest = { - requestType: Operation.EMAIL_SIGNIN, + requestType: ActionCodeOperation.EMAIL_SIGNIN, email: 'test@foo.com' }; @@ -234,9 +237,10 @@ describe('api/authentication/sendSignInLinkToEmail', () => { email: 'test@foo.com' }); + auth.tenantId = 'tenant-id'; const response = await sendSignInLinkToEmail(auth, request); expect(response.email).to.eq('test@foo.com'); - expect(mock.calls[0].request).to.eql(request); + expect(mock.calls[0].request).to.eql({ ...request, tenantId: 'tenant-id' }); expect(mock.calls[0].method).to.eq('POST'); expect(mock.calls[0].headers!.get(HttpHeader.CONTENT_TYPE)).to.eq( 'application/json' @@ -273,7 +277,7 @@ describe('api/authentication/sendSignInLinkToEmail', () => { describe('api/authentication/verifyAndChangeEmail', () => { const request: VerifyAndChangeEmailRequest = { - requestType: Operation.VERIFY_AND_CHANGE_EMAIL, + requestType: ActionCodeOperation.VERIFY_AND_CHANGE_EMAIL, idToken: 'id-token', newEmail: 'test@foo.com' }; @@ -292,9 +296,10 @@ describe('api/authentication/verifyAndChangeEmail', () => { email: 'test@foo.com' }); + auth.tenantId = 'tenant-id'; const response = await verifyAndChangeEmail(auth, request); expect(response.email).to.eq('test@foo.com'); - expect(mock.calls[0].request).to.eql(request); + expect(mock.calls[0].request).to.eql({ ...request, tenantId: 'tenant-id' }); expect(mock.calls[0].method).to.eq('POST'); expect(mock.calls[0].headers!.get(HttpHeader.CONTENT_TYPE)).to.eq( 'application/json' diff --git a/packages-exp/auth-exp/src/api/authentication/email_and_password.ts b/packages-exp/auth-exp/src/api/authentication/email_and_password.ts index 4c225f3d3eb..a620b3c2c27 100644 --- a/packages-exp/auth-exp/src/api/authentication/email_and_password.ts +++ b/packages-exp/auth-exp/src/api/authentication/email_and_password.ts @@ -15,20 +15,22 @@ * limitations under the License. */ -import { Operation, Auth } from '@firebase/auth-types-exp'; +import { ActionCodeOperation, Auth } from '../../model/public_types'; import { Endpoint, HttpMethod, + _addTidIfNecessary, _performApiRequest, _performSignInRequest -} from '..'; +} from '../index'; import { IdToken, IdTokenResponse } from '../../model/id_token'; export interface SignInWithPasswordRequest { returnSecureToken?: boolean; email: string; password: string; + tenantId?: string; } export interface SignInWithPasswordResponse extends IdTokenResponse { @@ -43,7 +45,12 @@ export async function signInWithPassword( return _performSignInRequest< SignInWithPasswordRequest, SignInWithPasswordResponse - >(auth, HttpMethod.POST, Endpoint.SIGN_IN_WITH_PASSWORD, request); + >( + auth, + HttpMethod.POST, + Endpoint.SIGN_IN_WITH_PASSWORD, + _addTidIfNecessary(auth, request) + ); } export interface GetOobCodeRequest { @@ -61,24 +68,23 @@ export interface GetOobCodeRequest { } export interface VerifyEmailRequest extends GetOobCodeRequest { - requestType: Operation.VERIFY_EMAIL; + requestType: ActionCodeOperation.VERIFY_EMAIL; idToken: IdToken; } export interface PasswordResetRequest extends GetOobCodeRequest { - requestType: Operation.PASSWORD_RESET; + requestType: ActionCodeOperation.PASSWORD_RESET; email: string; captchaResp?: string; - userIp?: string; } export interface EmailSignInRequest extends GetOobCodeRequest { - requestType: Operation.EMAIL_SIGNIN; + requestType: ActionCodeOperation.EMAIL_SIGNIN; email: string; } export interface VerifyAndChangeEmailRequest extends GetOobCodeRequest { - requestType: Operation.VERIFY_AND_CHANGE_EMAIL; + requestType: ActionCodeOperation.VERIFY_AND_CHANGE_EMAIL; idToken: IdToken; newEmail: string; } @@ -100,7 +106,7 @@ async function sendOobCode( auth, HttpMethod.POST, Endpoint.SEND_OOB_CODE, - request + _addTidIfNecessary(auth, request) ); } diff --git a/packages-exp/auth-exp/src/api/authentication/email_link.test.ts b/packages-exp/auth-exp/src/api/authentication/email_link.test.ts index ec45a02413c..da518187631 100644 --- a/packages-exp/auth-exp/src/api/authentication/email_link.test.ts +++ b/packages-exp/auth-exp/src/api/authentication/email_link.test.ts @@ -54,10 +54,14 @@ describe('api/authentication/email_link', () => { email: 'test@foo.com' }); + auth.tenantId = 'tenant-id'; const response = await signInWithEmailLink(auth, request); expect(response.displayName).to.eq('my-name'); expect(response.email).to.eq('test@foo.com'); - expect(mock.calls[0].request).to.eql(request); + expect(mock.calls[0].request).to.eql({ + ...request, + tenantId: 'tenant-id' + }); expect(mock.calls[0].method).to.eq('POST'); expect(mock.calls[0].headers!.get(HttpHeader.CONTENT_TYPE)).to.eq( 'application/json' @@ -105,10 +109,14 @@ describe('api/authentication/email_link', () => { email: 'test@foo.com' }); + auth.tenantId = 'tenant-id'; const response = await signInWithEmailLinkForLinking(auth, request); expect(response.displayName).to.eq('my-name'); expect(response.email).to.eq('test@foo.com'); - expect(mock.calls[0].request).to.eql(request); + expect(mock.calls[0].request).to.eql({ + ...request, + tenantId: 'tenant-id' + }); expect(mock.calls[0].method).to.eq('POST'); expect(mock.calls[0].headers!.get(HttpHeader.CONTENT_TYPE)).to.eq( 'application/json' diff --git a/packages-exp/auth-exp/src/api/authentication/email_link.ts b/packages-exp/auth-exp/src/api/authentication/email_link.ts index ff26fefed13..bae82634abd 100644 --- a/packages-exp/auth-exp/src/api/authentication/email_link.ts +++ b/packages-exp/auth-exp/src/api/authentication/email_link.ts @@ -15,13 +15,19 @@ * limitations under the License. */ -import { _performSignInRequest, Endpoint, HttpMethod } from '../'; +import { + _performSignInRequest, + Endpoint, + HttpMethod, + _addTidIfNecessary +} from '../index'; import { IdTokenResponse } from '../../model/id_token'; -import { Auth } from '@firebase/auth-types-exp'; +import { Auth } from '../../model/public_types'; export interface SignInWithEmailLinkRequest { email: string; oobCode: string; + tenantId?: string; } export interface SignInWithEmailLinkResponse extends IdTokenResponse { @@ -36,7 +42,12 @@ export async function signInWithEmailLink( return _performSignInRequest< SignInWithEmailLinkRequest, SignInWithEmailLinkResponse - >(auth, HttpMethod.POST, Endpoint.SIGN_IN_WITH_EMAIL_LINK, request); + >( + auth, + HttpMethod.POST, + Endpoint.SIGN_IN_WITH_EMAIL_LINK, + _addTidIfNecessary(auth, request) + ); } export interface SignInWithEmailLinkForLinkingRequest @@ -51,5 +62,10 @@ export async function signInWithEmailLinkForLinking( return _performSignInRequest< SignInWithEmailLinkForLinkingRequest, SignInWithEmailLinkResponse - >(auth, HttpMethod.POST, Endpoint.SIGN_IN_WITH_EMAIL_LINK, request); + >( + auth, + HttpMethod.POST, + Endpoint.SIGN_IN_WITH_EMAIL_LINK, + _addTidIfNecessary(auth, request) + ); } diff --git a/packages-exp/auth-exp/src/api/authentication/idp.test.ts b/packages-exp/auth-exp/src/api/authentication/idp.test.ts index 48ae6da45e7..02a05167ad3 100644 --- a/packages-exp/auth-exp/src/api/authentication/idp.test.ts +++ b/packages-exp/auth-exp/src/api/authentication/idp.test.ts @@ -32,8 +32,7 @@ use(chaiAsPromised); describe('api/authentication/signInWithIdp', () => { const request = { returnSecureToken: true, - requestUri: 'request-uri', - postBody: null + requestUri: 'request-uri' }; let auth: TestAuth; @@ -51,10 +50,11 @@ describe('api/authentication/signInWithIdp', () => { idToken: 'id-token' }); + auth.tenantId = 'tenant-id'; const response = await signInWithIdp(auth, request); expect(response.displayName).to.eq('my-name'); expect(response.idToken).to.eq('id-token'); - expect(mock.calls[0].request).to.eql(request); + expect(mock.calls[0].request).to.eql({ ...request, tenantId: 'tenant-id' }); expect(mock.calls[0].method).to.eq('POST'); expect(mock.calls[0].headers!.get(HttpHeader.CONTENT_TYPE)).to.eq( 'application/json' diff --git a/packages-exp/auth-exp/src/api/authentication/idp.ts b/packages-exp/auth-exp/src/api/authentication/idp.ts index 0e2bcc5a02a..53ab3f688e3 100644 --- a/packages-exp/auth-exp/src/api/authentication/idp.ts +++ b/packages-exp/auth-exp/src/api/authentication/idp.ts @@ -15,16 +15,22 @@ * limitations under the License. */ -import { Endpoint, HttpMethod, _performSignInRequest } from '..'; +import { + Endpoint, + HttpMethod, + _addTidIfNecessary, + _performSignInRequest +} from '../index'; import { IdToken, IdTokenResponse } from '../../model/id_token'; -import { Auth } from '@firebase/auth-types-exp'; +import { Auth } from '../../model/public_types'; export interface SignInWithIdpRequest { requestUri: string; - postBody: string | null; + postBody?: string; sessionId?: string; tenantId?: string; returnSecureToken: boolean; + returnIdpCredential?: boolean; idToken?: IdToken; autoCreate?: boolean; pendingToken?: string; @@ -46,6 +52,6 @@ export async function signInWithIdp( auth, HttpMethod.POST, Endpoint.SIGN_IN_WITH_IDP, - request + _addTidIfNecessary(auth, request) ); } diff --git a/packages-exp/auth-exp/src/api/authentication/mfa.test.ts b/packages-exp/auth-exp/src/api/authentication/mfa.test.ts index 767edd22943..ebb4b8f6045 100644 --- a/packages-exp/auth-exp/src/api/authentication/mfa.test.ts +++ b/packages-exp/auth-exp/src/api/authentication/mfa.test.ts @@ -158,7 +158,7 @@ describe('api/authentication/finalizeSignInPhoneMfa', () => { await expect(finalizeSignInPhoneMfa(auth, request)).to.be.rejectedWith( FirebaseError, - 'Firebase: The SMS verification code used to create the phone auth credential is invalid. Please resend the verification code sms and be sure use the verification code provided by the user. (auth/invalid-verification-code).' + 'Firebase: The SMS verification code used to create the phone auth credential is invalid. Please resend the verification code sms and be sure to use the verification code provided by the user. (auth/invalid-verification-code).' ); expect(mock.calls[0].request).to.eql({ tenantId: null, diff --git a/packages-exp/auth-exp/src/api/authentication/mfa.ts b/packages-exp/auth-exp/src/api/authentication/mfa.ts index 2dbc55823c5..165c0ad918f 100644 --- a/packages-exp/auth-exp/src/api/authentication/mfa.ts +++ b/packages-exp/auth-exp/src/api/authentication/mfa.ts @@ -15,8 +15,8 @@ * limitations under the License. */ -import { _performApiRequest, Endpoint, HttpMethod } from '../'; -import { Auth } from '@firebase/auth-types-exp'; +import { _performApiRequest, Endpoint, HttpMethod } from '../index'; +import { Auth } from '../../model/public_types'; import { IdTokenResponse } from '../../model/id_token'; import { MfaEnrollment } from '../account_management/mfa'; import { SignInWithIdpResponse } from './idp'; diff --git a/packages-exp/auth-exp/src/api/authentication/recaptcha.ts b/packages-exp/auth-exp/src/api/authentication/recaptcha.ts index 01dc3c7e3cd..0d5812b1e6f 100644 --- a/packages-exp/auth-exp/src/api/authentication/recaptcha.ts +++ b/packages-exp/auth-exp/src/api/authentication/recaptcha.ts @@ -15,8 +15,8 @@ * limitations under the License. */ -import { Endpoint, HttpMethod, _performApiRequest } from '..'; -import { Auth } from '@firebase/auth-types-exp'; +import { Endpoint, HttpMethod, _performApiRequest } from '../index'; +import { Auth } from '../../model/public_types'; interface GetRecaptchaParamResponse { recaptchaSiteKey?: string; diff --git a/packages-exp/auth-exp/src/api/authentication/sign_up.test.ts b/packages-exp/auth-exp/src/api/authentication/sign_up.test.ts index 215405402e5..dfd15127896 100644 --- a/packages-exp/auth-exp/src/api/authentication/sign_up.test.ts +++ b/packages-exp/auth-exp/src/api/authentication/sign_up.test.ts @@ -51,10 +51,11 @@ describe('api/authentication/signUp', () => { email: 'test@foo.com' }); + auth.tenantId = 'tenant-id'; const response = await signUp(auth, request); expect(response.displayName).to.eq('my-name'); expect(response.email).to.eq('test@foo.com'); - expect(mock.calls[0].request).to.eql(request); + expect(mock.calls[0].request).to.eql({ ...request, tenantId: 'tenant-id' }); expect(mock.calls[0].method).to.eq('POST'); expect(mock.calls[0].headers!.get(HttpHeader.CONTENT_TYPE)).to.eq( 'application/json' diff --git a/packages-exp/auth-exp/src/api/authentication/sign_up.ts b/packages-exp/auth-exp/src/api/authentication/sign_up.ts index 74ca71ea866..a271fb57053 100644 --- a/packages-exp/auth-exp/src/api/authentication/sign_up.ts +++ b/packages-exp/auth-exp/src/api/authentication/sign_up.ts @@ -15,14 +15,20 @@ * limitations under the License. */ -import { Endpoint, HttpMethod, _performSignInRequest } from '..'; +import { + Endpoint, + HttpMethod, + _addTidIfNecessary, + _performSignInRequest +} from '../index'; import { IdTokenResponse } from '../../model/id_token'; -import { Auth } from '@firebase/auth-types-exp'; +import { Auth } from '../../model/public_types'; export interface SignUpRequest { returnSecureToken?: boolean; email?: string; password?: string; + tenantId?: string; } export interface SignUpResponse extends IdTokenResponse { @@ -38,6 +44,6 @@ export async function signUp( auth, HttpMethod.POST, Endpoint.SIGN_UP, - request + _addTidIfNecessary(auth, request) ); } diff --git a/packages-exp/auth-exp/src/api/authentication/sms.test.ts b/packages-exp/auth-exp/src/api/authentication/sms.test.ts index b1ff8a3c923..b27db5d9475 100644 --- a/packages-exp/auth-exp/src/api/authentication/sms.test.ts +++ b/packages-exp/auth-exp/src/api/authentication/sms.test.ts @@ -18,7 +18,7 @@ import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; -import { ProviderId } from '@firebase/auth-types-exp'; +import { ProviderId } from '../../model/public_types'; import { FirebaseError } from '@firebase/util'; import { Endpoint, HttpHeader } from '../'; @@ -55,9 +55,10 @@ describe('api/authentication/sendPhoneVerificationCode', () => { sessionInfo: 'my-session' }); + auth.tenantId = 'tenant-id'; const response = await sendPhoneVerificationCode(auth, request); expect(response.sessionInfo).to.eq('my-session'); - expect(mock.calls[0].request).to.eql(request); + expect(mock.calls[0].request).to.eql({ ...request, tenantId: 'tenant-id' }); expect(mock.calls[0].method).to.eq('POST'); expect(mock.calls[0].headers!.get(HttpHeader.CONTENT_TYPE)).to.eq( 'application/json' @@ -118,12 +119,13 @@ describe('api/authentication/signInWithPhoneNumber', () => { localId: '1234' }); + auth.tenantId = 'tenant-id'; const response = await signInWithPhoneNumber(auth, request); expect(response.providerId).to.eq(ProviderId.PHONE); expect(response.idToken).to.eq('id-token'); expect(response.expiresIn).to.eq('1000'); expect(response.localId).to.eq('1234'); - expect(mock.calls[0].request).to.eql(request); + expect(mock.calls[0].request).to.eql({ ...request, tenantId: 'tenant-id' }); expect(mock.calls[0].method).to.eq('POST'); expect(mock.calls[0].headers!.get(HttpHeader.CONTENT_TYPE)).to.eq( 'application/json' @@ -152,7 +154,7 @@ describe('api/authentication/signInWithPhoneNumber', () => { await expect(signInWithPhoneNumber(auth, request)).to.be.rejectedWith( FirebaseError, - 'Firebase: The SMS verification code used to create the phone auth credential is invalid. Please resend the verification code sms and be sure use the verification code provided by the user. (auth/invalid-verification-code).' + 'Firebase: The SMS verification code used to create the phone auth credential is invalid. Please resend the verification code sms and be sure to use the verification code provided by the user. (auth/invalid-verification-code).' ); expect(mock.calls[0].request).to.eql(request); }); @@ -185,12 +187,13 @@ describe('api/authentication/linkWithPhoneNumber', () => { localId: '1234' }); + auth.tenantId = 'tenant-id'; const response = await linkWithPhoneNumber(auth, request); expect(response.providerId).to.eq(ProviderId.PHONE); expect(response.idToken).to.eq('id-token'); expect(response.expiresIn).to.eq('1000'); expect(response.localId).to.eq('1234'); - expect(mock.calls[0].request).to.eql(request); + expect(mock.calls[0].request).to.eql({ ...request, tenantId: 'tenant-id' }); expect(mock.calls[0].method).to.eq('POST'); expect(mock.calls[0].headers!.get(HttpHeader.CONTENT_TYPE)).to.eq( 'application/json' @@ -219,7 +222,7 @@ describe('api/authentication/linkWithPhoneNumber', () => { await expect(linkWithPhoneNumber(auth, request)).to.be.rejectedWith( FirebaseError, - 'Firebase: The SMS verification code used to create the phone auth credential is invalid. Please resend the verification code sms and be sure use the verification code provided by the user. (auth/invalid-verification-code).' + 'Firebase: The SMS verification code used to create the phone auth credential is invalid. Please resend the verification code sms and be sure to use the verification code provided by the user. (auth/invalid-verification-code).' ); expect(mock.calls[0].request).to.eql(request); }); @@ -251,6 +254,7 @@ describe('api/authentication/verifyPhoneNumberForExisting', () => { localId: '1234' }); + auth.tenantId = 'tenant-id'; const response = await verifyPhoneNumberForExisting(auth, request); expect(response.providerId).to.eq(ProviderId.PHONE); expect(response.idToken).to.eq('id-token'); @@ -258,7 +262,8 @@ describe('api/authentication/verifyPhoneNumberForExisting', () => { expect(response.localId).to.eq('1234'); expect(mock.calls[0].request).to.eql({ ...request, - operation: 'REAUTH' + operation: 'REAUTH', + tenantId: 'tenant-id' }); expect(mock.calls[0].method).to.eq('POST'); expect(mock.calls[0].headers!.get(HttpHeader.CONTENT_TYPE)).to.eq( diff --git a/packages-exp/auth-exp/src/api/authentication/sms.ts b/packages-exp/auth-exp/src/api/authentication/sms.ts index 739f14eddca..805d207375e 100644 --- a/packages-exp/auth-exp/src/api/authentication/sms.ts +++ b/packages-exp/auth-exp/src/api/authentication/sms.ts @@ -18,17 +18,20 @@ import { Endpoint, HttpMethod, + _addTidIfNecessary, + _makeTaggedError, _performApiRequest, _performSignInRequest -} from '..'; +} from '../index'; import { AuthErrorCode } from '../../core/errors'; import { IdTokenResponse } from '../../model/id_token'; import { ServerError, ServerErrorMap } from '../errors'; -import { Auth } from '@firebase/auth-types-exp'; +import { Auth } from '../../model/public_types'; export interface SendPhoneVerificationCodeRequest { phoneNumber: string; recaptchaToken: string; + tenantId?: string; } export interface SendPhoneVerificationCodeResponse { @@ -42,7 +45,12 @@ export async function sendPhoneVerificationCode( return _performApiRequest< SendPhoneVerificationCodeRequest, SendPhoneVerificationCodeResponse - >(auth, HttpMethod.POST, Endpoint.SEND_VERIFICATION_CODE, request); + >( + auth, + HttpMethod.POST, + Endpoint.SEND_VERIFICATION_CODE, + _addTidIfNecessary(auth, request) + ); } export interface SignInWithPhoneNumberRequest { @@ -50,6 +58,7 @@ export interface SignInWithPhoneNumberRequest { phoneNumber?: string; sessionInfo?: string; code?: string; + tenantId?: string; } export interface LinkWithPhoneNumberRequest @@ -69,17 +78,31 @@ export async function signInWithPhoneNumber( return _performSignInRequest< SignInWithPhoneNumberRequest, SignInWithPhoneNumberResponse - >(auth, HttpMethod.POST, Endpoint.SIGN_IN_WITH_PHONE_NUMBER, request); + >( + auth, + HttpMethod.POST, + Endpoint.SIGN_IN_WITH_PHONE_NUMBER, + _addTidIfNecessary(auth, request) + ); } export async function linkWithPhoneNumber( auth: Auth, request: LinkWithPhoneNumberRequest ): Promise { - return _performSignInRequest< + const response = await _performSignInRequest< LinkWithPhoneNumberRequest, SignInWithPhoneNumberResponse - >(auth, HttpMethod.POST, Endpoint.SIGN_IN_WITH_PHONE_NUMBER, request); + >( + auth, + HttpMethod.POST, + Endpoint.SIGN_IN_WITH_PHONE_NUMBER, + _addTidIfNecessary(auth, request) + ); + if (response.temporaryProof) { + throw _makeTaggedError(auth, AuthErrorCode.NEED_CONFIRMATION, response); + } + return response; } interface VerifyPhoneNumberForExistingRequest @@ -87,9 +110,9 @@ interface VerifyPhoneNumberForExistingRequest operation: 'REAUTH'; } -const VERIFY_PHONE_NUMBER_FOR_EXISTING_ERROR_MAP_: Partial> = { +const VERIFY_PHONE_NUMBER_FOR_EXISTING_ERROR_MAP_: Partial< + ServerErrorMap +> = { [ServerError.USER_NOT_FOUND]: AuthErrorCode.USER_DELETED }; @@ -108,7 +131,7 @@ export async function verifyPhoneNumberForExisting( auth, HttpMethod.POST, Endpoint.SIGN_IN_WITH_PHONE_NUMBER, - apiRequest, + _addTidIfNecessary(auth, apiRequest), VERIFY_PHONE_NUMBER_FOR_EXISTING_ERROR_MAP_ ); } diff --git a/packages-exp/auth-exp/src/api/authentication/token.test.ts b/packages-exp/auth-exp/src/api/authentication/token.test.ts index a33e6323953..6cb58abe9d8 100644 --- a/packages-exp/auth-exp/src/api/authentication/token.test.ts +++ b/packages-exp/auth-exp/src/api/authentication/token.test.ts @@ -18,13 +18,15 @@ import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; -import { FirebaseError, querystringDecode } from '@firebase/util'; +import { FirebaseError, getUA, querystringDecode } from '@firebase/util'; import { HttpHeader } from '../'; import { testAuth, TestAuth } from '../../../test/helpers/mock_auth'; import * as fetch from '../../../test/helpers/mock_fetch'; import { ServerError } from '../errors'; -import { _ENDPOINT, requestStsToken } from './token'; +import { Endpoint, requestStsToken } from './token'; +import { SDK_VERSION } from '@firebase/app-exp'; +import { _getBrowserName } from '../../core/util/browser'; use(chaiAsPromised); @@ -35,7 +37,7 @@ describe('requestStsToken', () => { beforeEach(async () => { auth = await testAuth(); const { apiKey, tokenApiHost, apiScheme } = auth.config; - endpoint = `${apiScheme}://${tokenApiHost}${_ENDPOINT}?key=${apiKey}`; + endpoint = `${apiScheme}://${tokenApiHost}${Endpoint.TOKEN}?key=${apiKey}`; fetch.setUp(); }); @@ -66,6 +68,28 @@ describe('requestStsToken', () => { ); }); + it('should set the framework in clientVersion if logged', async () => { + const mock = fetch.mock(endpoint, { + 'access_token': 'new-access-token', + 'expires_in': '3600', + 'refresh_token': 'new-refresh-token' + }); + + auth._logFramework('Mythical'); + await requestStsToken(auth, 'old-refresh-token'); + expect(mock.calls[0].headers!.get(HttpHeader.X_CLIENT_VERSION)).to.eq( + `${_getBrowserName(getUA())}/JsCore/${SDK_VERSION}/Mythical` + ); + + // If a new framework is logged, the client version header should change as well. + auth._logFramework('Magical'); + await requestStsToken(auth, 'old-refresh-token'); + expect(mock.calls[1].headers!.get(HttpHeader.X_CLIENT_VERSION)).to.eq( + // frameworks should be sorted alphabetically + `${_getBrowserName(getUA())}/JsCore/${SDK_VERSION}/Magical,Mythical` + ); + }); + it('should handle errors', async () => { const mock = fetch.mock( endpoint, diff --git a/packages-exp/auth-exp/src/api/authentication/token.ts b/packages-exp/auth-exp/src/api/authentication/token.ts index cc8243e2161..3966685def6 100644 --- a/packages-exp/auth-exp/src/api/authentication/token.ts +++ b/packages-exp/auth-exp/src/api/authentication/token.ts @@ -23,49 +23,58 @@ import { _getFinalTarget, _performFetchWithErrorHandling, HttpMethod -} from '../'; +} from '../index'; import { FetchProvider } from '../../core/util/fetch_provider'; -import { Auth } from '@firebase/auth-types-exp'; +import { Auth } from '../../model/public_types'; +import { AuthInternal } from '../../model/auth'; -export const _ENDPOINT = '/v1/token'; -const GRANT_TYPE = 'refresh_token'; +export const enum Endpoint { + TOKEN = '/v1/token' +} /** The server responses with snake_case; we convert to camelCase */ interface RequestStsTokenServerResponse { - access_token?: string; - expires_in?: string; - refresh_token?: string; + access_token: string; + expires_in: string; + refresh_token: string; } export interface RequestStsTokenResponse { - accessToken?: string; - expiresIn?: string; - refreshToken?: string; + accessToken: string; + expiresIn: string; + refreshToken: string; } export async function requestStsToken( auth: Auth, refreshToken: string ): Promise { - const response = await _performFetchWithErrorHandling< - RequestStsTokenServerResponse - >(auth, {}, () => { - const body = querystring({ - 'grant_type': GRANT_TYPE, - 'refresh_token': refreshToken - }).slice(1); - const { tokenApiHost, apiKey, sdkClientVersion } = auth.config; - const url = _getFinalTarget(auth, tokenApiHost, _ENDPOINT, `key=${apiKey}`); + const response = await _performFetchWithErrorHandling( + auth, + {}, + () => { + const body = querystring({ + 'grant_type': 'refresh_token', + 'refresh_token': refreshToken + }).slice(1); + const { tokenApiHost, apiKey } = auth.config; + const url = _getFinalTarget( + auth, + tokenApiHost, + Endpoint.TOKEN, + `key=${apiKey}` + ); - return FetchProvider.fetch()(url, { - method: HttpMethod.POST, - headers: { - 'X-Client-Version': sdkClientVersion, - 'Content-Type': 'application/x-www-form-urlencoded' - }, - body - }); - }); + return FetchProvider.fetch()(url, { + method: HttpMethod.POST, + headers: { + 'X-Client-Version': (auth as AuthInternal)._getSdkClientVersion(), + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body + }); + } + ); // The response comes back in snake_case. Convert to camel: return { diff --git a/packages-exp/auth-exp/src/api/errors.ts b/packages-exp/auth-exp/src/api/errors.ts index 9d0432c3387..e70f8523011 100644 --- a/packages-exp/auth-exp/src/api/errors.ts +++ b/packages-exp/auth-exp/src/api/errors.ts @@ -20,7 +20,7 @@ import { AuthErrorCode } from '../core/errors'; /** * Errors that can be returned by the backend */ -export enum ServerError { +export const enum ServerError { ADMIN_ONLY_OPERATION = 'ADMIN_ONLY_OPERATION', CAPTCHA_CHECK_FAILED = 'CAPTCHA_CHECK_FAILED', CORS_UNSUPPORTED = 'CORS_UNSUPPORTED', @@ -119,9 +119,8 @@ export declare type ServerErrorMap = { /** * Map from errors returned by the server to errors to developer visible errors */ -export const SERVER_ERROR_MAP: ServerErrorMap = { +export const SERVER_ERROR_MAP: Partial> = { // Custom token errors. - [ServerError.INVALID_CUSTOM_TOKEN]: AuthErrorCode.INVALID_CUSTOM_TOKEN, [ServerError.CREDENTIAL_MISMATCH]: AuthErrorCode.CREDENTIAL_MISMATCH, // This can only happen if the SDK sends a bad request. [ServerError.MISSING_CUSTOM_TOKEN]: AuthErrorCode.INTERNAL_ERROR, @@ -132,9 +131,7 @@ export const SERVER_ERROR_MAP: ServerErrorMap = { [ServerError.MISSING_CONTINUE_URI]: AuthErrorCode.INTERNAL_ERROR, // Sign in with email and password errors (some apply to sign up too). - [ServerError.INVALID_EMAIL]: AuthErrorCode.INVALID_EMAIL, [ServerError.INVALID_PASSWORD]: AuthErrorCode.INVALID_PASSWORD, - [ServerError.USER_DISABLED]: AuthErrorCode.USER_DISABLED, // This can only happen if the SDK sends a bad request. [ServerError.MISSING_PASSWORD]: AuthErrorCode.INTERNAL_ERROR, @@ -147,13 +144,7 @@ export const SERVER_ERROR_MAP: ServerErrorMap = { [ServerError.INVALID_PENDING_TOKEN]: AuthErrorCode.INVALID_IDP_RESPONSE, [ServerError.FEDERATED_USER_ID_ALREADY_LINKED]: AuthErrorCode.CREDENTIAL_ALREADY_IN_USE, - [ServerError.MISSING_OR_INVALID_NONCE]: - AuthErrorCode.MISSING_OR_INVALID_NONCE, - // Email template errors while sending emails: - [ServerError.INVALID_MESSAGE_PAYLOAD]: AuthErrorCode.INVALID_MESSAGE_PAYLOAD, - [ServerError.INVALID_RECIPIENT_EMAIL]: AuthErrorCode.INVALID_RECIPIENT_EMAIL, - [ServerError.INVALID_SENDER]: AuthErrorCode.INVALID_SENDER, // This can only happen if the SDK sends a bad request. [ServerError.MISSING_REQ_TYPE]: AuthErrorCode.INTERNAL_ERROR, @@ -162,15 +153,11 @@ export const SERVER_ERROR_MAP: ServerErrorMap = { [ServerError.RESET_PASSWORD_EXCEED_LIMIT]: AuthErrorCode.TOO_MANY_ATTEMPTS_TRY_LATER, - // Reset password errors: [ServerError.EXPIRED_OOB_CODE]: AuthErrorCode.EXPIRED_OOB_CODE, [ServerError.INVALID_OOB_CODE]: AuthErrorCode.INVALID_OOB_CODE, // This can only happen if the SDK sends a bad request. [ServerError.MISSING_OOB_CODE]: AuthErrorCode.INTERNAL_ERROR, - // Get Auth URI errors: - [ServerError.INVALID_PROVIDER_ID]: AuthErrorCode.INVALID_PROVIDER_ID, - // Operations that require ID token in request: [ServerError.CREDENTIAL_TOO_OLD_LOGIN_AGAIN]: AuthErrorCode.CREDENTIAL_TOO_OLD_LOGIN_AGAIN, @@ -178,66 +165,31 @@ export const SERVER_ERROR_MAP: ServerErrorMap = { [ServerError.TOKEN_EXPIRED]: AuthErrorCode.TOKEN_EXPIRED, [ServerError.USER_NOT_FOUND]: AuthErrorCode.TOKEN_EXPIRED, - // CORS issues. - [ServerError.CORS_UNSUPPORTED]: AuthErrorCode.CORS_UNSUPPORTED, - - // Dynamic link not activated. - [ServerError.DYNAMIC_LINK_NOT_ACTIVATED]: - AuthErrorCode.DYNAMIC_LINK_NOT_ACTIVATED, - - // iosBundleId or androidPackageName not valid error. - [ServerError.INVALID_APP_ID]: AuthErrorCode.INVALID_APP_ID, - // Other errors. [ServerError.TOO_MANY_ATTEMPTS_TRY_LATER]: AuthErrorCode.TOO_MANY_ATTEMPTS_TRY_LATER, - [ServerError.WEAK_PASSWORD]: AuthErrorCode.WEAK_PASSWORD, - [ServerError.OPERATION_NOT_ALLOWED]: AuthErrorCode.OPERATION_NOT_ALLOWED, - [ServerError.USER_CANCELLED]: AuthErrorCode.USER_CANCELLED, // Phone Auth related errors. - [ServerError.CAPTCHA_CHECK_FAILED]: AuthErrorCode.CAPTCHA_CHECK_FAILED, - [ServerError.INVALID_APP_CREDENTIAL]: AuthErrorCode.INVALID_APP_CREDENTIAL, [ServerError.INVALID_CODE]: AuthErrorCode.INVALID_CODE, - [ServerError.INVALID_PHONE_NUMBER]: AuthErrorCode.INVALID_PHONE_NUMBER, [ServerError.INVALID_SESSION_INFO]: AuthErrorCode.INVALID_SESSION_INFO, [ServerError.INVALID_TEMPORARY_PROOF]: AuthErrorCode.INVALID_IDP_RESPONSE, - [ServerError.MISSING_APP_CREDENTIAL]: AuthErrorCode.MISSING_APP_CREDENTIAL, - [ServerError.MISSING_CODE]: AuthErrorCode.MISSING_CODE, - [ServerError.MISSING_PHONE_NUMBER]: AuthErrorCode.MISSING_PHONE_NUMBER, [ServerError.MISSING_SESSION_INFO]: AuthErrorCode.MISSING_SESSION_INFO, - [ServerError.QUOTA_EXCEEDED]: AuthErrorCode.QUOTA_EXCEEDED, [ServerError.SESSION_EXPIRED]: AuthErrorCode.CODE_EXPIRED, - [ServerError.REJECTED_CREDENTIAL]: AuthErrorCode.REJECTED_CREDENTIAL, // Other action code errors when additional settings passed. - [ServerError.INVALID_CONTINUE_URI]: AuthErrorCode.INVALID_CONTINUE_URI, // MISSING_CONTINUE_URI is getting mapped to INTERNAL_ERROR above. // This is OK as this error will be caught by client side validation. [ServerError.MISSING_ANDROID_PACKAGE_NAME]: AuthErrorCode.MISSING_ANDROID_PACKAGE_NAME, - [ServerError.MISSING_IOS_BUNDLE_ID]: AuthErrorCode.MISSING_IOS_BUNDLE_ID, [ServerError.UNAUTHORIZED_DOMAIN]: AuthErrorCode.UNAUTHORIZED_DOMAIN, - [ServerError.INVALID_DYNAMIC_LINK_DOMAIN]: - AuthErrorCode.INVALID_DYNAMIC_LINK_DOMAIN, // getProjectConfig errors when clientId is passed. [ServerError.INVALID_OAUTH_CLIENT_ID]: AuthErrorCode.INVALID_OAUTH_CLIENT_ID, - // getProjectConfig errors when sha1Cert is passed. - [ServerError.INVALID_CERT_HASH]: AuthErrorCode.INVALID_CERT_HASH, - - // Multi-tenant related errors. - [ServerError.UNSUPPORTED_TENANT_OPERATION]: - AuthErrorCode.UNSUPPORTED_TENANT_OPERATION, - [ServerError.INVALID_TENANT_ID]: AuthErrorCode.INVALID_TENANT_ID, - [ServerError.TENANT_ID_MISMATCH]: AuthErrorCode.TENANT_ID_MISMATCH, // User actions (sign-up or deletion) disabled errors. [ServerError.ADMIN_ONLY_OPERATION]: AuthErrorCode.ADMIN_ONLY_OPERATION, // Multi factor related errors. - [ServerError.EMAIL_CHANGE_NEEDS_VERIFICATION]: - AuthErrorCode.EMAIL_CHANGE_NEEDS_VERIFICATION, [ServerError.INVALID_MFA_PENDING_CREDENTIAL]: AuthErrorCode.INVALID_MFA_SESSION, [ServerError.MFA_ENROLLMENT_NOT_FOUND]: AuthErrorCode.MFA_INFO_NOT_FOUND, @@ -247,8 +199,5 @@ export const SERVER_ERROR_MAP: ServerErrorMap = { [ServerError.SECOND_FACTOR_EXISTS]: AuthErrorCode.SECOND_FACTOR_ALREADY_ENROLLED, [ServerError.SECOND_FACTOR_LIMIT_EXCEEDED]: - AuthErrorCode.SECOND_FACTOR_LIMIT_EXCEEDED, - [ServerError.UNSUPPORTED_FIRST_FACTOR]: - AuthErrorCode.UNSUPPORTED_FIRST_FACTOR, - [ServerError.UNVERIFIED_EMAIL]: AuthErrorCode.UNVERIFIED_EMAIL + AuthErrorCode.SECOND_FACTOR_LIMIT_EXCEEDED }; diff --git a/packages-exp/auth-exp/src/api/index.test.ts b/packages-exp/auth-exp/src/api/index.test.ts index 99caaca6d14..7bbb5dd8314 100644 --- a/packages-exp/auth-exp/src/api/index.test.ts +++ b/packages-exp/auth-exp/src/api/index.test.ts @@ -21,7 +21,7 @@ import * as sinon from 'sinon'; import { useFakeTimers } from 'sinon'; import * as sinonChai from 'sinon-chai'; -import { FirebaseError } from '@firebase/util'; +import { FirebaseError, getUA } from '@firebase/util'; import { mockEndpoint } from '../../test/helpers/api/helper'; import { testAuth, TestAuth } from '../../test/helpers/mock_auth'; @@ -34,9 +34,12 @@ import { DEFAULT_API_TIMEOUT_MS, Endpoint, HttpHeader, - HttpMethod + HttpMethod, + _addTidIfNecessary } from './'; import { ServerError } from './errors'; +import { SDK_VERSION } from '@firebase/app-exp'; +import { _getBrowserName } from '../core/util/browser'; use(sinonChai); use(chaiAsPromised); @@ -91,6 +94,31 @@ describe('api/_performApiRequest', () => { ); }); + it('should set the framework in clientVersion if logged', async () => { + auth._logFramework('Mythical'); + const mock = mockEndpoint(Endpoint.SIGN_UP, serverResponse); + const response = await _performApiRequest< + typeof request, + typeof serverResponse + >(auth, HttpMethod.POST, Endpoint.SIGN_UP, request); + expect(response).to.eql(serverResponse); + expect(mock.calls[0].headers!.get(HttpHeader.X_CLIENT_VERSION)).to.eq( + `${_getBrowserName(getUA())}/JsCore/${SDK_VERSION}/Mythical` + ); + + // If a new framework is logged, the client version header should change as well. + auth._logFramework('Magical'); + const response2 = await _performApiRequest< + typeof request, + typeof serverResponse + >(auth, HttpMethod.POST, Endpoint.SIGN_UP, request); + expect(response2).to.eql(serverResponse); + expect(mock.calls[1].headers!.get(HttpHeader.X_CLIENT_VERSION)).to.eq( + // frameworks should be sorted alphabetically + `${_getBrowserName(getUA())}/JsCore/${SDK_VERSION}/Magical,Mythical` + ); + }); + it('should translate server errors to auth errors', async () => { const mock = mockEndpoint( Endpoint.SIGN_UP, @@ -120,6 +148,27 @@ describe('api/_performApiRequest', () => { expect(mock.calls[0].request).to.eql(request); }); + it('should translate server success with errorMessage into auth error', async () => { + const response = { + errorMessage: ServerError.FEDERATED_USER_ID_ALREADY_LINKED, + idToken: 'foo-bar' + }; + const mock = mockEndpoint(Endpoint.SIGN_IN_WITH_IDP, response, 200); + const promise = _performApiRequest( + auth, + HttpMethod.POST, + Endpoint.SIGN_IN_WITH_IDP, + request + ); + await expect(promise) + .to.be.rejectedWith(FirebaseError, 'auth/credential-already-in-use') + .eventually.with.deep.property('customData', { + appName: 'test-app', + _tokenResponse: response + }); + expect(mock.calls[0].request).to.eql(request); + }); + it('should translate complex server errors to auth errors', async () => { const mock = mockEndpoint( Endpoint.SIGN_UP, @@ -173,7 +222,7 @@ describe('api/_performApiRequest', () => { ); await expect(promise).to.be.rejectedWith( FirebaseError, - 'auth/internal-error' + 'auth/awesome-error' ); expect(mock.calls[0].request).to.eql(request); }); @@ -284,7 +333,7 @@ describe('api/_performApiRequest', () => { assert.fail('Call should have failed'); } catch (e) { expect(e.code).to.eq(`auth/${AuthErrorCode.NEED_CONFIRMATION}`); - expect(e._tokenResponse).to.eql({ + expect((e as FirebaseError).customData!._tokenResponse).to.eql({ needConfirmation: true, idToken: 'id-token' }); @@ -314,7 +363,9 @@ describe('api/_performApiRequest', () => { assert.fail('Call should have failed'); } catch (e) { expect(e.code).to.eq(`auth/${AuthErrorCode.CREDENTIAL_ALREADY_IN_USE}`); - expect(e._tokenResponse).to.eql(response); + expect((e as FirebaseError).customData!._tokenResponse).to.eql( + response + ); } }); @@ -343,8 +394,10 @@ describe('api/_performApiRequest', () => { assert.fail('Call should have failed'); } catch (e) { expect(e.code).to.eq(`auth/${AuthErrorCode.EMAIL_EXISTS}`); - expect(e.email).to.eq('email@test.com'); - expect(e.phoneNumber).to.eq('+1555-this-is-a-number'); + expect((e as FirebaseError).customData!.email).to.eq('email@test.com'); + expect((e as FirebaseError).customData!.phoneNumber).to.eq( + '+1555-this-is-a-number' + ); } }); }); @@ -358,11 +411,60 @@ describe('api/_performApiRequest', () => { it('works properly with an emulated environment', () => { (auth.config as ConfigInternal).emulator = { - url: 'http://localhost:5000' + url: 'http://localhost:5000/' }; expect(_getFinalTarget(auth, 'host', '/path', 'query=test')).to.eq( 'http://localhost:5000/host/path?query=test' ); }); }); + + context('_addTidIfNecessary', () => { + it('adds the tenant ID if it is not already defined', () => { + auth.tenantId = 'auth-tenant-id'; + expect( + _addTidIfNecessary>(auth, { foo: 'bar' }) + ).to.eql({ + tenantId: 'auth-tenant-id', + foo: 'bar' + }); + }); + + it('does not overwrite the tenant ID if already supplied', () => { + auth.tenantId = 'auth-tenant-id'; + expect( + _addTidIfNecessary>(auth, { + foo: 'bar', + tenantId: 'request-tenant-id' + }) + ).to.eql({ + tenantId: 'request-tenant-id', + foo: 'bar' + }); + }); + + it('leaves tenant id on the request even if not specified on auth', () => { + auth.tenantId = null; + expect( + _addTidIfNecessary>(auth, { + foo: 'bar', + tenantId: 'request-tenant-id' + }) + ).to.eql({ + tenantId: 'request-tenant-id', + foo: 'bar' + }); + }); + + it('does not attach the tenant ID at all if not specified', () => { + auth.tenantId = null; + expect( + _addTidIfNecessary>(auth, { foo: 'bar' }) + ) + .to.eql({ + foo: 'bar' + }) + .and.not.have.property('tenantId'); + }); + }); }); diff --git a/packages-exp/auth-exp/src/api/index.ts b/packages-exp/auth-exp/src/api/index.ts index 8b10a4a7ba7..7cedb429359 100644 --- a/packages-exp/auth-exp/src/api/index.ts +++ b/packages-exp/auth-exp/src/api/index.ts @@ -17,33 +17,29 @@ import { FirebaseError, querystring } from '@firebase/util'; -import { - AUTH_ERROR_FACTORY, - AuthErrorCode, - NamedErrorParams -} from '../core/errors'; -import { fail } from '../core/util/assert'; +import { AuthErrorCode, NamedErrorParams } from '../core/errors'; +import { _createError, _fail } from '../core/util/assert'; import { Delay } from '../core/util/delay'; import { _emulatorUrl } from '../core/util/emulator'; import { FetchProvider } from '../core/util/fetch_provider'; -import { Auth } from '@firebase/auth-types-exp'; -import { Auth as AuthInternal } from '../model/auth'; +import { Auth } from '../model/public_types'; +import { AuthInternal, ConfigInternal } from '../model/auth'; import { IdTokenResponse, TaggedWithTokenResponse } from '../model/id_token'; import { IdTokenMfaResponse } from './authentication/mfa'; import { SERVER_ERROR_MAP, ServerError, ServerErrorMap } from './errors'; -export enum HttpMethod { +export const enum HttpMethod { POST = 'POST', GET = 'GET' } -export enum HttpHeader { +export const enum HttpHeader { CONTENT_TYPE = 'Content-Type', X_FIREBASE_LOCALE = 'X-Firebase-Locale', X_CLIENT_VERSION = 'X-Client-Version' } -export enum Endpoint { +export const enum Endpoint { CREATE_AUTH_URI = '/v1/accounts:createAuthUri', DELETE_ACCOUNT = '/v1/accounts:delete', RESET_PASSWORD = '/v1/accounts:resetPassword', @@ -68,6 +64,19 @@ export enum Endpoint { export const DEFAULT_API_TIMEOUT_MS = new Delay(30_000, 60_000); +export function _addTidIfNecessary( + auth: Auth, + request: T +): T { + if (auth.tenantId && !request.tenantId) { + return { + ...request, + tenantId: auth.tenantId + }; + } + return request; +} + export async function _performApiRequest( auth: Auth, method: HttpMethod, @@ -95,7 +104,10 @@ export async function _performApiRequest( const headers = new (FetchProvider.headers())(); headers.set(HttpHeader.CONTENT_TYPE, 'application/json'); - headers.set(HttpHeader.X_CLIENT_VERSION, auth.config.sdkClientVersion); + headers.set( + HttpHeader.X_CLIENT_VERSION, + (auth as AuthInternal)._getSdkClientVersion() + ); if (auth.languageCode) { headers.set(HttpHeader.X_FIREBASE_LOCALE, auth.languageCode); @@ -121,7 +133,7 @@ export async function _performFetchWithErrorHandling( (auth as AuthInternal)._canInitEmulator = false; const errorMap = { ...SERVER_ERROR_MAP, ...customErrorMap }; try { - const networkTimeout = new NetworkTimeout(auth.name); + const networkTimeout = new NetworkTimeout(auth); const response: Response = await Promise.race>([ fetchFn(), networkTimeout.promise @@ -133,38 +145,35 @@ export async function _performFetchWithErrorHandling( const json = await response.json(); if ('needConfirmation' in json) { - throw makeTaggedError(auth, AuthErrorCode.NEED_CONFIRMATION, json); + throw _makeTaggedError(auth, AuthErrorCode.NEED_CONFIRMATION, json); } - if (response.ok) { + if (response.ok && !('errorMessage' in json)) { return json; } else { - const serverErrorCode = json.error.message.split(' : ')[0] as ServerError; + const errorMessage = response.ok ? json.errorMessage : json.error.message; + const serverErrorCode = errorMessage.split(' : ')[0] as ServerError; if (serverErrorCode === ServerError.FEDERATED_USER_ID_ALREADY_LINKED) { - throw makeTaggedError( + throw _makeTaggedError( auth, AuthErrorCode.CREDENTIAL_ALREADY_IN_USE, json ); } else if (serverErrorCode === ServerError.EMAIL_EXISTS) { - throw makeTaggedError(auth, AuthErrorCode.EMAIL_EXISTS, json); - } - - const authError = errorMap[serverErrorCode]; - if (authError) { - fail(authError, { appName: auth.name }); - } else { - // TODO probably should handle improperly formatted errors as well - // If you see this, add an entry to SERVER_ERROR_MAP for the corresponding error - console.error(`Unexpected API error: ${json.error.message}`); - fail(AuthErrorCode.INTERNAL_ERROR, { appName: auth.name }); + throw _makeTaggedError(auth, AuthErrorCode.EMAIL_EXISTS, json); } + const authError = + errorMap[serverErrorCode] || + ((serverErrorCode + .toLowerCase() + .replace(/[_\s]+/g, '-') as unknown) as AuthErrorCode); + _fail(auth, authError); } } catch (e) { if (e instanceof FirebaseError) { throw e; } - fail(AuthErrorCode.NETWORK_REQUEST_FAILED, { appName: auth.name }); + _fail(auth, AuthErrorCode.NETWORK_REQUEST_FAILED); } } @@ -183,8 +192,7 @@ export async function _performSignInRequest( customErrorMap )) as V; if ('mfaPendingCredential' in serverResponse) { - throw AUTH_ERROR_FACTORY.create(AuthErrorCode.MFA_REQUIRED, { - appName: auth.name, + _fail(auth, AuthErrorCode.MFA_REQUIRED, { serverResponse }); } @@ -204,7 +212,7 @@ export function _getFinalTarget( return `${auth.config.apiScheme}://${base}`; } - return _emulatorUrl(auth.config, base); + return _emulatorUrl(auth.config as ConfigInternal, base); } class NetworkTimeout { @@ -214,11 +222,7 @@ class NetworkTimeout { private timer: any | null = null; readonly promise = new Promise((_, reject) => { this.timer = setTimeout(() => { - return reject( - AUTH_ERROR_FACTORY.create(AuthErrorCode.TIMEOUT, { - appName: this.appName - }) - ); + return reject(_createError(this.auth, AuthErrorCode.TIMEOUT)); }, DEFAULT_API_TIMEOUT_MS.get()); }); @@ -226,7 +230,7 @@ class NetworkTimeout { clearTimeout(this.timer); } - constructor(private readonly appName: string) {} + constructor(private readonly auth: Auth) {} } interface PotentialResponse extends IdTokenResponse { @@ -234,13 +238,13 @@ interface PotentialResponse extends IdTokenResponse { phoneNumber?: string; } -function makeTaggedError( - { name }: Auth, +export function _makeTaggedError( + auth: Auth, code: AuthErrorCode, response: PotentialResponse ): FirebaseError { const errorParams: NamedErrorParams = { - appName: name + appName: auth.name }; if (response.email) { @@ -250,7 +254,9 @@ function makeTaggedError( errorParams.phoneNumber = response.phoneNumber; } - const error = AUTH_ERROR_FACTORY.create(code, errorParams); - (error as TaggedWithTokenResponse)._tokenResponse = response; + const error = _createError(auth, code, errorParams); + + // We know customData is defined on error because errorParams is defined + (error.customData! as TaggedWithTokenResponse)._tokenResponse = response; return error; } diff --git a/packages-exp/auth-exp/src/api/project_config/get_project_config.ts b/packages-exp/auth-exp/src/api/project_config/get_project_config.ts index 178d2aa4f1f..7aaca5d8309 100644 --- a/packages-exp/auth-exp/src/api/project_config/get_project_config.ts +++ b/packages-exp/auth-exp/src/api/project_config/get_project_config.ts @@ -15,22 +15,26 @@ * limitations under the License. */ -import { _performApiRequest, Endpoint, HttpMethod } from '../'; -import { Auth } from '@firebase/auth-types-exp'; +import { _performApiRequest, Endpoint, HttpMethod } from '../index'; +import { Auth } from '../../model/public_types'; -export interface GetProjectConfigRequest {} +export interface GetProjectConfigRequest { + androidPackageName?: string; + iosBundleId?: string; +} export interface GetProjectConfigResponse { authorizedDomains: string[]; } export async function _getProjectConfig( - auth: Auth + auth: Auth, + request: GetProjectConfigRequest = {} ): Promise { return _performApiRequest( auth, HttpMethod.GET, Endpoint.GET_PROJECT_CONFIG, - {} + request ); } diff --git a/packages-exp/auth-exp/src/core/action_code_url.test.ts b/packages-exp/auth-exp/src/core/action_code_url.test.ts index bf571e0def1..9896886f593 100644 --- a/packages-exp/auth-exp/src/core/action_code_url.test.ts +++ b/packages-exp/auth-exp/src/core/action_code_url.test.ts @@ -17,7 +17,7 @@ import { expect } from 'chai'; -import { Operation } from '@firebase/auth-types-exp'; +import { ActionCodeOperation } from '../model/public_types'; import { ActionCodeURL } from './action_code_url'; @@ -32,7 +32,7 @@ describe('core/action_code_url', () => { encodeURIComponent(continueUrl) + '&languageCode=en&tenantId=TENANT_ID&state=bla'; const actionCodeUrl = ActionCodeURL.parseLink(actionLink); - expect(actionCodeUrl!.operation).to.eq(Operation.EMAIL_SIGNIN); + expect(actionCodeUrl!.operation).to.eq(ActionCodeOperation.EMAIL_SIGNIN); expect(actionCodeUrl!.code).to.eq('CODE'); expect(actionCodeUrl!.apiKey).to.eq('API_KEY'); // ContinueUrl should be decoded. @@ -48,7 +48,9 @@ describe('core/action_code_url', () => { 'oobCode=CODE&mode=signIn&apiKey=API_KEY&' + 'languageCode=en'; const actionCodeUrl = ActionCodeURL.parseLink(actionLink); - expect(actionCodeUrl!.operation).to.eq(Operation.EMAIL_SIGNIN); + expect(actionCodeUrl!.operation).to.eq( + ActionCodeOperation.EMAIL_SIGNIN + ); }); it('should identitfy VERIFY_AND_CHANGE_EMAIL', () => { @@ -58,7 +60,7 @@ describe('core/action_code_url', () => { 'languageCode=en'; const actionCodeUrl = ActionCodeURL.parseLink(actionLink); expect(actionCodeUrl!.operation).to.eq( - Operation.VERIFY_AND_CHANGE_EMAIL + ActionCodeOperation.VERIFY_AND_CHANGE_EMAIL ); }); @@ -68,7 +70,9 @@ describe('core/action_code_url', () => { 'oobCode=CODE&mode=verifyEmail&apiKey=API_KEY&' + 'languageCode=en'; const actionCodeUrl = ActionCodeURL.parseLink(actionLink); - expect(actionCodeUrl!.operation).to.eq(Operation.VERIFY_EMAIL); + expect(actionCodeUrl!.operation).to.eq( + ActionCodeOperation.VERIFY_EMAIL + ); }); it('should identitfy RECOVER_EMAIL', () => { @@ -77,7 +81,9 @@ describe('core/action_code_url', () => { 'oobCode=CODE&mode=recoverEmail&apiKey=API_KEY&' + 'languageCode=en'; const actionCodeUrl = ActionCodeURL.parseLink(actionLink); - expect(actionCodeUrl!.operation).to.eq(Operation.RECOVER_EMAIL); + expect(actionCodeUrl!.operation).to.eq( + ActionCodeOperation.RECOVER_EMAIL + ); }); it('should identitfy PASSWORD_RESET', () => { @@ -86,7 +92,9 @@ describe('core/action_code_url', () => { 'oobCode=CODE&mode=resetPassword&apiKey=API_KEY&' + 'languageCode=en'; const actionCodeUrl = ActionCodeURL.parseLink(actionLink); - expect(actionCodeUrl!.operation).to.eq(Operation.PASSWORD_RESET); + expect(actionCodeUrl!.operation).to.eq( + ActionCodeOperation.PASSWORD_RESET + ); }); it('should identitfy REVERT_SECOND_FACTOR_ADDITION', () => { @@ -96,7 +104,7 @@ describe('core/action_code_url', () => { 'languageCode=en'; const actionCodeUrl = ActionCodeURL.parseLink(actionLink); expect(actionCodeUrl!.operation).to.eq( - Operation.REVERT_SECOND_FACTOR_ADDITION + ActionCodeOperation.REVERT_SECOND_FACTOR_ADDITION ); }); }); @@ -106,7 +114,7 @@ describe('core/action_code_url', () => { 'https://www.example.com:8080/finishSignIn?' + 'oobCode=CODE&mode=signIn&apiKey=API_KEY&state=bla'; const actionCodeUrl = ActionCodeURL.parseLink(actionLink); - expect(actionCodeUrl!.operation).to.eq(Operation.EMAIL_SIGNIN); + expect(actionCodeUrl!.operation).to.eq(ActionCodeOperation.EMAIL_SIGNIN); expect(actionCodeUrl!.code).to.eq('CODE'); expect(actionCodeUrl!.apiKey).to.eq('API_KEY'); expect(actionCodeUrl!.continueUrl).to.be.null; @@ -120,7 +128,7 @@ describe('core/action_code_url', () => { 'oobCode=CODE1&mode=signIn&apiKey=API_KEY1&state=bla' + '#oobCode=CODE2&mode=signIn&apiKey=API_KEY2&state=bla'; const actionCodeUrl = ActionCodeURL.parseLink(actionLink); - expect(actionCodeUrl!.operation).to.eq(Operation.EMAIL_SIGNIN); + expect(actionCodeUrl!.operation).to.eq(ActionCodeOperation.EMAIL_SIGNIN); expect(actionCodeUrl!.code).to.eq('CODE1'); expect(actionCodeUrl!.apiKey).to.eq('API_KEY1'); expect(actionCodeUrl!.continueUrl).to.be.null; diff --git a/packages-exp/auth-exp/src/core/action_code_url.ts b/packages-exp/auth-exp/src/core/action_code_url.ts index 38fb4c977eb..123ad6f7dde 100644 --- a/packages-exp/auth-exp/src/core/action_code_url.ts +++ b/packages-exp/auth-exp/src/core/action_code_url.ts @@ -15,16 +15,17 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; -import { AuthErrorCode, AUTH_ERROR_FACTORY } from './errors'; +import { extractQuerystring, querystringDecode } from '@firebase/util'; +import { ActionCodeOperation } from '../model/public_types'; +import { AuthErrorCode } from './errors'; +import { _assert } from './util/assert'; /** * Enums for fields in URL query string. * * @enum {string} - * @internal */ -enum QueryField { +const enum QueryField { API_KEY = 'apiKey', CODE = 'oobCode', CONTINUE_URL = 'continueUrl', @@ -33,66 +34,83 @@ enum QueryField { TENANT_ID = 'tenantId' } -/** - * Map from mode string in action code URL to Action Code Info operation. - * - * @internal - */ -const MODE_TO_OPERATION_MAP: { [key: string]: externs.Operation } = { - 'recoverEmail': externs.Operation.RECOVER_EMAIL, - 'resetPassword': externs.Operation.PASSWORD_RESET, - 'signIn': externs.Operation.EMAIL_SIGNIN, - 'verifyEmail': externs.Operation.VERIFY_EMAIL, - 'verifyAndChangeEmail': externs.Operation.VERIFY_AND_CHANGE_EMAIL, - 'revertSecondFactorAddition': externs.Operation.REVERT_SECOND_FACTOR_ADDITION -}; - /** * Maps the mode string in action code URL to Action Code Info operation. * * @param mode - * @internal */ -function parseMode(mode: string | null): externs.Operation | null { - return mode ? MODE_TO_OPERATION_MAP[mode] || null : null; +function parseMode(mode: string | null): ActionCodeOperation | null { + switch (mode) { + case 'recoverEmail': + return ActionCodeOperation.RECOVER_EMAIL; + case 'resetPassword': + return ActionCodeOperation.PASSWORD_RESET; + case 'signIn': + return ActionCodeOperation.EMAIL_SIGNIN; + case 'verifyEmail': + return ActionCodeOperation.VERIFY_EMAIL; + case 'verifyAndChangeEmail': + return ActionCodeOperation.VERIFY_AND_CHANGE_EMAIL; + case 'revertSecondFactorAddition': + return ActionCodeOperation.REVERT_SECOND_FACTOR_ADDITION; + default: + return null; + } } /** * Helper to parse FDL links * * @param url - * @internal */ function parseDeepLink(url: string): string { - const uri = new URL(url); - const link = uri.searchParams.get('link'); + const link = querystringDecode(extractQuerystring(url))['link']; + // Double link case (automatic redirect). - const doubleDeepLink = link ? new URL(link).searchParams.get('link') : null; + const doubleDeepLink = link + ? querystringDecode(extractQuerystring(link))['deep_link_id'] + : null; // iOS custom scheme links. - const iOSDeepLink = uri.searchParams.get('deep_link_id'); + const iOSDeepLink = querystringDecode(extractQuerystring(url))[ + 'deep_link_id' + ]; const iOSDoubleDeepLink = iOSDeepLink - ? new URL(iOSDeepLink).searchParams.get('link') + ? querystringDecode(extractQuerystring(iOSDeepLink))['link'] : null; return iOSDoubleDeepLink || iOSDeepLink || doubleDeepLink || link || url; } -/** - * {@inheritDoc @firebase/auth-types-exp#ActionCodeURL} - * +/** + * A utility class to parse email action URLs such as password reset, email verification, + * email link sign in, etc. + * * @public */ -export class ActionCodeURL implements externs.ActionCodeURL { - /** {@inheritDoc @firebase/auth-types-exp#ActionCodeURL.apiKey} */ +export class ActionCodeURL { + /** + * The API key of the email action link. + */ readonly apiKey: string; - /** {@inheritDoc @firebase/auth-types-exp#ActionCodeURL.code} */ + /** + * The action code of the email action link. + */ readonly code: string; - /** {@inheritDoc @firebase/auth-types-exp#ActionCodeURL.continueUrl} */ + /** + * The continue URL of the email action link. Null if not provided. + */ readonly continueUrl: string | null; - /** {@inheritDoc @firebase/auth-types-exp#ActionCodeURL.languageCode} */ + /** + * The language code of the email action link. Null if not provided. + */ readonly languageCode: string | null; - /** {@inheritDoc @firebase/auth-types-exp#ActionCodeURL.operation} */ - readonly operation: externs.Operation; - /** {@inheritDoc @firebase/auth-types-exp#ActionCodeURL.tenantId} */ + /** + * The action performed by the email action link. It returns from one of the types from + * {@link ActionCodeInfo} + */ + readonly operation: string; + /** + * The tenant ID of the email action link. Null if the email action is from the parent project. + */ readonly tenantId: string | null; /** @@ -102,24 +120,30 @@ export class ActionCodeURL implements externs.ActionCodeURL { * @internal */ constructor(actionLink: string) { - const uri = new URL(actionLink); - const apiKey = uri.searchParams.get(QueryField.API_KEY); - const code = uri.searchParams.get(QueryField.CODE); - const operation = parseMode(uri.searchParams.get(QueryField.MODE)); + const searchParams = querystringDecode(extractQuerystring(actionLink)); + const apiKey = searchParams[QueryField.API_KEY] ?? null; + const code = searchParams[QueryField.CODE] ?? null; + const operation = parseMode(searchParams[QueryField.MODE] ?? null); // Validate API key, code and mode. - if (!apiKey || !code || !operation) { - throw AUTH_ERROR_FACTORY.create(AuthErrorCode.ARGUMENT_ERROR, {}); - } + _assert(apiKey && code && operation, AuthErrorCode.ARGUMENT_ERROR); this.apiKey = apiKey; this.operation = operation; this.code = code; - this.continueUrl = uri.searchParams.get(QueryField.CONTINUE_URL); - this.languageCode = uri.searchParams.get(QueryField.LANGUAGE_CODE); - this.tenantId = uri.searchParams.get(QueryField.TENANT_ID); + this.continueUrl = searchParams[QueryField.CONTINUE_URL] ?? null; + this.languageCode = searchParams[QueryField.LANGUAGE_CODE] ?? null; + this.tenantId = searchParams[QueryField.TENANT_ID] ?? null; } - /** {@inheritDoc @firebase/auth-types-exp#ActionCodeURL.parseLink} */ - static parseLink(link: string): externs.ActionCodeURL | null { + /** + * Parses the email action link string and returns an {@link ActionCodeURL} if the link is valid, + * otherwise returns null. + * + * @param link - The email action link string. + * @returns The ActionCodeURL object, or null if the link is invalid. + * + * @public + */ + static parseLink(link: string): ActionCodeURL | null { const actionLink = parseDeepLink(link); try { return new ActionCodeURL(actionLink); @@ -129,11 +153,12 @@ export class ActionCodeURL implements externs.ActionCodeURL { } } -/** - * {@inheritDoc @firebase/auth-types-exp#ActionCodeURL.parseLink} - * +/** + * Parses the email action link string and returns an {@link ActionCodeURL} if + * the link is valid, otherwise returns null. + * * @public */ -export function parseActionCodeURL(link: string): externs.ActionCodeURL | null { +export function parseActionCodeURL(link: string): ActionCodeURL | null { return ActionCodeURL.parseLink(link); } diff --git a/packages-exp/auth-exp/src/core/auth/auth_event_manager.test.ts b/packages-exp/auth-exp/src/core/auth/auth_event_manager.test.ts index 08757c006b6..6e6bfc0128a 100644 --- a/packages-exp/auth-exp/src/core/auth/auth_event_manager.test.ts +++ b/packages-exp/auth-exp/src/core/auth/auth_event_manager.test.ts @@ -18,6 +18,7 @@ import { expect, use } from 'chai'; import * as sinon from 'sinon'; import * as sinonChai from 'sinon-chai'; +import { testAuth } from '../../../test/helpers/mock_auth'; import { AuthEvent, @@ -30,7 +31,7 @@ import { AuthEventManager } from './auth_event_manager'; use(sinonChai); -describe('src/core/auth/auth_event_manager', () => { +describe('core/auth/auth_event_manager', () => { let manager: AuthEventManager; function makeConsumer( @@ -53,8 +54,8 @@ describe('src/core/auth/auth_event_manager', () => { } as AuthEvent; } - beforeEach(() => { - manager = new AuthEventManager('app-name'); + beforeEach(async () => { + manager = new AuthEventManager(await testAuth()); }); it('multiple consumers may be registered for one event type', () => { diff --git a/packages-exp/auth-exp/src/core/auth/auth_event_manager.ts b/packages-exp/auth-exp/src/core/auth/auth_event_manager.ts index 86bc0912b42..1a5347f5308 100644 --- a/packages-exp/auth-exp/src/core/auth/auth_event_manager.ts +++ b/packages-exp/auth-exp/src/core/auth/auth_event_manager.ts @@ -21,7 +21,9 @@ import { AuthEventType, EventManager } from '../../model/popup_redirect'; -import { AUTH_ERROR_FACTORY, AuthErrorCode } from '../errors'; +import { AuthErrorCode } from '../errors'; +import { AuthInternal } from '../../model/auth'; +import { _createError } from '../util/assert'; // The amount of time to store the UIDs of seen events; this is // set to 10 min by default @@ -30,11 +32,11 @@ const EVENT_DUPLICATION_CACHE_DURATION_MS = 10 * 60 * 1000; export class AuthEventManager implements EventManager { private readonly cachedEventUids: Set = new Set(); private readonly consumers: Set = new Set(); - private queuedRedirectEvent: AuthEvent | null = null; - private hasHandledPotentialRedirect = false; + protected queuedRedirectEvent: AuthEvent | null = null; + protected hasHandledPotentialRedirect = false; private lastProcessedEventTime = Date.now(); - constructor(private readonly appName: string) {} + constructor(private readonly auth: AuthInternal) {} registerConsumer(authEventConsumer: AuthEventConsumer): void { this.consumers.add(authEventConsumer); @@ -90,11 +92,7 @@ export class AuthEventManager implements EventManager { const code = (event.error.code?.split('auth/')[1] as AuthErrorCode) || AuthErrorCode.INTERNAL_ERROR; - consumer.onError( - AUTH_ERROR_FACTORY.create(code, { - appName: this.appName - }) - ); + consumer.onError(_createError(this.auth, code)); } else { consumer.onAuthEvent(event); } diff --git a/packages-exp/auth-exp/src/core/auth/auth_impl.test.ts b/packages-exp/auth-exp/src/core/auth/auth_impl.test.ts index b880e69a53e..2799bf62719 100644 --- a/packages-exp/auth-exp/src/core/auth/auth_impl.test.ts +++ b/packages-exp/auth-exp/src/core/auth/auth_impl.test.ts @@ -20,28 +20,20 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as sinon from 'sinon'; import * as sinonChai from 'sinon-chai'; -import { FirebaseApp } from '@firebase/app-types-exp'; +import { FirebaseApp } from '@firebase/app-exp'; import { FirebaseError } from '@firebase/util'; -import { endpointUrl, mockEndpoint } from '../../../test/helpers/api/helper'; -import { testAuth, TestAuth, testUser } from '../../../test/helpers/mock_auth'; -import * as fetch from '../../../test/helpers/mock_fetch'; -import { Endpoint } from '../../api'; -import { Auth } from '../../model/auth'; -import { User } from '../../model/user'; -import { Persistence } from '../persistence'; +import { testAuth, testUser } from '../../../test/helpers/mock_auth'; +import { AuthInternal } from '../../model/auth'; +import { UserInternal } from '../../model/user'; +import { PersistenceInternal } from '../persistence'; import { inMemoryPersistence } from '../persistence/in_memory'; import { _getInstance } from '../util/instantiator'; import * as navigator from '../util/navigator'; import * as reload from '../user/reload'; -import { - _castAuth, - AuthImpl, - DEFAULT_API_HOST, - DEFAULT_API_SCHEME, - DEFAULT_TOKEN_API_HOST -} from './auth_impl'; +import { AuthImpl, DefaultConfig } from './auth_impl'; import { _initializeAuthInstance } from './initialize'; +import { ClientPlatform } from '../util/version'; use(sinonChai); use(chaiAsPromised); @@ -56,16 +48,17 @@ const FAKE_APP: FirebaseApp = { }; describe('core/auth/auth_impl', () => { - let auth: Auth; - let persistenceStub: sinon.SinonStubbedInstance; + let auth: AuthInternal; + let persistenceStub: sinon.SinonStubbedInstance; beforeEach(async () => { persistenceStub = sinon.stub(_getInstance(inMemoryPersistence)); const authImpl = new AuthImpl(FAKE_APP, { apiKey: FAKE_APP.options.apiKey!, - apiHost: DEFAULT_API_HOST, - apiScheme: DEFAULT_API_SCHEME, - tokenApiHost: DEFAULT_TOKEN_API_HOST, + apiHost: DefaultConfig.API_HOST, + apiScheme: DefaultConfig.API_SCHEME, + tokenApiHost: DefaultConfig.TOKEN_API_HOST, + clientPlatform: ClientPlatform.BROWSER, sdkClientVersion: 'v' }); @@ -77,11 +70,31 @@ describe('core/auth/auth_impl', () => { describe('#updateCurrentUser', () => { it('sets the field on the auth object', async () => { + const user = testUser(auth, 'uid'); + await auth._updateCurrentUser(user); + expect(auth.currentUser).to.eq(user); + }); + + it('public version makes a copy', async () => { const user = testUser(auth, 'uid'); await auth.updateCurrentUser(user); + + // currentUser should deeply equal the user passed in, but should be a + // different block in memory. + expect(auth.currentUser).not.to.eq(user); expect(auth.currentUser).to.eql(user); }); + it('public version throws if the auth is mismatched', async () => { + const auth2 = await testAuth(); + Object.assign(auth2.config, { apiKey: 'not-the-right-auth' }); + const user = testUser(auth2, 'uid'); + await expect(auth.updateCurrentUser(user)).to.be.rejectedWith( + FirebaseError, + 'auth/invalid-user-token' + ); + }); + it('orders async operations correctly', async () => { const users = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(n => { return testUser(auth, `${n}`); @@ -94,7 +107,7 @@ describe('core/auth/auth_impl', () => { }); }); - await Promise.all(users.map(u => auth.updateCurrentUser(u))); + await Promise.all(users.map(u => auth._updateCurrentUser(u))); for (let i = 0; i < 10; i++) { expect(persistenceStub._set.getCall(i)).to.have.been.calledWith( sinon.match.any, @@ -104,14 +117,14 @@ describe('core/auth/auth_impl', () => { }); it('setting to null triggers a remove call', async () => { - await auth.updateCurrentUser(null); + await auth._updateCurrentUser(null); expect(persistenceStub._remove).to.have.been.called; }); it('should throw an error if the user is from a different tenant', async () => { const user = testUser(auth, 'uid'); user.tenantId = 'other-tenant-id'; - await expect(auth.updateCurrentUser(user)).to.be.rejectedWith( + await expect(auth._updateCurrentUser(user)).to.be.rejectedWith( FirebaseError, '(auth/tenant-id-mismatch)' ); @@ -120,7 +133,7 @@ describe('core/auth/auth_impl', () => { describe('#signOut', () => { it('sets currentUser to null, calls remove', async () => { - await auth.updateCurrentUser(testUser(auth, 'test')); + await auth._updateCurrentUser(testUser(auth, 'test')); await auth.signOut(); expect(persistenceStub._remove).to.have.been.called; expect(auth.currentUser).to.be.null; @@ -192,7 +205,7 @@ describe('core/auth/auth_impl', () => { }); describe('user logs in/out, tokens refresh', () => { - let user: User; + let user: UserInternal; let authStateCallback: sinon.SinonSpy; let idTokenCallback: sinon.SinonSpy; @@ -206,18 +219,18 @@ describe('core/auth/auth_impl', () => { beforeEach(async () => { auth.onAuthStateChanged(authStateCallback); auth.onIdTokenChanged(idTokenCallback); - await auth.updateCurrentUser(null); + await auth._updateCurrentUser(null); authStateCallback.resetHistory(); idTokenCallback.resetHistory(); }); it('onAuthStateChange triggers on log in', async () => { - await auth.updateCurrentUser(user); + await auth._updateCurrentUser(user); expect(authStateCallback).to.have.been.calledWith(user); }); it('onIdTokenChange triggers on log in', async () => { - await auth.updateCurrentUser(user); + await auth._updateCurrentUser(user); expect(idTokenCallback).to.have.been.calledWith(user); }); }); @@ -226,36 +239,36 @@ describe('core/auth/auth_impl', () => { beforeEach(async () => { auth.onAuthStateChanged(authStateCallback); auth.onIdTokenChanged(idTokenCallback); - await auth.updateCurrentUser(user); + await auth._updateCurrentUser(user); authStateCallback.resetHistory(); idTokenCallback.resetHistory(); }); it('onAuthStateChange triggers on log out', async () => { - await auth.updateCurrentUser(null); + await auth._updateCurrentUser(null); expect(authStateCallback).to.have.been.calledWith(null); }); it('onIdTokenChange triggers on log out', async () => { - await auth.updateCurrentUser(null); + await auth._updateCurrentUser(null); expect(idTokenCallback).to.have.been.calledWith(null); }); it('onAuthStateChange does not trigger for user props change', async () => { user.photoURL = 'blah'; - await auth.updateCurrentUser(user); + await auth._updateCurrentUser(user); expect(authStateCallback).not.to.have.been.called; }); it('onIdTokenChange triggers for user props change', async () => { user.photoURL = 'hey look I changed'; - await auth.updateCurrentUser(user); + await auth._updateCurrentUser(user); expect(idTokenCallback).to.have.been.calledWith(user); }); it('onAuthStateChange triggers if uid changes', async () => { const newUser = testUser(auth, 'different-uid'); - await auth.updateCurrentUser(newUser); + await auth._updateCurrentUser(newUser); expect(authStateCallback).to.have.been.calledWith(newUser); }); }); @@ -265,11 +278,11 @@ describe('core/auth/auth_impl', () => { const cb2 = sinon.spy(); auth.onAuthStateChanged(cb1); auth.onAuthStateChanged(cb2); - await auth.updateCurrentUser(null); + await auth._updateCurrentUser(null); cb1.resetHistory(); cb2.resetHistory(); - await auth.updateCurrentUser(user); + await auth._updateCurrentUser(user); expect(cb1).to.have.been.calledWith(user); expect(cb2).to.have.been.calledWith(user); }); @@ -279,11 +292,11 @@ describe('core/auth/auth_impl', () => { const cb2 = sinon.spy(); auth.onIdTokenChanged(cb1); auth.onIdTokenChanged(cb2); - await auth.updateCurrentUser(null); + await auth._updateCurrentUser(null); cb1.resetHistory(); cb2.resetHistory(); - await auth.updateCurrentUser(user); + await auth._updateCurrentUser(user); expect(cb1).to.have.been.calledWith(user); expect(cb2).to.have.been.calledWith(user); }); @@ -299,7 +312,7 @@ describe('core/auth/auth_impl', () => { idTokenCallback = sinon.spy(); auth.onAuthStateChanged(authStateCallback); auth.onIdTokenChanged(idTokenCallback); - await auth.updateCurrentUser(null); // force event handlers to clear out + await auth._updateCurrentUser(null); // force event handlers to clear out authStateCallback.resetHistory(); idTokenCallback.resetHistory(); }); @@ -315,7 +328,7 @@ describe('core/auth/auth_impl', () => { }); context('now logged in', () => { - let user: User; + let user: UserInternal; beforeEach(() => { user = testUser(auth, 'uid'); @@ -333,11 +346,11 @@ describe('core/auth/auth_impl', () => { }); context('previously logged in', () => { - let user: User; + let user: UserInternal; beforeEach(async () => { user = testUser(auth, 'uid', undefined, true); - await auth.updateCurrentUser(user); + await auth._updateCurrentUser(user); authStateCallback.resetHistory(); idTokenCallback.resetHistory(); }); @@ -389,9 +402,9 @@ describe('core/auth/auth_impl', () => { await auth._onStorageEvent(); expect(auth.currentUser?.uid).to.eq(user.uid); - expect((auth.currentUser as User)?.stsTokenManager.accessToken).to.eq( - 'new-access-token' - ); + expect( + (auth.currentUser as UserInternal)?.stsTokenManager.accessToken + ).to.eq('new-access-token'); expect(authStateCallback).not.to.have.been.called; expect(idTokenCallback).to.have.been.called; }); @@ -420,9 +433,10 @@ describe('core/auth/auth_impl', () => { it('prevents initialization from completing', async () => { const authImpl = new AuthImpl(FAKE_APP, { apiKey: FAKE_APP.options.apiKey!, - apiHost: DEFAULT_API_HOST, - apiScheme: DEFAULT_API_SCHEME, - tokenApiHost: DEFAULT_TOKEN_API_HOST, + apiHost: DefaultConfig.API_HOST, + apiScheme: DefaultConfig.API_SCHEME, + tokenApiHost: DefaultConfig.TOKEN_API_HOST, + clientPlatform: ClientPlatform.BROWSER, sdkClientVersion: 'v' }); @@ -431,7 +445,7 @@ describe('core/auth/auth_impl', () => { ); await authImpl._delete(); await authImpl._initializeWithPersistence([ - persistenceStub as Persistence + persistenceStub as PersistenceInternal ]); expect(authImpl.currentUser).to.be.null; }); @@ -442,70 +456,8 @@ describe('core/auth/auth_impl', () => { await Promise.resolve(); spy.resetHistory(); await (auth as AuthImpl)._delete(); - await auth.updateCurrentUser(testUser(auth, 'blah')); + await auth._updateCurrentUser(testUser(auth, 'blah')); expect(spy).not.to.have.been.called; }); }); }); - -// These tests are separate because they are using a different auth with -// separate setup and config -describe('core/auth/auth_impl useEmulator', () => { - let auth: TestAuth; - let user: User; - let normalEndpoint: fetch.Route; - let emulatorEndpoint: fetch.Route; - - beforeEach(async () => { - auth = await testAuth(); - user = testUser(_castAuth(auth), 'uid', 'email', true); - fetch.setUp(); - normalEndpoint = mockEndpoint(Endpoint.DELETE_ACCOUNT, {}); - emulatorEndpoint = fetch.mock( - `http://localhost:2020/${endpointUrl(Endpoint.DELETE_ACCOUNT).replace( - /^.*:\/\//, - '' - )}`, - {} - ); - }); - - afterEach(() => { - fetch.tearDown(); - }); - - context('useEmulator', () => { - it('fails if a network request has already been made', async () => { - await user.delete(); - expect(() => auth.useEmulator('http://localhost:2020')).to.throw( - FirebaseError, - 'auth/emulator-config-failed' - ); - }); - - it('updates the endpoint appropriately', async () => { - auth.useEmulator('http://localhost:2020'); - await user.delete(); - expect(normalEndpoint.calls.length).to.eq(0); - expect(emulatorEndpoint.calls.length).to.eq(1); - }); - }); - - context('toJSON', () => { - it('works when theres no current user', () => { - expect(JSON.stringify(auth)).to.eq( - '{"apiKey":"test-api-key","authDomain":"localhost","appName":"test-app"}' - ); - }); - - it('also stringifies the current user', () => { - auth.currentUser = ({ - toJSON: (): object => ({ foo: 'bar' }) - } as unknown) as User; - expect(JSON.stringify(auth)).to.eq( - '{"apiKey":"test-api-key","authDomain":"localhost",' + - '"appName":"test-app","currentUser":{"foo":"bar"}}' - ); - }); - }); -}); diff --git a/packages-exp/auth-exp/src/core/auth/auth_impl.ts b/packages-exp/auth-exp/src/core/auth/auth_impl.ts index 5c00cec7a8c..4f3344bb77b 100644 --- a/packages-exp/auth-exp/src/core/auth/auth_impl.ts +++ b/packages-exp/auth-exp/src/core/auth/auth_impl.ts @@ -15,49 +15,71 @@ * limitations under the License. */ -import { _FirebaseService, FirebaseApp } from '@firebase/app-types-exp'; -import * as externs from '@firebase/auth-types-exp'; +import { _FirebaseService, FirebaseApp } from '@firebase/app-exp'; import { + Auth, + AuthErrorMap, + AuthSettings, + EmulatorConfig, + NextOrObserver, + Persistence, + PopupRedirectResolver, + User, + UserCredential, CompleteFn, - createSubscribe, ErrorFn, NextFn, - Observer, - Subscribe, Unsubscribe +} from '../../model/public_types'; +import { + createSubscribe, + ErrorFactory, + getModularInstance, + Observer, + Subscribe } from '@firebase/util'; -import { Auth, ConfigInternal } from '../../model/auth'; -import { PopupRedirectResolver } from '../../model/popup_redirect'; -import { User } from '../../model/user'; -import { AuthErrorCode } from '../errors'; -import { Persistence } from '../persistence'; +import { AuthInternal, ConfigInternal } from '../../model/auth'; +import { PopupRedirectResolverInternal } from '../../model/popup_redirect'; +import { UserInternal } from '../../model/user'; +import { + AuthErrorCode, + AuthErrorParams, + ErrorMapRetriever, + _DEFAULT_AUTH_ERROR_FACTORY +} from '../errors'; +import { PersistenceInternal } from '../persistence'; import { - _REDIRECT_USER_KEY_NAME, + KeyName, PersistenceUserManager } from '../persistence/persistence_user_manager'; import { _reloadWithoutSaving } from '../user/reload'; -import { assert } from '../util/assert'; +import { _assert } from '../util/assert'; import { _getInstance } from '../util/instantiator'; import { _getUserLanguage } from '../util/navigator'; +import { _getClientVersion } from '../util/version'; interface AsyncAction { (): Promise; } -export const DEFAULT_TOKEN_API_HOST = 'securetoken.googleapis.com'; -export const DEFAULT_API_HOST = 'identitytoolkit.googleapis.com'; -export const DEFAULT_API_SCHEME = 'https'; +export const enum DefaultConfig { + TOKEN_API_HOST = 'securetoken.googleapis.com', + API_HOST = 'identitytoolkit.googleapis.com', + API_SCHEME = 'https' +} -export class AuthImpl implements Auth, _FirebaseService { - currentUser: externs.User | null = null; +export class AuthImpl implements AuthInternal, _FirebaseService { + currentUser: User | null = null; + emulatorConfig: EmulatorConfig | null = null; private operations = Promise.resolve(); private persistenceManager?: PersistenceUserManager; private redirectPersistenceManager?: PersistenceUserManager; - private authStateSubscription = new Subscription(this); - private idTokenSubscription = new Subscription(this); - private redirectUser: User | null = null; + private authStateSubscription = new Subscription(this); + private idTokenSubscription = new Subscription(this); + private redirectUser: UserInternal | null = null; private isProactiveRefreshEnabled = false; + private redirectInitializerError: Error | null = null; // Any network calls will set this to true and prevent subsequent emulator // initialization @@ -65,28 +87,38 @@ export class AuthImpl implements Auth, _FirebaseService { _isInitialized = false; _deleted = false; _initializationPromise: Promise | null = null; - _popupRedirectResolver: PopupRedirectResolver | null = null; + _popupRedirectResolver: PopupRedirectResolverInternal | null = null; + _errorFactory: ErrorFactory< + AuthErrorCode, + AuthErrorParams + > = _DEFAULT_AUTH_ERROR_FACTORY; readonly name: string; // Tracks the last notified UID for state change listeners to prevent - // repeated calls to the callbacks - private lastNotifiedUid: string | undefined = undefined; + // repeated calls to the callbacks. Undefined means it's never been + // called, whereas null means it's been called with a signed out user + private lastNotifiedUid: string | null | undefined = undefined; languageCode: string | null = null; tenantId: string | null = null; - settings: externs.AuthSettings = { appVerificationDisabledForTesting: false }; + settings: AuthSettings = { appVerificationDisabledForTesting: false }; constructor( public readonly app: FirebaseApp, public readonly config: ConfigInternal ) { this.name = app.name; + this.clientVersion = config.sdkClientVersion; } _initializeWithPersistence( - persistenceHierarchy: Persistence[], - popupRedirectResolver?: externs.PopupRedirectResolver + persistenceHierarchy: PersistenceInternal[], + popupRedirectResolver?: PopupRedirectResolver ): Promise { + if (popupRedirectResolver) { + this._popupRedirectResolver = _getInstance(popupRedirectResolver); + } + // Have to check for app deletion throughout initialization (after each // promise resolution) this._initializationPromise = this.queue(async () => { @@ -94,10 +126,6 @@ export class AuthImpl implements Auth, _FirebaseService { return; } - if (popupRedirectResolver) { - this._popupRedirectResolver = _getInstance(popupRedirectResolver); - } - this.persistenceManager = await PersistenceUserManager.create( this, persistenceHierarchy @@ -107,17 +135,27 @@ export class AuthImpl implements Auth, _FirebaseService { return; } - await this.initializeCurrentUser(); + // Initialize the resolver early if necessary (only applicable to web: + // this will cause the iframe to load immediately in certain cases) + if (this._popupRedirectResolver?._shouldInitProactively) { + await this._popupRedirectResolver._initialize(this); + } + + await this.initializeCurrentUser(popupRedirectResolver); if (this._deleted) { return; } this._isInitialized = true; - this.notifyAuthListeners(); }); - return this._initializationPromise; + // After initialization completes, throw any error caused by redirect flow + return this._initializationPromise.then(() => { + if (this.redirectInitializerError) { + throw this.redirectInitializerError; + } + }); } /** @@ -138,7 +176,7 @@ export class AuthImpl implements Auth, _FirebaseService { // If the same user is to be synchronized. if (this.currentUser && user && this.currentUser.uid === user.uid) { // Data update, simply copy data changes. - this._currentUser._copy(user); + this._currentUser._assign(user); // If tokens changed from previous user tokens, this will trigger // notifyAuthListeners_. await this.currentUser.getIdToken(); @@ -146,25 +184,45 @@ export class AuthImpl implements Auth, _FirebaseService { } // Update current Auth state. Either a new login or logout. - await this.updateCurrentUser(user); - // Notify external Auth changes of Auth change event. - this.notifyAuthListeners(); + await this._updateCurrentUser(user); } - private async initializeCurrentUser(): Promise { - const storedUser = (await this.assertedPersistence.getCurrentUser()) as User | null; + private async initializeCurrentUser( + popupRedirectResolver?: PopupRedirectResolver + ): Promise { + // First check to see if we have a pending redirect event. + let storedUser = (await this.assertedPersistence.getCurrentUser()) as UserInternal | null; + if (popupRedirectResolver && this.config.authDomain) { + await this.getOrInitRedirectPersistenceManager(); + const redirectUserEventId = this.redirectUser?._redirectEventId; + const storedUserEventId = storedUser?._redirectEventId; + const result = await this.tryRedirectSignIn(popupRedirectResolver); + + // If the stored user (i.e. the old "currentUser") has a redirectId that + // matches the redirect user, then we want to initially sign in with the + // new user object from result. + // TODO(samgho): More thoroughly test all of this + if ( + (!redirectUserEventId || redirectUserEventId === storedUserEventId) && + result?.user + ) { + storedUser = result.user as UserInternal; + } + } + + // If no user in persistence, there is no current user. Set to null. if (!storedUser) { - return this.directlySetCurrentUser(storedUser); + return this.directlySetCurrentUser(null); } if (!storedUser._redirectEventId) { // This isn't a redirect user, we can reload and bail + // This will also catch the redirected user, if available, as that method + // strips the _redirectEventId return this.reloadAndSetCurrentUserOrClear(storedUser); } - assert(this._popupRedirectResolver, AuthErrorCode.ARGUMENT_ERROR, { - appName: this.name - }); + _assert(this._popupRedirectResolver, this, AuthErrorCode.ARGUMENT_ERROR); await this.getOrInitRedirectPersistenceManager(); // If the redirect user's event ID matches the current user's event ID, @@ -180,7 +238,45 @@ export class AuthImpl implements Auth, _FirebaseService { return this.reloadAndSetCurrentUserOrClear(storedUser); } - private async reloadAndSetCurrentUserOrClear(user: User): Promise { + private async tryRedirectSignIn( + redirectResolver: PopupRedirectResolver + ): Promise { + // The redirect user needs to be checked (and signed in if available) + // during auth initialization. All of the normal sign in and link/reauth + // flows call back into auth and push things onto the promise queue. We + // need to await the result of the redirect sign in *inside the promise + // queue*. This presents a problem: we run into deadlock. See: + // ┌> [Initialization] ─────┐ + // ┌> [] │ + // └─ [getRedirectResult] <─┘ + // where [] are tasks on the queue and arrows denote awaits + // Initialization will never complete because it's waiting on something + // that's waiting for initialization to complete! + // + // Instead, this method calls getRedirectResult() (stored in + // _completeRedirectFn) with an optional parameter that instructs all of + // the underlying auth operations to skip anything that mutates auth state. + + let result: UserCredential | null = null; + try { + // We know this._popupRedirectResolver is set since redirectResolver + // is passed in. The _completeRedirectFn expects the unwrapped extern. + result = await this._popupRedirectResolver!._completeRedirectFn( + this, + redirectResolver, + true + ); + } catch (e) { + this.redirectInitializerError = e; + await this._setRedirectUser(null); + } + + return result; + } + + private async reloadAndSetCurrentUserOrClear( + user: UserInternal + ): Promise { try { await _reloadWithoutSaving(user); } catch (e) { @@ -198,32 +294,40 @@ export class AuthImpl implements Auth, _FirebaseService { this.languageCode = _getUserLanguage(); } - useEmulator(url: string): void { - assert(this._canInitEmulator, AuthErrorCode.EMULATOR_CONFIG_FAILED, { - appName: this.name - }); - - this.config.emulator = { url }; - this.settings.appVerificationDisabledForTesting = true; - } - async _delete(): Promise { this._deleted = true; } - async updateCurrentUser(user: externs.User | null): Promise { + async updateCurrentUser(userExtern: User | null): Promise { + // The public updateCurrentUser method needs to make a copy of the user, + // and also check that the project matches + const user = userExtern + ? (getModularInstance(userExtern) as UserInternal) + : null; + if (user) { + _assert( + user.auth.config.apiKey === this.config.apiKey, + this, + AuthErrorCode.INVALID_AUTH + ); + } + return this._updateCurrentUser(user && user._clone(this)); + } + + async _updateCurrentUser(user: User | null): Promise { if (this._deleted) { return; } if (user) { - assert( + _assert( this.tenantId === user.tenantId, - AuthErrorCode.TENANT_ID_MISMATCH, - { appName: this.name } + this, + AuthErrorCode.TENANT_ID_MISMATCH ); } + return this.queue(async () => { - await this.directlySetCurrentUser(user as User | null); + await this.directlySetCurrentUser(user as UserInternal | null); this.notifyAuthListeners(); }); } @@ -234,17 +338,29 @@ export class AuthImpl implements Auth, _FirebaseService { await this._setRedirectUser(null); } - return this.updateCurrentUser(null); + return this._updateCurrentUser(null); } - setPersistence(persistence: externs.Persistence): Promise { + setPersistence(persistence: Persistence): Promise { return this.queue(async () => { await this.assertedPersistence.setPersistence(_getInstance(persistence)); }); } + _getPersistence(): string { + return this.assertedPersistence.persistence.type; + } + + _updateErrorMap(errorMap: AuthErrorMap): void { + this._errorFactory = new ErrorFactory( + 'auth', + 'Firebase', + (errorMap as ErrorMapRetriever)() + ); + } + onAuthStateChanged( - nextOrObserver: externs.NextOrObserver, + nextOrObserver: NextOrObserver, error?: ErrorFn, completed?: CompleteFn ): Unsubscribe { @@ -257,7 +373,7 @@ export class AuthImpl implements Auth, _FirebaseService { } onIdTokenChanged( - nextOrObserver: externs.NextOrObserver, + nextOrObserver: NextOrObserver, error?: ErrorFn, completed?: CompleteFn ): Unsubscribe { @@ -279,8 +395,8 @@ export class AuthImpl implements Auth, _FirebaseService { } async _setRedirectUser( - user: User | null, - popupRedirectResolver?: externs.PopupRedirectResolver + user: UserInternal | null, + popupRedirectResolver?: PopupRedirectResolver ): Promise { const redirectManager = await this.getOrInitRedirectPersistenceManager( popupRedirectResolver @@ -291,17 +407,17 @@ export class AuthImpl implements Auth, _FirebaseService { } private async getOrInitRedirectPersistenceManager( - popupRedirectResolver?: externs.PopupRedirectResolver + popupRedirectResolver?: PopupRedirectResolver ): Promise { if (!this.redirectPersistenceManager) { - const resolver: PopupRedirectResolver | null = + const resolver: PopupRedirectResolverInternal | null = (popupRedirectResolver && _getInstance(popupRedirectResolver)) || this._popupRedirectResolver; - assert(resolver, AuthErrorCode.ARGUMENT_ERROR, { appName: this.name }); + _assert(resolver, this, AuthErrorCode.ARGUMENT_ERROR); this.redirectPersistenceManager = await PersistenceUserManager.create( this, [_getInstance(resolver._redirectPersistence)], - _REDIRECT_USER_KEY_NAME + KeyName.REDIRECT_USER ); this.redirectUser = await this.redirectPersistenceManager.getCurrentUser(); } @@ -309,9 +425,12 @@ export class AuthImpl implements Auth, _FirebaseService { return this.redirectPersistenceManager; } - async _redirectUserForId(id: string): Promise { - // Make sure we've cleared any pending ppersistence actions - await this.queue(async () => {}); + async _redirectUserForId(id: string): Promise { + // Make sure we've cleared any pending persistence actions if we're not in + // the initializer + if (this._isInitialized) { + await this.queue(async () => {}); + } if (this._currentUser?._redirectEventId === id) { return this._currentUser; @@ -324,14 +443,14 @@ export class AuthImpl implements Auth, _FirebaseService { return null; } - async _persistUserIfCurrent(user: User): Promise { + async _persistUserIfCurrent(user: UserInternal): Promise { if (user === this.currentUser) { return this.queue(async () => this.directlySetCurrentUser(user)); } } /** Notifies listeners only if the user is current */ - _notifyListenersIfCurrent(user: User): void { + _notifyListenersIfCurrent(user: UserInternal): void { if (user === this.currentUser) { this.notifyAuthListeners(); } @@ -356,8 +475,8 @@ export class AuthImpl implements Auth, _FirebaseService { } /** Returns the current user cast as the internal type */ - get _currentUser(): User { - return this.currentUser as User; + get _currentUser(): UserInternal { + return this.currentUser as UserInternal; } private notifyAuthListeners(): void { @@ -367,15 +486,16 @@ export class AuthImpl implements Auth, _FirebaseService { this.idTokenSubscription.next(this.currentUser); - if (this.lastNotifiedUid !== this.currentUser?.uid) { - this.lastNotifiedUid = this.currentUser?.uid; + const currentUid = this.currentUser?.uid ?? null; + if (this.lastNotifiedUid !== currentUid) { + this.lastNotifiedUid = currentUid; this.authStateSubscription.next(this.currentUser); } } private registerStateListener( - subscription: Subscription, - nextOrObserver: externs.NextOrObserver, + subscription: Subscription, + nextOrObserver: NextOrObserver, error?: ErrorFn, completed?: CompleteFn ): Unsubscribe { @@ -386,12 +506,12 @@ export class AuthImpl implements Auth, _FirebaseService { const cb = typeof nextOrObserver === 'function' ? nextOrObserver - : nextOrObserver.next; + : nextOrObserver.next.bind(nextOrObserver); const promise = this._isInitialized ? Promise.resolve() : this._initializationPromise; - assert(promise, AuthErrorCode.INTERNAL_ERROR, { appName: this.name }); + _assert(promise, this, AuthErrorCode.INTERNAL_ERROR); // The callback needs to be called asynchronously per the spec. // eslint-disable-next-line @typescript-eslint/no-floating-promises promise.then(() => cb(this.currentUser)); @@ -408,7 +528,9 @@ export class AuthImpl implements Auth, _FirebaseService { * should only be called from within a queued callback. This is necessary * because the queue shouldn't rely on another queued callback. */ - private async directlySetCurrentUser(user: User | null): Promise { + private async directlySetCurrentUser( + user: UserInternal | null + ): Promise { if (this.currentUser && this.currentUser !== user) { this._currentUser._stopProactiveRefresh(); if (user && this.isProactiveRefreshEnabled) { @@ -433,20 +555,42 @@ export class AuthImpl implements Auth, _FirebaseService { } private get assertedPersistence(): PersistenceUserManager { - assert(this.persistenceManager, AuthErrorCode.INTERNAL_ERROR, { - appName: this.name - }); + _assert(this.persistenceManager, this, AuthErrorCode.INTERNAL_ERROR); return this.persistenceManager; } + + private frameworks: string[] = []; + private clientVersion: string; + _logFramework(framework: string): void { + if (!framework || this.frameworks.includes(framework)) { + return; + } + this.frameworks.push(framework); + + // Sort alphabetically so that "FirebaseCore-web,FirebaseUI-web" and + // "FirebaseUI-web,FirebaseCore-web" aren't viewed as different. + this.frameworks.sort(); + this.clientVersion = _getClientVersion( + this.config.clientPlatform, + this._getFrameworks() + ); + } + _getFrameworks(): readonly string[] { + return this.frameworks; + } + _getSdkClientVersion(): string { + return this.clientVersion; + } } /** - * Method to be used to cast down to our private implmentation of Auth + * Method to be used to cast down to our private implmentation of Auth. + * It will also handle unwrapping from the compat type if necessary * * @param auth Auth object passed in from developer */ -export function _castAuth(auth: externs.Auth): Auth { - return (auth as unknown) as Auth; +export function _castAuth(auth: Auth): AuthInternal { + return getModularInstance(auth) as AuthInternal; } /** Helper class to wrap subscriber logic */ @@ -456,12 +600,10 @@ class Subscription { observer => (this.observer = observer) ); - constructor(readonly auth: Auth) {} + constructor(readonly auth: AuthInternal) {} get next(): NextFn { - assert(this.observer, AuthErrorCode.INTERNAL_ERROR, { - appName: this.auth.name - }); + _assert(this.observer, this.auth, AuthErrorCode.INTERNAL_ERROR); return this.observer.next.bind(this.observer); } } diff --git a/packages-exp/auth-exp/src/core/auth/emulator.test.ts b/packages-exp/auth-exp/src/core/auth/emulator.test.ts new file mode 100644 index 00000000000..933123afa75 --- /dev/null +++ b/packages-exp/auth-exp/src/core/auth/emulator.test.ts @@ -0,0 +1,193 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect, use } from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import * as sinon from 'sinon'; +import * as sinonChai from 'sinon-chai'; + +import { FirebaseError } from '@firebase/util'; + +import { endpointUrl, mockEndpoint } from '../../../test/helpers/api/helper'; +import { testAuth, TestAuth, testUser } from '../../../test/helpers/mock_auth'; +import * as fetch from '../../../test/helpers/mock_fetch'; +import { Endpoint } from '../../api'; +import { UserInternal } from '../../model/user'; +import { _castAuth } from './auth_impl'; +import { useAuthEmulator } from './emulator'; + +use(sinonChai); +use(chaiAsPromised); + +describe('core/auth/emulator', () => { + let auth: TestAuth; + let user: UserInternal; + let normalEndpoint: fetch.Route; + let emulatorEndpoint: fetch.Route; + + beforeEach(async () => { + auth = await testAuth(); + user = testUser(_castAuth(auth), 'uid', 'email', true); + fetch.setUp(); + normalEndpoint = mockEndpoint(Endpoint.DELETE_ACCOUNT, {}); + emulatorEndpoint = fetch.mock( + `http://localhost:2020/${endpointUrl(Endpoint.DELETE_ACCOUNT).replace( + /^.*:\/\//, + '' + )}`, + {} + ); + }); + + afterEach(() => { + fetch.tearDown(); + sinon.restore(); + + // The DOM persists through tests; remove the banner if it is attached + const banner = + typeof document !== 'undefined' + ? document.querySelector('.firebase-emulator-warning') + : null; + if (banner) { + banner.parentElement?.removeChild(banner); + } + }); + + context('useAuthEmulator', () => { + it('fails if a network request has already been made', async () => { + await user.delete(); + expect(() => useAuthEmulator(auth, 'http://localhost:2020')).to.throw( + FirebaseError, + 'auth/emulator-config-failed' + ); + }); + + it('updates the endpoint appropriately', async () => { + useAuthEmulator(auth, 'http://localhost:2020'); + await user.delete(); + expect(normalEndpoint.calls.length).to.eq(0); + expect(emulatorEndpoint.calls.length).to.eq(1); + }); + + it('updates the endpoint appropriately with trailing slash', async () => { + useAuthEmulator(auth, 'http://localhost:2020/'); + await user.delete(); + expect(normalEndpoint.calls.length).to.eq(0); + expect(emulatorEndpoint.calls.length).to.eq(1); + }); + + it('checks the scheme properly', () => { + expect(() => useAuthEmulator(auth, 'http://localhost:2020')).not.to.throw; + delete auth.config.emulator; + expect(() => useAuthEmulator(auth, 'https://localhost:2020')).not.to + .throw; + delete auth.config.emulator; + expect(() => useAuthEmulator(auth, 'ssh://localhost:2020')).to.throw( + FirebaseError, + 'auth/invalid-emulator-scheme' + ); + delete auth.config.emulator; + expect(() => useAuthEmulator(auth, 'localhost:2020')).to.throw( + FirebaseError, + 'auth/invalid-emulator-scheme' + ); + }); + + it('attaches a banner to the DOM', () => { + useAuthEmulator(auth, 'http://localhost:2020'); + if (typeof document !== 'undefined') { + const el = document.querySelector('.firebase-emulator-warning')!; + expect(el).not.to.be.null; + expect(el.textContent).to.eq( + 'Running in emulator mode. ' + + 'Do not use with production credentials.' + ); + } + }); + + it('logs out a warning to the console', () => { + sinon.stub(console, 'info'); + useAuthEmulator(auth, 'http://localhost:2020'); + expect(console.info).to.have.been.calledWith( + 'WARNING: You are using the Auth Emulator,' + + ' which is intended for local testing only. Do not use with' + + ' production credentials.' + ); + }); + + it('logs out the warning but has no banner if disableBanner true', () => { + sinon.stub(console, 'info'); + useAuthEmulator(auth, 'http://localhost:2020', { disableWarnings: true }); + expect(console.info).to.have.been.calledWith( + 'WARNING: You are using the Auth Emulator,' + + ' which is intended for local testing only. Do not use with' + + ' production credentials.' + ); + if (typeof document !== 'undefined') { + expect(document.querySelector('.firebase-emulator-warning')).to.be.null; + } + }); + + it('sets emulatorConfig on the Auth object', async () => { + useAuthEmulator(auth, 'http://localhost:2020'); + expect(auth.emulatorConfig).to.eql({ + protocol: 'http', + host: 'localhost', + port: 2020, + options: { disableWarnings: false } + }); + }); + + it('sets disableWarnings in emulatorConfig accordingly', async () => { + useAuthEmulator(auth, 'https://127.0.0.1', { disableWarnings: true }); + expect(auth.emulatorConfig).to.eql({ + protocol: 'https', + host: '127.0.0.1', + port: null, + options: { disableWarnings: true } + }); + }); + + it('quotes IPv6 address in emulatorConfig', async () => { + useAuthEmulator(auth, 'http://[::1]:2020/'); + expect(auth.emulatorConfig).to.eql({ + protocol: 'http', + host: '[::1]', + port: 2020, + options: { disableWarnings: false } + }); + }); + }); + + context('toJSON', () => { + it('works when theres no current user', () => { + expect(JSON.stringify(auth)).to.eq( + '{"apiKey":"test-api-key","authDomain":"localhost","appName":"test-app"}' + ); + }); + + it('also stringifies the current user', () => { + auth.currentUser = ({ + toJSON: (): object => ({ foo: 'bar' }) + } as unknown) as UserInternal; + expect(JSON.stringify(auth)).to.eq( + '{"apiKey":"test-api-key","authDomain":"localhost",' + + '"appName":"test-app","currentUser":{"foo":"bar"}}' + ); + }); + }); +}); diff --git a/packages-exp/auth-exp/src/core/auth/emulator.ts b/packages-exp/auth-exp/src/core/auth/emulator.ts new file mode 100644 index 00000000000..4f6d12ae0c2 --- /dev/null +++ b/packages-exp/auth-exp/src/core/auth/emulator.ts @@ -0,0 +1,154 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Auth } from '../../model/public_types'; +import { AuthErrorCode } from '../errors'; +import { _assert } from '../util/assert'; +import { _castAuth } from './auth_impl'; + +/** + * Changes the Auth instance to communicate with the Firebase Auth Emulator, instead of production + * Firebase Auth services. + * + * @remarks + * This must be called synchronously immediately following the first call to + * {@link initializeAuth}. Do not use with production credentials as emulator + * traffic is not encrypted. + * + * + * @example + * ```javascript + * useAuthEmulator(auth, 'http://127.0.0.1:9099', { disableWarnings: true }); + * ``` + * + * @param auth - The Auth instance. + * @param url - The URL at which the emulator is running (eg, 'http://localhost:9099'). + * @param options.disableWarnings - (Optional: default false) Disable the warning banner attached to the DOM + * + * @public + */ +export function useAuthEmulator( + auth: Auth, + url: string, + options?: { disableWarnings: boolean } +): void { + const authInternal = _castAuth(auth); + _assert( + authInternal._canInitEmulator, + authInternal, + AuthErrorCode.EMULATOR_CONFIG_FAILED + ); + + _assert( + /^https?:\/\//.test(url), + authInternal, + AuthErrorCode.INVALID_EMULATOR_SCHEME + ); + + const disableWarnings = !!options?.disableWarnings; + + const protocol = extractProtocol(url); + const { host, port } = extractHostAndPort(url); + const portStr = port === null ? '' : `:${port}`; + + // Always replace path with "/" (even if input url had no path at all, or had a different one). + authInternal.config.emulator = { url: `${protocol}//${host}${portStr}/` }; + authInternal.settings.appVerificationDisabledForTesting = true; + authInternal.emulatorConfig = Object.freeze({ + host, + port, + protocol: protocol.replace(':', ''), + options: Object.freeze({ disableWarnings }) + }); + + emitEmulatorWarning(disableWarnings); +} + +function extractProtocol(url: string): string { + const protocolEnd = url.indexOf(':'); + return protocolEnd < 0 ? '' : url.substr(0, protocolEnd + 1); +} + +function extractHostAndPort(url: string): { + host: string; + port: number | null; +} { + const protocol = extractProtocol(url); + const authority = /(\/\/)?([^?#/]+)/.exec(url.substr(protocol.length)); // Between // and /, ? or #. + if (!authority) { + return { host: '', port: null }; + } + const hostAndPort = authority[2].split('@').pop() || ''; // Strip out "username:password@". + const bracketedIPv6 = /^(\[[^\]]+\])(:|$)/.exec(hostAndPort); + if (bracketedIPv6) { + const host = bracketedIPv6[1]; + return { host, port: parsePort(hostAndPort.substr(host.length + 1)) }; + } else { + const [host, port] = hostAndPort.split(':'); + return { host, port: parsePort(port) }; + } +} + +function parsePort(portStr: string): number | null { + if (!portStr) { + return null; + } + const port = Number(portStr); + if (isNaN(port)) { + return null; + } + return port; +} + +function emitEmulatorWarning(disableBanner: boolean): void { + function attachBanner(): void { + const el = document.createElement('p'); + const sty = el.style; + el.innerText = + 'Running in emulator mode. Do not use with production credentials.'; + sty.position = 'fixed'; + sty.width = '100%'; + sty.backgroundColor = '#ffffff'; + sty.border = '.1em solid #000000'; + sty.color = '#b50000'; + sty.bottom = '0px'; + sty.left = '0px'; + sty.margin = '0px'; + sty.zIndex = '10000'; + sty.textAlign = 'center'; + el.classList.add('firebase-emulator-warning'); + document.body.appendChild(el); + } + + if (typeof console !== 'undefined' && typeof console.info === 'function') { + console.info( + 'WARNING: You are using the Auth Emulator,' + + ' which is intended for local testing only. Do not use with' + + ' production credentials.' + ); + } + if ( + typeof window !== 'undefined' && + typeof document !== 'undefined' && + !disableBanner + ) { + if (document.readyState === 'loading') { + window.addEventListener('DOMContentLoaded', attachBanner); + } else { + attachBanner(); + } + } +} diff --git a/packages-exp/auth-exp/src/core/auth/firebase_internal.test.ts b/packages-exp/auth-exp/src/core/auth/firebase_internal.test.ts index b5a84490890..edf2e7fee3f 100644 --- a/packages-exp/auth-exp/src/core/auth/firebase_internal.test.ts +++ b/packages-exp/auth-exp/src/core/auth/firebase_internal.test.ts @@ -15,20 +15,24 @@ * limitations under the License. */ -import { expect } from 'chai'; +import { FirebaseError } from '@firebase/util'; +import { expect, use } from 'chai'; import * as sinon from 'sinon'; +import * as chaiAsPromised from 'chai-as-promised'; import { testAuth, testUser } from '../../../test/helpers/mock_auth'; -import { Auth } from '../../model/auth'; -import { User } from '../../model/user'; -import { AuthInternal } from './firebase_internal'; +import { AuthInternal } from '../../model/auth'; +import { UserInternal } from '../../model/user'; +import { AuthInterop } from './firebase_internal'; -describe('src/core/auth/firebase_internal', () => { - let auth: Auth; - let authInternal: AuthInternal; +use(chaiAsPromised); + +describe('core/auth/firebase_internal', () => { + let auth: AuthInternal; + let authInternal: AuthInterop; beforeEach(async () => { auth = await testAuth(); - authInternal = new AuthInternal(auth); + authInternal = new AuthInterop(auth); }); afterEach(() => { @@ -42,9 +46,19 @@ describe('src/core/auth/firebase_internal', () => { it('returns the uid of the user if set', async () => { const user = testUser(auth, 'uid'); - await auth.updateCurrentUser(user); + await auth._updateCurrentUser(user); expect(authInternal.getUid()).to.eq('uid'); }); + + it('errors if Auth is not initialized', () => { + delete ((auth as unknown) as Record)[ + '_initializationPromise' + ]; + expect(() => authInternal.getUid()).to.throw( + FirebaseError, + 'auth/dependent-sdk-initialized-before-auth' + ); + }); }); context('getToken', () => { @@ -54,22 +68,33 @@ describe('src/core/auth/firebase_internal', () => { it('returns the id token of the current user correctly', async () => { const user = testUser(auth, 'uid'); - await auth.updateCurrentUser(user); + await auth._updateCurrentUser(user); user.stsTokenManager.accessToken = 'access-token'; + user.stsTokenManager.refreshToken = 'refresh-token'; user.stsTokenManager.expirationTime = Date.now() + 1000 * 60 * 60 * 24; expect(await authInternal.getToken()).to.eql({ accessToken: 'access-token' }); }); + + it('errors if Auth is not initialized', async () => { + delete ((auth as unknown) as Record)[ + '_initializationPromise' + ]; + await expect(authInternal.getToken()).to.be.rejectedWith( + FirebaseError, + 'auth/dependent-sdk-initialized-before-auth' + ); + }); }); context('token listeners', () => { let isProactiveRefresh = false; - let user: User; + let user: UserInternal; beforeEach(async () => { user = testUser(auth, 'uid', undefined, true); - await auth.updateCurrentUser(user); + await auth._updateCurrentUser(user); let i = 0; sinon.stub(user.stsTokenManager, 'getToken').callsFake(async () => { i += 1; @@ -114,6 +139,16 @@ describe('src/core/auth/firebase_internal', () => { expect(tokenCount).to.eq(5); }); + + it('errors if Auth is not initialized', () => { + delete ((auth as unknown) as Record)[ + '_initializationPromise' + ]; + expect(() => authInternal.addAuthTokenListener(() => {})).to.throw( + FirebaseError, + 'auth/dependent-sdk-initialized-before-auth' + ); + }); }); context('removeAuthTokenListener', () => { @@ -167,6 +202,16 @@ describe('src/core/auth/firebase_internal', () => { authInternal.addAuthTokenListener(listenerB); expect(isProactiveRefresh).to.be.true; }); + + it('errors if Auth is not initialized', () => { + delete ((auth as unknown) as Record)[ + '_initializationPromise' + ]; + expect(() => authInternal.removeAuthTokenListener(() => {})).to.throw( + FirebaseError, + 'auth/dependent-sdk-initialized-before-auth' + ); + }); }); }); }); diff --git a/packages-exp/auth-exp/src/core/auth/firebase_internal.ts b/packages-exp/auth-exp/src/core/auth/firebase_internal.ts index 63088abf376..fcde523050c 100644 --- a/packages-exp/auth-exp/src/core/auth/firebase_internal.ts +++ b/packages-exp/auth-exp/src/core/auth/firebase_internal.ts @@ -16,35 +16,34 @@ */ import { Unsubscribe } from '@firebase/util'; +import { FirebaseAuthInternal } from '@firebase/auth-interop-types'; -import { Auth } from '../../model/auth'; -import { User } from '../../model/user'; - -declare module '@firebase/component' { - interface NameServiceMapping { - 'auth-internal-exp': AuthInternal; - } -} +import { AuthInternal } from '../../model/auth'; +import { UserInternal } from '../../model/user'; +import { _assert } from '../util/assert'; +import { AuthErrorCode } from '../errors'; interface TokenListener { (tok: string | null): unknown; } -export class AuthInternal { +export class AuthInterop implements FirebaseAuthInternal { private readonly internalListeners: Map< TokenListener, Unsubscribe > = new Map(); - constructor(private readonly auth: Auth) {} + constructor(private readonly auth: AuthInternal) {} getUid(): string | null { + this.assertAuthConfigured(); return this.auth.currentUser?.uid || null; } async getToken( forceRefresh?: boolean ): Promise<{ accessToken: string } | null> { + this.assertAuthConfigured(); await this.auth._initializationPromise; if (!this.auth.currentUser) { return null; @@ -55,18 +54,22 @@ export class AuthInternal { } addAuthTokenListener(listener: TokenListener): void { + this.assertAuthConfigured(); if (this.internalListeners.has(listener)) { return; } const unsubscribe = this.auth.onIdTokenChanged(user => { - listener((user as User | null)?.stsTokenManager.accessToken || null); + listener( + (user as UserInternal | null)?.stsTokenManager.accessToken || null + ); }); this.internalListeners.set(listener, unsubscribe); this.updateProactiveRefresh(); } removeAuthTokenListener(listener: TokenListener): void { + this.assertAuthConfigured(); const unsubscribe = this.internalListeners.get(listener); if (!unsubscribe) { return; @@ -77,6 +80,13 @@ export class AuthInternal { this.updateProactiveRefresh(); } + private assertAuthConfigured(): void { + _assert( + this.auth._initializationPromise, + AuthErrorCode.DEPENDENT_SDK_INIT_BEFORE_AUTH + ); + } + private updateProactiveRefresh(): void { if (this.internalListeners.size > 0) { this.auth._startProactiveRefresh(); diff --git a/packages-exp/auth-exp/src/core/auth/initialize.test.ts b/packages-exp/auth-exp/src/core/auth/initialize.test.ts new file mode 100644 index 00000000000..63a9864b187 --- /dev/null +++ b/packages-exp/auth-exp/src/core/auth/initialize.test.ts @@ -0,0 +1,217 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + deleteApp, + initializeApp, + FirebaseApp, + _FirebaseService +} from '@firebase/app-exp'; +import { + Auth, + AuthProvider, + Persistence as PersistencePublic, + PopupRedirectResolver, + UserCredential +} from '../../model/public_types'; +import { isNode } from '@firebase/util'; + +import { expect } from 'chai'; +import { inMemoryPersistence } from '../../../internal'; + +import { AuthInternal } from '../../model/auth'; +import { + PopupRedirectResolverInternal, + AuthEventType, + EventManager, + AuthEventConsumer +} from '../../model/popup_redirect'; +import { AuthPopup } from '../../platform_browser/util/popup'; +import { + PersistenceInternal, + PersistenceType, + PersistenceValue, + StorageEventListener +} from '../persistence'; +import { ClientPlatform, _getClientVersion } from '../util/version'; +import { initializeAuth } from './initialize'; +import { registerAuth } from './register'; + +describe('core/auth/initialize', () => { + let fakeApp: FirebaseApp; + + class FakeSessionPersistence implements PersistenceInternal { + static type: 'SESSION' = 'SESSION'; + readonly type = PersistenceType.SESSION; + storage: Record = {}; + + async _isAvailable(): Promise { + return true; + } + async _set(_key: string, _value: PersistenceValue): Promise { + return; + } + async _get(_key: string): Promise { + return null; + } + async _remove(_key: string): Promise { + return; + } + _addListener(_key: string, _listener: StorageEventListener): void { + return; + } + _removeListener(_key: string, _listener: StorageEventListener): void { + return; + } + } + + const fakeSessionPersistence: PersistencePublic = FakeSessionPersistence; + + class FakePopupRedirectResolver implements PopupRedirectResolverInternal { + readonly _redirectPersistence = fakeSessionPersistence; + + async _initialize(_auth: AuthInternal): Promise { + return new (class implements EventManager { + registerConsumer(_authEventConsumer: AuthEventConsumer): void { + return; + } + unregisterConsumer(_authEventConsumer: AuthEventConsumer): void { + return; + } + })(); + } + async _openPopup( + _auth: AuthInternal, + _provider: AuthProvider, + _authType: AuthEventType, + _eventId?: string + ): Promise { + return new AuthPopup(null); + } + _openRedirect( + _auth: AuthInternal, + _provider: AuthProvider, + _authType: AuthEventType, + _eventId?: string + ): Promise { + return new Promise((_, reject) => reject()); + } + _isIframeWebStorageSupported( + _auth: AuthInternal, + cb: (support: boolean) => unknown + ): void { + cb(true); + } + async _originValidation(): Promise {} + _shouldInitProactively = false; + async _completeRedirectFn( + _auth: Auth, + _resolver: PopupRedirectResolver, + _bypassAuthState: boolean + ): Promise { + return null; + } + } + + const fakePopupRedirectResolver: PopupRedirectResolver = FakePopupRedirectResolver; + + before(() => { + registerAuth(ClientPlatform.BROWSER); + }); + + beforeEach(() => { + fakeApp = initializeApp({ + apiKey: 'fake-key', + appId: 'fake-app-id', + authDomain: 'fake-auth-domain' + }); + }); + + afterEach(async () => { + await deleteApp(fakeApp); + }); + + describe('initializeAuth', () => { + it('should work with no deps', async () => { + const auth = initializeAuth(fakeApp) as AuthInternal; + await auth._initializationPromise; + + expect(auth.name).to.eq(fakeApp.name); + const expectedClientPlatform = isNode() + ? ClientPlatform.NODE + : ClientPlatform.BROWSER; + const expectedSdkClientVersion = _getClientVersion( + expectedClientPlatform + ); + + expect(auth.config).to.eql({ + apiHost: 'identitytoolkit.googleapis.com', + apiKey: 'fake-key', + apiScheme: 'https', + authDomain: 'fake-auth-domain', + clientPlatform: expectedClientPlatform, + sdkClientVersion: expectedSdkClientVersion, + tokenApiHost: 'securetoken.googleapis.com' + }); + expect(auth._getPersistence()).to.eq('NONE'); + }); + + it('should set persistence', async () => { + const auth = initializeAuth(fakeApp, { + persistence: fakeSessionPersistence + }) as AuthInternal; + await auth._initializationPromise; + + expect(auth._getPersistence()).to.eq('SESSION'); + }); + + it('should set persistence with fallback', async () => { + const auth = initializeAuth(fakeApp, { + persistence: [fakeSessionPersistence, inMemoryPersistence] + }) as AuthInternal; + await auth._initializationPromise; + + expect(auth._getPersistence()).to.eq('SESSION'); + }); + + it('should set resolver', async () => { + const auth = initializeAuth(fakeApp, { + popupRedirectResolver: fakePopupRedirectResolver + }) as AuthInternal; + await auth._initializationPromise; + + expect(auth._popupRedirectResolver).to.be.instanceof( + FakePopupRedirectResolver + ); + }); + + it('should abort initialization if deleted synchronously', async () => { + const auth = initializeAuth(fakeApp, { + popupRedirectResolver: fakePopupRedirectResolver + }) as AuthInternal; + await ((auth as unknown) as _FirebaseService)._delete(); + await auth._initializationPromise; + + expect(auth._isInitialized).to.be.false; + }); + + it('should throw if called more than once', () => { + initializeAuth(fakeApp); + expect(() => initializeAuth(fakeApp)).to.throw(); + }); + }); +}); diff --git a/packages-exp/auth-exp/src/core/auth/initialize.ts b/packages-exp/auth-exp/src/core/auth/initialize.ts index 3dae56a94e6..32e254ad84a 100644 --- a/packages-exp/auth-exp/src/core/auth/initialize.ts +++ b/packages-exp/auth-exp/src/core/auth/initialize.ts @@ -15,21 +15,49 @@ * limitations under the License. */ -import { _getProvider, getApp } from '@firebase/app-exp'; -import { FirebaseApp } from '@firebase/app-types-exp'; -import * as externs from '@firebase/auth-types-exp'; +import { _getProvider, FirebaseApp } from '@firebase/app-exp'; +import { Auth, Dependencies } from '../../model/public_types'; -import { Dependencies } from '../../model/auth'; -import { Persistence } from '../persistence'; +import { AuthErrorCode } from '../errors'; +import { PersistenceInternal } from '../persistence'; +import { _fail } from '../util/assert'; import { _getInstance } from '../util/instantiator'; -import { _castAuth, AuthImpl } from './auth_impl'; +import { AuthImpl } from './auth_impl'; -export function initializeAuth( - app: FirebaseApp = getApp(), - deps?: Dependencies -): externs.Auth { - const auth = _getProvider(app, 'auth-exp').getImmediate() as AuthImpl; - _initializeAuthInstance(auth, deps); +/** + * Initializes an Auth instance with fine-grained control over + * {@link Dependencies}. + * + * @remarks + * + * This function allows more control over the Auth instance than + * {@link getAuth}. `getAuth` uses platform-specific defaults to supply + * the {@link Dependencies}. In general, `getAuth` is the easiest way to + * initialize Auth and works for most use cases. Use `initializeAuth` if you + * need control over which persistence layer is used, or to minimize bundle + * size if you're not using either `signInWithPopup` or `signInWithRedirect`. + * + * For example, if your app only uses anonymous accounts and you only want + * accounts saved for the current session, initialize Auth with: + * + * ```js + * const auth = initializeAuth(app, { + * persistence: browserSessionPersistence, + * popupRedirectResolver: undefined, + * }); + * ``` + * + * @public + */ +export function initializeAuth(app: FirebaseApp, deps?: Dependencies): Auth { + const provider = _getProvider(app, 'auth-exp'); + + if (provider.isInitialized()) { + const auth = provider.getImmediate() as AuthImpl; + _fail(auth, AuthErrorCode.ALREADY_INITIALIZED); + } + + const auth = provider.initialize({ options: deps }) as AuthImpl; return auth; } @@ -42,7 +70,10 @@ export function _initializeAuthInstance( const hierarchy = (Array.isArray(persistence) ? persistence : [persistence] - ).map(_getInstance); + ).map(_getInstance); + if (deps?.errorMap) { + auth._updateErrorMap(deps.errorMap); + } // This promise is intended to float; auth initialization happens in the // background, meanwhile the auth object may be used by the app. diff --git a/packages-exp/auth-exp/src/core/auth/register.ts b/packages-exp/auth-exp/src/core/auth/register.ts index 7d612968639..7d7664d2181 100644 --- a/packages-exp/auth-exp/src/core/auth/register.ts +++ b/packages-exp/auth-exp/src/core/auth/register.ts @@ -16,24 +16,26 @@ */ import { _registerComponent, registerVersion } from '@firebase/app-exp'; -import * as externs from '@firebase/auth-types-exp'; -import { Component, ComponentType } from '@firebase/component'; +import { + Component, + ComponentType, + InstantiationMode +} from '@firebase/component'; -import { version } from '../../../package.json'; +import { name, version } from '../../../package.json'; import { AuthErrorCode } from '../errors'; -import { assert } from '../util/assert'; +import { _assert } from '../util/assert'; import { _getClientVersion, ClientPlatform } from '../util/version'; -import { - _castAuth, - AuthImpl, - DEFAULT_API_HOST, - DEFAULT_API_SCHEME, - DEFAULT_TOKEN_API_HOST -} from './auth_impl'; -import { AuthInternal } from './firebase_internal'; +import { _castAuth, AuthImpl, DefaultConfig } from './auth_impl'; +import { AuthInterop } from './firebase_internal'; +import { ConfigInternal } from '../../model/auth'; +import { Dependencies } from '../../model/public_types'; +import { _initializeAuthInstance } from './initialize'; -export const _AUTH_COMPONENT_NAME = 'auth-exp'; -export const _AUTH_INTERNAL_COMPONENT_NAME = 'auth-internal-exp'; +export const enum _ComponentName { + AUTH = 'auth-exp', + AUTH_INTERNAL = 'auth-internal' +} function getVersionForPlatform( clientPlatform: ClientPlatform @@ -45,51 +47,80 @@ function getVersionForPlatform( return 'rn'; case ClientPlatform.WORKER: return 'webworker'; + case ClientPlatform.CORDOVA: + return 'cordova'; default: return undefined; } } +/** @internal */ export function registerAuth(clientPlatform: ClientPlatform): void { _registerComponent( new Component( - _AUTH_COMPONENT_NAME, - container => { + _ComponentName.AUTH, + (container, { options: deps }: { options?: Dependencies }) => { const app = container.getProvider('app-exp').getImmediate()!; const { apiKey, authDomain } = app.options; return (app => { - assert(apiKey, AuthErrorCode.INVALID_API_KEY, { appName: app.name }); - const config: externs.Config = { + _assert( + apiKey && !apiKey.includes(':'), + AuthErrorCode.INVALID_API_KEY, + { appName: app.name } + ); + // Auth domain is optional if IdP sign in isn't being used + _assert(!authDomain?.includes(':'), AuthErrorCode.ARGUMENT_ERROR, { + appName: app.name + }); + const config: ConfigInternal = { apiKey, authDomain, - apiHost: DEFAULT_API_HOST, - tokenApiHost: DEFAULT_TOKEN_API_HOST, - apiScheme: DEFAULT_API_SCHEME, + clientPlatform, + apiHost: DefaultConfig.API_HOST, + tokenApiHost: DefaultConfig.TOKEN_API_HOST, + apiScheme: DefaultConfig.API_SCHEME, sdkClientVersion: _getClientVersion(clientPlatform) }; - return new AuthImpl(app, config); + + const authInstance = new AuthImpl(app, config); + _initializeAuthInstance(authInstance, deps); + + return authInstance; })(app); }, ComponentType.PUBLIC ) + /** + * Auth can only be initialized by explicitly calling getAuth() or initializeAuth() + * For why we do this, See go/firebase-next-auth-init + */ + .setInstantiationMode(InstantiationMode.EXPLICIT) + /** + * Because all firebase products that depend on auth depend on auth-internal directly, + * we need to initialize auth-internal after auth is initialized to make it available to other firebase products. + */ + .setInstanceCreatedCallback( + (container, _instanceIdentifier, _instance) => { + const authInternalProvider = container.getProvider( + _ComponentName.AUTH_INTERNAL + ); + authInternalProvider.initialize(); + } + ) ); _registerComponent( new Component( - _AUTH_INTERNAL_COMPONENT_NAME, + _ComponentName.AUTH_INTERNAL, container => { const auth = _castAuth( - container.getProvider(_AUTH_COMPONENT_NAME).getImmediate()! + container.getProvider(_ComponentName.AUTH).getImmediate()! ); - return (auth => new AuthInternal(auth))(auth); + return (auth => new AuthInterop(auth))(auth); }, ComponentType.PRIVATE - ) + ).setInstantiationMode(InstantiationMode.EXPLICIT) ); - registerVersion( - _AUTH_COMPONENT_NAME, - version, - getVersionForPlatform(clientPlatform) - ); + registerVersion(name, version, getVersionForPlatform(clientPlatform)); } diff --git a/packages-exp/auth-exp/src/core/credentials/auth_credential.ts b/packages-exp/auth-exp/src/core/credentials/auth_credential.ts index 9a6842a5d88..59d7482e4b0 100644 --- a/packages-exp/auth-exp/src/core/credentials/auth_credential.ts +++ b/packages-exp/auth-exp/src/core/credentials/auth_credential.ts @@ -16,27 +16,61 @@ */ import { PhoneOrOauthTokenResponse } from '../../api/authentication/mfa'; -import { Auth } from '../../model/auth'; +import { AuthInternal } from '../../model/auth'; import { IdTokenResponse } from '../../model/id_token'; import { debugFail } from '../util/assert'; +/** + * Interface that represents the credentials returned by an {@link AuthProvider}. + * + * @remarks + * Implementations specify the details about each auth provider's credential requirements. + * + * @public + */ export class AuthCredential { + /** @internal */ protected constructor( + /** + * The authentication provider ID for the credential. + * + * @remarks + * For example, 'facebook.com', or 'google.com'. + */ readonly providerId: string, + /** + * The authentication sign in method for the credential. + * + * @remarks + * For example, {@link SignInMethod}.EMAIL_PASSWORD, or + * {@link SignInMethod}.EMAIL_LINK. This corresponds to the sign-in method + * identifier as returned in {@link fetchSignInMethodsForEmail}. + */ readonly signInMethod: string ) {} + /** + * Returns a JSON-serializable representation of this object. + * + * @returns a JSON-serializable representation of this object. + */ toJSON(): object { return debugFail('not implemented'); } - _getIdTokenResponse(_auth: Auth): Promise { + /** @internal */ + _getIdTokenResponse(_auth: AuthInternal): Promise { return debugFail('not implemented'); } - _linkToIdToken(_auth: Auth, _idToken: string): Promise { + /** @internal */ + _linkToIdToken( + _auth: AuthInternal, + _idToken: string + ): Promise { return debugFail('not implemented'); } - _getReauthenticationResolver(_auth: Auth): Promise { + /** @internal */ + _getReauthenticationResolver(_auth: AuthInternal): Promise { return debugFail('not implemented'); } } diff --git a/packages-exp/auth-exp/src/core/credentials/email.test.ts b/packages-exp/auth-exp/src/core/credentials/email.test.ts index a39fde9c873..b0d02f2f50c 100644 --- a/packages-exp/auth-exp/src/core/credentials/email.test.ts +++ b/packages-exp/auth-exp/src/core/credentials/email.test.ts @@ -18,7 +18,7 @@ import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; -import { ProviderId, SignInMethod } from '@firebase/auth-types-exp'; +import { ProviderId, SignInMethod } from '../../model/public_types'; import { mockEndpoint } from '../../../test/helpers/api/helper'; import { testAuth, TestAuth } from '../../../test/helpers/mock_auth'; diff --git a/packages-exp/auth-exp/src/core/credentials/email.ts b/packages-exp/auth-exp/src/core/credentials/email.ts index c82a4b65bc1..3d06319b329 100644 --- a/packages-exp/auth-exp/src/core/credentials/email.ts +++ b/packages-exp/auth-exp/src/core/credentials/email.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { ProviderId, SignInMethod } from '../../model/public_types'; import { updateEmailPassword } from '../../api/account_management/email_and_password'; import { signInWithPassword } from '../../api/authentication/email_and_password'; @@ -23,24 +23,37 @@ import { signInWithEmailLink, signInWithEmailLinkForLinking } from '../../api/authentication/email_link'; -import { Auth } from '../../model/auth'; +import { AuthInternal } from '../../model/auth'; import { IdTokenResponse } from '../../model/id_token'; import { AuthErrorCode } from '../errors'; -import { fail } from '../util/assert'; +import { _fail } from '../util/assert'; import { AuthCredential } from './auth_credential'; -export class EmailAuthCredential - extends AuthCredential - implements externs.AuthCredential { +/** + * Interface that represents the credentials returned by {@link EmailAuthProvider} for + * {@link ProviderId}.PASSWORD + * + * @remarks + * Covers both {@link SignInMethod}.EMAIL_PASSWORD and + * {@link SignInMethod}.EMAIL_LINK. + * + * @public + */ +export class EmailAuthCredential extends AuthCredential { + /** @internal */ private constructor( - readonly email: string, - readonly password: string, - signInMethod: externs.SignInMethod, - readonly tenantId: string | null = null + /** @internal */ + readonly _email: string, + /** @internal */ + readonly _password: string, + signInMethod: SignInMethod, + /** @internal */ + readonly _tenantId: string | null = null ) { - super(externs.ProviderId.PASSWORD, signInMethod); + super(ProviderId.PASSWORD, signInMethod); } + /** @internal */ static _fromEmailAndPassword( email: string, password: string @@ -48,10 +61,11 @@ export class EmailAuthCredential return new EmailAuthCredential( email, password, - externs.SignInMethod.EMAIL_PASSWORD + SignInMethod.EMAIL_PASSWORD ); } + /** @internal */ static _fromEmailAndCode( email: string, oobCode: string, @@ -60,71 +74,86 @@ export class EmailAuthCredential return new EmailAuthCredential( email, oobCode, - externs.SignInMethod.EMAIL_LINK, + SignInMethod.EMAIL_LINK, tenantId ); } + /** {@inheritdoc AuthCredential.toJSON} */ toJSON(): object { return { - email: this.email, - password: this.password, + email: this._email, + password: this._password, signInMethod: this.signInMethod, - tenantId: this.tenantId + tenantId: this._tenantId }; } + /** + * Static method to deserialize a JSON representation of an object into an {@link AuthCredential}. + * + * @param json - Either `object` or the stringified representation of the object. When string is + * provided, `JSON.parse` would be called first. + * + * @returns If the JSON input does not represent an {@link AuthCredential}, null is returned. + */ static fromJSON(json: object | string): EmailAuthCredential | null { const obj = typeof json === 'string' ? JSON.parse(json) : json; if (obj?.email && obj?.password) { - if (obj.signInMethod === externs.SignInMethod.EMAIL_PASSWORD) { + if (obj.signInMethod === SignInMethod.EMAIL_PASSWORD) { return this._fromEmailAndPassword(obj.email, obj.password); - } else if (obj.signInMethod === externs.SignInMethod.EMAIL_LINK) { + } else if (obj.signInMethod === SignInMethod.EMAIL_LINK) { return this._fromEmailAndCode(obj.email, obj.password, obj.tenantId); } } return null; } - async _getIdTokenResponse(auth: Auth): Promise { + /** @internal */ + async _getIdTokenResponse(auth: AuthInternal): Promise { switch (this.signInMethod) { - case externs.SignInMethod.EMAIL_PASSWORD: + case SignInMethod.EMAIL_PASSWORD: return signInWithPassword(auth, { returnSecureToken: true, - email: this.email, - password: this.password + email: this._email, + password: this._password }); - case externs.SignInMethod.EMAIL_LINK: + case SignInMethod.EMAIL_LINK: return signInWithEmailLink(auth, { - email: this.email, - oobCode: this.password + email: this._email, + oobCode: this._password }); default: - fail(AuthErrorCode.INTERNAL_ERROR, { appName: auth.name }); + _fail(auth, AuthErrorCode.INTERNAL_ERROR); } } - async _linkToIdToken(auth: Auth, idToken: string): Promise { + /** @internal */ + async _linkToIdToken( + auth: AuthInternal, + idToken: string + ): Promise { switch (this.signInMethod) { - case externs.SignInMethod.EMAIL_PASSWORD: + case SignInMethod.EMAIL_PASSWORD: return updateEmailPassword(auth, { idToken, returnSecureToken: true, - email: this.email, - password: this.password + email: this._email, + password: this._password }); - case externs.SignInMethod.EMAIL_LINK: + case SignInMethod.EMAIL_LINK: return signInWithEmailLinkForLinking(auth, { idToken, - email: this.email, - oobCode: this.password + email: this._email, + oobCode: this._password }); default: - fail(AuthErrorCode.INTERNAL_ERROR, { appName: auth.name }); + _fail(auth, AuthErrorCode.INTERNAL_ERROR); } } - _getReauthenticationResolver(auth: Auth): Promise { + /** @internal */ + _getReauthenticationResolver(auth: AuthInternal): Promise { return this._getIdTokenResponse(auth); } } diff --git a/packages-exp/auth-exp/src/core/credentials/index.ts b/packages-exp/auth-exp/src/core/credentials/index.ts index a3ec8ce895f..3585da618b0 100644 --- a/packages-exp/auth-exp/src/core/credentials/index.ts +++ b/packages-exp/auth-exp/src/core/credentials/index.ts @@ -21,4 +21,4 @@ export { AuthCredential } from './auth_credential'; export { EmailAuthCredential } from './email'; export { OAuthCredential } from './oauth'; -export { PhoneAuthCredential } from './phone'; +export { PhoneAuthCredential as PhoneAuthCredential } from './phone'; diff --git a/packages-exp/auth-exp/src/core/credentials/oauth.test.ts b/packages-exp/auth-exp/src/core/credentials/oauth.test.ts index 3b2602c2336..982b8e963dd 100644 --- a/packages-exp/auth-exp/src/core/credentials/oauth.test.ts +++ b/packages-exp/auth-exp/src/core/credentials/oauth.test.ts @@ -17,7 +17,7 @@ import { expect } from 'chai'; -import { ProviderId, SignInMethod } from '@firebase/auth-types-exp'; +import { ProviderId, SignInMethod } from '../../model/public_types'; import { mockEndpoint } from '../../../test/helpers/api/helper'; import { TEST_ID_TOKEN_RESPONSE } from '../../../test/helpers/id_token_response'; @@ -32,7 +32,7 @@ const BASE_PARAMS: OAuthCredentialParams = { signInMethod: SignInMethod.GOOGLE }; -describe('src/core/credentials/oauth', () => { +describe('core/credentials/oauth', () => { let auth: TestAuth; let signInWithIdp: fetch.Route; @@ -165,7 +165,7 @@ describe('src/core/credentials/oauth', () => { expect(request.requestUri).to.eq('http://localhost'); expect(request.returnSecureToken).to.be.true; expect(request.pendingToken).to.eq('pending-token'); - expect(request.postBody).to.be.null; + expect(request.postBody).to.be.undefined; }); }); diff --git a/packages-exp/auth-exp/src/core/credentials/oauth.ts b/packages-exp/auth-exp/src/core/credentials/oauth.ts index 50327253207..5904a8873e4 100644 --- a/packages-exp/auth-exp/src/core/credentials/oauth.ts +++ b/packages-exp/auth-exp/src/core/credentials/oauth.ts @@ -15,17 +15,16 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; import { querystring } from '@firebase/util'; import { signInWithIdp, SignInWithIdpRequest } from '../../api/authentication/idp'; -import { Auth } from '../../model/auth'; +import { AuthInternal } from '../../model/auth'; import { IdTokenResponse } from '../../model/id_token'; import { AuthErrorCode } from '../errors'; -import { fail } from '../util/assert'; +import { _fail } from '../util/assert'; import { AuthCredential } from './auth_credential'; const IDP_REQUEST_URI = 'http://localhost'; @@ -49,15 +48,38 @@ export interface OAuthCredentialParams { signInMethod: string; } -export class OAuthCredential - extends AuthCredential - implements externs.OAuthCredential { +/** + * Represents the OAuth credentials returned by an {@link OAuthProvider}. + * + * @remarks + * Implementations specify the details about each auth provider's credential requirements. + * + * @public + */ +export class OAuthCredential extends AuthCredential { + /** + * The OAuth ID token associated with the credential if it belongs to an OIDC provider, + * such as `google.com`. + * @readonly + */ idToken?: string; + /** + * The OAuth access token associated with the credential if it belongs to an + * {@link OAuthProvider}, such as `facebook.com`, `twitter.com`, etc. + * @readonly + */ accessToken?: string; + /** + * The OAuth access token secret associated with the credential if it belongs to an OAuth 1.0 + * provider, such as `twitter.com`. + * @readonly + */ secret?: string; + /** @internal */ nonce?: string; private pendingToken: string | null = null; + /** @internal */ static _fromParams(params: OAuthCredentialParams): OAuthCredential { const cred = new OAuthCredential(params.providerId, params.signInMethod); @@ -84,12 +106,13 @@ export class OAuthCredential cred.accessToken = params.oauthToken; cred.secret = params.oauthTokenSecret; } else { - fail(AuthErrorCode.ARGUMENT_ERROR, {}); + _fail(AuthErrorCode.ARGUMENT_ERROR); } return cred; } + /** {@inheritdoc AuthCredential.toJSON} */ toJSON(): object { return { idToken: this.idToken, @@ -102,6 +125,15 @@ export class OAuthCredential }; } + /** + * Static method to deserialize a JSON representation of an object into an + * {@link AuthCredential}. + * + * @param json - Input can be either Object or the stringified representation of the object. + * When string is provided, JSON.parse would be called first. + * + * @returns If the JSON input does not represent an {@link AuthCredential}, null is returned. + */ static fromJSON(json: string | object): OAuthCredential | null { const obj = typeof json === 'string' ? JSON.parse(json) : json; const { providerId, signInMethod, ...rest }: Partial = obj; @@ -114,18 +146,24 @@ export class OAuthCredential return cred; } - _getIdTokenResponse(auth: Auth): Promise { + /** @internal */ + _getIdTokenResponse(auth: AuthInternal): Promise { const request = this.buildRequest(); return signInWithIdp(auth, request); } - _linkToIdToken(auth: Auth, idToken: string): Promise { + /** @internal */ + _linkToIdToken( + auth: AuthInternal, + idToken: string + ): Promise { const request = this.buildRequest(); request.idToken = idToken; return signInWithIdp(auth, request); } - _getReauthenticationResolver(auth: Auth): Promise { + /** @internal */ + _getReauthenticationResolver(auth: AuthInternal): Promise { const request = this.buildRequest(); request.autoCreate = false; return signInWithIdp(auth, request); @@ -134,8 +172,7 @@ export class OAuthCredential private buildRequest(): SignInWithIdpRequest { const request: SignInWithIdpRequest = { requestUri: IDP_REQUEST_URI, - returnSecureToken: true, - postBody: null + returnSecureToken: true }; if (this.pendingToken) { diff --git a/packages-exp/auth-exp/src/core/credentials/phone.ts b/packages-exp/auth-exp/src/core/credentials/phone.ts index 5daef1ac753..03117d3e63a 100644 --- a/packages-exp/auth-exp/src/core/credentials/phone.ts +++ b/packages-exp/auth-exp/src/core/credentials/phone.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { ProviderId, SignInMethod } from '../../model/public_types'; import { PhoneOrOauthTokenResponse } from '../../api/authentication/mfa'; import { @@ -24,7 +24,7 @@ import { SignInWithPhoneNumberRequest, verifyPhoneNumberForExisting } from '../../api/authentication/sms'; -import { Auth } from '../../model/auth'; +import { AuthInternal } from '../../model/auth'; import { IdTokenResponse } from '../../model/id_token'; import { AuthCredential } from './auth_credential'; @@ -35,13 +35,17 @@ export interface PhoneAuthCredentialParameters { temporaryProof?: string; } -export class PhoneAuthCredential - extends AuthCredential - implements externs.PhoneAuthCredential { +/** + * Represents the credentials returned by {@link PhoneAuthProvider}. + * + * @public + */ +export class PhoneAuthCredential extends AuthCredential { private constructor(private readonly params: PhoneAuthCredentialParameters) { - super(externs.ProviderId.PHONE, externs.SignInMethod.PHONE); + super(ProviderId.PHONE, SignInMethod.PHONE); } + /** @internal */ static _fromVerification( verificationId: string, verificationCode: string @@ -49,6 +53,7 @@ export class PhoneAuthCredential return new PhoneAuthCredential({ verificationId, verificationCode }); } + /** @internal */ static _fromTokenResponse( phoneNumber: string, temporaryProof: string @@ -56,21 +61,28 @@ export class PhoneAuthCredential return new PhoneAuthCredential({ phoneNumber, temporaryProof }); } - _getIdTokenResponse(auth: Auth): Promise { + /** @internal */ + _getIdTokenResponse(auth: AuthInternal): Promise { return signInWithPhoneNumber(auth, this._makeVerificationRequest()); } - _linkToIdToken(auth: Auth, idToken: string): Promise { + /** @internal */ + _linkToIdToken( + auth: AuthInternal, + idToken: string + ): Promise { return linkWithPhoneNumber(auth, { idToken, ...this._makeVerificationRequest() }); } - _getReauthenticationResolver(auth: Auth): Promise { + /** @internal */ + _getReauthenticationResolver(auth: AuthInternal): Promise { return verifyPhoneNumberForExisting(auth, this._makeVerificationRequest()); } + /** @internal */ _makeVerificationRequest(): SignInWithPhoneNumberRequest { const { temporaryProof, @@ -88,6 +100,7 @@ export class PhoneAuthCredential }; } + /** {@inheritdoc AuthCredential.toJSON} */ toJSON(): object { const obj: Record = { providerId: this.providerId @@ -108,6 +121,7 @@ export class PhoneAuthCredential return obj; } + /** Generates a phone credential based on a plain object or a JSON string. */ static fromJSON(json: object | string): PhoneAuthCredential | null { if (typeof json === 'string') { json = JSON.parse(json); diff --git a/packages-exp/auth-exp/src/core/credentials/saml.test.ts b/packages-exp/auth-exp/src/core/credentials/saml.test.ts new file mode 100644 index 00000000000..a0025689110 --- /dev/null +++ b/packages-exp/auth-exp/src/core/credentials/saml.test.ts @@ -0,0 +1,124 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from 'chai'; + +import { mockEndpoint } from '../../../test/helpers/api/helper'; +import { TEST_ID_TOKEN_RESPONSE } from '../../../test/helpers/id_token_response'; +import { testAuth, TestAuth } from '../../../test/helpers/mock_auth'; +import * as fetch from '../../../test/helpers/mock_fetch'; +import { Endpoint } from '../../api'; +import { SignInWithIdpRequest } from '../../api/authentication/idp'; +import { SAMLAuthCredential } from './saml'; + +describe('core/credentials/saml', () => { + let auth: TestAuth; + let signInWithIdp: fetch.Route; + + beforeEach(async () => { + auth = await testAuth(); + fetch.setUp(); + + signInWithIdp = mockEndpoint(Endpoint.SIGN_IN_WITH_IDP, { + ...TEST_ID_TOKEN_RESPONSE + }); + }); + + afterEach(() => { + fetch.tearDown(); + }); + + context('_create', () => { + it('sets the provider', () => { + const cred = SAMLAuthCredential._create('saml.provider', 'pending-token'); + expect(cred.providerId).to.eq('saml.provider'); + }); + }); + + context('#toJSON', () => { + it('packs up everything', () => { + const cred = SAMLAuthCredential._create('saml.provider', 'pending-token'); + + expect(cred.toJSON()).to.eql({ + signInMethod: 'saml.provider', + providerId: 'saml.provider', + pendingToken: 'pending-token' + }); + }); + }); + + context('fromJSON', () => { + it('builds the new object correctly', () => { + const cred = SAMLAuthCredential.fromJSON({ + signInMethod: 'saml.provider', + providerId: 'saml.provider', + pendingToken: 'pending-token' + }); + + expect(cred).to.be.instanceOf(SAMLAuthCredential); + expect(cred!.providerId).to.eq('saml.provider'); + expect(cred!.signInMethod).to.eq('saml.provider'); + }); + }); + + context('#makeRequest', () => { + it('generates the proper request', async () => { + await SAMLAuthCredential._create( + 'saml.provider', + 'pending-token' + )._getIdTokenResponse(auth); + + const request = signInWithIdp.calls[0].request as SignInWithIdpRequest; + expect(request.requestUri).to.eq('http://localhost'); + expect(request.returnSecureToken).to.be.true; + expect(request.pendingToken).to.eq('pending-token'); + expect(request.postBody).to.be.undefined; + }); + }); + + context('internal methods', () => { + let cred: SAMLAuthCredential; + + beforeEach(() => { + cred = SAMLAuthCredential._create('saml.provider', 'pending-token'); + }); + + it('_getIdTokenResponse calls through correctly', async () => { + await cred._getIdTokenResponse(auth); + + const request = signInWithIdp.calls[0].request as SignInWithIdpRequest; + expect(request.postBody).to.be.undefined; + expect(request.pendingToken).to.eq('pending-token'); + }); + + it('_linkToIdToken sets the idToken field on the request', async () => { + await cred._linkToIdToken(auth, 'new-id-token'); + const request = signInWithIdp.calls[0].request as SignInWithIdpRequest; + expect(request.postBody).to.be.undefined; + expect(request.pendingToken).to.eq('pending-token'); + expect(request.idToken).to.eq('new-id-token'); + }); + + it('_getReauthenticationResolver sets autoCreate to false', async () => { + await cred._getReauthenticationResolver(auth); + const request = signInWithIdp.calls[0].request as SignInWithIdpRequest; + expect(request.postBody).to.be.undefined; + expect(request.pendingToken).to.eq('pending-token'); + expect(request.autoCreate).to.be.false; + }); + }); +}); diff --git a/packages-exp/auth-exp/src/core/credentials/saml.ts b/packages-exp/auth-exp/src/core/credentials/saml.ts new file mode 100644 index 00000000000..91bd3ad381a --- /dev/null +++ b/packages-exp/auth-exp/src/core/credentials/saml.ts @@ -0,0 +1,122 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Represents the SAML credentials returned by an {@link SAMLAuthProvider}. + * + * @public + */ + +import { + signInWithIdp, + SignInWithIdpRequest +} from '../../api/authentication/idp'; +import { AuthInternal } from '../../model/auth'; +import { IdTokenResponse } from '../../model/id_token'; +import { AuthCredential } from './auth_credential'; + +const IDP_REQUEST_URI = 'http://localhost'; + +/** + * @public + */ +export class SAMLAuthCredential extends AuthCredential { + /** @internal */ + private constructor( + providerId: string, + private readonly pendingToken: string + ) { + super(providerId, providerId); + } + + /** @internal */ + _getIdTokenResponse(auth: AuthInternal): Promise { + const request = this.buildRequest(); + return signInWithIdp(auth, request); + } + + /** @internal */ + _linkToIdToken( + auth: AuthInternal, + idToken: string + ): Promise { + const request = this.buildRequest(); + request.idToken = idToken; + return signInWithIdp(auth, request); + } + + /** @internal */ + _getReauthenticationResolver(auth: AuthInternal): Promise { + const request = this.buildRequest(); + request.autoCreate = false; + return signInWithIdp(auth, request); + } + + /** {@inheritdoc AuthCredential.toJSON} */ + toJSON(): object { + return { + signInMethod: this.signInMethod, + providerId: this.providerId, + pendingToken: this.pendingToken + }; + } + + /** + * Static method to deserialize a JSON representation of an object into an + * {@link AuthCredential}. + * + * @param json - Input can be either Object or the stringified representation of the object. + * When string is provided, JSON.parse would be called first. + * + * @returns If the JSON input does not represent an {@link AuthCredential}, null is returned. + */ + static fromJSON(json: string | object): SAMLAuthCredential | null { + const obj = typeof json === 'string' ? JSON.parse(json) : json; + const { + providerId, + signInMethod, + pendingToken + }: Record = obj; + if ( + !providerId || + !signInMethod || + !pendingToken || + providerId !== signInMethod + ) { + return null; + } + + return new SAMLAuthCredential(providerId, pendingToken); + } + + /** + * Helper static method to avoid exposing the constructor to end users. + * + * @internal + */ + static _create(providerId: string, pendingToken: string): SAMLAuthCredential { + return new SAMLAuthCredential(providerId, pendingToken); + } + + private buildRequest(): SignInWithIdpRequest { + return { + requestUri: IDP_REQUEST_URI, + returnSecureToken: true, + pendingToken: this.pendingToken + }; + } +} diff --git a/packages-exp/auth-exp/src/core/errors.test.ts b/packages-exp/auth-exp/src/core/errors.test.ts index cfab7d39a1e..73526d664a4 100644 --- a/packages-exp/auth-exp/src/core/errors.test.ts +++ b/packages-exp/auth-exp/src/core/errors.test.ts @@ -16,13 +16,34 @@ */ import { expect } from 'chai'; -import { AuthErrorCode, AUTH_ERROR_FACTORY } from './errors'; +import { + AuthErrorCode, + debugErrorMap, + prodErrorMap, + ErrorMapRetriever, + AuthErrorParams +} from './errors'; +import { AuthErrorMap } from '../model/public_types'; +import { ErrorFactory } from '@firebase/util'; -describe('core/AUTH_ERROR_FACTORY', () => { - it('should create an Auth namespaced FirebaseError', () => { - const error = AUTH_ERROR_FACTORY.create(AuthErrorCode.INTERNAL_ERROR, { - appName: 'my-app' - }); +function getErrorFactory( + errorMap: AuthErrorMap +): ErrorFactory { + const map = (errorMap as ErrorMapRetriever)(); + const factory = new ErrorFactory( + 'auth', + 'Firebase', + map + ); + return factory; +} + +describe('verboseErrorMap', () => { + it('should create an Auth namespaced FirebaseError with full message', () => { + const error = getErrorFactory(debugErrorMap).create( + AuthErrorCode.INTERNAL_ERROR, + {} + ); expect(error.code).to.eq('auth/internal-error'); expect(error.message).to.eq( 'Firebase: An internal AuthError has occurred. (auth/internal-error).' @@ -30,3 +51,15 @@ describe('core/AUTH_ERROR_FACTORY', () => { expect(error.name).to.eq('FirebaseError'); }); }); + +describe('prodErrorMap', () => { + it('should create an Auth namespaced FirebaseError with full message', () => { + const error = getErrorFactory(prodErrorMap).create( + AuthErrorCode.INTERNAL_ERROR, + {} + ); + expect(error.code).to.eq('auth/internal-error'); + expect(error.message).to.eq('Firebase: Error (auth/internal-error).'); + expect(error.name).to.eq('FirebaseError'); + }); +}); diff --git a/packages-exp/auth-exp/src/core/errors.ts b/packages-exp/auth-exp/src/core/errors.ts index a48e5fc8de7..651e6b08a78 100644 --- a/packages-exp/auth-exp/src/core/errors.ts +++ b/packages-exp/auth-exp/src/core/errors.ts @@ -16,14 +16,17 @@ */ // eslint-disable-next-line import/no-extraneous-dependencies -import * as externs from '@firebase/auth-types-exp'; +import { AuthErrorMap, User } from '../model/public_types'; import { ErrorFactory, ErrorMap } from '@firebase/util'; import { IdTokenMfaResponse } from '../api/authentication/mfa'; import { AppName } from '../model/auth'; +import { AuthCredential } from './credentials'; -/* - * Developer facing Firebase Auth error codes. +/** + * Enumeration of Firebase Auth error codes. + * + * @public */ export const enum AuthErrorCode { ADMIN_ONLY_OPERATION = 'admin-restricted-operation', @@ -37,6 +40,7 @@ export const enum AuthErrorCode { CREDENTIAL_ALREADY_IN_USE = 'credential-already-in-use', CREDENTIAL_MISMATCH = 'custom-token-mismatch', CREDENTIAL_TOO_OLD_LOGIN_AGAIN = 'requires-recent-login', + DEPENDENT_SDK_INIT_BEFORE_AUTH = 'dependent-sdk-initialized-before-auth', DYNAMIC_LINK_NOT_ACTIVATED = 'dynamic-link-not-activated', EMAIL_CHANGE_NEEDS_VERIFICATION = 'email-change-needs-verification', EMAIL_EXISTS = 'email-already-in-use', @@ -56,6 +60,7 @@ export const enum AuthErrorCode { INVALID_CUSTOM_TOKEN = 'invalid-custom-token', INVALID_DYNAMIC_LINK_DOMAIN = 'invalid-dynamic-link-domain', INVALID_EMAIL = 'invalid-email', + INVALID_EMULATOR_SCHEME = 'invalid-emulator-scheme', INVALID_IDP_RESPONSE = 'invalid-credential', INVALID_MESSAGE_PAYLOAD = 'invalid-message-payload', INVALID_MFA_SESSION = 'invalid-multi-factor-session', @@ -117,232 +122,280 @@ export const enum AuthErrorCode { USER_MISMATCH = 'user-mismatch', USER_SIGNED_OUT = 'user-signed-out', WEAK_PASSWORD = 'weak-password', - WEB_STORAGE_UNSUPPORTED = 'web-storage-unsupported' + WEB_STORAGE_UNSUPPORTED = 'web-storage-unsupported', + ALREADY_INITIALIZED = 'already-initialized' } -const ERRORS: ErrorMap = { - [AuthErrorCode.ADMIN_ONLY_OPERATION]: - 'This operation is restricted to administrators only.', - [AuthErrorCode.ARGUMENT_ERROR]: '', - [AuthErrorCode.APP_NOT_AUTHORIZED]: - "This app, identified by the domain where it's hosted, is not " + - 'authorized to use Firebase Authentication with the provided API key. ' + - 'Review your key configuration in the Google API console.', - [AuthErrorCode.APP_NOT_INSTALLED]: - 'The requested mobile application corresponding to the identifier (' + - 'Android package name or iOS bundle ID) provided is not installed on ' + - 'this device.', - [AuthErrorCode.CAPTCHA_CHECK_FAILED]: - 'The reCAPTCHA response token provided is either invalid, expired, ' + - 'already used or the domain associated with it does not match the list ' + - 'of whitelisted domains.', - [AuthErrorCode.CODE_EXPIRED]: - 'The SMS code has expired. Please re-send the verification code to try ' + - 'again.', - [AuthErrorCode.CORDOVA_NOT_READY]: 'Cordova framework is not ready.', - [AuthErrorCode.CORS_UNSUPPORTED]: 'This browser is not supported.', - [AuthErrorCode.CREDENTIAL_ALREADY_IN_USE]: - 'This credential is already associated with a different user account.', - [AuthErrorCode.CREDENTIAL_MISMATCH]: - 'The custom token corresponds to a different audience.', - [AuthErrorCode.CREDENTIAL_TOO_OLD_LOGIN_AGAIN]: - 'This operation is sensitive and requires recent authentication. Log in ' + - 'again before retrying this request.', - [AuthErrorCode.DYNAMIC_LINK_NOT_ACTIVATED]: - 'Please activate Dynamic Links in the Firebase Console and agree to the terms and ' + - 'conditions.', - [AuthErrorCode.EMAIL_CHANGE_NEEDS_VERIFICATION]: - 'Multi-factor users must always have a verified email.', - [AuthErrorCode.EMAIL_EXISTS]: - 'The email address is already in use by another account.', - [AuthErrorCode.EMULATOR_CONFIG_FAILED]: - 'Auth instance has already been used to make a network call. Auth can ' + - 'no longer be configured to use the emulator. Try calling ' + - '"useEmulator()" sooner.', - [AuthErrorCode.EXPIRED_OOB_CODE]: 'The action code has expired.', - [AuthErrorCode.EXPIRED_POPUP_REQUEST]: - 'This operation has been cancelled due to another conflicting popup being opened.', - [AuthErrorCode.INTERNAL_ERROR]: 'An internal AuthError has occurred.', - [AuthErrorCode.INVALID_APP_CREDENTIAL]: - 'The phone verification request contains an invalid application verifier.' + - ' The reCAPTCHA token response is either invalid or expired.', - [AuthErrorCode.INVALID_APP_ID]: - 'The mobile app identifier is not registed for the current project.', - [AuthErrorCode.INVALID_AUTH]: - "This user's credential isn't valid for this project. This can happen " + - "if the user's token has been tampered with, or if the user isn't for " + - 'the project associated with this API key.', - [AuthErrorCode.INVALID_AUTH_EVENT]: 'An internal AuthError has occurred.', - [AuthErrorCode.INVALID_CODE]: - 'The SMS verification code used to create the phone auth credential is ' + - 'invalid. Please resend the verification code sms and be sure use the ' + - 'verification code provided by the user.', - [AuthErrorCode.INVALID_CONTINUE_URI]: - 'The continue URL provided in the request is invalid.', - [AuthErrorCode.INVALID_CORDOVA_CONFIGURATION]: - 'The following Cordova plugins must be installed to enable OAuth sign-in: ' + - 'cordova-plugin-buildinfo, cordova-universal-links-plugin, ' + - 'cordova-plugin-browsertab, cordova-plugin-inappbrowser and ' + - 'cordova-plugin-customurlscheme.', - [AuthErrorCode.INVALID_CUSTOM_TOKEN]: - 'The custom token format is incorrect. Please check the documentation.', - [AuthErrorCode.INVALID_DYNAMIC_LINK_DOMAIN]: - 'The provided dynamic link domain is not configured or authorized for the current project.', - [AuthErrorCode.INVALID_EMAIL]: 'The email address is badly formatted.', - [AuthErrorCode.INVALID_API_KEY]: - 'Your API key is invalid, please check you have copied it correctly.', - [AuthErrorCode.INVALID_CERT_HASH]: - 'The SHA-1 certificate hash provided is invalid.', - [AuthErrorCode.INVALID_IDP_RESPONSE]: - 'The supplied auth credential is malformed or has expired.', - [AuthErrorCode.INVALID_MESSAGE_PAYLOAD]: - 'The email template corresponding to this action contains invalid characters in its message. ' + - 'Please fix by going to the Auth email templates section in the Firebase Console.', - [AuthErrorCode.INVALID_MFA_SESSION]: - 'The request does not contain a valid proof of first factor successful sign-in.', - [AuthErrorCode.INVALID_OAUTH_PROVIDER]: - 'EmailAuthProvider is not supported for this operation. This operation ' + - 'only supports OAuth providers.', - [AuthErrorCode.INVALID_OAUTH_CLIENT_ID]: - 'The OAuth client ID provided is either invalid or does not match the ' + - 'specified API key.', - [AuthErrorCode.INVALID_ORIGIN]: - 'This domain is not authorized for OAuth operations for your Firebase ' + - 'project. Edit the list of authorized domains from the Firebase console.', - [AuthErrorCode.INVALID_OOB_CODE]: - 'The action code is invalid. This can happen if the code is malformed, ' + - 'expired, or has already been used.', - [AuthErrorCode.INVALID_PASSWORD]: - 'The password is invalid or the user does not have a password.', - [AuthErrorCode.INVALID_PERSISTENCE]: - 'The specified persistence type is invalid. It can only be local, session or none.', - [AuthErrorCode.INVALID_PHONE_NUMBER]: - 'The format of the phone number provided is incorrect. Please enter the ' + - 'phone number in a format that can be parsed into E.164 format. E.164 ' + - 'phone numbers are written in the format [+][country code][subscriber ' + - 'number including area code].', - [AuthErrorCode.INVALID_PROVIDER_ID]: 'The specified provider ID is invalid.', - [AuthErrorCode.INVALID_RECIPIENT_EMAIL]: - 'The email corresponding to this action failed to send as the provided ' + - 'recipient email address is invalid.', - [AuthErrorCode.INVALID_SENDER]: - 'The email template corresponding to this action contains an invalid sender email or name. ' + - 'Please fix by going to the Auth email templates section in the Firebase Console.', - [AuthErrorCode.INVALID_SESSION_INFO]: - 'The verification ID used to create the phone auth credential is invalid.', - [AuthErrorCode.INVALID_TENANT_ID]: - "The Auth instance's tenant ID is invalid.", - [AuthErrorCode.MISSING_ANDROID_PACKAGE_NAME]: - 'An Android Package Name must be provided if the Android App is required to be installed.', - [AuthErrorCode.MISSING_AUTH_DOMAIN]: - 'Be sure to include authDomain when calling firebase.initializeApp(), ' + - 'by following the instructions in the Firebase console.', - [AuthErrorCode.MISSING_APP_CREDENTIAL]: - 'The phone verification request is missing an application verifier ' + - 'assertion. A reCAPTCHA response token needs to be provided.', - [AuthErrorCode.MISSING_CODE]: - 'The phone auth credential was created with an empty SMS verification code.', - [AuthErrorCode.MISSING_CONTINUE_URI]: - 'A continue URL must be provided in the request.', - [AuthErrorCode.MISSING_IFRAME_START]: 'An internal AuthError has occurred.', - [AuthErrorCode.MISSING_IOS_BUNDLE_ID]: - 'An iOS Bundle ID must be provided if an App Store ID is provided.', - [AuthErrorCode.MISSING_OR_INVALID_NONCE]: - 'The request does not contain a valid nonce. This can occur if the ' + - 'SHA-256 hash of the provided raw nonce does not match the hashed nonce ' + - 'in the ID token payload.', - [AuthErrorCode.MISSING_MFA_INFO]: 'No second factor identifier is provided.', - [AuthErrorCode.MISSING_MFA_SESSION]: - 'The request is missing proof of first factor successful sign-in.', - [AuthErrorCode.MISSING_PHONE_NUMBER]: - 'To send verification codes, provide a phone number for the recipient.', - [AuthErrorCode.MISSING_SESSION_INFO]: - 'The phone auth credential was created with an empty verification ID.', - [AuthErrorCode.MODULE_DESTROYED]: - 'This instance of FirebaseApp has been deleted.', - [AuthErrorCode.MFA_INFO_NOT_FOUND]: - 'The user does not have a second factor matching the identifier provided.', - [AuthErrorCode.MFA_REQUIRED]: - 'Proof of ownership of a second factor is required to complete sign-in.', - [AuthErrorCode.NEED_CONFIRMATION]: - 'An account already exists with the same email address but different ' + - 'sign-in credentials. Sign in using a provider associated with this ' + - 'email address.', - [AuthErrorCode.NETWORK_REQUEST_FAILED]: - 'A network AuthError (such as timeout, interrupted connection or unreachable host) has occurred.', - [AuthErrorCode.NO_AUTH_EVENT]: 'An internal AuthError has occurred.', - [AuthErrorCode.NO_SUCH_PROVIDER]: - 'User was not linked to an account with the given provider.', - [AuthErrorCode.NULL_USER]: - 'A null user object was provided as the argument for an operation which ' + - 'requires a non-null user object.', - [AuthErrorCode.OPERATION_NOT_ALLOWED]: - 'The given sign-in provider is disabled for this Firebase project. ' + - 'Enable it in the Firebase console, under the sign-in method tab of the ' + - 'Auth section.', - [AuthErrorCode.OPERATION_NOT_SUPPORTED]: - 'This operation is not supported in the environment this application is ' + - 'running on. "location.protocol" must be http, https or chrome-extension' + - ' and web storage must be enabled.', - [AuthErrorCode.POPUP_BLOCKED]: - 'Unable to establish a connection with the popup. It may have been blocked by the browser.', - [AuthErrorCode.POPUP_CLOSED_BY_USER]: - 'The popup has been closed by the user before finalizing the operation.', - [AuthErrorCode.PROVIDER_ALREADY_LINKED]: - 'User can only be linked to one identity for the given provider.', - [AuthErrorCode.QUOTA_EXCEEDED]: - "The project's quota for this operation has been exceeded.", - [AuthErrorCode.REDIRECT_CANCELLED_BY_USER]: - 'The redirect operation has been cancelled by the user before finalizing.', - [AuthErrorCode.REDIRECT_OPERATION_PENDING]: - 'A redirect sign-in operation is already pending.', - [AuthErrorCode.REJECTED_CREDENTIAL]: - 'The request contains malformed or mismatching credentials.', - [AuthErrorCode.SECOND_FACTOR_ALREADY_ENROLLED]: - 'The second factor is already enrolled on this account.', - [AuthErrorCode.SECOND_FACTOR_LIMIT_EXCEEDED]: - 'The maximum allowed number of second factors on a user has been exceeded.', - [AuthErrorCode.TENANT_ID_MISMATCH]: - "The provided tenant ID does not match the Auth instance's tenant ID", - [AuthErrorCode.TIMEOUT]: 'The operation has timed out.', - [AuthErrorCode.TOKEN_EXPIRED]: - "The user's credential is no longer valid. The user must sign in again.", - [AuthErrorCode.TOO_MANY_ATTEMPTS_TRY_LATER]: - 'We have blocked all requests from this device due to unusual activity. ' + - 'Try again later.', - [AuthErrorCode.UNAUTHORIZED_DOMAIN]: - 'The domain of the continue URL is not whitelisted. Please whitelist ' + - 'the domain in the Firebase console.', - [AuthErrorCode.UNSUPPORTED_FIRST_FACTOR]: - 'Enrolling a second factor or signing in with a multi-factor account requires sign-in with a supported first factor.', - [AuthErrorCode.UNSUPPORTED_PERSISTENCE]: - 'The current environment does not support the specified persistence type.', - [AuthErrorCode.UNSUPPORTED_TENANT_OPERATION]: - 'This operation is not supported in a multi-tenant context.', - [AuthErrorCode.UNVERIFIED_EMAIL]: 'The operation requires a verified email.', - [AuthErrorCode.USER_CANCELLED]: - 'The user did not grant your application the permissions it requested.', - [AuthErrorCode.USER_DELETED]: - 'There is no user record corresponding to this identifier. The user may ' + - 'have been deleted.', - [AuthErrorCode.USER_DISABLED]: - 'The user account has been disabled by an administrator.', - [AuthErrorCode.USER_MISMATCH]: - 'The supplied credentials do not correspond to the previously signed in user.', - [AuthErrorCode.USER_SIGNED_OUT]: '', - [AuthErrorCode.WEAK_PASSWORD]: - 'The password must be 6 characters long or more.', - [AuthErrorCode.WEB_STORAGE_UNSUPPORTED]: - 'This browser is not supported or 3rd party cookies and data may be disabled.' -}; +function _debugErrorMap(): ErrorMap { + return { + [AuthErrorCode.ADMIN_ONLY_OPERATION]: + 'This operation is restricted to administrators only.', + [AuthErrorCode.ARGUMENT_ERROR]: '', + [AuthErrorCode.APP_NOT_AUTHORIZED]: + "This app, identified by the domain where it's hosted, is not " + + 'authorized to use Firebase Authentication with the provided API key. ' + + 'Review your key configuration in the Google API console.', + [AuthErrorCode.APP_NOT_INSTALLED]: + 'The requested mobile application corresponding to the identifier (' + + 'Android package name or iOS bundle ID) provided is not installed on ' + + 'this device.', + [AuthErrorCode.CAPTCHA_CHECK_FAILED]: + 'The reCAPTCHA response token provided is either invalid, expired, ' + + 'already used or the domain associated with it does not match the list ' + + 'of whitelisted domains.', + [AuthErrorCode.CODE_EXPIRED]: + 'The SMS code has expired. Please re-send the verification code to try ' + + 'again.', + [AuthErrorCode.CORDOVA_NOT_READY]: 'Cordova framework is not ready.', + [AuthErrorCode.CORS_UNSUPPORTED]: 'This browser is not supported.', + [AuthErrorCode.CREDENTIAL_ALREADY_IN_USE]: + 'This credential is already associated with a different user account.', + [AuthErrorCode.CREDENTIAL_MISMATCH]: + 'The custom token corresponds to a different audience.', + [AuthErrorCode.CREDENTIAL_TOO_OLD_LOGIN_AGAIN]: + 'This operation is sensitive and requires recent authentication. Log in ' + + 'again before retrying this request.', + [AuthErrorCode.DEPENDENT_SDK_INIT_BEFORE_AUTH]: + 'Another Firebase SDK was initialized and is trying to use Auth before Auth is ' + + 'initialized. Please be sure to call `initializeAuth` or `getAuth` before ' + + 'starting any other Firebase SDK.', + [AuthErrorCode.DYNAMIC_LINK_NOT_ACTIVATED]: + 'Please activate Dynamic Links in the Firebase Console and agree to the terms and ' + + 'conditions.', + [AuthErrorCode.EMAIL_CHANGE_NEEDS_VERIFICATION]: + 'Multi-factor users must always have a verified email.', + [AuthErrorCode.EMAIL_EXISTS]: + 'The email address is already in use by another account.', + [AuthErrorCode.EMULATOR_CONFIG_FAILED]: + 'Auth instance has already been used to make a network call. Auth can ' + + 'no longer be configured to use the emulator. Try calling ' + + '"useAuthEmulator()" sooner.', + [AuthErrorCode.EXPIRED_OOB_CODE]: 'The action code has expired.', + [AuthErrorCode.EXPIRED_POPUP_REQUEST]: + 'This operation has been cancelled due to another conflicting popup being opened.', + [AuthErrorCode.INTERNAL_ERROR]: 'An internal AuthError has occurred.', + [AuthErrorCode.INVALID_APP_CREDENTIAL]: + 'The phone verification request contains an invalid application verifier.' + + ' The reCAPTCHA token response is either invalid or expired.', + [AuthErrorCode.INVALID_APP_ID]: + 'The mobile app identifier is not registed for the current project.', + [AuthErrorCode.INVALID_AUTH]: + "This user's credential isn't valid for this project. This can happen " + + "if the user's token has been tampered with, or if the user isn't for " + + 'the project associated with this API key.', + [AuthErrorCode.INVALID_AUTH_EVENT]: 'An internal AuthError has occurred.', + [AuthErrorCode.INVALID_CODE]: + 'The SMS verification code used to create the phone auth credential is ' + + 'invalid. Please resend the verification code sms and be sure to use the ' + + 'verification code provided by the user.', + [AuthErrorCode.INVALID_CONTINUE_URI]: + 'The continue URL provided in the request is invalid.', + [AuthErrorCode.INVALID_CORDOVA_CONFIGURATION]: + 'The following Cordova plugins must be installed to enable OAuth sign-in: ' + + 'cordova-plugin-buildinfo, cordova-universal-links-plugin, ' + + 'cordova-plugin-browsertab, cordova-plugin-inappbrowser and ' + + 'cordova-plugin-customurlscheme.', + [AuthErrorCode.INVALID_CUSTOM_TOKEN]: + 'The custom token format is incorrect. Please check the documentation.', + [AuthErrorCode.INVALID_DYNAMIC_LINK_DOMAIN]: + 'The provided dynamic link domain is not configured or authorized for the current project.', + [AuthErrorCode.INVALID_EMAIL]: 'The email address is badly formatted.', + [AuthErrorCode.INVALID_EMULATOR_SCHEME]: + 'Emulator URL must start with a valid scheme (http:// or https://).', + [AuthErrorCode.INVALID_API_KEY]: + 'Your API key is invalid, please check you have copied it correctly.', + [AuthErrorCode.INVALID_CERT_HASH]: + 'The SHA-1 certificate hash provided is invalid.', + [AuthErrorCode.INVALID_IDP_RESPONSE]: + 'The supplied auth credential is malformed or has expired.', + [AuthErrorCode.INVALID_MESSAGE_PAYLOAD]: + 'The email template corresponding to this action contains invalid characters in its message. ' + + 'Please fix by going to the Auth email templates section in the Firebase Console.', + [AuthErrorCode.INVALID_MFA_SESSION]: + 'The request does not contain a valid proof of first factor successful sign-in.', + [AuthErrorCode.INVALID_OAUTH_PROVIDER]: + 'EmailAuthProvider is not supported for this operation. This operation ' + + 'only supports OAuth providers.', + [AuthErrorCode.INVALID_OAUTH_CLIENT_ID]: + 'The OAuth client ID provided is either invalid or does not match the ' + + 'specified API key.', + [AuthErrorCode.INVALID_ORIGIN]: + 'This domain is not authorized for OAuth operations for your Firebase ' + + 'project. Edit the list of authorized domains from the Firebase console.', + [AuthErrorCode.INVALID_OOB_CODE]: + 'The action code is invalid. This can happen if the code is malformed, ' + + 'expired, or has already been used.', + [AuthErrorCode.INVALID_PASSWORD]: + 'The password is invalid or the user does not have a password.', + [AuthErrorCode.INVALID_PERSISTENCE]: + 'The specified persistence type is invalid. It can only be local, session or none.', + [AuthErrorCode.INVALID_PHONE_NUMBER]: + 'The format of the phone number provided is incorrect. Please enter the ' + + 'phone number in a format that can be parsed into E.164 format. E.164 ' + + 'phone numbers are written in the format [+][country code][subscriber ' + + 'number including area code].', + [AuthErrorCode.INVALID_PROVIDER_ID]: + 'The specified provider ID is invalid.', + [AuthErrorCode.INVALID_RECIPIENT_EMAIL]: + 'The email corresponding to this action failed to send as the provided ' + + 'recipient email address is invalid.', + [AuthErrorCode.INVALID_SENDER]: + 'The email template corresponding to this action contains an invalid sender email or name. ' + + 'Please fix by going to the Auth email templates section in the Firebase Console.', + [AuthErrorCode.INVALID_SESSION_INFO]: + 'The verification ID used to create the phone auth credential is invalid.', + [AuthErrorCode.INVALID_TENANT_ID]: + "The Auth instance's tenant ID is invalid.", + [AuthErrorCode.MISSING_ANDROID_PACKAGE_NAME]: + 'An Android Package Name must be provided if the Android App is required to be installed.', + [AuthErrorCode.MISSING_AUTH_DOMAIN]: + 'Be sure to include authDomain when calling firebase.initializeApp(), ' + + 'by following the instructions in the Firebase console.', + [AuthErrorCode.MISSING_APP_CREDENTIAL]: + 'The phone verification request is missing an application verifier ' + + 'assertion. A reCAPTCHA response token needs to be provided.', + [AuthErrorCode.MISSING_CODE]: + 'The phone auth credential was created with an empty SMS verification code.', + [AuthErrorCode.MISSING_CONTINUE_URI]: + 'A continue URL must be provided in the request.', + [AuthErrorCode.MISSING_IFRAME_START]: 'An internal AuthError has occurred.', + [AuthErrorCode.MISSING_IOS_BUNDLE_ID]: + 'An iOS Bundle ID must be provided if an App Store ID is provided.', + [AuthErrorCode.MISSING_OR_INVALID_NONCE]: + 'The request does not contain a valid nonce. This can occur if the ' + + 'SHA-256 hash of the provided raw nonce does not match the hashed nonce ' + + 'in the ID token payload.', + [AuthErrorCode.MISSING_MFA_INFO]: + 'No second factor identifier is provided.', + [AuthErrorCode.MISSING_MFA_SESSION]: + 'The request is missing proof of first factor successful sign-in.', + [AuthErrorCode.MISSING_PHONE_NUMBER]: + 'To send verification codes, provide a phone number for the recipient.', + [AuthErrorCode.MISSING_SESSION_INFO]: + 'The phone auth credential was created with an empty verification ID.', + [AuthErrorCode.MODULE_DESTROYED]: + 'This instance of FirebaseApp has been deleted.', + [AuthErrorCode.MFA_INFO_NOT_FOUND]: + 'The user does not have a second factor matching the identifier provided.', + [AuthErrorCode.MFA_REQUIRED]: + 'Proof of ownership of a second factor is required to complete sign-in.', + [AuthErrorCode.NEED_CONFIRMATION]: + 'An account already exists with the same email address but different ' + + 'sign-in credentials. Sign in using a provider associated with this ' + + 'email address.', + [AuthErrorCode.NETWORK_REQUEST_FAILED]: + 'A network AuthError (such as timeout, interrupted connection or unreachable host) has occurred.', + [AuthErrorCode.NO_AUTH_EVENT]: 'An internal AuthError has occurred.', + [AuthErrorCode.NO_SUCH_PROVIDER]: + 'User was not linked to an account with the given provider.', + [AuthErrorCode.NULL_USER]: + 'A null user object was provided as the argument for an operation which ' + + 'requires a non-null user object.', + [AuthErrorCode.OPERATION_NOT_ALLOWED]: + 'The given sign-in provider is disabled for this Firebase project. ' + + 'Enable it in the Firebase console, under the sign-in method tab of the ' + + 'Auth section.', + [AuthErrorCode.OPERATION_NOT_SUPPORTED]: + 'This operation is not supported in the environment this application is ' + + 'running on. "location.protocol" must be http, https or chrome-extension' + + ' and web storage must be enabled.', + [AuthErrorCode.POPUP_BLOCKED]: + 'Unable to establish a connection with the popup. It may have been blocked by the browser.', + [AuthErrorCode.POPUP_CLOSED_BY_USER]: + 'The popup has been closed by the user before finalizing the operation.', + [AuthErrorCode.PROVIDER_ALREADY_LINKED]: + 'User can only be linked to one identity for the given provider.', + [AuthErrorCode.QUOTA_EXCEEDED]: + "The project's quota for this operation has been exceeded.", + [AuthErrorCode.REDIRECT_CANCELLED_BY_USER]: + 'The redirect operation has been cancelled by the user before finalizing.', + [AuthErrorCode.REDIRECT_OPERATION_PENDING]: + 'A redirect sign-in operation is already pending.', + [AuthErrorCode.REJECTED_CREDENTIAL]: + 'The request contains malformed or mismatching credentials.', + [AuthErrorCode.SECOND_FACTOR_ALREADY_ENROLLED]: + 'The second factor is already enrolled on this account.', + [AuthErrorCode.SECOND_FACTOR_LIMIT_EXCEEDED]: + 'The maximum allowed number of second factors on a user has been exceeded.', + [AuthErrorCode.TENANT_ID_MISMATCH]: + "The provided tenant ID does not match the Auth instance's tenant ID", + [AuthErrorCode.TIMEOUT]: 'The operation has timed out.', + [AuthErrorCode.TOKEN_EXPIRED]: + "The user's credential is no longer valid. The user must sign in again.", + [AuthErrorCode.TOO_MANY_ATTEMPTS_TRY_LATER]: + 'We have blocked all requests from this device due to unusual activity. ' + + 'Try again later.', + [AuthErrorCode.UNAUTHORIZED_DOMAIN]: + 'The domain of the continue URL is not whitelisted. Please whitelist ' + + 'the domain in the Firebase console.', + [AuthErrorCode.UNSUPPORTED_FIRST_FACTOR]: + 'Enrolling a second factor or signing in with a multi-factor account requires sign-in with a supported first factor.', + [AuthErrorCode.UNSUPPORTED_PERSISTENCE]: + 'The current environment does not support the specified persistence type.', + [AuthErrorCode.UNSUPPORTED_TENANT_OPERATION]: + 'This operation is not supported in a multi-tenant context.', + [AuthErrorCode.UNVERIFIED_EMAIL]: + 'The operation requires a verified email.', + [AuthErrorCode.USER_CANCELLED]: + 'The user did not grant your application the permissions it requested.', + [AuthErrorCode.USER_DELETED]: + 'There is no user record corresponding to this identifier. The user may ' + + 'have been deleted.', + [AuthErrorCode.USER_DISABLED]: + 'The user account has been disabled by an administrator.', + [AuthErrorCode.USER_MISMATCH]: + 'The supplied credentials do not correspond to the previously signed in user.', + [AuthErrorCode.USER_SIGNED_OUT]: '', + [AuthErrorCode.WEAK_PASSWORD]: + 'The password must be 6 characters long or more.', + [AuthErrorCode.WEB_STORAGE_UNSUPPORTED]: + 'This browser is not supported or 3rd party cookies and data may be disabled.', + [AuthErrorCode.ALREADY_INITIALIZED]: + 'Auth can only be initialized once per app.' + }; +} + +export interface ErrorMapRetriever extends AuthErrorMap { + (): ErrorMap; +} + +function _prodErrorMap(): ErrorMap { + // We will include this one message in the prod error map since by the very + // nature of this error, developers will never be able to see the message + // using the debugErrorMap (which is installed during auth initialization). + return { + [AuthErrorCode.DEPENDENT_SDK_INIT_BEFORE_AUTH]: + 'Another Firebase SDK was initialized and is trying to use Auth before Auth is ' + + 'initialized. Please be sure to call `initializeAuth` or `getAuth` before ' + + 'starting any other Firebase SDK.' + } as ErrorMap; +} + +/** + * A verbose error map with detailed descriptions for most error codes. + * + * See discussion at {@link AuthErrorMap} + * + * @public + */ +export const debugErrorMap: AuthErrorMap = _debugErrorMap; + +/** + * A minimal error map with all verbose error messages stripped. + * + * See discussion at {@link AuthErrorMap} + * + * @public + */ +export const prodErrorMap: AuthErrorMap = _prodErrorMap; export interface NamedErrorParams { appName: AppName; - credential?: externs.AuthCredential; + credential?: AuthCredential; email?: string; phoneNumber?: string; tenantId?: string; - user?: externs.User; + user?: User; serverResponse?: object; } @@ -350,8 +403,10 @@ type GenericAuthErrorParams = { [key in Exclude< AuthErrorCode, | AuthErrorCode.ARGUMENT_ERROR + | AuthErrorCode.DEPENDENT_SDK_INIT_BEFORE_AUTH | AuthErrorCode.INTERNAL_ERROR | AuthErrorCode.MFA_REQUIRED + | AuthErrorCode.NO_AUTH_EVENT >]: { appName: AppName; email?: string; @@ -361,14 +416,20 @@ type GenericAuthErrorParams = { export interface AuthErrorParams extends GenericAuthErrorParams { [AuthErrorCode.ARGUMENT_ERROR]: { appName?: AppName }; + [AuthErrorCode.DEPENDENT_SDK_INIT_BEFORE_AUTH]: { appName?: AppName }; [AuthErrorCode.INTERNAL_ERROR]: { appName?: AppName }; + [AuthErrorCode.NO_AUTH_EVENT]: { appName?: AppName }; [AuthErrorCode.MFA_REQUIRED]: { appName: AppName; serverResponse: IdTokenMfaResponse; }; + [AuthErrorCode.INVALID_CORDOVA_CONFIGURATION]: { + appName: AppName; + missingPlugin?: string; + }; } -export const AUTH_ERROR_FACTORY = new ErrorFactory< +export const _DEFAULT_AUTH_ERROR_FACTORY = new ErrorFactory< AuthErrorCode, AuthErrorParams ->('auth', 'Firebase', ERRORS); +>('auth', 'Firebase', _prodErrorMap()); diff --git a/packages-exp/auth-exp/src/core/index.ts b/packages-exp/auth-exp/src/core/index.ts index cf90414a58d..9041cd72d38 100644 --- a/packages-exp/auth-exp/src/core/index.ts +++ b/packages-exp/auth-exp/src/core/index.ts @@ -15,46 +15,142 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; -import { CompleteFn, ErrorFn, Unsubscribe } from '@firebase/util'; +import { getModularInstance } from '@firebase/util'; +import { + Auth, + NextOrObserver, + Persistence, + User, + CompleteFn, + ErrorFn, + Unsubscribe +} from '../model/public_types'; + +export { debugErrorMap, prodErrorMap } from './errors'; // Non-optional auth methods. +/** + * Changes the type of persistence on the Auth instance for the currently saved + * Auth session and applies this type of persistence for future sign-in requests, including + * sign-in with redirect requests. + * + * @remarks + * This makes it easy for a user signing in to specify whether their session should be + * remembered or not. It also makes it easier to never persist the Auth state for applications + * that are shared by other users or have sensitive data. + * + * @example + * ```javascript + * setPersistence(auth, browserSessionPersistence); + * ``` + * + * @param auth - The Auth instance. + * @param persistence - The {@link Persistence} to use. + * @returns A promise that resolves once the persistence change has completed + * + * @public + */ export function setPersistence( - auth: externs.Auth, - persistence: externs.Persistence -): void { - auth.setPersistence(persistence); + auth: Auth, + persistence: Persistence +): Promise { + return getModularInstance(auth).setPersistence(persistence); } +/** + * Adds an observer for changes to the signed-in user's ID token, which includes sign-in, + * sign-out, and token refresh events. + * + * @param auth - The Auth instance. + * @param nextOrObserver - callback triggered on change. + * @param error - callback triggered on error. + * @param completed - callback triggered when observer is removed. + * + * @public + */ export function onIdTokenChanged( - auth: externs.Auth, - nextOrObserver: externs.NextOrObserver, + auth: Auth, + nextOrObserver: NextOrObserver, error?: ErrorFn, completed?: CompleteFn ): Unsubscribe { - return auth.onIdTokenChanged(nextOrObserver, error, completed); + return getModularInstance(auth).onIdTokenChanged( + nextOrObserver, + error, + completed + ); } +/** + * Adds an observer for changes to the user's sign-in state. + * + * @remarks + * To keep the old behavior, see {@link onIdTokenChanged}. + * + * @param auth - The Auth instance. + * @param nextOrObserver - callback triggered on change. + * @param error - callback triggered on error. + * @param completed - callback triggered when observer is removed. + * + * @public + */ export function onAuthStateChanged( - auth: externs.Auth, - nextOrObserver: externs.NextOrObserver, + auth: Auth, + nextOrObserver: NextOrObserver, error?: ErrorFn, completed?: CompleteFn ): Unsubscribe { - return auth.onAuthStateChanged(nextOrObserver, error, completed); + return getModularInstance(auth).onAuthStateChanged( + nextOrObserver, + error, + completed + ); } -export function useDeviceLanguage(auth: externs.Auth): void { - auth.useDeviceLanguage(); +/** + * Sets the current language to the default device/browser preference. + * + * @param auth - The Auth instanec. + * + * @public + */ +export function useDeviceLanguage(auth: Auth): void { + getModularInstance(auth).useDeviceLanguage(); } +/** + * Asynchronously sets the provided user as {@link Auth.currentUser} on the + * {@link Auth} instance. + * + * @remarks + * A new instance copy of the user provided will be made and set as currentUser. + * + * This will trigger {@link onAuthStateChanged} and {@link onIdTokenChanged} listeners + * like other sign in methods. + * + * The operation fails with an error if the user to be updated belongs to a different Firebase + * project. + * + * @param auth - The Auth instance. + * @param user - The new {@link User}. + * + * @public + */ export function updateCurrentUser( - auth: externs.Auth, - user: externs.User | null + auth: Auth, + user: User | null ): Promise { - return auth.updateCurrentUser(user); + return getModularInstance(auth).updateCurrentUser(user); } -export function signOut(auth: externs.Auth): Promise { - return auth.signOut(); +/** + * Signs out the current user. + * + * @param auth - The Auth instance. + * + * @public + */ +export function signOut(auth: Auth): Promise { + return getModularInstance(auth).signOut(); } export { initializeAuth } from './auth/initialize'; +export { useAuthEmulator } from './auth/emulator'; // credentials export { AuthCredential } from './credentials'; @@ -68,9 +164,11 @@ export { inMemoryPersistence } from './persistence/in_memory'; // providers export { EmailAuthProvider } from './providers/email'; export { FacebookAuthProvider } from './providers/facebook'; +export { CustomParameters } from './providers/federated'; export { GoogleAuthProvider } from './providers/google'; export { GithubAuthProvider } from './providers/github'; -export { OAuthProvider } from './providers/oauth'; +export { OAuthProvider, OAuthCredentialOptions } from './providers/oauth'; +export { SAMLAuthProvider } from './providers/saml'; export { TwitterAuthProvider } from './providers/twitter'; // strategies @@ -116,6 +214,18 @@ export { getAdditionalUserInfo } from './user/additional_user_info'; // Non-optional user methods. export { reload } from './user/reload'; -export async function deleteUser(user: externs.User): Promise { - return user.delete(); +/** + * Deletes and signs out the user. + * + * @remarks + * Important: this is a security-sensitive operation that requires the user to have recently + * signed in. If this requirement isn't met, ask the user to authenticate again and then call + * {@link reauthenticateWithCredential}. + * + * @param user - The user. + * + * @public + */ +export async function deleteUser(user: User): Promise { + return getModularInstance(user).delete(); } diff --git a/packages-exp/auth-exp/src/core/persistence/in_memory.test.ts b/packages-exp/auth-exp/src/core/persistence/in_memory.test.ts index 9b913230031..9686f1af3bc 100644 --- a/packages-exp/auth-exp/src/core/persistence/in_memory.test.ts +++ b/packages-exp/auth-exp/src/core/persistence/in_memory.test.ts @@ -19,10 +19,10 @@ import { expect } from 'chai'; import { testUser, testAuth } from '../../../test/helpers/mock_auth'; import { _getInstance } from '../util/instantiator'; -import { Persistence, PersistenceType } from './'; +import { PersistenceInternal, PersistenceType } from './'; import { inMemoryPersistence } from './in_memory'; -const persistence: Persistence = _getInstance(inMemoryPersistence); +const persistence: PersistenceInternal = _getInstance(inMemoryPersistence); describe('core/persistence/in_memory', () => { it('should work with persistence type', async () => { diff --git a/packages-exp/auth-exp/src/core/persistence/in_memory.ts b/packages-exp/auth-exp/src/core/persistence/in_memory.ts index 4bfaf94b887..86188df8a62 100644 --- a/packages-exp/auth-exp/src/core/persistence/in_memory.ts +++ b/packages-exp/auth-exp/src/core/persistence/in_memory.ts @@ -15,16 +15,16 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { Persistence } from '../../model/public_types'; import { - Persistence, + PersistenceInternal, PersistenceType, PersistenceValue, StorageEventListener } from '../persistence'; -export class InMemoryPersistence implements Persistence { +export class InMemoryPersistence implements PersistenceInternal { static type: 'NONE' = 'NONE'; readonly type = PersistenceType.NONE; storage: Record = {}; @@ -57,4 +57,9 @@ export class InMemoryPersistence implements Persistence { } } -export const inMemoryPersistence: externs.Persistence = InMemoryPersistence; +/** + * An implementation of {@link Persistence} of type 'NONE'. + * + * @public + */ +export const inMemoryPersistence: Persistence = InMemoryPersistence; diff --git a/packages-exp/auth-exp/src/core/persistence/index.ts b/packages-exp/auth-exp/src/core/persistence/index.ts index 78ce579739b..b231d4cdc86 100644 --- a/packages-exp/auth-exp/src/core/persistence/index.ts +++ b/packages-exp/auth-exp/src/core/persistence/index.ts @@ -14,8 +14,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { Persistence } from '../../model/public_types'; -export enum PersistenceType { +export const enum PersistenceType { SESSION = 'SESSION', LOCAL = 'LOCAL', NONE = 'NONE' @@ -35,7 +36,7 @@ export interface StorageEventListener { (value: PersistenceValue | null): void; } -export interface Persistence { +export interface PersistenceInternal extends Persistence { type: PersistenceType; _isAvailable(): Promise; _set(key: string, value: PersistenceValue): Promise; diff --git a/packages-exp/auth-exp/src/core/persistence/persistence_user_manager.test.ts b/packages-exp/auth-exp/src/core/persistence/persistence_user_manager.test.ts index 69ac510d172..90d7ece961c 100644 --- a/packages-exp/auth-exp/src/core/persistence/persistence_user_manager.test.ts +++ b/packages-exp/auth-exp/src/core/persistence/persistence_user_manager.test.ts @@ -23,19 +23,24 @@ import * as sinonChai from 'sinon-chai'; import { testAuth, testUser, TestAuth } from '../../../test/helpers/mock_auth'; import { UserImpl } from '../user/user_impl'; import { _getInstance } from '../util/instantiator'; -import { Persistence, PersistenceType, StorageEventListener } from './'; +import { + PersistenceInternal, + PersistenceType, + PersistenceValue, + StorageEventListener +} from './'; import { inMemoryPersistence } from './in_memory'; -import { PersistenceUserManager } from './persistence_user_manager'; +import { KeyName, PersistenceUserManager } from './persistence_user_manager'; chai.use(sinonChai); function makePersistence( type = PersistenceType.NONE ): { - persistence: Persistence; - stub: sinon.SinonStubbedInstance; + persistence: PersistenceInternal; + stub: sinon.SinonStubbedInstance; } { - const persistence: Persistence = { + const persistence: PersistenceInternal = { type, _isAvailable: () => Promise.resolve(true), _set: async () => {}, @@ -64,19 +69,78 @@ describe('core/persistence/persistence_user_manager', () => { expect(manager.persistence).to.eq(_getInstance(inMemoryPersistence)); }); - it('searches in order for a user', async () => { + it('chooses the first one available', async () => { const a = makePersistence(); const b = makePersistence(); const c = makePersistence(); const search = [a.persistence, b.persistence, c.persistence]; const auth = await testAuth(); - b.stub._get.returns(Promise.resolve(testUser(auth, 'uid').toJSON())); + a.stub._isAvailable.resolves(false); + a.stub._get.onFirstCall().resolves(testUser(auth, 'uid').toJSON()); + b.stub._isAvailable.resolves(true); const out = await PersistenceUserManager.create(auth, search); + expect(a.stub._isAvailable).to.have.been.calledOnce; + expect(b.stub._isAvailable).to.have.been.calledOnce; + expect(c.stub._isAvailable).to.not.have.been.called; + + // a should not be chosen since it is not available (despite having a user). expect(out.persistence).to.eq(b.persistence); + }); + + it('searches in order for a user', async () => { + const a = makePersistence(); + const b = makePersistence(); + const c = makePersistence(); + const search = [a.persistence, b.persistence, c.persistence]; + const auth = await testAuth(); + const user = testUser(auth, 'uid'); + a.stub._isAvailable.resolves(true); + a.stub._get.resolves(user.toJSON()); + b.stub._get.resolves(testUser(auth, 'wrong-uid').toJSON()); + + const out = await PersistenceUserManager.create(auth, search); expect(a.stub._get).to.have.been.calledOnce; - expect(b.stub._get).to.have.been.calledOnce; + expect(b.stub._get).not.to.have.been.called; expect(c.stub._get).not.to.have.been.called; + + expect(out.persistence).to.eq(a.persistence); + expect((await out.getCurrentUser())!.uid).to.eq(user.uid); + }); + + it('migrate found user to the selected persistence and clear others', async () => { + const a = makePersistence(); + const b = makePersistence(); + const c = makePersistence(); + const search = [a.persistence, b.persistence, c.persistence]; + const auth = await testAuth(); + const user = testUser(auth, 'uid'); + a.stub._isAvailable.resolves(true); + b.stub._get.resolves(user.toJSON()); + c.stub._get.resolves(testUser(auth, 'wrong-uid').toJSON()); + + let persistedUserInA: PersistenceValue | null = null; + a.stub._set.callsFake(async (_, value) => { + persistedUserInA = value; + }); + a.stub._get.callsFake(async () => persistedUserInA); + + const out = await PersistenceUserManager.create(auth, search); + expect(a.stub._set).to.have.been.calledOnceWith( + 'firebase:authUser:test-api-key:test-app', + user.toJSON() + ); + expect(b.stub._set).to.not.have.been.called; + expect(c.stub._set).to.not.have.been.called; + expect(b.stub._remove).to.have.been.calledOnceWith( + 'firebase:authUser:test-api-key:test-app' + ); + expect(c.stub._remove).to.have.been.calledOnceWith( + 'firebase:authUser:test-api-key:test-app' + ); + + expect(out.persistence).to.eq(a.persistence); + expect((await out.getCurrentUser())!.uid).to.eq(user.uid); }); it('uses default user key if none provided', async () => { @@ -89,19 +153,27 @@ describe('core/persistence/persistence_user_manager', () => { it('uses user key if provided', async () => { const { stub, persistence } = makePersistence(); - await PersistenceUserManager.create(auth, [persistence], 'redirectUser'); + await PersistenceUserManager.create( + auth, + [persistence], + KeyName.REDIRECT_USER + ); expect(stub._get).to.have.been.calledWith( 'firebase:redirectUser:test-api-key:test-app' ); }); - it('returns zeroth persistence if all else fails', async () => { + it('returns in-memory persistence if all else fails', async () => { const a = makePersistence(); const b = makePersistence(); const c = makePersistence(); const search = [a.persistence, b.persistence, c.persistence]; + a.stub._isAvailable.resolves(false); + b.stub._isAvailable.resolves(false); + c.stub._isAvailable.resolves(false); + const out = await PersistenceUserManager.create(auth, search); - expect(out.persistence).to.eq(a.persistence); + expect(out.persistence).to.eq(_getInstance(inMemoryPersistence)); expect(a.stub._get).to.have.been.calledOnce; expect(b.stub._get).to.have.been.calledOnce; expect(c.stub._get).to.have.been.called; @@ -109,11 +181,12 @@ describe('core/persistence/persistence_user_manager', () => { }); describe('manager methods', () => { - let persistenceStub: sinon.SinonStubbedInstance; + let persistenceStub: sinon.SinonStubbedInstance; let manager: PersistenceUserManager; beforeEach(async () => { const { persistence, stub } = makePersistence(PersistenceType.SESSION); + stub._isAvailable.resolves(true); persistenceStub = stub; manager = await PersistenceUserManager.create(auth, [persistence]); }); @@ -154,12 +227,9 @@ describe('core/persistence/persistence_user_manager', () => { }); describe('#setPersistence', () => { - it('returns immediately if types match', async () => { - const { persistence: nextPersistence } = makePersistence( - PersistenceType.SESSION - ); + it('returns immediately if persistence is not changed', async () => { const spy = sinon.spy(manager, 'getCurrentUser'); - await manager.setPersistence(nextPersistence); + await manager.setPersistence(manager.persistence); expect(spy).not.to.have.been.called; spy.restore(); }); @@ -181,6 +251,29 @@ describe('core/persistence/persistence_user_manager', () => { user.toJSON() ); }); + + it('migrates user for a different persistence even if .type matches', async () => { + const { persistence, stub } = makePersistence(PersistenceType.LOCAL); + await manager.setPersistence(persistence); + const auth = await testAuth(); + const user = testUser(auth, 'uid'); + stub._get.returns(Promise.resolve(user.toJSON())); + + const { + persistence: nextPersistence, + stub: nextStub + } = makePersistence(PersistenceType.LOCAL); + + // This should migrate the user even if both has type LOCAL. For example, developer may want + // to switch from localStorage to indexedDB (both type LOCAL) and we should honor that. + await manager.setPersistence(nextPersistence); + expect(stub._get).to.have.been.called; + expect(stub._remove).to.have.been.called; + expect(nextStub._set).to.have.been.calledWith( + 'firebase:authUser:test-api-key:test-app', + user.toJSON() + ); + }); }); }); }); diff --git a/packages-exp/auth-exp/src/core/persistence/persistence_user_manager.ts b/packages-exp/auth-exp/src/core/persistence/persistence_user_manager.ts index 04b1f74203e..f6d0291ade0 100644 --- a/packages-exp/auth-exp/src/core/persistence/persistence_user_manager.ts +++ b/packages-exp/auth-exp/src/core/persistence/persistence_user_manager.ts @@ -15,53 +15,57 @@ * limitations under the License. */ -import { ApiKey, AppName, Auth } from '../../model/auth'; -import { User } from '../../model/user'; -import { PersistedBlob, Persistence } from '../persistence'; +import { ApiKey, AppName, AuthInternal } from '../../model/auth'; +import { UserInternal } from '../../model/user'; +import { PersistedBlob, PersistenceInternal } from '../persistence'; import { UserImpl } from '../user/user_impl'; import { _getInstance } from '../util/instantiator'; import { inMemoryPersistence } from './in_memory'; -export const _AUTH_USER_KEY_NAME = 'authUser'; -export const _REDIRECT_USER_KEY_NAME = 'redirectUser'; -export const _PERSISTENCE_KEY_NAME = 'persistence'; -const PERSISTENCE_NAMESPACE = 'firebase'; +export const enum KeyName { + AUTH_USER = 'authUser', + AUTH_EVENT = 'authEvent', + REDIRECT_USER = 'redirectUser', + PERSISTENCE_USER = 'persistence' +} +export const enum Namespace { + PERSISTENCE = 'firebase' +} -function _persistenceKeyName( +export function _persistenceKeyName( key: string, apiKey: ApiKey, appName: AppName ): string { - return `${PERSISTENCE_NAMESPACE}:${key}:${apiKey}:${appName}`; + return `${Namespace.PERSISTENCE}:${key}:${apiKey}:${appName}`; } export class PersistenceUserManager { private readonly fullUserKey: string; private readonly fullPersistenceKey: string; + private readonly boundEventHandler: () => void; private constructor( - public persistence: Persistence, - private readonly auth: Auth, + public persistence: PersistenceInternal, + private readonly auth: AuthInternal, private readonly userKey: string ) { const { config, name } = this.auth; this.fullUserKey = _persistenceKeyName(this.userKey, config.apiKey, name); this.fullPersistenceKey = _persistenceKeyName( - _PERSISTENCE_KEY_NAME, + KeyName.PERSISTENCE_USER, config.apiKey, name ); - this.persistence._addListener( - this.fullUserKey, - auth._onStorageEvent.bind(auth) - ); + this.boundEventHandler = auth._onStorageEvent.bind(auth); + this.persistence._addListener(this.fullUserKey, this.boundEventHandler); } - setCurrentUser(user: User): Promise { + setCurrentUser(user: UserInternal): Promise { return this.persistence._set(this.fullUserKey, user.toJSON()); } - async getCurrentUser(): Promise { + async getCurrentUser(): Promise { const blob = await this.persistence._get(this.fullUserKey); return blob ? UserImpl._fromJSON(this.auth, blob) : null; } @@ -77,8 +81,8 @@ export class PersistenceUserManager { ); } - async setPersistence(newPersistence: Persistence): Promise { - if (this.persistence.type === newPersistence.type) { + async setPersistence(newPersistence: PersistenceInternal): Promise { + if (this.persistence === newPersistence) { return; } @@ -93,16 +97,13 @@ export class PersistenceUserManager { } delete(): void { - this.persistence._removeListener( - this.fullUserKey, - this.auth._onStorageEvent - ); + this.persistence._removeListener(this.fullUserKey, this.boundEventHandler); } static async create( - auth: Auth, - persistenceHierarchy: Persistence[], - userKey = _AUTH_USER_KEY_NAME + auth: AuthInternal, + persistenceHierarchy: PersistenceInternal[], + userKey = KeyName.AUTH_USER ): Promise { if (!persistenceHierarchy.length) { return new PersistenceUserManager( @@ -112,19 +113,52 @@ export class PersistenceUserManager { ); } - const key = _persistenceKeyName(userKey, auth.config.apiKey, auth.name); + // Use the first persistence that supports a full read-write roundtrip (or fallback to memory). + let chosenPersistence = _getInstance( + inMemoryPersistence + ); for (const persistence of persistenceHierarchy) { - if (await persistence._get(key)) { - return new PersistenceUserManager(persistence, auth, userKey); + if (await persistence._isAvailable()) { + chosenPersistence = persistence; + break; } } - // Check all the available storage options. - // TODO: Migrate from local storage to indexedDB - // TODO: Clear other forms once one is found + // However, attempt to migrate users stored in other persistences (in the hierarchy order). + let userToMigrate: UserInternal | null = null; + const key = _persistenceKeyName(userKey, auth.config.apiKey, auth.name); + for (const persistence of persistenceHierarchy) { + // We attempt to call _get without checking _isAvailable since here we don't care if the full + // round-trip (read+write) is supported. We'll take the first one that we can read or give up. + try { + const blob = await persistence._get(key); // throws if unsupported + if (blob) { + const user = UserImpl._fromJSON(auth, blob); // throws for unparsable blob (wrong format) + if (persistence !== chosenPersistence) { + userToMigrate = user; + } + break; + } + } catch {} + } - // All else failed, fall back to zeroth persistence - // TODO: Modify this to support non-browser devices - return new PersistenceUserManager(persistenceHierarchy[0], auth, userKey); + if (userToMigrate) { + // This normally shouldn't throw since chosenPersistence.isAvailable() is true, but if it does + // we'll just let it bubble to surface the error. + await chosenPersistence._set(key, userToMigrate.toJSON()); + } + + // Attempt to clear the key in other persistences but ignore errors. This helps prevent issues + // such as users getting stuck with a previous account after signing out and refreshing the tab. + await Promise.all( + persistenceHierarchy.map(async persistence => { + if (persistence !== chosenPersistence) { + try { + await persistence._remove(key); + } catch {} + } + }) + ); + return new PersistenceUserManager(chosenPersistence, auth, userKey); } } diff --git a/packages-exp/auth-exp/src/core/providers/email.test.ts b/packages-exp/auth-exp/src/core/providers/email.test.ts index 08aa6a9f39a..7046d1006ae 100644 --- a/packages-exp/auth-exp/src/core/providers/email.test.ts +++ b/packages-exp/auth-exp/src/core/providers/email.test.ts @@ -18,7 +18,7 @@ import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; -import { ProviderId, SignInMethod } from '@firebase/auth-types-exp'; +import { ProviderId, SignInMethod } from '../../model/public_types'; // eslint-disable-next-line import/no-extraneous-dependencies import { FirebaseError } from '@firebase/util'; @@ -33,8 +33,8 @@ describe('core/providers/email', () => { 'some-email', 'some-password' ); - expect(credential.email).to.eq('some-email'); - expect(credential.password).to.eq('some-password'); + expect(credential._email).to.eq('some-email'); + expect(credential._password).to.eq('some-password'); expect(credential.providerId).to.eq(ProviderId.PASSWORD); expect(credential.signInMethod).to.eq(SignInMethod.EMAIL_PASSWORD); }); @@ -54,8 +54,8 @@ describe('core/providers/email', () => { 'some-email', actionLink ); - expect(credential.email).to.eq('some-email'); - expect(credential.password).to.eq('CODE'); + expect(credential._email).to.eq('some-email'); + expect(credential._password).to.eq('CODE'); expect(credential.providerId).to.eq(ProviderId.PASSWORD); expect(credential.signInMethod).to.eq(SignInMethod.EMAIL_LINK); }); diff --git a/packages-exp/auth-exp/src/core/providers/email.ts b/packages-exp/auth-exp/src/core/providers/email.ts index d3d67172ddf..3104c01858b 100644 --- a/packages-exp/auth-exp/src/core/providers/email.ts +++ b/packages-exp/auth-exp/src/core/providers/email.ts @@ -15,30 +15,90 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { + ProviderId, + SignInMethod, + AuthProvider +} from '../../model/public_types'; import { ActionCodeURL } from '../action_code_url'; import { EmailAuthCredential } from '../credentials/email'; import { AuthErrorCode } from '../errors'; -import { assert } from '../util/assert'; +import { _assert } from '../util/assert'; -export class EmailAuthProvider implements externs.EmailAuthProvider { - static readonly PROVIDER_ID = externs.ProviderId.PASSWORD; - static readonly EMAIL_PASSWORD_SIGN_IN_METHOD = - externs.SignInMethod.EMAIL_PASSWORD; - static readonly EMAIL_LINK_SIGN_IN_METHOD = externs.SignInMethod.EMAIL_LINK; +/** + * Provider for generating {@link EmailAuthCredential}. + * + * @public + */ +export class EmailAuthProvider implements AuthProvider { + /** + * Always set to {@link ProviderId}.PASSWORD, even for email link. + */ + static readonly PROVIDER_ID = ProviderId.PASSWORD; + /** + * Always set to {@link SignInMethod}.EMAIL_PASSWORD. + */ + static readonly EMAIL_PASSWORD_SIGN_IN_METHOD = SignInMethod.EMAIL_PASSWORD; + /** + * Always set to {@link SignInMethod}.EMAIL_LINK. + */ + static readonly EMAIL_LINK_SIGN_IN_METHOD = SignInMethod.EMAIL_LINK; + /** + * Always set to {@link ProviderId}.PASSWORD, even for email link. + */ readonly providerId = EmailAuthProvider.PROVIDER_ID; + /** + * Initialize an {@link AuthCredential} using an email and password. + * + * @example + * ```javascript + * const authCredential = EmailAuthProvider.credential(email, password); + * const userCredential = await signInWithCredential(auth, authCredential); + * ``` + * + * @example + * ```javascript + * const userCredential = await signInWithEmailAndPassword(auth, email, password); + * ``` + * + * @param email - Email address. + * @param password - User account password. + * @returns The auth provider credential. + */ static credential(email: string, password: string): EmailAuthCredential { return EmailAuthCredential._fromEmailAndPassword(email, password); } + /** + * Initialize an {@link AuthCredential} using an email and an email link after a sign in with + * email link operation. + * + * @example + * ```javascript + * const authCredential = EmailAuthProvider.credentialWithLink(auth, email, emailLink); + * const userCredential = await signInWithCredential(auth, authCredential); + * ``` + * + * @example + * ```javascript + * await sendSignInLinkToEmail(auth, email); + * // Obtain emailLink from user. + * const userCredential = await signInWithEmailLink(auth, email, emailLink); + * ``` + * + * @param auth - The Auth instance used to verify the link. + * @param email - Email address. + * @param emailLink - Sign-in email link. + * @returns - The auth provider credential. + */ static credentialWithLink( email: string, emailLink: string ): EmailAuthCredential { const actionCodeUrl = ActionCodeURL.parseLink(emailLink); - assert(actionCodeUrl, AuthErrorCode.ARGUMENT_ERROR, {}); + _assert(actionCodeUrl, AuthErrorCode.ARGUMENT_ERROR); return EmailAuthCredential._fromEmailAndCode( email, diff --git a/packages-exp/auth-exp/src/core/providers/facebook.test.ts b/packages-exp/auth-exp/src/core/providers/facebook.test.ts index dab3fedce31..dcf310dee64 100644 --- a/packages-exp/auth-exp/src/core/providers/facebook.test.ts +++ b/packages-exp/auth-exp/src/core/providers/facebook.test.ts @@ -21,16 +21,17 @@ import { OperationType, ProviderId, SignInMethod -} from '@firebase/auth-types-exp'; +} from '../../model/public_types'; import { TEST_ID_TOKEN_RESPONSE } from '../../../test/helpers/id_token_response'; import { testUser, testAuth } from '../../../test/helpers/mock_auth'; import { TaggedWithTokenResponse } from '../../model/id_token'; -import { AUTH_ERROR_FACTORY, AuthErrorCode } from '../errors'; +import { AuthErrorCode } from '../errors'; import { UserCredentialImpl } from '../user/user_credential_impl'; import { FacebookAuthProvider } from './facebook'; +import { _createError } from '../util/assert'; -describe('src/core/providers/facebook', () => { +describe('core/providers/facebook', () => { it('generates the correct type of oauth credential', () => { const cred = FacebookAuthProvider.credential('access-token'); expect(cred.accessToken).to.eq('access-token'); @@ -56,10 +57,10 @@ describe('src/core/providers/facebook', () => { }); it('credentialFromError creates the cred from a tagged error', () => { - const error = AUTH_ERROR_FACTORY.create(AuthErrorCode.NEED_CONFIRMATION, { + const error = _createError(AuthErrorCode.NEED_CONFIRMATION, { appName: 'foo' }); - (error as TaggedWithTokenResponse)._tokenResponse = { + (error.customData! as TaggedWithTokenResponse)._tokenResponse = { ...TEST_ID_TOKEN_RESPONSE, oauthAccessToken: 'access-token' }; diff --git a/packages-exp/auth-exp/src/core/providers/facebook.ts b/packages-exp/auth-exp/src/core/providers/facebook.ts index d35e79dfdf7..5dbc38d0af2 100644 --- a/packages-exp/auth-exp/src/core/providers/facebook.ts +++ b/packages-exp/auth-exp/src/core/providers/facebook.ts @@ -15,20 +15,80 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { + ProviderId, + SignInMethod, + UserCredential +} from '../../model/public_types'; import { FirebaseError } from '@firebase/util'; import { TaggedWithTokenResponse } from '../../model/id_token'; -import { UserCredential } from '../../model/user'; +import { UserCredentialInternal } from '../../model/user'; import { OAuthCredential } from '../credentials/oauth'; -import { OAuthProvider } from './oauth'; +import { BaseOAuthProvider } from './oauth'; -export class FacebookAuthProvider extends OAuthProvider { - static readonly FACEBOOK_SIGN_IN_METHOD = externs.SignInMethod.FACEBOOK; - static readonly PROVIDER_ID = externs.ProviderId.FACEBOOK; - readonly providerId = FacebookAuthProvider.PROVIDER_ID; +/** + * Provider for generating an {@link OAuthCredential} for {@link ProviderId}.FACEBOOK. + * + * @example + * ```javascript + * // Sign in using a redirect. + * const provider = new FacebookAuthProvider(); + * // Start a sign in process for an unauthenticated user. + * provider.addScope('user_birthday'); + * await signInWithRedirect(auth, provider); + * // This will trigger a full page redirect away from your app + * + * // After returning from the redirect when your app initializes you can obtain the result + * const result = await getRedirectResult(auth); + * if (result) { + * // This is the signed-in user + * const user = result.user; + * // This gives you a Facebook Access Token. + * const credential = provider.credentialFromResult(auth, result); + * const token = credential.accessToken; + * } + * ``` + * + * @example + * ```javascript + * // Sign in using a popup. + * const provider = new FacebookAuthProvider(); + * provider.addScope('user_birthday'); + * const result = await signInWithPopup(auth, provider); + * + * // The signed-in user info. + * const user = result.user; + * // This gives you a Facebook Access Token. + * const credential = provider.credentialFromResult(auth, result); + * const token = credential.accessToken; + * ``` + * + * @public + */ +export class FacebookAuthProvider extends BaseOAuthProvider { + /** Always set to {@link SignInMethod}.FACEBOOK. */ + static readonly FACEBOOK_SIGN_IN_METHOD = SignInMethod.FACEBOOK; + /** Always set to {@link ProviderId}.FACEBOOK. */ + static readonly PROVIDER_ID = ProviderId.FACEBOOK; + + constructor() { + super(ProviderId.FACEBOOK); + } - static credential(accessToken: string): externs.OAuthCredential { + /** + * Creates a credential for Facebook. + * + * @example + * ```javascript + * // `event` from the Facebook auth.authResponseChange callback. + * const credential = FacebookAuthProvider.credential(event.authResponse.accessToken); + * const result = await signInWithCredential(credential); + * ``` + * + * @param accessToken - Facebook access token. + */ + static credential(accessToken: string): OAuthCredential { return OAuthCredential._fromParams({ providerId: FacebookAuthProvider.PROVIDER_ID, signInMethod: FacebookAuthProvider.FACEBOOK_SIGN_IN_METHOD, @@ -36,25 +96,34 @@ export class FacebookAuthProvider extends OAuthProvider { }); } + /** + * Used to extract the underlying {@link OAuthCredential} from a {@link UserCredential}. + * + * @param userCredential - The user credential. + */ static credentialFromResult( - userCredential: externs.UserCredential - ): externs.OAuthCredential | null { + userCredential: UserCredential + ): OAuthCredential | null { return FacebookAuthProvider.credentialFromTaggedObject( - userCredential as UserCredential + userCredential as UserCredentialInternal ); } - static credentialFromError( - error: FirebaseError - ): externs.OAuthCredential | null { + /** + * Used to extract the underlying {@link OAuthCredential} from a {@link AuthError} which was + * thrown during a sign-in, link, or reauthenticate operation. + * + * @param userCredential - The user credential. + */ + static credentialFromError(error: FirebaseError): OAuthCredential | null { return FacebookAuthProvider.credentialFromTaggedObject( - error as TaggedWithTokenResponse + (error.customData || {}) as TaggedWithTokenResponse ); } private static credentialFromTaggedObject({ _tokenResponse: tokenResponse - }: TaggedWithTokenResponse): externs.OAuthCredential | null { + }: TaggedWithTokenResponse): OAuthCredential | null { if (!tokenResponse || !('oauthAccessToken' in tokenResponse)) { return null; } diff --git a/packages-exp/auth-exp/src/core/providers/federated.test.ts b/packages-exp/auth-exp/src/core/providers/federated.test.ts new file mode 100644 index 00000000000..3ea94258fdb --- /dev/null +++ b/packages-exp/auth-exp/src/core/providers/federated.test.ts @@ -0,0 +1,48 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from 'chai'; +import { FederatedAuthProvider } from './federated'; + +/** Federated provider is marked abstract; create a pass-through class */ +class SimpleFederatedProvider extends FederatedAuthProvider {} + +describe('core/providers/federated', () => { + let federatedProvider: FederatedAuthProvider; + + beforeEach(() => { + federatedProvider = new SimpleFederatedProvider('federated'); + }); + + it('has the providerId', () => { + expect(federatedProvider.providerId).to.eq('federated'); + }); + + it('allows setting a default language code', () => { + expect(federatedProvider.defaultLanguageCode).to.be.null; + federatedProvider.setDefaultLanguage('en-US'); + expect(federatedProvider.defaultLanguageCode).to.eq('en-US'); + }); + + it('can set and retrieve custom parameters', () => { + expect(federatedProvider.getCustomParameters()).to.eql({}); + expect(federatedProvider.setCustomParameters({ foo: 'bar' })).to.eq( + federatedProvider + ); + expect(federatedProvider.getCustomParameters()).to.eql({ foo: 'bar' }); + }); +}); diff --git a/packages-exp/auth-exp/src/core/providers/federated.ts b/packages-exp/auth-exp/src/core/providers/federated.ts new file mode 100644 index 00000000000..dcf1340f09f --- /dev/null +++ b/packages-exp/auth-exp/src/core/providers/federated.ts @@ -0,0 +1,77 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AuthProvider } from '../../model/public_types'; + +/** + * Map of OAuth Custom Parameters. + * + * @public + */ +export type CustomParameters = Record; + +/** + * The base class for all Federated providers (OAuth (including OIDC), SAML). + * + * This class is not meant to be instantiated directly. + * + * @public + */ +export abstract class FederatedAuthProvider implements AuthProvider { + /** @internal */ + defaultLanguageCode: string | null = null; + /** @internal */ + private customParameters: CustomParameters = {}; + + /** + * Constructor for generic OAuth providers. + * + * @param providerId - Provider for which credentials should be generated. + */ + constructor(readonly providerId: string) {} + + /** + * Set the language gode. + * + * @param languageCode - language code + */ + setDefaultLanguage(languageCode: string | null): void { + this.defaultLanguageCode = languageCode; + } + + /** + * Sets the OAuth custom parameters to pass in an OAuth request for popup and redirect sign-in + * operations. + * + * @remarks + * For a detailed list, check the reserved required OAuth 2.0 parameters such as `client_id`, + * `redirect_uri`, `scope`, `response_type`, and `state` are not allowed and will be ignored. + * + * @param customOAuthParameters - The custom OAuth parameters to pass in the OAuth request. + */ + setCustomParameters(customOAuthParameters: CustomParameters): AuthProvider { + this.customParameters = customOAuthParameters; + return this; + } + + /** + * Retrieve the current list of {@link CustomParameters}. + */ + getCustomParameters(): CustomParameters { + return this.customParameters; + } +} diff --git a/packages-exp/auth-exp/src/core/providers/github.test.ts b/packages-exp/auth-exp/src/core/providers/github.test.ts index ecacbeb299e..3a0f8bb29a1 100644 --- a/packages-exp/auth-exp/src/core/providers/github.test.ts +++ b/packages-exp/auth-exp/src/core/providers/github.test.ts @@ -21,16 +21,17 @@ import { OperationType, ProviderId, SignInMethod -} from '@firebase/auth-types-exp'; +} from '../../model/public_types'; import { TEST_ID_TOKEN_RESPONSE } from '../../../test/helpers/id_token_response'; import { testUser, testAuth } from '../../../test/helpers/mock_auth'; import { TaggedWithTokenResponse } from '../../model/id_token'; -import { AUTH_ERROR_FACTORY, AuthErrorCode } from '../errors'; +import { AuthErrorCode } from '../errors'; import { UserCredentialImpl } from '../user/user_credential_impl'; import { GithubAuthProvider } from './github'; +import { _createError } from '../util/assert'; -describe('src/core/providers/github', () => { +describe('core/providers/github', () => { it('generates the correct type of oauth credential', () => { const cred = GithubAuthProvider.credential('access-token'); expect(cred.accessToken).to.eq('access-token'); @@ -56,10 +57,10 @@ describe('src/core/providers/github', () => { }); it('credentialFromError creates the cred from a tagged error', () => { - const error = AUTH_ERROR_FACTORY.create(AuthErrorCode.NEED_CONFIRMATION, { + const error = _createError(AuthErrorCode.NEED_CONFIRMATION, { appName: 'foo' }); - (error as TaggedWithTokenResponse)._tokenResponse = { + (error.customData! as TaggedWithTokenResponse)._tokenResponse = { ...TEST_ID_TOKEN_RESPONSE, oauthAccessToken: 'access-token' }; diff --git a/packages-exp/auth-exp/src/core/providers/github.ts b/packages-exp/auth-exp/src/core/providers/github.ts index 4735f02041c..2b08a3784f2 100644 --- a/packages-exp/auth-exp/src/core/providers/github.ts +++ b/packages-exp/auth-exp/src/core/providers/github.ts @@ -15,20 +15,76 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { + ProviderId, + SignInMethod, + UserCredential +} from '../../model/public_types'; import { FirebaseError } from '@firebase/util'; import { TaggedWithTokenResponse } from '../../model/id_token'; -import { UserCredential } from '../../model/user'; +import { UserCredentialInternal } from '../../model/user'; import { OAuthCredential } from '../credentials/oauth'; -import { OAuthProvider } from './oauth'; +import { BaseOAuthProvider } from './oauth'; -export class GithubAuthProvider extends OAuthProvider { - static readonly GITHUB_SIGN_IN_METHOD = externs.SignInMethod.GITHUB; - static readonly PROVIDER_ID = externs.ProviderId.GITHUB; - readonly providerId = GithubAuthProvider.PROVIDER_ID; +/** + * Provider for generating an {@link OAuthCredential} for {@link ProviderId}.GITHUB. + * + * @remarks + * GitHub requires an OAuth 2.0 redirect, so you can either handle the redirect directly, or use + * the {@link signInWithPopup} handler: + * + * @example + * ```javascript + * // Sign in using a redirect. + * const provider = new GithubAuthProvider(); + * // Start a sign in process for an unauthenticated user. + * provider.addScope('repo'); + * await signInWithRedirect(auth, provider); + * // This will trigger a full page redirect away from your app + * + * // After returning from the redirect when your app initializes you can obtain the result + * const result = await getRedirectResult(auth); + * if (result) { + * // This is the signed-in user + * const user = result.user; + * // This gives you a Github Access Token. + * const credential = provider.credentialFromResult(auth, result); + * const token = credential.accessToken; + * } + * ``` + * + * @example + * ```javascript + * // Sign in using a popup. + * const provider = new GithubAuthProvider(); + * provider.addScope('repo'); + * const result = await signInWithPopup(auth, provider); + * + * // The signed-in user info. + * const user = result.user; + * // This gives you a Github Access Token. + * const credential = provider.credentialFromResult(auth, result); + * const token = credential.accessToken; + * ``` + * @public + */ +export class GithubAuthProvider extends BaseOAuthProvider { + /** Always set to {@link SignInMethod}.GITHUB. */ + static readonly GITHUB_SIGN_IN_METHOD = SignInMethod.GITHUB; + /** Always set to {@link ProviderId}.GITHUB. */ + static readonly PROVIDER_ID = ProviderId.GITHUB; + + constructor() { + super(ProviderId.GITHUB); + } - static credential(accessToken: string): externs.OAuthCredential { + /** + * Creates a credential for Github. + * + * @param accessToken - Github access token. + */ + static credential(accessToken: string): OAuthCredential { return OAuthCredential._fromParams({ providerId: GithubAuthProvider.PROVIDER_ID, signInMethod: GithubAuthProvider.GITHUB_SIGN_IN_METHOD, @@ -36,25 +92,34 @@ export class GithubAuthProvider extends OAuthProvider { }); } + /** + * Used to extract the underlying {@link OAuthCredential} from a {@link UserCredential}. + * + * @param userCredential - The user credential. + */ static credentialFromResult( - userCredential: externs.UserCredential - ): externs.OAuthCredential | null { + userCredential: UserCredential + ): OAuthCredential | null { return GithubAuthProvider.credentialFromTaggedObject( - userCredential as UserCredential + userCredential as UserCredentialInternal ); } - static credentialFromError( - error: FirebaseError - ): externs.OAuthCredential | null { + /** + * Used to extract the underlying {@link OAuthCredential} from a {@link AuthError} which was + * thrown during a sign-in, link, or reauthenticate operation. + * + * @param userCredential - The user credential. + */ + static credentialFromError(error: FirebaseError): OAuthCredential | null { return GithubAuthProvider.credentialFromTaggedObject( - error as TaggedWithTokenResponse + (error.customData || {}) as TaggedWithTokenResponse ); } private static credentialFromTaggedObject({ _tokenResponse: tokenResponse - }: TaggedWithTokenResponse): externs.OAuthCredential | null { + }: TaggedWithTokenResponse): OAuthCredential | null { if (!tokenResponse || !('oauthAccessToken' in tokenResponse)) { return null; } diff --git a/packages-exp/auth-exp/src/core/providers/google.test.ts b/packages-exp/auth-exp/src/core/providers/google.test.ts index 5ae62384522..4df94d378b5 100644 --- a/packages-exp/auth-exp/src/core/providers/google.test.ts +++ b/packages-exp/auth-exp/src/core/providers/google.test.ts @@ -21,16 +21,17 @@ import { OperationType, ProviderId, SignInMethod -} from '@firebase/auth-types-exp'; +} from '../../model/public_types'; import { TEST_ID_TOKEN_RESPONSE } from '../../../test/helpers/id_token_response'; import { testUser, testAuth } from '../../../test/helpers/mock_auth'; import { TaggedWithTokenResponse } from '../../model/id_token'; -import { AUTH_ERROR_FACTORY, AuthErrorCode } from '../errors'; +import { AuthErrorCode } from '../errors'; import { UserCredentialImpl } from '../user/user_credential_impl'; import { GoogleAuthProvider } from './google'; +import { _createError } from '../util/assert'; -describe('src/core/providers/google', () => { +describe('core/providers/google', () => { it('generates the correct type of oauth credential', () => { const cred = GoogleAuthProvider.credential('id-token', 'access-token'); expect(cred.accessToken).to.eq('access-token'); @@ -39,6 +40,12 @@ describe('src/core/providers/google', () => { expect(cred.signInMethod).to.eq(SignInMethod.GOOGLE); }); + it('adds the profile scope by default', () => { + const provider = new GoogleAuthProvider(); + expect(provider.providerId).to.eq(ProviderId.GOOGLE); + expect(provider.getScopes()).to.eql(['profile']); + }); + it('credentialFromResult creates the cred from a tagged result', async () => { const auth = await testAuth(); const userCred = new UserCredentialImpl({ @@ -59,10 +66,10 @@ describe('src/core/providers/google', () => { }); it('credentialFromError creates the cred from a tagged error', () => { - const error = AUTH_ERROR_FACTORY.create(AuthErrorCode.NEED_CONFIRMATION, { + const error = _createError(AuthErrorCode.NEED_CONFIRMATION, { appName: 'foo' }); - (error as TaggedWithTokenResponse)._tokenResponse = { + (error.customData! as TaggedWithTokenResponse)._tokenResponse = { ...TEST_ID_TOKEN_RESPONSE, oauthAccessToken: 'access-token', oauthIdToken: 'id-token' diff --git a/packages-exp/auth-exp/src/core/providers/google.ts b/packages-exp/auth-exp/src/core/providers/google.ts index 27fe7d3abf2..a95397c0de6 100644 --- a/packages-exp/auth-exp/src/core/providers/google.ts +++ b/packages-exp/auth-exp/src/core/providers/google.ts @@ -15,24 +15,88 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { + ProviderId, + SignInMethod, + UserCredential +} from '../../model/public_types'; import { FirebaseError } from '@firebase/util'; import { SignInWithIdpResponse } from '../../api/authentication/idp'; import { TaggedWithTokenResponse } from '../../model/id_token'; -import { UserCredential } from '../../model/user'; +import { UserCredentialInternal } from '../../model/user'; import { OAuthCredential } from '../credentials/oauth'; -import { OAuthProvider } from './oauth'; +import { BaseOAuthProvider } from './oauth'; -export class GoogleAuthProvider extends OAuthProvider { - static readonly GOOGLE_SIGN_IN_METHOD = externs.SignInMethod.GOOGLE; - static readonly PROVIDER_ID = externs.ProviderId.GOOGLE; - readonly providerId = GoogleAuthProvider.PROVIDER_ID; +/** + * Provider for generating an an {@link OAuthCredential} for {@link ProviderId}.GOOGLE. + * + * @example + * ```javascript + * // Sign in using a redirect. + * const provider = new GoogleAuthProvider(); + * // Start a sign in process for an unauthenticated user. + * provider.addScope('profile'); + * provider.addScope('email'); + * await signInWithRedirect(auth, provider); + * // This will trigger a full page redirect away from your app + * + * // After returning from the redirect when your app initializes you can obtain the result + * const result = await getRedirectResult(auth); + * if (result) { + * // This is the signed-in user + * const user = result.user; + * // This gives you a Google Access Token. + * const credential = provider.credentialFromResult(auth, result); + * const token = credential.accessToken; + * } + * ``` + * + * @example + * ```javascript + * // Sign in using a popup. + * const provider = new GoogleAuthProvider(); + * provider.addScope('profile'); + * provider.addScope('email'); + * const result = await signInWithPopup(auth, provider); + * + * // The signed-in user info. + * const user = result.user; + * // This gives you a Google Access Token. + * const credential = provider.credentialFromResult(auth, result); + * const token = credential.accessToken; + * ``` + * + * @public + */ +export class GoogleAuthProvider extends BaseOAuthProvider { + /** Always set to {@link SignInMethod}.GOOGLE. */ + static readonly GOOGLE_SIGN_IN_METHOD = SignInMethod.GOOGLE; + /** Always set to {@link ProviderId}.GOOGLE. */ + static readonly PROVIDER_ID = ProviderId.GOOGLE; + constructor() { + super(ProviderId.GOOGLE); + this.addScope('profile'); + } + + /** + * Creates a credential for Google. At least one of ID token and access token is required. + * + * @example + * ```javascript + * // \`googleUser\` from the onsuccess Google Sign In callback. + * const credential = GoogleAuthProvider.credential(googleUser.getAuthResponse().id_token); + * const result = await signInWithCredential(credential); + * ``` + * + * @param idToken - Google ID token. + * @param accessToken - Google access token. + */ static credential( idToken?: string | null, accessToken?: string | null - ): externs.OAuthCredential { + ): OAuthCredential { return OAuthCredential._fromParams({ providerId: GoogleAuthProvider.PROVIDER_ID, signInMethod: GoogleAuthProvider.GOOGLE_SIGN_IN_METHOD, @@ -41,33 +105,39 @@ export class GoogleAuthProvider extends OAuthProvider { }); } + /** + * Used to extract the underlying {@link OAuthCredential} from a {@link UserCredential}. + * + * @param userCredential - The user credential. + */ static credentialFromResult( - userCredential: externs.UserCredential - ): externs.OAuthCredential | null { + userCredential: UserCredential + ): OAuthCredential | null { return GoogleAuthProvider.credentialFromTaggedObject( - userCredential as UserCredential + userCredential as UserCredentialInternal ); } - - static credentialFromError( - error: FirebaseError - ): externs.OAuthCredential | null { + /** + * Used to extract the underlying {@link OAuthCredential} from a {@link AuthError} which was + * thrown during a sign-in, link, or reauthenticate operation. + * + * @param userCredential - The user credential. + */ + static credentialFromError(error: FirebaseError): OAuthCredential | null { return GoogleAuthProvider.credentialFromTaggedObject( - error as TaggedWithTokenResponse + (error.customData || {}) as TaggedWithTokenResponse ); } private static credentialFromTaggedObject({ _tokenResponse: tokenResponse - }: TaggedWithTokenResponse): externs.OAuthCredential | null { + }: TaggedWithTokenResponse): OAuthCredential | null { if (!tokenResponse) { return null; } - const { - oauthIdToken, - oauthAccessToken - } = tokenResponse as SignInWithIdpResponse; + const { oauthIdToken, oauthAccessToken } = + tokenResponse as SignInWithIdpResponse; if (!oauthIdToken && !oauthAccessToken) { // This could be an oauth 1 credential or a phone credential return null; diff --git a/packages-exp/auth-exp/src/core/providers/oauth.test.ts b/packages-exp/auth-exp/src/core/providers/oauth.test.ts new file mode 100644 index 00000000000..c647055ffba --- /dev/null +++ b/packages-exp/auth-exp/src/core/providers/oauth.test.ts @@ -0,0 +1,120 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from 'chai'; + +import { + OperationType, + ProviderId, + SignInMethod +} from '../../model/public_types'; + +import { TEST_ID_TOKEN_RESPONSE } from '../../../test/helpers/id_token_response'; +import { testUser, testAuth } from '../../../test/helpers/mock_auth'; +import { TaggedWithTokenResponse } from '../../model/id_token'; +import { AuthErrorCode } from '../errors'; +import { UserCredentialImpl } from '../user/user_credential_impl'; +import { _createError } from '../util/assert'; +import { OAuthProvider } from './oauth'; + +describe('core/providers/oauth', () => { + it('generates the correct type of oauth credential', () => { + const cred = new OAuthProvider('google.com').credential({ + idToken: 'id-token', + accessToken: 'access-token' + }); + expect(cred.accessToken).to.eq('access-token'); + expect(cred.idToken).to.eq('id-token'); + expect(cred.providerId).to.eq(ProviderId.GOOGLE); + expect(cred.signInMethod).to.eq(SignInMethod.GOOGLE); + }); + + it('credentialFromResult creates the cred from a tagged result', async () => { + const auth = await testAuth(); + const userCred = new UserCredentialImpl({ + user: testUser(auth, 'uid'), + providerId: ProviderId.GOOGLE, + _tokenResponse: { + ...TEST_ID_TOKEN_RESPONSE, + oauthAccessToken: 'access-token', + oauthIdToken: 'id-token', + providerId: ProviderId.FACEBOOK + }, + operationType: OperationType.SIGN_IN + }); + const cred = OAuthProvider.credentialFromResult(userCred)!; + expect(cred.accessToken).to.eq('access-token'); + expect(cred.idToken).to.eq('id-token'); + expect(cred.providerId).to.eq(ProviderId.FACEBOOK); + expect(cred.signInMethod).to.eq(SignInMethod.FACEBOOK); + }); + + it('credentialFromResult returns null if provider ID not specified', async () => { + const auth = await testAuth(); + const userCred = new UserCredentialImpl({ + user: testUser(auth, 'uid'), + providerId: ProviderId.GOOGLE, + _tokenResponse: { + ...TEST_ID_TOKEN_RESPONSE, + oauthAccessToken: 'access-token', + oauthIdToken: 'id-token' + }, + operationType: OperationType.SIGN_IN + }); + expect(OAuthProvider.credentialFromResult(userCred)).to.be.null; + }); + + it('credentialFromResult works for oidc', async () => { + const auth = await testAuth(); + const userCred = new UserCredentialImpl({ + user: testUser(auth, 'uid'), + providerId: ProviderId.GOOGLE, + _tokenResponse: { + ...TEST_ID_TOKEN_RESPONSE, + pendingToken: 'pending-token', + oauthIdToken: 'id-token', + providerId: 'oidc.oidctest' + }, + operationType: OperationType.SIGN_IN + }); + const cred = OAuthProvider.credentialFromResult(userCred)!; + expect(cred.idToken).to.eq('id-token'); + expect(cred.providerId).to.eq('oidc.oidctest'); + expect(cred.signInMethod).to.eq('oidc.oidctest'); + expect((cred.toJSON() as Record).pendingToken).to.eq( + 'pending-token' + ); + }); + + it('credentialFromError creates the cred from a tagged error', () => { + const error = _createError(AuthErrorCode.NEED_CONFIRMATION, { + appName: 'foo' + }); + (error.customData! as TaggedWithTokenResponse)._tokenResponse = { + ...TEST_ID_TOKEN_RESPONSE, + oauthAccessToken: 'access-token', + oauthIdToken: 'id-token', + providerId: ProviderId.FACEBOOK + }; + + const cred = OAuthProvider.credentialFromError(error)!; + expect(cred.accessToken).to.eq('access-token'); + expect(cred.idToken).to.eq('id-token'); + expect(cred.providerId).to.eq(ProviderId.FACEBOOK); + expect(cred.signInMethod).to.eq(SignInMethod.FACEBOOK); + }); +}); diff --git a/packages-exp/auth-exp/src/core/providers/oauth.ts b/packages-exp/auth-exp/src/core/providers/oauth.ts index 0e84e989a1f..be8a8a6e985 100644 --- a/packages-exp/auth-exp/src/core/providers/oauth.ts +++ b/packages-exp/auth-exp/src/core/providers/oauth.ts @@ -15,75 +15,231 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { AuthProvider, UserCredential } from '../../model/public_types'; -import { assert } from '../util/assert'; +import { _assert } from '../util/assert'; import { AuthErrorCode } from '../errors'; -import { OAuthCredential } from '../credentials/oauth'; +import { OAuthCredential, OAuthCredentialParams } from '../credentials/oauth'; +import { UserCredentialInternal } from '../../model/user'; +import { FirebaseError } from '@firebase/util'; +import { TaggedWithTokenResponse } from '../../model/id_token'; +import { SignInWithIdpResponse } from '../../../internal'; +import { FederatedAuthProvider } from './federated'; -export type CustomParameters = Record; - -interface CredentialParameters { +/** + * Defines the options for initializing an {@link OAuthCredential}. + * + * @remarks + * For ID tokens with nonce claim, the raw nonce has to also be provided. + * + * @public + */ +export interface OAuthCredentialOptions { + /** + * The OAuth ID token used to initialize the {@link OAuthCredential}. + */ idToken?: string; + /** + * The OAuth access token used to initialize the {@link OAuthCredential}. + */ accessToken?: string; + /** + * The raw nonce associated with the ID token. + * + * @remarks + * It is required when an ID token with a nonce field is provided. The SHA-256 hash of the + * raw nonce must match the nonce field in the ID token. + */ rawNonce?: string; } -export class OAuthProvider implements externs.AuthProvider { - defaultLanguageCode: string | null = null; +/** + * Common code to all OAuth providers. This is separate from the + * {@link OAuthProvider} so that child providers (like + * {@link GoogleAuthProvider}) don't inherit the `credential` instance method. + * Instead, they rely on a static `credential` method. + */ +export abstract class BaseOAuthProvider + extends FederatedAuthProvider + implements AuthProvider { + /** @internal */ private scopes: string[] = []; - private customParameters: CustomParameters = {}; - constructor(readonly providerId: string) {} - static credentialFromJSON(json: object | string): externs.OAuthCredential { + /** + * Add an OAuth scope to the credential. + * + * @param scope - Provider OAuth scope to add. + */ + addScope(scope: string): AuthProvider { + // If not already added, add scope to list. + if (!this.scopes.includes(scope)) { + this.scopes.push(scope); + } + return this; + } + + /** + * Retrieve the current list of OAuth scopes. + */ + getScopes(): string[] { + return [...this.scopes]; + } +} + +/** + * Provider for generating generic {@link OAuthCredential}. + * + * @example + * ```javascript + * // Sign in using a redirect. + * const provider = new OAuthProvider('google.com'); + * // Start a sign in process for an unauthenticated user. + * provider.addScope('profile'); + * provider.addScope('email'); + * await signInWithRedirect(auth, provider); + * // This will trigger a full page redirect away from your app + * + * // After returning from the redirect when your app initializes you can obtain the result + * const result = await getRedirectResult(auth); + * if (result) { + * // This is the signed-in user + * const user = result.user; + * // This gives you a OAuth Access Token for the provider. + * const credential = provider.credentialFromResult(auth, result); + * const token = credential.accessToken; + * } + * ``` + * + * @example + * ```javascript + * // Sign in using a popup. + * const provider = new OAuthProvider('google.com'); + * provider.addScope('profile'); + * provider.addScope('email'); + * const result = await signInWithPopup(auth, provider); + * + * // The signed-in user info. + * const user = result.user; + * // This gives you a OAuth Access Token for the provider. + * const credential = provider.credentialFromResult(auth, result); + * const token = credential.accessToken; + * ``` + * @public + */ +export class OAuthProvider extends BaseOAuthProvider { + /** + * Creates an {@link OAuthCredential} from a JSON string or a plain object. + * @param json A plain object or a JSON string + */ + static credentialFromJSON(json: object | string): OAuthCredential { const obj = typeof json === 'string' ? JSON.parse(json) : json; - assert( + _assert( 'providerId' in obj && 'signInMethod' in obj, - AuthErrorCode.ARGUMENT_ERROR, - {} + AuthErrorCode.ARGUMENT_ERROR ); return OAuthCredential._fromParams(obj); } - credential(params: CredentialParameters): externs.OAuthCredential { - assert( - params.idToken && params.accessToken, - AuthErrorCode.ARGUMENT_ERROR, - {} - ); + /** + * Creates a {@link OAuthCredential} from a generic OAuth provider's access token or ID token. + * + * @remarks + * The raw nonce is required when an ID token with a nonce field is provided. The SHA-256 hash of + * the raw nonce must match the nonce field in the ID token. + * + * @example + * ```javascript + * // `googleUser` from the onsuccess Google Sign In callback. + * // Initialize a generate OAuth provider with a `google.com` providerId. + * const provider = new OAuthProvider('google.com'); + * const credential = provider.credential({ + * idToken: googleUser.getAuthResponse().id_token, + * }); + * const result = await signInWithCredential(credential); + * ``` + * + * @param params - Either the options object containing the ID token, access token and raw nonce + * or the ID token string. + */ + credential(params: OAuthCredentialOptions): OAuthCredential { + return this._credential(params); + } + + /** An internal credential method that accepts more permissive options */ + private _credential( + params: OAuthCredentialOptions | OAuthCredentialParams + ): OAuthCredential { + _assert(params.idToken || params.accessToken, AuthErrorCode.ARGUMENT_ERROR); // For OAuthCredential, sign in method is same as providerId. return OAuthCredential._fromParams({ + ...params, providerId: this.providerId, - signInMethod: this.providerId, - ...params + signInMethod: this.providerId }); } - setDefaultLanguage(languageCode: string | null): void { - this.defaultLanguageCode = languageCode; + /** + * Used to extract the underlying {@link OAuthCredential} from a {@link UserCredential}. + * + * @param userCredential - The user credential. + */ + static credentialFromResult( + userCredential: UserCredential + ): OAuthCredential | null { + return OAuthProvider.oauthCredentialFromTaggedObject( + userCredential as UserCredentialInternal + ); } - - setCustomParameters( - customOAuthParameters: CustomParameters - ): externs.AuthProvider { - this.customParameters = customOAuthParameters; - return this; + /** + * Used to extract the underlying {@link OAuthCredential} from a {@link AuthError} which was + * thrown during a sign-in, link, or reauthenticate operation. + * + * @param userCredential - The user credential. + */ + static credentialFromError(error: FirebaseError): OAuthCredential | null { + return OAuthProvider.oauthCredentialFromTaggedObject( + (error.customData || {}) as TaggedWithTokenResponse + ); } - getCustomParameters(): CustomParameters { - return this.customParameters; - } + private static oauthCredentialFromTaggedObject({ + _tokenResponse: tokenResponse + }: TaggedWithTokenResponse): OAuthCredential | null { + if (!tokenResponse) { + return null; + } - addScope(scope: string): externs.AuthProvider { - // If not already added, add scope to list. - if (!this.scopes.includes(scope)) { - this.scopes.push(scope); + const { + oauthIdToken, + oauthAccessToken, + oauthTokenSecret, + pendingToken, + nonce, + providerId + } = tokenResponse as SignInWithIdpResponse; + if ( + !oauthAccessToken && + !oauthTokenSecret && + !oauthIdToken && + !pendingToken + ) { + return null; } - return this; - } - getScopes(): string[] { - return [...this.scopes]; + if (!providerId) { + return null; + } + + try { + return new OAuthProvider(providerId)._credential({ + idToken: oauthIdToken, + accessToken: oauthAccessToken, + rawNonce: nonce, + pendingToken + }); + } catch (e) { + return null; + } } } diff --git a/packages-exp/auth-exp/src/core/providers/saml.test.ts b/packages-exp/auth-exp/src/core/providers/saml.test.ts new file mode 100644 index 00000000000..5d3e882227e --- /dev/null +++ b/packages-exp/auth-exp/src/core/providers/saml.test.ts @@ -0,0 +1,76 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from 'chai'; + +import { OperationType } from '../../model/public_types'; + +import { TEST_ID_TOKEN_RESPONSE } from '../../../test/helpers/id_token_response'; +import { testUser, testAuth } from '../../../test/helpers/mock_auth'; +import { TaggedWithTokenResponse } from '../../model/id_token'; +import { AuthErrorCode } from '../errors'; +import { UserCredentialImpl } from '../user/user_credential_impl'; +import { _createError } from '../util/assert'; +import { SAMLAuthProvider } from './saml'; + +describe('core/providers/saml', () => { + it('credentialFromResult creates the cred from a tagged result', async () => { + const auth = await testAuth(); + const userCred = new UserCredentialImpl({ + user: testUser(auth, 'uid'), + providerId: 'firebase', + _tokenResponse: { + ...TEST_ID_TOKEN_RESPONSE, + pendingToken: 'pending-token', + providerId: 'saml.provider' + }, + operationType: OperationType.SIGN_IN + }); + const cred = SAMLAuthProvider.credentialFromResult(userCred)!; + expect(cred.providerId).to.eq('saml.provider'); + expect(cred.signInMethod).to.eq('saml.provider'); + }); + + it('credentialFromResult returns null if provider ID not specified', async () => { + const auth = await testAuth(); + const userCred = new UserCredentialImpl({ + user: testUser(auth, 'uid'), + providerId: 'firebase', + _tokenResponse: { + ...TEST_ID_TOKEN_RESPONSE, + pendingToken: 'pending-token' + }, + operationType: OperationType.SIGN_IN + }); + expect(SAMLAuthProvider.credentialFromResult(userCred)).to.be.null; + }); + + it('credentialFromError creates the cred from a tagged error', () => { + const error = _createError(AuthErrorCode.NEED_CONFIRMATION, { + appName: 'foo' + }); + (error.customData! as TaggedWithTokenResponse)._tokenResponse = { + ...TEST_ID_TOKEN_RESPONSE, + pendingToken: 'pending-token', + providerId: 'saml.provider' + }; + + const cred = SAMLAuthProvider.credentialFromError(error)!; + expect(cred.providerId).to.eq('saml.provider'); + expect(cred.signInMethod).to.eq('saml.provider'); + }); +}); diff --git a/packages-exp/auth-exp/src/core/providers/saml.ts b/packages-exp/auth-exp/src/core/providers/saml.ts new file mode 100644 index 00000000000..e4378bb6d48 --- /dev/null +++ b/packages-exp/auth-exp/src/core/providers/saml.ts @@ -0,0 +1,114 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FirebaseError } from '@firebase/util'; +import { SignInWithIdpResponse } from '../../api/authentication/idp'; +import { TaggedWithTokenResponse } from '../../model/id_token'; +import { UserCredential } from '../../model/public_types'; +import { UserCredentialInternal } from '../../model/user'; +import { AuthCredential } from '../credentials'; +import { SAMLAuthCredential } from '../credentials/saml'; +import { AuthErrorCode } from '../errors'; +import { _assert } from '../util/assert'; +import { FederatedAuthProvider } from './federated'; + +const SAML_PROVIDER_PREFIX = 'saml.'; + +/** + * An AuthProvider for SAML. + * + * @public + */ +export class SAMLAuthProvider extends FederatedAuthProvider { + /** + * Constructor. The providerId must start with "saml." + * @param - providerId + */ + constructor(providerId: string) { + _assert( + providerId.startsWith(SAML_PROVIDER_PREFIX), + AuthErrorCode.ARGUMENT_ERROR + ); + super(providerId); + } + + /** + * Generates an {@link AuthCredential} from a {@link UserCredential} after a + * successful SAML flow completes. + * + * @remarks + * + * For example, to get an {@link AuthCredential}, you could write the + * following code: + * + * ```js + * const userCredential = await signInWithPopup(auth, samlProvider); + * const credential = SAMLAuthProvider.credentialFromResult(userCredential); + * ``` + * + * @param userCredential + */ + static credentialFromResult( + userCredential: UserCredential + ): AuthCredential | null { + return SAMLAuthProvider.samlCredentialFromTaggedObject( + userCredential as UserCredentialInternal + ); + } + + /** + * Used to extract the underlying {@link OAuthCredential} from a {@link AuthError} which was + * thrown during a sign-in, link, or reauthenticate operation. + * + * @param userCredential - The user credential. + */ + static credentialFromError(error: FirebaseError): AuthCredential | null { + return SAMLAuthProvider.samlCredentialFromTaggedObject( + (error.customData || {}) as TaggedWithTokenResponse + ); + } + + /** + * Creates an {@link AuthCredential} from a JSON string or a plain object. + * @param json A plain object or a JSON string + */ + static credentialFromJSON(json: string | object): AuthCredential { + const credential = SAMLAuthCredential.fromJSON(json); + _assert(credential, AuthErrorCode.ARGUMENT_ERROR); + return credential; + } + + private static samlCredentialFromTaggedObject({ + _tokenResponse: tokenResponse + }: TaggedWithTokenResponse): SAMLAuthCredential | null { + if (!tokenResponse) { + return null; + } + + const { pendingToken, providerId } = tokenResponse as SignInWithIdpResponse; + + if (!pendingToken || !providerId) { + return null; + } + + try { + return SAMLAuthCredential._create(providerId, pendingToken); + } catch (e) { + return null; + } + } +} diff --git a/packages-exp/auth-exp/src/core/providers/twitter.test.ts b/packages-exp/auth-exp/src/core/providers/twitter.test.ts index 8d1c42edc04..8e8f766eb37 100644 --- a/packages-exp/auth-exp/src/core/providers/twitter.test.ts +++ b/packages-exp/auth-exp/src/core/providers/twitter.test.ts @@ -38,16 +38,17 @@ import { OperationType, ProviderId, SignInMethod -} from '@firebase/auth-types-exp'; +} from '../../model/public_types'; import { TEST_ID_TOKEN_RESPONSE } from '../../../test/helpers/id_token_response'; import { testUser, testAuth } from '../../../test/helpers/mock_auth'; import { TaggedWithTokenResponse } from '../../model/id_token'; -import { AUTH_ERROR_FACTORY, AuthErrorCode } from '../errors'; +import { AuthErrorCode } from '../errors'; import { UserCredentialImpl } from '../user/user_credential_impl'; import { TwitterAuthProvider } from './twitter'; +import { _createError } from '../util/assert'; -describe('src/core/providers/twitter', () => { +describe('core/providers/twitter', () => { it('generates the correct type of oauth credential', () => { const cred = TwitterAuthProvider.credential('token', 'secret'); expect(cred.accessToken).to.eq('token'); @@ -76,10 +77,10 @@ describe('src/core/providers/twitter', () => { }); it('credentialFromError creates the cred from a tagged error', () => { - const error = AUTH_ERROR_FACTORY.create(AuthErrorCode.NEED_CONFIRMATION, { + const error = _createError(AuthErrorCode.NEED_CONFIRMATION, { appName: 'foo' }); - (error as TaggedWithTokenResponse)._tokenResponse = { + (error.customData! as TaggedWithTokenResponse)._tokenResponse = { ...TEST_ID_TOKEN_RESPONSE, oauthAccessToken: 'access-token', oauthTokenSecret: 'token-secret' diff --git a/packages-exp/auth-exp/src/core/providers/twitter.ts b/packages-exp/auth-exp/src/core/providers/twitter.ts index 8e9e456b828..74e1f5e32d3 100644 --- a/packages-exp/auth-exp/src/core/providers/twitter.ts +++ b/packages-exp/auth-exp/src/core/providers/twitter.ts @@ -32,21 +32,75 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { + ProviderId, + SignInMethod, + UserCredential +} from '../../model/public_types'; import { FirebaseError } from '@firebase/util'; import { SignInWithIdpResponse } from '../../api/authentication/idp'; import { TaggedWithTokenResponse } from '../../model/id_token'; -import { UserCredential } from '../../model/user'; +import { UserCredentialInternal } from '../../model/user'; import { OAuthCredential } from '../credentials/oauth'; -import { OAuthProvider } from './oauth'; +import { BaseOAuthProvider } from './oauth'; -export class TwitterAuthProvider extends OAuthProvider { - static readonly TWITTER_SIGN_IN_METHOD = externs.SignInMethod.TWITTER; - static readonly PROVIDER_ID = externs.ProviderId.TWITTER; - readonly providerId = TwitterAuthProvider.PROVIDER_ID; +/** + * Provider for generating an {@link OAuthCredential} for {@link ProviderId}.TWITTER. + * + * @example + * ```javascript + * // Sign in using a redirect. + * const provider = new TwitterAuthProvider(); + * // Start a sign in process for an unauthenticated user. + * await signInWithRedirect(auth, provider); + * // This will trigger a full page redirect away from your app + * + * // After returning from the redirect when your app initializes you can obtain the result + * const result = await getRedirectResult(auth); + * if (result) { + * // This is the signed-in user + * const user = result.user; + * // This gives you a Twitter Access Token and Secret. + * const credential = provider.credentialFromResult(auth, result); + * const token = credential.accessToken; + * const secret = credential.secret; + * } + * ``` + * + * @example + * ```javascript + * // Sign in using a popup. + * const provider = new TwitterAuthProvider(); + * const result = await signInWithPopup(auth, provider); + * + * // The signed-in user info. + * const user = result.user; + * // This gives you a Twitter Access Token and Secret. + * const credential = provider.credentialFromResult(auth, result); + * const token = credential.accessToken; + * const secret = credential.secret; + * ``` + * + * @public + */ +export class TwitterAuthProvider extends BaseOAuthProvider { + /** Always set to {@link SignInMethod}.TWITTER. */ + static readonly TWITTER_SIGN_IN_METHOD = SignInMethod.TWITTER; + /** Always set to {@link ProviderId}.TWITTER. */ + static readonly PROVIDER_ID = ProviderId.TWITTER; + + constructor() { + super(ProviderId.TWITTER); + } - static credential(token: string, secret: string): externs.OAuthCredential { + /** + * Creates a credential for Twitter. + * + * @param token - Twitter access token. + * @param secret - Twitter secret. + */ + static credential(token: string, secret: string): OAuthCredential { return OAuthCredential._fromParams({ providerId: TwitterAuthProvider.PROVIDER_ID, signInMethod: TwitterAuthProvider.TWITTER_SIGN_IN_METHOD, @@ -55,32 +109,39 @@ export class TwitterAuthProvider extends OAuthProvider { }); } + /** + * Used to extract the underlying {@link OAuthCredential} from a {@link UserCredential}. + * + * @param userCredential - The user credential. + */ static credentialFromResult( - userCredential: externs.UserCredential - ): externs.OAuthCredential | null { + userCredential: UserCredential + ): OAuthCredential | null { return TwitterAuthProvider.credentialFromTaggedObject( - userCredential as UserCredential + userCredential as UserCredentialInternal ); } - static credentialFromError( - error: FirebaseError - ): externs.OAuthCredential | null { + /** + * Used to extract the underlying {@link OAuthCredential} from a {@link AuthError} which was + * thrown during a sign-in, link, or reauthenticate operation. + * + * @param userCredential - The user credential. + */ + static credentialFromError(error: FirebaseError): OAuthCredential | null { return TwitterAuthProvider.credentialFromTaggedObject( - error as TaggedWithTokenResponse + (error.customData || {}) as TaggedWithTokenResponse ); } private static credentialFromTaggedObject({ _tokenResponse: tokenResponse - }: TaggedWithTokenResponse): externs.OAuthCredential | null { + }: TaggedWithTokenResponse): OAuthCredential | null { if (!tokenResponse) { return null; } - const { - oauthAccessToken, - oauthTokenSecret - } = tokenResponse as SignInWithIdpResponse; + const { oauthAccessToken, oauthTokenSecret } = + tokenResponse as SignInWithIdpResponse; if (!oauthAccessToken || !oauthTokenSecret) { return null; } diff --git a/packages-exp/auth-exp/src/platform_browser/strategies/abstract_popup_redirect_operation.test.ts b/packages-exp/auth-exp/src/core/strategies/abstract_popup_redirect_operation.test.ts similarity index 84% rename from packages-exp/auth-exp/src/platform_browser/strategies/abstract_popup_redirect_operation.test.ts rename to packages-exp/auth-exp/src/core/strategies/abstract_popup_redirect_operation.test.ts index fb0e1f854dd..fbdee4d6d95 100644 --- a/packages-exp/auth-exp/src/platform_browser/strategies/abstract_popup_redirect_operation.test.ts +++ b/packages-exp/auth-exp/src/core/strategies/abstract_popup_redirect_operation.test.ts @@ -20,10 +20,9 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as sinon from 'sinon'; import * as sinonChai from 'sinon-chai'; -import { OperationType, ProviderId } from '@firebase/auth-types-exp'; +import { OperationType, ProviderId } from '../../model/public_types'; import { FirebaseError } from '@firebase/util'; -import { delay } from '../../../test/helpers/delay'; import { TEST_ID_TOKEN_RESPONSE } from '../../../test/helpers/id_token_response'; import { authEvent, BASE_AUTH_EVENT } from '../../../test/helpers/iframe_event'; import { testAuth, testUser, TestAuth } from '../../../test/helpers/mock_auth'; @@ -32,19 +31,20 @@ import { AuthEvent, AuthEventType, EventManager, - PopupRedirectResolver + PopupRedirectResolverInternal } from '../../model/popup_redirect'; -import { AuthEventManager } from '../../core/auth/auth_event_manager'; -import { AUTH_ERROR_FACTORY, AuthErrorCode } from '../../core/errors'; -import { UserCredentialImpl } from '../../core/user/user_credential_impl'; -import { _getInstance } from '../../core/util/instantiator'; +import { AuthEventManager } from '../auth/auth_event_manager'; +import { AuthErrorCode } from '../errors'; +import { UserCredentialImpl } from '../user/user_credential_impl'; +import { _getInstance } from '../util/instantiator'; import { AbstractPopupRedirectOperation } from './abstract_popup_redirect_operation'; -import * as idp from '../../core/strategies/idp'; +import * as idp from '../strategies/idp'; +import { _createError } from '../util/assert'; use(sinonChai); use(chaiAsPromised); -const ERROR = AUTH_ERROR_FACTORY.create(AuthErrorCode.INTERNAL_ERROR, { +const ERROR = _createError(AuthErrorCode.INTERNAL_ERROR, { appName: 'test' }); @@ -57,15 +57,15 @@ class WrapperOperation extends AbstractPopupRedirectOperation { cleanUp = sinon.stub(); } -describe('src/core/strategies/abstract_popup_redirect_operation', () => { +describe('core/strategies/abstract_popup_redirect_operation', () => { let auth: TestAuth; - let resolver: PopupRedirectResolver; + let resolver: PopupRedirectResolverInternal; let eventManager: EventManager; let idpStubs: sinon.SinonStubbedInstance; beforeEach(async () => { auth = await testAuth(); - eventManager = new AuthEventManager(auth.name); + eventManager = new AuthEventManager(auth); resolver = _getInstance(makeMockPopupRedirectResolver(eventManager)); idpStubs = sinon.stub(idp); }); @@ -97,14 +97,14 @@ describe('src/core/strategies/abstract_popup_redirect_operation', () => { /** Finishes out the promise */ function finishPromise(outcome: AuthEvent | FirebaseError): void { - delay((): void => { + setTimeout((): void => { if (outcome instanceof FirebaseError) { operation.onError(outcome); } else { // eslint-disable-next-line @typescript-eslint/no-floating-promises operation.onAuthEvent(outcome); } - }); + }, 1); } it('initializes the resolver', async () => { @@ -185,7 +185,8 @@ describe('src/core/strategies/abstract_popup_redirect_operation', () => { sessionId: BASE_AUTH_EVENT.sessionId!, tenantId: BASE_AUTH_EVENT.tenantId || undefined, postBody: BASE_AUTH_EVENT.postBody || undefined, - user: undefined + user: undefined, + bypassAuthState: false }; } @@ -236,6 +237,25 @@ describe('src/core/strategies/abstract_popup_redirect_operation', () => { await operation.execute(); expect(idp._reauth).to.have.been.calledWith(expectedIdpTaskParams()); }); + + it('includes the bypassAuthState parameter', async () => { + operation = new WrapperOperation( + auth, + AuthEventType.REAUTH_VIA_REDIRECT, + resolver, + undefined, + /** bypassAuthState */ true + ); + + const type = AuthEventType.REAUTH_VIA_REDIRECT; + updateFilter(type); + finishPromise(authEvent({ type })); + await operation.execute(); + expect(idp._reauth).to.have.been.calledWith({ + ...expectedIdpTaskParams(), + bypassAuthState: true + }); + }); }); }); }); diff --git a/packages-exp/auth-exp/src/core/strategies/abstract_popup_redirect_operation.ts b/packages-exp/auth-exp/src/core/strategies/abstract_popup_redirect_operation.ts new file mode 100644 index 00000000000..477b63919de --- /dev/null +++ b/packages-exp/auth-exp/src/core/strategies/abstract_popup_redirect_operation.ts @@ -0,0 +1,150 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FirebaseError } from '@firebase/util'; + +import { + AuthEvent, + AuthEventConsumer, + AuthEventType, + EventManager, + PopupRedirectResolverInternal +} from '../../model/popup_redirect'; +import { UserInternal, UserCredentialInternal } from '../../model/user'; +import { AuthErrorCode } from '../errors'; +import { debugAssert, _fail } from '../util/assert'; +import { + _link, + _reauth, + _signIn, + IdpTask, + IdpTaskParams +} from '../strategies/idp'; +import { AuthInternal } from '../../model/auth'; + +interface PendingPromise { + resolve: (cred: UserCredentialInternal | null) => void; + reject: (error: Error) => void; +} + +/** + * Popup event manager. Handles the popup's entire lifecycle; listens to auth + * events + */ +export abstract class AbstractPopupRedirectOperation + implements AuthEventConsumer { + private pendingPromise: PendingPromise | null = null; + private eventManager: EventManager | null = null; + readonly filter: AuthEventType[]; + + abstract eventId: string | null; + + constructor( + protected readonly auth: AuthInternal, + filter: AuthEventType | AuthEventType[], + protected readonly resolver: PopupRedirectResolverInternal, + protected user?: UserInternal, + private readonly bypassAuthState = false + ) { + this.filter = Array.isArray(filter) ? filter : [filter]; + } + + abstract onExecution(): Promise; + + execute(): Promise { + return new Promise( + async (resolve, reject) => { + this.pendingPromise = { resolve, reject }; + + try { + this.eventManager = await this.resolver._initialize(this.auth); + await this.onExecution(); + this.eventManager.registerConsumer(this); + } catch (e) { + this.reject(e); + } + } + ); + } + + async onAuthEvent(event: AuthEvent): Promise { + const { urlResponse, sessionId, postBody, tenantId, error, type } = event; + if (error) { + this.reject(error); + return; + } + + const params: IdpTaskParams = { + auth: this.auth, + requestUri: urlResponse!, + sessionId: sessionId!, + tenantId: tenantId || undefined, + postBody: postBody || undefined, + user: this.user, + bypassAuthState: this.bypassAuthState + }; + + try { + this.resolve(await this.getIdpTask(type)(params)); + } catch (e) { + this.reject(e); + } + } + + onError(error: FirebaseError): void { + this.reject(error); + } + + private getIdpTask(type: AuthEventType): IdpTask { + switch (type) { + case AuthEventType.SIGN_IN_VIA_POPUP: + case AuthEventType.SIGN_IN_VIA_REDIRECT: + return _signIn; + case AuthEventType.LINK_VIA_POPUP: + case AuthEventType.LINK_VIA_REDIRECT: + return _link; + case AuthEventType.REAUTH_VIA_POPUP: + case AuthEventType.REAUTH_VIA_REDIRECT: + return _reauth; + default: + _fail(this.auth, AuthErrorCode.INTERNAL_ERROR); + } + } + + protected resolve(cred: UserCredentialInternal | null): void { + debugAssert(this.pendingPromise, 'Pending promise was never set'); + this.pendingPromise.resolve(cred); + this.unregisterAndCleanUp(); + } + + protected reject(error: Error): void { + debugAssert(this.pendingPromise, 'Pending promise was never set'); + this.pendingPromise.reject(error); + this.unregisterAndCleanUp(); + } + + private unregisterAndCleanUp(): void { + if (this.eventManager) { + this.eventManager.unregisterConsumer(this); + } + + this.pendingPromise = null; + this.cleanUp(); + } + + abstract cleanUp(): void; +} diff --git a/packages-exp/auth-exp/src/core/strategies/action_code_settings.test.ts b/packages-exp/auth-exp/src/core/strategies/action_code_settings.test.ts new file mode 100644 index 00000000000..36784151156 --- /dev/null +++ b/packages-exp/auth-exp/src/core/strategies/action_code_settings.test.ts @@ -0,0 +1,96 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FirebaseError } from '@firebase/util'; +import { expect } from 'chai'; + +import { testAuth, TestAuth } from '../../../test/helpers/mock_auth'; +import { GetOobCodeRequest } from '../../api/authentication/email_and_password'; +import { _setActionCodeSettingsOnRequest } from './action_code_settings'; + +describe('core/strategies/action_code_settings', () => { + let auth: TestAuth; + const request: GetOobCodeRequest = {}; + + beforeEach(async () => { + auth = await testAuth(); + }); + + it('should require a non empty continue URL', () => { + expect(() => + _setActionCodeSettingsOnRequest(auth, request, { + handleCodeInApp: true, + iOS: { + bundleId: 'my-bundle' + }, + url: '', + dynamicLinkDomain: 'fdl-domain' + }) + ).to.throw(FirebaseError, '(auth/invalid-continue-uri)'); + }); + + it('should allow undefined dynamic link URL', () => { + expect(() => + _setActionCodeSettingsOnRequest(auth, request, { + handleCodeInApp: true, + iOS: { + bundleId: 'my-´bundle' + }, + url: 'my-url' + }) + ).to.not.throw(); + }); + + it('should require a non empty dynamic link URL', () => { + expect(() => + _setActionCodeSettingsOnRequest(auth, request, { + handleCodeInApp: true, + iOS: { + bundleId: 'my-´bundle' + }, + url: 'my-url', + dynamicLinkDomain: '' + }) + ).to.throw(FirebaseError, '(auth/invalid-dynamic-link-domain)'); + }); + + it('should require a non-empty bundle ID', () => { + expect(() => + _setActionCodeSettingsOnRequest(auth, request, { + handleCodeInApp: true, + iOS: { + bundleId: '' + }, + url: 'my-url', + dynamicLinkDomain: 'fdl-domain' + }) + ).to.throw(FirebaseError, '(auth/missing-ios-bundle-id)'); + }); + + it('should require a non-empty package name', () => { + expect(() => + _setActionCodeSettingsOnRequest(auth, request, { + handleCodeInApp: true, + android: { + packageName: '' + }, + url: 'my-url', + dynamicLinkDomain: 'fdl-domain' + }) + ).to.throw(FirebaseError, '(auth/missing-android-pkg-name)'); + }); +}); diff --git a/packages-exp/auth-exp/src/core/strategies/action_code_settings.ts b/packages-exp/auth-exp/src/core/strategies/action_code_settings.ts index b5c120b9fe7..1c93bc7155d 100644 --- a/packages-exp/auth-exp/src/core/strategies/action_code_settings.ts +++ b/packages-exp/auth-exp/src/core/strategies/action_code_settings.ts @@ -15,23 +15,48 @@ * limitations under the License. */ -import { ActionCodeSettings } from '@firebase/auth-types-exp'; +import { ActionCodeSettings, Auth } from '../../model/public_types'; import { GetOobCodeRequest } from '../../api/authentication/email_and_password'; +import { AuthErrorCode } from '../errors'; +import { _assert } from '../util/assert'; -export function setActionCodeSettingsOnRequest( +export function _setActionCodeSettingsOnRequest( + auth: Auth, request: GetOobCodeRequest, actionCodeSettings: ActionCodeSettings ): void { + _assert( + actionCodeSettings.url?.length > 0, + auth, + AuthErrorCode.INVALID_CONTINUE_URI + ); + _assert( + typeof actionCodeSettings.dynamicLinkDomain === 'undefined' || + actionCodeSettings.dynamicLinkDomain.length > 0, + auth, + AuthErrorCode.INVALID_DYNAMIC_LINK_DOMAIN + ); + request.continueUrl = actionCodeSettings.url; request.dynamicLinkDomain = actionCodeSettings.dynamicLinkDomain; request.canHandleCodeInApp = actionCodeSettings.handleCodeInApp; if (actionCodeSettings.iOS) { + _assert( + actionCodeSettings.iOS.bundleId.length > 0, + auth, + AuthErrorCode.MISSING_IOS_BUNDLE_ID + ); request.iosBundleId = actionCodeSettings.iOS.bundleId; } if (actionCodeSettings.android) { + _assert( + actionCodeSettings.android.packageName.length > 0, + auth, + AuthErrorCode.MISSING_ANDROID_PACKAGE_NAME + ); request.androidInstallApp = actionCodeSettings.android.installApp; request.androidMinimumVersionCode = actionCodeSettings.android.minimumVersion; diff --git a/packages-exp/auth-exp/src/core/strategies/anonymous.test.ts b/packages-exp/auth-exp/src/core/strategies/anonymous.test.ts index 5e9af147f76..887878fe5d1 100644 --- a/packages-exp/auth-exp/src/core/strategies/anonymous.test.ts +++ b/packages-exp/auth-exp/src/core/strategies/anonymous.test.ts @@ -17,7 +17,7 @@ import { expect } from 'chai'; -import { OperationType } from '@firebase/auth-types-exp'; +import { OperationType } from '../../model/public_types'; import { mockEndpoint } from '../../../test/helpers/api/helper'; import { testAuth, testUser, TestAuth } from '../../../test/helpers/mock_auth'; @@ -70,7 +70,7 @@ describe('core/strategies/anonymous', () => { context('already signed in with a non-anonymous account', () => { it('should sign in as a new user user', async () => { const fakeUser = testUser(auth, 'other-uid'); - await auth.updateCurrentUser(fakeUser); + await auth._updateCurrentUser(fakeUser); expect(fakeUser.isAnonymous).to.be.false; const { user, operationType } = await signInAnonymously(auth); diff --git a/packages-exp/auth-exp/src/core/strategies/anonymous.ts b/packages-exp/auth-exp/src/core/strategies/anonymous.ts index d613ca692ff..97ddab32d6e 100644 --- a/packages-exp/auth-exp/src/core/strategies/anonymous.ts +++ b/packages-exp/auth-exp/src/core/strategies/anonymous.ts @@ -15,32 +15,43 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { Auth, OperationType, UserCredential } from '../../model/public_types'; import { signUp } from '../../api/authentication/sign_up'; -import { User } from '../../model/user'; +import { UserInternal } from '../../model/user'; import { UserCredentialImpl } from '../user/user_credential_impl'; import { _castAuth } from '../auth/auth_impl'; -export async function signInAnonymously( - auth: externs.Auth -): Promise { - if (auth.currentUser?.isAnonymous) { +/** + * Asynchronously signs in as an anonymous user. + * + * @remarks + * If there is already an anonymous user signed in, that user will be returned; otherwise, a + * new anonymous user identity will be created and returned. + * + * @param auth - The Auth instance. + * + * @public + */ +export async function signInAnonymously(auth: Auth): Promise { + const authInternal = _castAuth(auth); + await authInternal._initializationPromise; + if (authInternal.currentUser?.isAnonymous) { // If an anonymous user is already signed in, no need to sign them in again. return new UserCredentialImpl({ - user: auth.currentUser as User, + user: authInternal.currentUser as UserInternal, providerId: null, - operationType: externs.OperationType.SIGN_IN + operationType: OperationType.SIGN_IN }); } - const response = await signUp(auth, { + const response = await signUp(authInternal, { returnSecureToken: true }); const userCredential = await UserCredentialImpl._fromIdTokenResponse( - _castAuth(auth), - externs.OperationType.SIGN_IN, + authInternal, + OperationType.SIGN_IN, response, true ); - await auth.updateCurrentUser(userCredential.user); + await authInternal._updateCurrentUser(userCredential.user); return userCredential; } diff --git a/packages-exp/auth-exp/src/core/strategies/credential.test.ts b/packages-exp/auth-exp/src/core/strategies/credential.test.ts index 3671680f5da..837ec2bd5e6 100644 --- a/packages-exp/auth-exp/src/core/strategies/credential.test.ts +++ b/packages-exp/auth-exp/src/core/strategies/credential.test.ts @@ -23,7 +23,7 @@ import { OperationType, ProviderId, SignInMethod -} from '@firebase/auth-types-exp'; +} from '../../model/public_types'; import { FirebaseError } from '@firebase/util'; import { mockEndpoint } from '../../../test/helpers/api/helper'; @@ -36,14 +36,16 @@ import { APIUserInfo } from '../../api/account_management/account'; import { IdTokenMfaResponse } from '../../api/authentication/mfa'; import { MultiFactorError } from '../../mfa/mfa_error'; import { IdTokenResponse, IdTokenResponseKind } from '../../model/id_token'; -import { User, UserCredential } from '../../model/user'; +import { UserInternal, UserCredentialInternal } from '../../model/user'; import { AuthCredential } from '../credentials'; -import { AUTH_ERROR_FACTORY, AuthErrorCode } from '../errors'; +import { AuthErrorCode } from '../errors'; import { linkWithCredential, reauthenticateWithCredential, - signInWithCredential + signInWithCredential, + _signInWithCredential } from './credential'; +import { _createError } from '../util/assert'; use(chaiAsPromised); @@ -70,7 +72,7 @@ describe('core/strategies/credential', () => { let authCredential: AuthCredential; let auth: TestAuth; let getAccountInfoEndpoint: mockFetch.Route; - let user: User; + let user: UserInternal; beforeEach(async () => { auth = await testAuth(); @@ -97,7 +99,9 @@ describe('core/strategies/credential', () => { auth, authCredential ); - expect((rest as UserCredential)._tokenResponse).to.eq(idTokenResponse); + expect((rest as UserCredentialInternal)._tokenResponse).to.eq( + idTokenResponse + ); expect(user.uid).to.eq('local-id'); expect(user.displayName).to.eq('display-name'); expect(operationType).to.eq(OperationType.SIGN_IN); @@ -111,6 +115,15 @@ describe('core/strategies/credential', () => { expect(auth.currentUser).to.eq(user); }); + it('does not update the current user if bypass is true', async () => { + stub(authCredential, '_getIdTokenResponse').returns( + Promise.resolve(idTokenResponse) + ); + const { user } = await _signInWithCredential(auth, authCredential, true); + expect(auth.currentUser).to.be.null; + expect(user).not.to.be.null; + }); + it('should handle MFA', async () => { const serverResponse: IdTokenMfaResponse = { localId: 'uid', @@ -125,8 +138,7 @@ describe('core/strategies/credential', () => { }; stub(authCredential, '_getIdTokenResponse').returns( Promise.reject( - AUTH_ERROR_FACTORY.create(AuthErrorCode.MFA_REQUIRED, { - appName: auth.name, + _createError(auth, AuthErrorCode.MFA_REQUIRED, { serverResponse }) ) @@ -134,7 +146,6 @@ describe('core/strategies/credential', () => { const error = await expect( signInWithCredential(auth, authCredential) ).to.be.rejectedWith(MultiFactorError); - expect(error.credential).to.eq(authCredential); expect(error.operationType).to.eq(OperationType.SIGN_IN); expect(error.serverResponse).to.eql(serverResponse); expect(error.user).to.be.undefined; @@ -173,7 +184,7 @@ describe('core/strategies/credential', () => { } = await reauthenticateWithCredential(user, authCredential); expect(operationType).to.eq(OperationType.REAUTHENTICATE); expect(newUser).to.eq(user); - expect((rest as UserCredential)._tokenResponse).to.eql({ + expect((rest as UserCredentialInternal)._tokenResponse).to.eql({ ...idTokenResponse, idToken: makeJWT({ sub: 'uid' }) }); @@ -211,7 +222,9 @@ describe('core/strategies/credential', () => { } = await linkWithCredential(user, authCredential); expect(operationType).to.eq(OperationType.LINK); expect(newUser).to.eq(user); - expect((rest as UserCredential)._tokenResponse).to.eq(idTokenResponse); + expect((rest as UserCredentialInternal)._tokenResponse).to.eq( + idTokenResponse + ); }); }); }); diff --git a/packages-exp/auth-exp/src/core/strategies/credential.ts b/packages-exp/auth-exp/src/core/strategies/credential.ts index e5615be244a..af3880ec85a 100644 --- a/packages-exp/auth-exp/src/core/strategies/credential.ts +++ b/packages-exp/auth-exp/src/core/strategies/credential.ts @@ -15,21 +15,27 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; -import { OperationType, UserCredential } from '@firebase/auth-types-exp'; +import { + OperationType, + UserCredential, + Auth, + User +} from '../../model/public_types'; import { _processCredentialSavingMfaContextIfNecessary } from '../../mfa/mfa_error'; -import { Auth } from '../../model/auth'; -import { User } from '../../model/user'; +import { AuthInternal } from '../../model/auth'; +import { UserInternal } from '../../model/user'; import { AuthCredential } from '../credentials'; import { _assertLinkedStatus, _link } from '../user/link_unlink'; import { _reauthenticate } from '../user/reauthenticate'; import { UserCredentialImpl } from '../user/user_credential_impl'; import { _castAuth } from '../auth/auth_impl'; +import { getModularInstance } from '@firebase/util'; export async function _signInWithCredential( - auth: Auth, - credential: AuthCredential + auth: AuthInternal, + credential: AuthCredential, + bypassAuthState = false ): Promise { const operationType = OperationType.SIGN_IN; const response = await _processCredentialSavingMfaContextIfNecessary( @@ -42,35 +48,68 @@ export async function _signInWithCredential( operationType, response ); - await auth.updateCurrentUser(userCredential.user); + + if (!bypassAuthState) { + await auth._updateCurrentUser(userCredential.user); + } return userCredential; } +/** + * Asynchronously signs in with the given credentials. + * + * @remarks + * An {@link AuthProvider} can be used to generate the credential. + * + * @param auth - The Auth instance. + * @param credential - The auth credential. + * + * @public + */ export async function signInWithCredential( - auth: externs.Auth, - credential: externs.AuthCredential -): Promise { - return _signInWithCredential(_castAuth(auth), credential as AuthCredential); + auth: Auth, + credential: AuthCredential +): Promise { + return _signInWithCredential(_castAuth(auth), credential); } +/** + * Links the user account with the given credentials. + * + * @remarks + * An {@link AuthProvider} can be used to generate the credential. + * + * @param user - The user. + * @param credential - The auth credential. + * + * @public + */ export async function linkWithCredential( - userExtern: externs.User, - credentialExtern: externs.AuthCredential + user: User, + credential: AuthCredential ): Promise { - const user = userExtern as User; - const credential = credentialExtern as AuthCredential; + const userInternal = getModularInstance(user) as UserInternal; - await _assertLinkedStatus(false, user, credential.providerId); + await _assertLinkedStatus(false, userInternal, credential.providerId); - return _link(user, credential); + return _link(userInternal, credential); } +/** + * Re-authenticates a user using a fresh credential. + * + * @remarks + * Use before operations such as {@link updatePassword} that require tokens from recent sign-in + * attempts. This method can be used to recover from a CREDENTIAL_TOO_OLD_LOGIN_AGAIN error. + * + * @param user - The user. + * @param credential - The auth credential. + * + * @public + */ export async function reauthenticateWithCredential( - userExtern: externs.User, - credentialExtern: externs.AuthCredential -): Promise { - const credential = credentialExtern as AuthCredential; - const user = userExtern as User; - - return _reauthenticate(user, credential); + user: User, + credential: AuthCredential +): Promise { + return _reauthenticate(getModularInstance(user) as UserInternal, credential); } diff --git a/packages-exp/auth-exp/src/core/strategies/custom_token.test.ts b/packages-exp/auth-exp/src/core/strategies/custom_token.test.ts index 0491b965ed8..082b678dad3 100644 --- a/packages-exp/auth-exp/src/core/strategies/custom_token.test.ts +++ b/packages-exp/auth-exp/src/core/strategies/custom_token.test.ts @@ -18,7 +18,7 @@ import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; -import { OperationType } from '@firebase/auth-types-exp'; +import { OperationType } from '../../model/public_types'; import { mockEndpoint } from '../../../test/helpers/api/helper'; import { testAuth, TestAuth } from '../../../test/helpers/mock_auth'; @@ -26,7 +26,7 @@ import * as mockFetch from '../../../test/helpers/mock_fetch'; import { Endpoint } from '../../api'; import { APIUserInfo } from '../../api/account_management/account'; import { IdTokenResponse, IdTokenResponseKind } from '../../model/id_token'; -import { UserCredential } from '../../model/user'; +import { UserCredentialInternal } from '../../model/user'; import { signInWithCustomToken } from './custom_token'; use(chaiAsPromised); @@ -52,11 +52,15 @@ describe('core/strategies/signInWithCustomToken', () => { }; let auth: TestAuth; + let signInRoute: mockFetch.Route; beforeEach(async () => { auth = await testAuth(); mockFetch.setUp(); - mockEndpoint(Endpoint.SIGN_IN_WITH_CUSTOM_TOKEN, idTokenResponse); + signInRoute = mockEndpoint( + Endpoint.SIGN_IN_WITH_CUSTOM_TOKEN, + idTokenResponse + ); mockEndpoint(Endpoint.GET_ACCOUNT_INFO, { users: [serverUser] }); @@ -71,13 +75,21 @@ describe('core/strategies/signInWithCustomToken', () => { } = (await signInWithCustomToken( auth, 'look-at-me-im-a-jwt' - )) as UserCredential; + )) as UserCredentialInternal; expect(_tokenResponse).to.eql(idTokenResponse); expect(user.uid).to.eq('local-id'); expect(user.displayName).to.eq('display-name'); expect(operationType).to.eq(OperationType.SIGN_IN); }); + it('should send with a valid request', async () => { + await signInWithCustomToken(auth, 'j.w.t'); + expect(signInRoute.calls[0].request).to.eql({ + token: 'j.w.t', + returnSecureToken: true + }); + }); + it('should update the current user', async () => { const { user } = await signInWithCustomToken(auth, 'oh.no'); expect(auth.currentUser).to.eq(user); diff --git a/packages-exp/auth-exp/src/core/strategies/custom_token.ts b/packages-exp/auth-exp/src/core/strategies/custom_token.ts index 448c115cc5e..1b535036265 100644 --- a/packages-exp/auth-exp/src/core/strategies/custom_token.ts +++ b/packages-exp/auth-exp/src/core/strategies/custom_token.ts @@ -15,26 +15,43 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { Auth, OperationType, UserCredential } from '../../model/public_types'; import { signInWithCustomToken as getIdTokenResponse } from '../../api/authentication/custom_token'; import { IdTokenResponse } from '../../model/id_token'; import { UserCredentialImpl } from '../user/user_credential_impl'; import { _castAuth } from '../auth/auth_impl'; +/** + * Asynchronously signs in using a custom token. + * + * @remarks + * Custom tokens are used to integrate Firebase Auth with existing auth systems, and must + * be generated by an auth backend using the + * {@link https://firebase.google.com/docs/reference/admin/node/admin.auth.Auth#createcustomtoken | createCustomToken} + * method in the {@link https://firebase.google.com/docs/auth/admin | Admin SDK} . + * + * Fails with an error if the token is invalid, expired, or not accepted by the Firebase Auth service. + * + * @param auth - The Auth instance. + * @param customToken - The custom token to sign in with. + * + * @public + */ export async function signInWithCustomToken( - authExtern: externs.Auth, + auth: Auth, customToken: string -): Promise { - const response: IdTokenResponse = await getIdTokenResponse(authExtern, { - token: customToken +): Promise { + const authInternal = _castAuth(auth); + const response: IdTokenResponse = await getIdTokenResponse(authInternal, { + token: customToken, + returnSecureToken: true }); - const auth = _castAuth(authExtern); const cred = await UserCredentialImpl._fromIdTokenResponse( - auth, - externs.OperationType.SIGN_IN, + authInternal, + OperationType.SIGN_IN, response ); - await auth.updateCurrentUser(cred.user); + await authInternal._updateCurrentUser(cred.user); return cred; } diff --git a/packages-exp/auth-exp/src/core/strategies/email.test.ts b/packages-exp/auth-exp/src/core/strategies/email.test.ts index 90b76923bf2..2a7911958be 100644 --- a/packages-exp/auth-exp/src/core/strategies/email.test.ts +++ b/packages-exp/auth-exp/src/core/strategies/email.test.ts @@ -20,7 +20,7 @@ import * as chaiAsPromised from 'chai-as-promised'; import { restore, SinonStub, stub } from 'sinon'; import * as sinonChai from 'sinon-chai'; -import { Operation, ProviderId } from '@firebase/auth-types-exp'; +import { ActionCodeOperation, ProviderId } from '../../model/public_types'; import { FirebaseError, isNode } from '@firebase/util'; import { mockEndpoint } from '../../../test/helpers/api/helper'; @@ -28,7 +28,7 @@ import { testAuth, TestAuth, testUser } from '../../../test/helpers/mock_auth'; import * as mockFetch from '../../../test/helpers/mock_fetch'; import { Endpoint } from '../../api'; import { ServerError } from '../../api/errors'; -import { User } from '../../model/user'; +import { UserInternal } from '../../model/user'; import { fetchSignInMethodsForEmail, sendEmailVerification, @@ -103,7 +103,7 @@ describe('core/strategies/fetchSignInMethodsForEmail', () => { describe('core/strategies/sendEmailVerification', () => { const email = 'foo@bar.com'; const idToken = 'access-token'; - let user: User; + let user: UserInternal; let auth: TestAuth; let reloadStub: SinonStub; @@ -121,7 +121,7 @@ describe('core/strategies/sendEmailVerification', () => { it('should send the email verification', async () => { const mock = mockEndpoint(Endpoint.SEND_OOB_CODE, { - requestType: Operation.VERIFY_EMAIL, + requestType: ActionCodeOperation.VERIFY_EMAIL, email }); @@ -129,14 +129,14 @@ describe('core/strategies/sendEmailVerification', () => { expect(reloadStub).to.not.have.been.called; expect(mock.calls[0].request).to.eql({ - requestType: Operation.VERIFY_EMAIL, + requestType: ActionCodeOperation.VERIFY_EMAIL, idToken }); }); it('should reload the user if the API returns a different email', async () => { const mock = mockEndpoint(Endpoint.SEND_OOB_CODE, { - requestType: Operation.VERIFY_EMAIL, + requestType: ActionCodeOperation.VERIFY_EMAIL, email: 'other@email.com' }); @@ -144,7 +144,7 @@ describe('core/strategies/sendEmailVerification', () => { expect(reloadStub).to.have.been.calledOnce; expect(mock.calls[0].request).to.eql({ - requestType: Operation.VERIFY_EMAIL, + requestType: ActionCodeOperation.VERIFY_EMAIL, idToken }); }); @@ -152,7 +152,7 @@ describe('core/strategies/sendEmailVerification', () => { context('on iOS', () => { it('should pass action code parameters', async () => { const mock = mockEndpoint(Endpoint.SEND_OOB_CODE, { - requestType: Operation.VERIFY_EMAIL, + requestType: ActionCodeOperation.VERIFY_EMAIL, email }); await sendEmailVerification(user, { @@ -165,7 +165,7 @@ describe('core/strategies/sendEmailVerification', () => { }); expect(mock.calls[0].request).to.eql({ - requestType: Operation.VERIFY_EMAIL, + requestType: ActionCodeOperation.VERIFY_EMAIL, idToken, continueUrl: 'my-url', dynamicLinkDomain: 'fdl-domain', @@ -178,7 +178,7 @@ describe('core/strategies/sendEmailVerification', () => { context('on Android', () => { it('should pass action code parameters', async () => { const mock = mockEndpoint(Endpoint.SEND_OOB_CODE, { - requestType: Operation.VERIFY_EMAIL, + requestType: ActionCodeOperation.VERIFY_EMAIL, email }); await sendEmailVerification(user, { @@ -192,7 +192,7 @@ describe('core/strategies/sendEmailVerification', () => { dynamicLinkDomain: 'fdl-domain' }); expect(mock.calls[0].request).to.eql({ - requestType: Operation.VERIFY_EMAIL, + requestType: ActionCodeOperation.VERIFY_EMAIL, idToken, continueUrl: 'my-url', dynamicLinkDomain: 'fdl-domain', @@ -209,7 +209,7 @@ describe('core/strategies/verifyBeforeUpdateEmail', () => { const email = 'foo@bar.com'; const newEmail = 'newemail@bar.com'; const idToken = 'access-token'; - let user: User; + let user: UserInternal; let auth: TestAuth; let reloadStub: SinonStub; @@ -227,7 +227,7 @@ describe('core/strategies/verifyBeforeUpdateEmail', () => { it('should send the email verification', async () => { const mock = mockEndpoint(Endpoint.SEND_OOB_CODE, { - requestType: Operation.VERIFY_AND_CHANGE_EMAIL, + requestType: ActionCodeOperation.VERIFY_AND_CHANGE_EMAIL, email }); @@ -235,7 +235,7 @@ describe('core/strategies/verifyBeforeUpdateEmail', () => { expect(reloadStub).to.not.have.been.called; expect(mock.calls[0].request).to.eql({ - requestType: Operation.VERIFY_AND_CHANGE_EMAIL, + requestType: ActionCodeOperation.VERIFY_AND_CHANGE_EMAIL, idToken, newEmail }); @@ -243,7 +243,7 @@ describe('core/strategies/verifyBeforeUpdateEmail', () => { it('should reload the user if the API returns a different email', async () => { const mock = mockEndpoint(Endpoint.SEND_OOB_CODE, { - requestType: Operation.VERIFY_AND_CHANGE_EMAIL, + requestType: ActionCodeOperation.VERIFY_AND_CHANGE_EMAIL, email: 'other@email.com' }); @@ -251,7 +251,7 @@ describe('core/strategies/verifyBeforeUpdateEmail', () => { expect(reloadStub).to.have.been.calledOnce; expect(mock.calls[0].request).to.eql({ - requestType: Operation.VERIFY_AND_CHANGE_EMAIL, + requestType: ActionCodeOperation.VERIFY_AND_CHANGE_EMAIL, idToken, newEmail }); @@ -260,7 +260,7 @@ describe('core/strategies/verifyBeforeUpdateEmail', () => { context('on iOS', () => { it('should pass action code parameters', async () => { const mock = mockEndpoint(Endpoint.SEND_OOB_CODE, { - requestType: Operation.VERIFY_AND_CHANGE_EMAIL, + requestType: ActionCodeOperation.VERIFY_AND_CHANGE_EMAIL, email }); await verifyBeforeUpdateEmail(user, newEmail, { @@ -273,7 +273,7 @@ describe('core/strategies/verifyBeforeUpdateEmail', () => { }); expect(mock.calls[0].request).to.eql({ - requestType: Operation.VERIFY_AND_CHANGE_EMAIL, + requestType: ActionCodeOperation.VERIFY_AND_CHANGE_EMAIL, idToken, newEmail, continueUrl: 'my-url', @@ -287,7 +287,7 @@ describe('core/strategies/verifyBeforeUpdateEmail', () => { context('on Android', () => { it('should pass action code parameters', async () => { const mock = mockEndpoint(Endpoint.SEND_OOB_CODE, { - requestType: Operation.VERIFY_AND_CHANGE_EMAIL, + requestType: ActionCodeOperation.VERIFY_AND_CHANGE_EMAIL, email }); await verifyBeforeUpdateEmail(user, newEmail, { @@ -301,7 +301,7 @@ describe('core/strategies/verifyBeforeUpdateEmail', () => { dynamicLinkDomain: 'fdl-domain' }); expect(mock.calls[0].request).to.eql({ - requestType: Operation.VERIFY_AND_CHANGE_EMAIL, + requestType: ActionCodeOperation.VERIFY_AND_CHANGE_EMAIL, idToken, newEmail, continueUrl: 'my-url', diff --git a/packages-exp/auth-exp/src/core/strategies/email.ts b/packages-exp/auth-exp/src/core/strategies/email.ts index db129a8128f..70dee6febc6 100644 --- a/packages-exp/auth-exp/src/core/strategies/email.ts +++ b/packages-exp/auth-exp/src/core/strategies/email.ts @@ -15,20 +15,39 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { + ActionCodeOperation, + ActionCodeSettings, + Auth, + User +} from '../../model/public_types'; import { createAuthUri, CreateAuthUriRequest } from '../../api/authentication/create_auth_uri'; import * as api from '../../api/authentication/email_and_password'; -import { User } from '../../model/user'; +import { UserInternal } from '../../model/user'; import { _getCurrentUrl, _isHttpOrHttps } from '../util/location'; -import { setActionCodeSettingsOnRequest } from './action_code_settings'; -import { _castAuth } from '../auth/auth_impl'; +import { _setActionCodeSettingsOnRequest } from './action_code_settings'; +import { getModularInstance } from '@firebase/util'; +/** + * Gets the list of possible sign in methods for the given email address. + * + * @remarks + * This is useful to differentiate methods of sign-in for the same provider, eg. + * {@link EmailAuthProvider} which has 2 methods of sign-in, + * {@link SignInMethod}.EMAIL_PASSWORD and + * {@link SignInMethod}.EMAIL_LINK. + * + * @param auth - The Auth instance. + * @param email - The user's email address. + * + * @public + */ export async function fetchSignInMethodsForEmail( - auth: externs.Auth, + auth: Auth, email: string ): Promise { // createAuthUri returns an error if continue URI is not http or https. @@ -40,49 +59,124 @@ export async function fetchSignInMethodsForEmail( continueUri }; - const { signinMethods } = await createAuthUri(auth, request); + const { signinMethods } = await createAuthUri( + getModularInstance(auth), + request + ); return signinMethods || []; } +/** + * Sends a verification email to a user. + * + * @remarks + * The verification process is completed by calling {@link applyActionCode}. + * + * @example + * ```javascript + * const 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 + * }; + * await sendEmailVerification(user, actionCodeSettings); + * // Obtain code from the user. + * await applyActionCode(auth, code); + * ``` + * + * @param user - The user. + * @param actionCodeSettings - The {@link ActionCodeSettings}. + * + * @public + */ export async function sendEmailVerification( - userExtern: externs.User, - actionCodeSettings?: externs.ActionCodeSettings | null + user: User, + actionCodeSettings?: ActionCodeSettings | null ): Promise { - const user = userExtern as User; + const userInternal = getModularInstance(user) as UserInternal; const idToken = await user.getIdToken(); const request: api.VerifyEmailRequest = { - requestType: externs.Operation.VERIFY_EMAIL, + requestType: ActionCodeOperation.VERIFY_EMAIL, idToken }; if (actionCodeSettings) { - setActionCodeSettingsOnRequest(request, actionCodeSettings); + _setActionCodeSettingsOnRequest( + userInternal.auth, + request, + actionCodeSettings + ); } - const { email } = await api.sendEmailVerification(user.auth, request); + const { email } = await api.sendEmailVerification(userInternal.auth, request); if (email !== user.email) { await user.reload(); } } +/** + * Sends a verification email to a new email address. + * + * @remarks + * The user's email will be updated to the new one after being verified. + * + * If you have a custom email action handler, you can complete the verification process by calling + * {@link applyActionCode}. + * + * @example + * ```javascript + * const 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 + * }; + * await verifyBeforeUpdateEmail(user, 'newemail@example.com', actionCodeSettings); + * // Obtain code from the user. + * await applyActionCode(auth, code); + * ``` + * + * @param user - The user. + * @param newEmail - The new email address to be verified before update. + * @param actionCodeSettings - The {@link ActionCodeSettings}. + * + * @public + */ export async function verifyBeforeUpdateEmail( - userExtern: externs.User, + user: User, newEmail: string, - actionCodeSettings?: externs.ActionCodeSettings | null + actionCodeSettings?: ActionCodeSettings | null ): Promise { - const user = userExtern as User; + const userInternal = getModularInstance(user) as UserInternal; const idToken = await user.getIdToken(); const request: api.VerifyAndChangeEmailRequest = { - requestType: externs.Operation.VERIFY_AND_CHANGE_EMAIL, + requestType: ActionCodeOperation.VERIFY_AND_CHANGE_EMAIL, idToken, newEmail }; if (actionCodeSettings) { - setActionCodeSettingsOnRequest(request, actionCodeSettings); + _setActionCodeSettingsOnRequest( + userInternal.auth, + request, + actionCodeSettings + ); } - const { email } = await api.verifyAndChangeEmail(user.auth, request); + const { email } = await api.verifyAndChangeEmail(userInternal.auth, request); if (email !== user.email) { // If the local copy of the email on user is outdated, reload the diff --git a/packages-exp/auth-exp/src/core/strategies/email_and_password.test.ts b/packages-exp/auth-exp/src/core/strategies/email_and_password.test.ts index 45bfc001154..e0b60903aa9 100644 --- a/packages-exp/auth-exp/src/core/strategies/email_and_password.test.ts +++ b/packages-exp/auth-exp/src/core/strategies/email_and_password.test.ts @@ -19,7 +19,7 @@ import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import * as sinonChai from 'sinon-chai'; -import { Operation, OperationType } from '@firebase/auth-types-exp'; +import { ActionCodeOperation, OperationType } from '../../model/public_types'; import { FirebaseError } from '@firebase/util'; import { mockEndpoint } from '../../../test/helpers/api/helper'; @@ -28,7 +28,7 @@ import * as mockFetch from '../../../test/helpers/mock_fetch'; import { Endpoint } from '../../api'; import { APIUserInfo } from '../../api/account_management/account'; import { ServerError } from '../../api/errors'; -import { UserCredential } from '../../model/user'; +import { UserCredentialInternal } from '../../model/user'; import { applyActionCode, checkActionCode, @@ -60,7 +60,7 @@ describe('core/strategies/sendPasswordResetEmail', () => { }); await sendPasswordResetEmail(auth, email); expect(mock.calls[0].request).to.eql({ - requestType: Operation.PASSWORD_RESET, + requestType: ActionCodeOperation.PASSWORD_RESET, email }); }); @@ -98,7 +98,7 @@ describe('core/strategies/sendPasswordResetEmail', () => { }); expect(mock.calls[0].request).to.eql({ - requestType: Operation.PASSWORD_RESET, + requestType: ActionCodeOperation.PASSWORD_RESET, email, continueUrl: 'my-url', dynamicLinkDomain: 'fdl-domain', @@ -124,7 +124,7 @@ describe('core/strategies/sendPasswordResetEmail', () => { dynamicLinkDomain: 'fdl-domain' }); expect(mock.calls[0].request).to.eql({ - requestType: Operation.PASSWORD_RESET, + requestType: ActionCodeOperation.PASSWORD_RESET, email, continueUrl: 'my-url', dynamicLinkDomain: 'fdl-domain', @@ -238,7 +238,7 @@ describe('core/strategies/checkActionCode', () => { it('should verify the oob code', async () => { const mock = mockEndpoint(Endpoint.RESET_PASSWORD, { - requestType: Operation.PASSWORD_RESET, + requestType: ActionCodeOperation.PASSWORD_RESET, email: 'foo@bar.com' }); const response = await checkActionCode(auth, oobCode); @@ -248,7 +248,7 @@ describe('core/strategies/checkActionCode', () => { previousEmail: null, multiFactorInfo: null }, - operation: Operation.PASSWORD_RESET + operation: ActionCodeOperation.PASSWORD_RESET }); expect(mock.calls[0].request).to.eql({ oobCode @@ -257,7 +257,7 @@ describe('core/strategies/checkActionCode', () => { it('should return the newEmail', async () => { const mock = mockEndpoint(Endpoint.RESET_PASSWORD, { - requestType: Operation.PASSWORD_RESET, + requestType: ActionCodeOperation.PASSWORD_RESET, email, newEmail }); @@ -268,7 +268,7 @@ describe('core/strategies/checkActionCode', () => { previousEmail: newEmail, multiFactorInfo: null }, - operation: Operation.PASSWORD_RESET + operation: ActionCodeOperation.PASSWORD_RESET }); expect(mock.calls[0].request).to.eql({ oobCode @@ -320,7 +320,7 @@ describe('core/strategies/verifyPasswordResetCode', () => { it('should verify the oob code', async () => { const mock = mockEndpoint(Endpoint.RESET_PASSWORD, { - requestType: Operation.PASSWORD_RESET, + requestType: ActionCodeOperation.PASSWORD_RESET, email: 'foo@bar.com', previousEmail: null }); @@ -391,7 +391,7 @@ describe('core/strategies/email_and_password/createUserWithEmailAndPassword', () auth, 'some-email', 'some-password' - )) as UserCredential; + )) as UserCredentialInternal; expect(_tokenResponse).to.eql({ idToken: 'id-token', refreshToken: 'refresh-token', @@ -434,7 +434,7 @@ describe('core/strategies/email_and_password/signInWithEmailAndPassword', () => auth, 'some-email', 'some-password' - )) as UserCredential; + )) as UserCredentialInternal; expect(_tokenResponse).to.eql({ idToken: 'id-token', refreshToken: 'refresh-token', diff --git a/packages-exp/auth-exp/src/core/strategies/email_and_password.ts b/packages-exp/auth-exp/src/core/strategies/email_and_password.ts index 5d8134673ff..ec4c0f6fc15 100644 --- a/packages-exp/auth-exp/src/core/strategies/email_and_password.ts +++ b/packages-exp/auth-exp/src/core/strategies/email_and_password.ts @@ -15,60 +15,129 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { + ActionCodeInfo, + ActionCodeOperation, + ActionCodeSettings, + Auth, + OperationType, + UserCredential +} from '../../model/public_types'; import * as account from '../../api/account_management/email_and_password'; import * as authentication from '../../api/authentication/email_and_password'; import { signUp } from '../../api/authentication/sign_up'; -import { MultiFactorInfo } from '../../mfa/mfa_info'; +import { MultiFactorInfoImpl } from '../../mfa/mfa_info'; import { EmailAuthProvider } from '../providers/email'; import { UserCredentialImpl } from '../user/user_credential_impl'; -import { assert } from '../util/assert'; -import { setActionCodeSettingsOnRequest } from './action_code_settings'; +import { _assert } from '../util/assert'; +import { _setActionCodeSettingsOnRequest } from './action_code_settings'; import { signInWithCredential } from './credential'; import { _castAuth } from '../auth/auth_impl'; import { AuthErrorCode } from '../errors'; +import { getModularInstance } from '@firebase/util'; +/** + * Sends a password reset email to the given email address. + * + * @remarks + * To complete the password reset, call {@link confirmPasswordReset} with the code supplied in + * the email sent to the user, along with the new password specified by the user. + * + * @example + * ```javascript + * const 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 + * }; + * await sendPasswordResetEmail(auth, 'user@example.com', actionCodeSettings); + * // Obtain code from user. + * await confirmPasswordReset('user@example.com', code); + * ``` + * + * @param auth - The Auth instance. + * @param email - The user's email address. + * @param actionCodeSettings - The {@link ActionCodeSettings}. + * + * @public + */ export async function sendPasswordResetEmail( - auth: externs.Auth, + auth: Auth, email: string, - actionCodeSettings?: externs.ActionCodeSettings + actionCodeSettings?: ActionCodeSettings ): Promise { + const authModular = getModularInstance(auth); const request: authentication.PasswordResetRequest = { - requestType: externs.Operation.PASSWORD_RESET, + requestType: ActionCodeOperation.PASSWORD_RESET, email }; if (actionCodeSettings) { - setActionCodeSettingsOnRequest(request, actionCodeSettings); + _setActionCodeSettingsOnRequest(authModular, request, actionCodeSettings); } - await authentication.sendPasswordResetEmail(auth, request); + await authentication.sendPasswordResetEmail(authModular, request); } +/** + * Completes the password reset process, given a confirmation code and new password. + * + * @param auth - The Auth instance. + * @param oobCode - A confirmation code sent to the user. + * @param newPassword - The new password. + * + * @public + */ export async function confirmPasswordReset( - auth: externs.Auth, + auth: Auth, oobCode: string, newPassword: string ): Promise { - await account.resetPassword(auth, { + await account.resetPassword(getModularInstance(auth), { oobCode, newPassword }); // Do not return the email. } +/** + * Applies a verification code sent to the user by email or other out-of-band mechanism. + * + * @param auth - The Auth instance. + * @param oobCode - A verification code sent to the user. + * + * @public + */ export async function applyActionCode( - auth: externs.Auth, + auth: Auth, oobCode: string ): Promise { - await account.applyActionCode(auth, { oobCode }); + await account.applyActionCode(getModularInstance(auth), { oobCode }); } +/** + * Checks a verification code sent to the user by email or other out-of-band mechanism. + * + * @returns metadata about the code. + * + * @param auth - The Auth instance. + * @param oobCode - A verification code sent to the user. + * + * @public + */ export async function checkActionCode( - auth: externs.Auth, + auth: Auth, oobCode: string -): Promise { - const response = await account.resetPassword(auth, { oobCode }); +): Promise { + const authModular = getModularInstance(auth); + const response = await account.resetPassword(authModular, { oobCode }); // Email could be empty only if the request type is EMAIL_SIGNIN or // VERIFY_AND_CHANGE_EMAIL. @@ -77,31 +146,25 @@ export async function checkActionCode( // Multi-factor info could not be empty if the request type is // REVERT_SECOND_FACTOR_ADDITION. const operation = response.requestType; - assert(operation, AuthErrorCode.INTERNAL_ERROR, { appName: auth.name }); + _assert(operation, authModular, AuthErrorCode.INTERNAL_ERROR); switch (operation) { - case externs.Operation.EMAIL_SIGNIN: + case ActionCodeOperation.EMAIL_SIGNIN: break; - case externs.Operation.VERIFY_AND_CHANGE_EMAIL: - assert(response.newEmail, AuthErrorCode.INTERNAL_ERROR, { - appName: auth.name - }); + case ActionCodeOperation.VERIFY_AND_CHANGE_EMAIL: + _assert(response.newEmail, authModular, AuthErrorCode.INTERNAL_ERROR); break; - case externs.Operation.REVERT_SECOND_FACTOR_ADDITION: - assert(response.mfaInfo, AuthErrorCode.INTERNAL_ERROR, { - appName: auth.name - }); + case ActionCodeOperation.REVERT_SECOND_FACTOR_ADDITION: + _assert(response.mfaInfo, authModular, AuthErrorCode.INTERNAL_ERROR); // fall through default: - assert(response.email, AuthErrorCode.INTERNAL_ERROR, { - appName: auth.name - }); + _assert(response.email, authModular, AuthErrorCode.INTERNAL_ERROR); } // The multi-factor info for revert second factor addition - let multiFactorInfo: MultiFactorInfo | null = null; + let multiFactorInfo: MultiFactorInfoImpl | null = null; if (response.mfaInfo) { - multiFactorInfo = MultiFactorInfo._fromServerResponse( - _castAuth(auth), + multiFactorInfo = MultiFactorInfoImpl._fromServerResponse( + _castAuth(authModular), response.mfaInfo ); } @@ -109,11 +172,11 @@ export async function checkActionCode( return { data: { email: - (response.requestType === externs.Operation.VERIFY_AND_CHANGE_EMAIL + (response.requestType === ActionCodeOperation.VERIFY_AND_CHANGE_EMAIL ? response.newEmail : response.email) || null, previousEmail: - (response.requestType === externs.Operation.VERIFY_AND_CHANGE_EMAIL + (response.requestType === ActionCodeOperation.VERIFY_AND_CHANGE_EMAIL ? response.email : response.newEmail) || null, multiFactorInfo @@ -122,43 +185,87 @@ export async function checkActionCode( }; } +/** + * Checks a password reset code sent to the user by email or other out-of-band mechanism. + * + * @returns the user's email address if valid. + * + * @param auth - The Auth instance. + * @param code - A verification code sent to the user. + * + * @public + */ export async function verifyPasswordResetCode( - auth: externs.Auth, + auth: Auth, code: string ): Promise { - const { data } = await checkActionCode(auth, code); + const { data } = await checkActionCode(getModularInstance(auth), code); // Email should always be present since a code was sent to it return data.email!; } +/** + * Creates a new user account associated with the specified email address and password. + * + * @remarks + * On successful creation of the user account, this user will also be signed in to your application. + * + * User account creation can fail if the account already exists or the password is invalid. + * + * Note: The email address acts as a unique identifier for the user and enables an email-based + * password reset. This function will create a new user account and set the initial user password. + * + * @param auth - The Auth instance. + * @param email - The user's email address. + * @param password - The user's chosen password. + * + * @public + */ export async function createUserWithEmailAndPassword( - auth: externs.Auth, + auth: Auth, email: string, password: string -): Promise { - const response = await signUp(auth, { +): Promise { + const authInternal = _castAuth(auth); + const response = await signUp(authInternal, { returnSecureToken: true, email, password }); const userCredential = await UserCredentialImpl._fromIdTokenResponse( - _castAuth(auth), - externs.OperationType.SIGN_IN, + authInternal, + OperationType.SIGN_IN, response ); - await auth.updateCurrentUser(userCredential.user); + await authInternal._updateCurrentUser(userCredential.user); return userCredential; } +/** + * Asynchronously signs in using an email and password. + * + * @remarks + * Fails with an error if the email address and password do not match. + * + * Note: The user's password is NOT the password used to access the user's email account. The + * email address serves as a unique identifier for the user, and the password is used to access + * the user's account in your Firebase project. See also: {@link createUserWithEmailAndPassword}. + * + * @param auth - The Auth instance. + * @param email - The users email address. + * @param password - The users password. + * + * @public + */ export function signInWithEmailAndPassword( - auth: externs.Auth, + auth: Auth, email: string, password: string -): Promise { +): Promise { return signInWithCredential( - auth, + getModularInstance(auth), EmailAuthProvider.credential(email, password) ); } diff --git a/packages-exp/auth-exp/src/core/strategies/email_link.test.ts b/packages-exp/auth-exp/src/core/strategies/email_link.test.ts index 0f8907456fc..e158aa648c6 100644 --- a/packages-exp/auth-exp/src/core/strategies/email_link.test.ts +++ b/packages-exp/auth-exp/src/core/strategies/email_link.test.ts @@ -19,8 +19,7 @@ import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import * as sinonChai from 'sinon-chai'; -import * as externs from '@firebase/auth-types-exp'; -import { OperationType } from '@firebase/auth-types-exp'; +import { ActionCodeOperation, OperationType } from '../../model/public_types'; import { FirebaseError } from '@firebase/util'; import { mockEndpoint } from '../../../test/helpers/api/helper'; @@ -29,7 +28,7 @@ import * as mockFetch from '../../../test/helpers/mock_fetch'; import { Endpoint } from '../../api'; import { APIUserInfo } from '../../api/account_management/account'; import { ServerError } from '../../api/errors'; -import { UserCredential } from '../../model/user'; +import { UserCredentialInternal } from '../../model/user'; import { isSignInWithEmailLink, sendSignInLinkToEmail, @@ -55,13 +54,27 @@ describe('core/strategies/sendSignInLinkToEmail', () => { const mock = mockEndpoint(Endpoint.SEND_OOB_CODE, { email }); - await sendSignInLinkToEmail(auth, email); + await sendSignInLinkToEmail(auth, email, { + handleCodeInApp: true, + url: 'continue-url' + }); expect(mock.calls[0].request).to.eql({ - requestType: externs.Operation.EMAIL_SIGNIN, - email + requestType: ActionCodeOperation.EMAIL_SIGNIN, + email, + canHandleCodeInApp: true, + continueUrl: 'continue-url' }); }); + it('should require handleCodeInApp to be true', async () => { + await expect( + sendSignInLinkToEmail(auth, email, { + handleCodeInApp: false, + url: 'continue-url' + }) + ).to.be.rejectedWith(FirebaseError, 'auth/argument-error).'); + }); + it('should surface errors', async () => { const mock = mockEndpoint( Endpoint.SEND_OOB_CODE, @@ -73,7 +86,12 @@ describe('core/strategies/sendSignInLinkToEmail', () => { }, 400 ); - await expect(sendSignInLinkToEmail(auth, email)).to.be.rejectedWith( + await expect( + sendSignInLinkToEmail(auth, email, { + handleCodeInApp: true, + url: 'continue-url' + }) + ).to.be.rejectedWith( FirebaseError, 'Firebase: The email address is badly formatted. (auth/invalid-email).' ); @@ -95,7 +113,7 @@ describe('core/strategies/sendSignInLinkToEmail', () => { }); expect(mock.calls[0].request).to.eql({ - requestType: externs.Operation.EMAIL_SIGNIN, + requestType: ActionCodeOperation.EMAIL_SIGNIN, email, continueUrl: 'my-url', dynamicLinkDomain: 'fdl-domain', @@ -121,7 +139,7 @@ describe('core/strategies/sendSignInLinkToEmail', () => { dynamicLinkDomain: 'fdl-domain' }); expect(mock.calls[0].request).to.eql({ - requestType: externs.Operation.EMAIL_SIGNIN, + requestType: ActionCodeOperation.EMAIL_SIGNIN, email, continueUrl: 'my-url', dynamicLinkDomain: 'fdl-domain', @@ -231,7 +249,7 @@ describe('core/strategies/email_and_password/signInWithEmailLink', () => { auth, 'some-email', actionLink - )) as UserCredential; + )) as UserCredentialInternal; expect(_tokenResponse).to.eql({ idToken: 'id-token', refreshToken: 'refresh-token', diff --git a/packages-exp/auth-exp/src/core/strategies/email_link.ts b/packages-exp/auth-exp/src/core/strategies/email_link.ts index fe5e397bd50..31707296ef2 100644 --- a/packages-exp/auth-exp/src/core/strategies/email_link.ts +++ b/packages-exp/auth-exp/src/core/strategies/email_link.ts @@ -15,56 +15,149 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { + ActionCodeOperation, + ActionCodeSettings, + Auth, + UserCredential +} from '../../model/public_types'; import * as api from '../../api/authentication/email_and_password'; import { ActionCodeURL } from '../action_code_url'; import { EmailAuthProvider } from '../providers/email'; import { _getCurrentUrl } from '../util/location'; -import { setActionCodeSettingsOnRequest } from './action_code_settings'; +import { _setActionCodeSettingsOnRequest } from './action_code_settings'; import { signInWithCredential } from './credential'; import { AuthErrorCode } from '../errors'; -import { assert } from '../util/assert'; +import { _assert } from '../util/assert'; +import { getModularInstance } from '@firebase/util'; +/** + * Sends a sign-in email link to the user with the specified email. + * + * @remarks + * The sign-in operation has to always be completed in the app unlike other out of band email + * actions (password reset and email verifications). This is because, at the end of the flow, + * the user is expected to be signed in and their Auth state persisted within the app. + * + * To complete sign in with the email link, call {@link signInWithEmailLink} with the email + * address and the email link supplied in the email sent to the user. + * + * @example + * ```javascript + * const 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 + * }; + * await sendSignInLinkToEmail(auth, 'user@example.com', actionCodeSettings); + * // Obtain emailLink from the user. + * if(isSignInWithEmailLink(auth, emailLink)) { + * await signInWithEmailLink('user@example.com', 'user@example.com', emailLink); + * } + * ``` + * + * @param authInternal - The Auth instance. + * @param email - The user's email address. + * @param actionCodeSettings - The {@link ActionCodeSettings}. + * + * @public + */ export async function sendSignInLinkToEmail( - auth: externs.Auth, + auth: Auth, email: string, - actionCodeSettings?: externs.ActionCodeSettings + actionCodeSettings: ActionCodeSettings ): Promise { + const authModular = getModularInstance(auth); const request: api.EmailSignInRequest = { - requestType: externs.Operation.EMAIL_SIGNIN, + requestType: ActionCodeOperation.EMAIL_SIGNIN, email }; + _assert( + actionCodeSettings.handleCodeInApp, + authModular, + AuthErrorCode.ARGUMENT_ERROR + ); if (actionCodeSettings) { - setActionCodeSettingsOnRequest(request, actionCodeSettings); + _setActionCodeSettingsOnRequest(authModular, request, actionCodeSettings); } - await api.sendSignInLinkToEmail(auth, request); + await api.sendSignInLinkToEmail(authModular, request); } -export function isSignInWithEmailLink( - auth: externs.Auth, - emailLink: string -): boolean { +/** + * Checks if an incoming link is a sign-in with email link suitable for {@link signInWithEmailLink}. + * + * @param auth - The Auth instance. + * @param emailLink - The link sent to the user's email address. + * + * @public + */ +export function isSignInWithEmailLink(auth: Auth, emailLink: string): boolean { const actionCodeUrl = ActionCodeURL.parseLink(emailLink); - return actionCodeUrl?.operation === externs.Operation.EMAIL_SIGNIN; + return actionCodeUrl?.operation === ActionCodeOperation.EMAIL_SIGNIN; } +/** + * Asynchronously signs in using an email and sign-in email link. + * + * @remarks + * If no link is passed, the link is inferred from the current URL. + * + * Fails with an error if the email address is invalid or OTP in email link expires. + * + * Note: Confirm the link is a sign-in email link before calling this method firebase.auth.Auth.isSignInWithEmailLink. + * + * @example + * ```javascript + * const 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 + * }; + * await sendSignInLinkToEmail(auth, 'user@example.com', actionCodeSettings); + * // Obtain emailLink from the user. + * if(isSignInWithEmailLink(auth, emailLink)) { + * await signInWithEmailLink('user@example.com', 'user@example.com', emailLink); + * } + * ``` + * + * @param auth - The Auth instance. + * @param email - The user's email address. + * @param emailLink - The link sent to the user's email address. + * + * @public + */ export async function signInWithEmailLink( - auth: externs.Auth, + auth: Auth, email: string, emailLink?: string -): Promise { +): Promise { + const authModular = getModularInstance(auth); const credential = EmailAuthProvider.credentialWithLink( email, emailLink || _getCurrentUrl() ); // Check if the tenant ID in the email link matches the tenant ID on Auth // instance. - assert( - credential.tenantId === (auth.tenantId || null), - AuthErrorCode.TENANT_ID_MISMATCH, - { appName: auth.name } + _assert( + credential._tenantId === (authModular.tenantId || null), + authModular, + AuthErrorCode.TENANT_ID_MISMATCH ); - return signInWithCredential(auth, credential); + return signInWithCredential(authModular, credential); } diff --git a/packages-exp/auth-exp/src/core/strategies/idp.test.ts b/packages-exp/auth-exp/src/core/strategies/idp.test.ts index 3a44fbf9f66..87e0c4ac303 100644 --- a/packages-exp/auth-exp/src/core/strategies/idp.test.ts +++ b/packages-exp/auth-exp/src/core/strategies/idp.test.ts @@ -15,10 +15,12 @@ * limitations under the License. */ +import * as sinon from 'sinon'; +import * as sinonChai from 'sinon-chai'; import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; -import { OperationType } from '@firebase/auth-types-exp'; +import { OperationType } from '../../model/public_types'; import { mockEndpoint } from '../../../test/helpers/api/helper'; import { TEST_ID_TOKEN_RESPONSE } from '../../../test/helpers/id_token_response'; @@ -26,14 +28,20 @@ import { makeJWT } from '../../../test/helpers/jwt'; import { testAuth, testUser, TestAuth } from '../../../test/helpers/mock_auth'; import * as fetch from '../../../test/helpers/mock_fetch'; import { Endpoint } from '../../api'; -import { User } from '../../model/user'; +import { UserInternal } from '../../model/user'; +import * as reauthenticate from '../../core/user/reauthenticate'; +import * as linkUnlink from '../../core/user/link_unlink'; +import * as credential from '../../core/strategies/credential'; + import * as idpTasks from './idp'; +import { UserCredentialImpl } from '../user/user_credential_impl'; use(chaiAsPromised); +use(sinonChai); -describe('src/core/strategies/idb', () => { +describe('core/strategies/idb', () => { let auth: TestAuth; - let user: User; + let user: UserInternal; let signInEndpoint: fetch.Route; beforeEach(async () => { @@ -54,6 +62,7 @@ describe('src/core/strategies/idb', () => { afterEach(() => { fetch.tearDown(); + sinon.restore(); }); describe('signIn', () => { @@ -73,7 +82,8 @@ describe('src/core/strategies/idb', () => { postBody: 'post-body', tenantId: 'tenant-id', pendingToken: 'pending-token', - returnSecureToken: true + returnSecureToken: true, + returnIdpCredential: true }); }); @@ -90,6 +100,23 @@ describe('src/core/strategies/idb', () => { expect(userCred.operationType).to.eq(OperationType.SIGN_IN); expect(userCred.user.uid).to.eq('uid'); }); + + it('passes through the bypassAuthState flag', async () => { + const stub = sinon + .stub(credential, '_signInWithCredential') + .returns(Promise.resolve(({} as unknown) as UserCredentialImpl)); + await idpTasks._signIn({ + auth, + user, + requestUri: 'request-uri', + sessionId: 'session-id', + tenantId: 'tenant-id', + pendingToken: 'pending-token', + postBody: 'post-body', + bypassAuthState: true + }); + expect(stub.getCall(0).lastArg).to.be.true; + }); }); describe('reauth', () => { @@ -110,7 +137,8 @@ describe('src/core/strategies/idb', () => { postBody: 'post-body', tenantId: 'tenant-id', pendingToken: 'pending-token', - returnSecureToken: true + returnSecureToken: true, + returnIdpCredential: true }); }); @@ -128,6 +156,23 @@ describe('src/core/strategies/idb', () => { expect(userCred.operationType).to.eq(OperationType.REAUTHENTICATE); expect(userCred.user.uid).to.eq('uid'); }); + + it('passes through the bypassAuthState flag', async () => { + const stub = sinon + .stub(reauthenticate, '_reauthenticate') + .returns(Promise.resolve(({} as unknown) as UserCredentialImpl)); + await idpTasks._reauth({ + auth, + user, + requestUri: 'request-uri', + sessionId: 'session-id', + tenantId: 'tenant-id', + pendingToken: 'pending-token', + postBody: 'post-body', + bypassAuthState: true + }); + expect(stub.getCall(0).lastArg).to.be.true; + }); }); describe('link', () => { @@ -150,6 +195,7 @@ describe('src/core/strategies/idb', () => { tenantId: 'tenant-id', pendingToken: 'pending-token', returnSecureToken: true, + returnIdpCredential: true, idToken: idTokenBeforeLink }); }); @@ -168,5 +214,22 @@ describe('src/core/strategies/idb', () => { expect(userCred.operationType).to.eq(OperationType.LINK); expect(userCred.user.uid).to.eq('uid'); }); + + it('passes through the bypassAuthState flag', async () => { + const stub = sinon + .stub(linkUnlink, '_link') + .returns(Promise.resolve(({} as unknown) as UserCredentialImpl)); + await idpTasks._link({ + auth, + user, + requestUri: 'request-uri', + sessionId: 'session-id', + tenantId: 'tenant-id', + pendingToken: 'pending-token', + postBody: 'post-body', + bypassAuthState: true + }); + expect(stub.getCall(0).lastArg).to.be.true; + }); }); }); diff --git a/packages-exp/auth-exp/src/core/strategies/idp.ts b/packages-exp/auth-exp/src/core/strategies/idp.ts index 1dc7302dcdf..677e142a99b 100644 --- a/packages-exp/auth-exp/src/core/strategies/idp.ts +++ b/packages-exp/auth-exp/src/core/strategies/idp.ts @@ -15,51 +15,54 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { ProviderId } from '../../model/public_types'; import { signInWithIdp, SignInWithIdpRequest } from '../../api/authentication/idp'; import { PhoneOrOauthTokenResponse } from '../../api/authentication/mfa'; -import { Auth } from '../../model/auth'; +import { AuthInternal } from '../../model/auth'; import { IdTokenResponse } from '../../model/id_token'; -import { User, UserCredential } from '../../model/user'; +import { UserInternal, UserCredentialInternal } from '../../model/user'; import { AuthCredential } from '../credentials'; import { _link as _linkUser } from '../user/link_unlink'; import { _reauthenticate } from '../user/reauthenticate'; -import { assert } from '../util/assert'; +import { _assert } from '../util/assert'; import { _signInWithCredential } from './credential'; import { AuthErrorCode } from '../errors'; export interface IdpTaskParams { - auth: Auth; + auth: AuthInternal; requestUri: string; sessionId?: string; tenantId?: string; postBody?: string; pendingToken?: string; - user?: User; + user?: UserInternal; + bypassAuthState?: boolean; } -export type IdpTask = (params: IdpTaskParams) => Promise; +export type IdpTask = ( + params: IdpTaskParams +) => Promise; -/** - * Private implementation, not for public export - */ class IdpCredential extends AuthCredential { constructor(readonly params: IdpTaskParams) { - super(externs.ProviderId.CUSTOM, externs.ProviderId.CUSTOM); + super(ProviderId.CUSTOM, ProviderId.CUSTOM); } - _getIdTokenResponse(auth: Auth): Promise { + _getIdTokenResponse(auth: AuthInternal): Promise { return signInWithIdp(auth, this._buildIdpRequest()); } - _linkToIdToken(auth: Auth, idToken: string): Promise { + _linkToIdToken( + auth: AuthInternal, + idToken: string + ): Promise { return signInWithIdp(auth, this._buildIdpRequest(idToken)); } - _getReauthenticationResolver(auth: Auth): Promise { + _getReauthenticationResolver(auth: AuthInternal): Promise { return signInWithIdp(auth, this._buildIdpRequest()); } @@ -67,10 +70,11 @@ class IdpCredential extends AuthCredential { const request: SignInWithIdpRequest = { requestUri: this.params.requestUri, sessionId: this.params.sessionId, - postBody: this.params.postBody || null, + postBody: this.params.postBody, tenantId: this.params.tenantId, pendingToken: this.params.pendingToken, - returnSecureToken: true + returnSecureToken: true, + returnIdpCredential: true }; if (idToken) { @@ -81,21 +85,32 @@ class IdpCredential extends AuthCredential { } } -export function _signIn(params: IdpTaskParams): Promise { +export function _signIn( + params: IdpTaskParams +): Promise { return _signInWithCredential( params.auth, - new IdpCredential(params) - ) as Promise; + new IdpCredential(params), + params.bypassAuthState + ) as Promise; } -export function _reauth(params: IdpTaskParams): Promise { +export function _reauth( + params: IdpTaskParams +): Promise { const { auth, user } = params; - assert(user, AuthErrorCode.INTERNAL_ERROR, { appName: auth.name }); - return _reauthenticate(user, new IdpCredential(params)); + _assert(user, auth, AuthErrorCode.INTERNAL_ERROR); + return _reauthenticate( + user, + new IdpCredential(params), + params.bypassAuthState + ); } -export async function _link(params: IdpTaskParams): Promise { +export async function _link( + params: IdpTaskParams +): Promise { const { auth, user } = params; - assert(user, AuthErrorCode.INTERNAL_ERROR, { appName: auth.name }); - return _linkUser(user, new IdpCredential(params)); + _assert(user, auth, AuthErrorCode.INTERNAL_ERROR); + return _linkUser(user, new IdpCredential(params), params.bypassAuthState); } diff --git a/packages-exp/auth-exp/src/core/strategies/redirect.test.ts b/packages-exp/auth-exp/src/core/strategies/redirect.test.ts new file mode 100644 index 00000000000..e7ffb04f4d0 --- /dev/null +++ b/packages-exp/auth-exp/src/core/strategies/redirect.test.ts @@ -0,0 +1,212 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + AuthError, + OperationType, + PopupRedirectResolver, + ProviderId +} from '../../model/public_types'; +import * as sinon from 'sinon'; +import * as sinonChai from 'sinon-chai'; +import { _clearInstanceMap, _getInstance } from '../util/instantiator'; +import { + MockPersistenceLayer, + TestAuth, + testAuth, + testUser +} from '../../../test/helpers/mock_auth'; +import { makeMockPopupRedirectResolver } from '../../../test/helpers/mock_popup_redirect_resolver'; +import { AuthInternal } from '../../model/auth'; +import { AuthEventManager } from '../auth/auth_event_manager'; +import { RedirectAction, _clearRedirectOutcomes } from './redirect'; +import { + AuthEvent, + AuthEventType, + PopupRedirectResolverInternal +} from '../../model/popup_redirect'; +import { BASE_AUTH_EVENT } from '../../../test/helpers/iframe_event'; +import { UserCredentialImpl } from '../user/user_credential_impl'; +import * as idpTasks from '../strategies/idp'; +import { expect, use } from 'chai'; +import { AuthErrorCode } from '../errors'; +import { RedirectPersistence } from '../../../test/helpers/redirect_persistence'; + +use(sinonChai); + +const MATCHING_EVENT_ID = 'matching-event-id'; +const OTHER_EVENT_ID = 'wrong-id'; + +describe('core/strategies/redirect', () => { + let auth: AuthInternal; + let redirectAction: RedirectAction; + let eventManager: AuthEventManager; + let resolver: PopupRedirectResolver; + let idpStubs: sinon.SinonStubbedInstance; + let redirectPersistence: RedirectPersistence; + + beforeEach(async () => { + eventManager = new AuthEventManager(({} as unknown) as TestAuth); + idpStubs = sinon.stub(idpTasks); + resolver = makeMockPopupRedirectResolver(eventManager); + _getInstance( + resolver + )._redirectPersistence = RedirectPersistence; + auth = await testAuth(); + redirectAction = new RedirectAction(auth, _getInstance(resolver), false); + redirectPersistence = _getInstance(RedirectPersistence); + + // Default to has redirect for most test + redirectPersistence.hasPendingRedirect = true; + }); + + afterEach(() => { + sinon.restore(); + _clearRedirectOutcomes(); + _clearInstanceMap(); + }); + + function iframeEvent(event: Partial): void { + // Push the dispatch out of the synchronous flow + setTimeout(() => { + eventManager.onEvent({ + ...BASE_AUTH_EVENT, + eventId: MATCHING_EVENT_ID, + ...event + }); + }, 1); + } + + async function reInitAuthWithRedirectUser(eventId: string): Promise { + const mainPersistence = new MockPersistenceLayer(); + const oldAuth = await testAuth(); + const user = testUser(oldAuth, 'uid'); + user._redirectEventId = eventId; + redirectPersistence.redirectUser = user.toJSON(); + sinon.stub(mainPersistence, '_get').returns(Promise.resolve(user.toJSON())); + + auth = await testAuth(resolver, mainPersistence); + redirectAction = new RedirectAction(auth, _getInstance(resolver), true); + } + + it('completes with the cred', async () => { + const cred = new UserCredentialImpl({ + user: testUser(auth, 'uid'), + providerId: ProviderId.GOOGLE, + operationType: OperationType.SIGN_IN + }); + idpStubs._signIn.returns(Promise.resolve(cred)); + const promise = redirectAction.execute(); + iframeEvent({ + type: AuthEventType.SIGN_IN_VIA_REDIRECT + }); + expect(await promise).to.eq(cred); + }); + + it('returns the same value if called multiple times', async () => { + const cred = new UserCredentialImpl({ + user: testUser(auth, 'uid'), + providerId: ProviderId.GOOGLE, + operationType: OperationType.SIGN_IN + }); + idpStubs._signIn.returns(Promise.resolve(cred)); + const promise = redirectAction.execute(); + iframeEvent({ + type: AuthEventType.SIGN_IN_VIA_REDIRECT + }); + expect(await promise).to.eq(cred); + expect(await redirectAction.execute()).to.eq(cred); + }); + + it('interacts with redirectUser loading from auth object', async () => { + // We need to re-initialize auth since it pulls the redirect user at + // auth load + await reInitAuthWithRedirectUser(MATCHING_EVENT_ID); + + const cred = new UserCredentialImpl({ + user: testUser(auth, 'uid'), + providerId: ProviderId.GOOGLE, + operationType: OperationType.LINK + }); + idpStubs._link.returns(Promise.resolve(cred)); + const promise = redirectAction.execute(); + iframeEvent({ + type: AuthEventType.LINK_VIA_REDIRECT + }); + expect(await promise).to.eq(cred); + }); + + it('returns null if the event id mismatches', async () => { + // We need to re-initialize auth since it pulls the redirect user at + // auth load + await reInitAuthWithRedirectUser(OTHER_EVENT_ID); + + const cred = new UserCredentialImpl({ + user: testUser(auth, 'uid'), + providerId: ProviderId.GOOGLE, + operationType: OperationType.LINK + }); + idpStubs._link.returns(Promise.resolve(cred)); + const promise = redirectAction.execute(); + iframeEvent({ + type: AuthEventType.LINK_VIA_REDIRECT + }); + expect(await promise).to.be.null; + }); + + it('returns null if there is no pending redirect', async () => { + const promise = redirectAction.execute(); + iframeEvent({ + type: AuthEventType.UNKNOWN, + error: { + code: `auth/${AuthErrorCode.NO_AUTH_EVENT}` + } as AuthError + }); + expect(await promise).to.be.null; + }); + + it('works with reauthenticate', async () => { + await reInitAuthWithRedirectUser(MATCHING_EVENT_ID); + + const cred = new UserCredentialImpl({ + user: testUser(auth, 'uid'), + providerId: ProviderId.GOOGLE, + operationType: OperationType.REAUTHENTICATE + }); + idpStubs._reauth.returns(Promise.resolve(cred)); + const promise = redirectAction.execute(); + iframeEvent({ + type: AuthEventType.REAUTH_VIA_REDIRECT + }); + expect(await promise).to.eq(cred); + expect(await redirectAction.execute()).to.eq(cred); + }); + + it('bypasses initialization if no key set', async () => { + await reInitAuthWithRedirectUser(MATCHING_EVENT_ID); + const resolverInstance = _getInstance( + resolver + ); + + sinon.spy(resolverInstance, '_initialize'); + redirectPersistence.hasPendingRedirect = false; + + expect(await redirectAction.execute()).to.eq(null); + expect(await redirectAction.execute()).to.eq(null); + expect(resolverInstance._initialize).not.to.have.been.called; + }); +}); diff --git a/packages-exp/auth-exp/src/core/strategies/redirect.ts b/packages-exp/auth-exp/src/core/strategies/redirect.ts new file mode 100644 index 00000000000..0794d6576dc --- /dev/null +++ b/packages-exp/auth-exp/src/core/strategies/redirect.ts @@ -0,0 +1,144 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AuthInternal } from '../../model/auth'; +import { + AuthEvent, + AuthEventType, + PopupRedirectResolverInternal +} from '../../model/popup_redirect'; +import { UserCredentialInternal } from '../../model/user'; +import { PersistenceInternal } from '../persistence'; +import { _persistenceKeyName } from '../persistence/persistence_user_manager'; +import { _getInstance } from '../util/instantiator'; +import { AbstractPopupRedirectOperation } from './abstract_popup_redirect_operation'; + +const PENDING_REDIRECT_KEY = 'pendingRedirect'; + +// We only get one redirect outcome for any one auth, so just store it +// in here. +const redirectOutcomeMap: Map< + string, + () => Promise +> = new Map(); + +export class RedirectAction extends AbstractPopupRedirectOperation { + eventId = null; + + constructor( + auth: AuthInternal, + resolver: PopupRedirectResolverInternal, + bypassAuthState = false + ) { + super( + auth, + [ + AuthEventType.SIGN_IN_VIA_REDIRECT, + AuthEventType.LINK_VIA_REDIRECT, + AuthEventType.REAUTH_VIA_REDIRECT, + AuthEventType.UNKNOWN + ], + resolver, + undefined, + bypassAuthState + ); + } + + /** + * Override the execute function; if we already have a redirect result, then + * just return it. + */ + async execute(): Promise { + let readyOutcome = redirectOutcomeMap.get(this.auth._key()); + if (!readyOutcome) { + try { + const hasPendingRedirect = await _getAndClearPendingRedirectStatus( + this.resolver, + this.auth + ); + const result = hasPendingRedirect ? await super.execute() : null; + readyOutcome = () => Promise.resolve(result); + } catch (e) { + readyOutcome = () => Promise.reject(e); + } + + redirectOutcomeMap.set(this.auth._key(), readyOutcome); + } + + return readyOutcome(); + } + + async onAuthEvent(event: AuthEvent): Promise { + if (event.type === AuthEventType.SIGN_IN_VIA_REDIRECT) { + return super.onAuthEvent(event); + } else if (event.type === AuthEventType.UNKNOWN) { + // This is a sentinel value indicating there's no pending redirect + this.resolve(null); + return; + } + + if (event.eventId) { + const user = await this.auth._redirectUserForId(event.eventId); + if (user) { + this.user = user; + return super.onAuthEvent(event); + } else { + this.resolve(null); + } + } + } + + async onExecution(): Promise {} + + cleanUp(): void {} +} + +export async function _getAndClearPendingRedirectStatus( + resolver: PopupRedirectResolverInternal, + auth: AuthInternal +): Promise { + const key = pendingRedirectKey(auth); + const hasPendingRedirect = + (await resolverPersistence(resolver)._get(key)) === 'true'; + await resolverPersistence(resolver)._remove(key); + return hasPendingRedirect; +} + +export async function _setPendingRedirectStatus( + resolver: PopupRedirectResolverInternal, + auth: AuthInternal +): Promise { + return resolverPersistence(resolver)._set(pendingRedirectKey(auth), 'true'); +} + +export function _clearRedirectOutcomes(): void { + redirectOutcomeMap.clear(); +} + +function resolverPersistence( + resolver: PopupRedirectResolverInternal +): PersistenceInternal { + return _getInstance(resolver._redirectPersistence); +} + +function pendingRedirectKey(auth: AuthInternal): string { + return _persistenceKeyName( + PENDING_REDIRECT_KEY, + auth.config.apiKey, + auth.name + ); +} diff --git a/packages-exp/auth-exp/src/core/user/account_info.test.ts b/packages-exp/auth-exp/src/core/user/account_info.test.ts index b7180e73700..7c33f05de79 100644 --- a/packages-exp/auth-exp/src/core/user/account_info.test.ts +++ b/packages-exp/auth-exp/src/core/user/account_info.test.ts @@ -20,13 +20,13 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as sinon from 'sinon'; import * as sinonChai from 'sinon-chai'; -import { ProviderId, UserInfo } from '@firebase/auth-types-exp'; +import { ProviderId, UserInfo } from '../../model/public_types'; import { mockEndpoint } from '../../../test/helpers/api/helper'; import { TestAuth, testAuth, testUser } from '../../../test/helpers/mock_auth'; import * as fetch from '../../../test/helpers/mock_fetch'; import { Endpoint } from '../../api'; -import { User } from '../../model/user'; +import { UserInternal } from '../../model/user'; import { updateEmail, updatePassword, updateProfile } from './account_info'; use(chaiAsPromised); @@ -42,7 +42,7 @@ const PASSWORD_PROVIDER: UserInfo = { }; describe('core/user/profile', () => { - let user: User; + let user: UserInternal; let auth: TestAuth; beforeEach(async () => { @@ -73,7 +73,8 @@ describe('core/user/profile', () => { expect(ep.calls[0].request).to.eql({ idToken: 'access-token', displayName: 'displayname', - photoUrl: 'photo' + photoUrl: 'photo', + returnSecureToken: true }); }); @@ -118,7 +119,8 @@ describe('core/user/profile', () => { await updateEmail(user, 'hello@test.com'); expect(set.calls[0].request).to.eql({ idToken: 'access-token', - email: 'hello@test.com' + email: 'hello@test.com', + returnSecureToken: true }); expect(user.uid).to.eq('new-uid-to-prove-refresh-got-called'); @@ -135,7 +137,8 @@ describe('core/user/profile', () => { await updatePassword(user, 'pass'); expect(set.calls[0].request).to.eql({ idToken: 'access-token', - password: 'pass' + password: 'pass', + returnSecureToken: true }); expect(user.uid).to.eq('new-uid-to-prove-refresh-got-called'); @@ -150,7 +153,7 @@ describe('core/user/profile', () => { auth.onIdTokenChanged(idTokenChange); // Flush token change promises which are floating - await auth.updateCurrentUser(user); + await auth._updateCurrentUser(user); auth._isInitialized = true; idTokenChange.resetHistory(); }); diff --git a/packages-exp/auth-exp/src/core/user/account_info.ts b/packages-exp/auth-exp/src/core/user/account_info.ts index 74abf49c2cf..2d1c4f5fc53 100644 --- a/packages-exp/auth-exp/src/core/user/account_info.ts +++ b/packages-exp/auth-exp/src/core/user/account_info.ts @@ -15,77 +15,120 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { ProviderId, User } from '../../model/public_types'; import { updateEmailPassword as apiUpdateEmailPassword, UpdateEmailPasswordRequest } from '../../api/account_management/email_and_password'; import { updateProfile as apiUpdateProfile } from '../../api/account_management/profile'; -import { User } from '../../model/user'; +import { UserInternal } from '../../model/user'; import { _logoutIfInvalidated } from './invalidation'; -import { _reloadWithoutSaving } from './reload'; - -interface Profile { - displayName?: string | null; - photoURL?: string | null; -} +import { getModularInstance } from '@firebase/util'; +/** + * Updates a user's profile data. + * + * @param user - The user. + * @param profile - The profile's `displayName` and `photoURL` to update. + * + * @public + */ export async function updateProfile( - externUser: externs.User, - { displayName, photoURL: photoUrl }: Profile + user: User, + { + displayName, + photoURL: photoUrl + }: { displayName?: string | null; photoURL?: string | null } ): Promise { if (displayName === undefined && photoUrl === undefined) { return; } - const user = externUser as User; - const idToken = await user.getIdToken(); - const profileRequest = { idToken, displayName, photoUrl }; + const userInternal = getModularInstance(user) as UserInternal; + const idToken = await userInternal.getIdToken(); + const profileRequest = { + idToken, + displayName, + photoUrl, + returnSecureToken: true + }; const response = await _logoutIfInvalidated( - user, - apiUpdateProfile(user.auth, profileRequest) + userInternal, + apiUpdateProfile(userInternal.auth, profileRequest) ); - user.displayName = response.displayName || null; - user.photoURL = response.photoUrl || null; + userInternal.displayName = response.displayName || null; + userInternal.photoURL = response.photoUrl || null; // Update the password provider as well - const passwordProvider = user.providerData.find( - ({ providerId }) => providerId === externs.ProviderId.PASSWORD + const passwordProvider = userInternal.providerData.find( + ({ providerId }) => providerId === ProviderId.PASSWORD ); if (passwordProvider) { - passwordProvider.displayName = user.displayName; - passwordProvider.photoURL = user.photoURL; + passwordProvider.displayName = userInternal.displayName; + passwordProvider.photoURL = userInternal.photoURL; } - await user._updateTokensIfNecessary(response); + await userInternal._updateTokensIfNecessary(response); } -export function updateEmail( - externUser: externs.User, - newEmail: string -): Promise { - const user = externUser as User; - return updateEmailOrPassword(user, newEmail, null); +/** + * Updates the user's email address. + * + * @remarks + * An email will be sent to the original email address (if it was set) that allows to revoke the + * email address change, in order to protect them from account hijacking. + * + * Important: this is a security sensitive operation that requires the user to have recently signed + * in. If this requirement isn't met, ask the user to authenticate again and then call + * {@link reauthenticateWithCredential}. + * + * @param user - The user. + * @param newEmail - The new email address. + * + * @public + */ +export function updateEmail(user: User, newEmail: string): Promise { + return updateEmailOrPassword( + getModularInstance(user) as UserInternal, + newEmail, + null + ); } -export function updatePassword( - externUser: externs.User, - newPassword: string -): Promise { - const user = externUser as User; - return updateEmailOrPassword(user, null, newPassword); +/** + * Updates the user's password. + * + * @remarks + * Important: this is a security sensitive operation that requires the user to have recently signed + * in. If this requirement isn't met, ask the user to authenticate again and then call + * {@link reauthenticateWithCredential}. + * + * @param user - The user. + * @param newPassword - The new password. + * + * @public + */ +export function updatePassword(user: User, newPassword: string): Promise { + return updateEmailOrPassword( + getModularInstance(user) as UserInternal, + null, + newPassword + ); } async function updateEmailOrPassword( - user: User, + user: UserInternal, email: string | null, password: string | null ): Promise { const { auth } = user; const idToken = await user.getIdToken(); - const request: UpdateEmailPasswordRequest = { idToken }; + const request: UpdateEmailPasswordRequest = { + idToken, + returnSecureToken: true + }; if (email) { request.email = email; diff --git a/packages-exp/auth-exp/src/core/user/additional_user_info.test.ts b/packages-exp/auth-exp/src/core/user/additional_user_info.test.ts index 0f98bb5f9e2..34e314bf4c1 100644 --- a/packages-exp/auth-exp/src/core/user/additional_user_info.test.ts +++ b/packages-exp/auth-exp/src/core/user/additional_user_info.test.ts @@ -17,23 +17,30 @@ import { expect } from 'chai'; -import { ProviderId, UserProfile } from '@firebase/auth-types-exp'; +import { OperationType, ProviderId } from '../../model/public_types'; import { IdTokenResponse, IdTokenResponseKind } from '../../model/id_token'; -import { _fromIdTokenResponse } from './additional_user_info'; +import { + _fromIdTokenResponse, + getAdditionalUserInfo +} from './additional_user_info'; import { base64Encode } from '@firebase/util'; +import { UserCredentialImpl } from './user_credential_impl'; +import { AuthInternal } from '../../model/auth'; +import { UserInternal, UserCredentialInternal } from '../../model/user'; +import { testAuth, testUser } from '../../../test/helpers/mock_auth'; +import { makeJWT } from '../../../test/helpers/jwt'; describe('core/user/additional_user_info', () => { + const userProfileWithLogin: Record = { + login: 'scott', + friends: [], + netWorth: 5.0 + }; + const rawUserInfoWithLogin = JSON.stringify(userProfileWithLogin); + const userProfileNoLogin: Record = { sample: 'data' }; + const rawUserInfoNoLogin = JSON.stringify(userProfileNoLogin); describe('_fromIdTokenResponse', () => { - const userProfileWithLogin: UserProfile = { - login: 'scott', - friends: [], - netWorth: 5.0 - }; - const rawUserInfoWithLogin = JSON.stringify(userProfileWithLogin); - const userProfileNoLogin: UserProfile = { sample: 'data' }; - const rawUserInfoNoLogin = JSON.stringify(userProfileNoLogin); - describe('parses federated IDP response tokens', () => { it('for FacebookAdditionalUserInfo', () => { const idResponse = idTokenResponse({ @@ -148,8 +155,12 @@ describe('core/user/additional_user_info', () => { describe('creates generic AdditionalUserInfo', () => { it('for custom auth', () => { const idResponse = idTokenResponse({ - providerId: ProviderId.CUSTOM, - rawUserInfo: rawUserInfoWithLogin + rawUserInfo: rawUserInfoWithLogin, + idToken: makeJWT({ + firebase: { + 'sign_in_provider': 'custom' + } + }) }); const { isNewUser, @@ -165,8 +176,12 @@ describe('core/user/additional_user_info', () => { it('for anonymous auth', () => { const idResponse = idTokenResponse({ - providerId: ProviderId.ANONYMOUS, - rawUserInfo: rawUserInfoWithLogin + rawUserInfo: rawUserInfoWithLogin, + idToken: makeJWT({ + firebase: { + 'sign_in_provider': 'anonymous' + } + }) }); const { isNewUser, @@ -211,6 +226,70 @@ describe('core/user/additional_user_info', () => { }); }); }); + + describe('getAdditionalUserInfo()', () => { + let auth: AuthInternal; + let user: UserInternal; + let cred: UserCredentialInternal; + beforeEach(async () => { + auth = await testAuth(); + user = testUser(auth, 'uid'); + cred = new UserCredentialImpl({ + user, + providerId: null, + operationType: OperationType.SIGN_IN + }); + }); + + it('calls through to _fromIdTokenResponse', () => { + cred._tokenResponse = idTokenResponse({ + providerId: ProviderId.ANONYMOUS, + rawUserInfo: rawUserInfoWithLogin + }); + const { + isNewUser, + providerId, + username, + profile + } = getAdditionalUserInfo(cred)!; + expect(isNewUser).to.be.false; + expect(providerId).to.be.null; + expect(username).to.be.undefined; + expect(profile).to.eq(profile); + }); + + it('calls through to _fromIdTokenResponse preserving isNewUser', () => { + cred._tokenResponse = idTokenResponse({ + providerId: ProviderId.ANONYMOUS, + rawUserInfo: rawUserInfoWithLogin, + isNewUser: true + }); + const { + isNewUser, + providerId, + username, + profile + } = getAdditionalUserInfo(cred)!; + expect(isNewUser).to.be.true; + expect(providerId).to.be.null; + expect(username).to.be.undefined; + expect(profile).to.eq(profile); + }); + + it('returns bespoke info if existing anonymous user', () => { + // Note that _tokenResponse is not set on cred + ((user as unknown) as Record).isAnonymous = true; + const { isNewUser, providerId, profile } = getAdditionalUserInfo(cred)!; + expect(isNewUser).to.be.false; + expect(providerId).to.be.null; + expect(profile).to.eq(profile); + }); + + it('returns null if not anonymous', () => { + // Note that _tokenResponse is not set on cred + expect(getAdditionalUserInfo(cred)).to.be.null; + }); + }); }); function idTokenResponse(partial: Partial): IdTokenResponse { diff --git a/packages-exp/auth-exp/src/core/user/additional_user_info.ts b/packages-exp/auth-exp/src/core/user/additional_user_info.ts index 0e61f7c2a67..06321190b3c 100644 --- a/packages-exp/auth-exp/src/core/user/additional_user_info.ts +++ b/packages-exp/auth-exp/src/core/user/additional_user_info.ts @@ -15,17 +15,22 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { + AdditionalUserInfo, + ProviderId, + UserCredential +} from '../../model/public_types'; import { IdTokenResponse, IdTokenResponseKind } from '../../model/id_token'; import { _parseToken } from './id_token_result'; -import { UserCredential } from '../../model/user'; +import { UserCredentialInternal } from '../../model/user'; /** * Parse the `AdditionalUserInfo` from the ID token response. + * */ export function _fromIdTokenResponse( idTokenResponse?: IdTokenResponse -): externs.AdditionalUserInfo | null { +): AdditionalUserInfo | null { if (!idTokenResponse) { return null; } @@ -42,9 +47,9 @@ export function _fromIdTokenResponse( ]; if (signInProvider) { const filteredProviderId = - providerId !== externs.ProviderId.ANONYMOUS && - providerId !== externs.ProviderId.CUSTOM - ? (signInProvider as externs.ProviderId) + signInProvider !== ProviderId.ANONYMOUS && + signInProvider !== ProviderId.CUSTOM + ? (signInProvider as ProviderId) : null; // Uses generic class in accordance with the legacy SDK. return new GenericAdditionalUserInfo(isNewUser, filteredProviderId); @@ -54,39 +59,39 @@ export function _fromIdTokenResponse( return null; } switch (providerId) { - case externs.ProviderId.FACEBOOK: + case ProviderId.FACEBOOK: return new FacebookAdditionalUserInfo(isNewUser, profile); - case externs.ProviderId.GITHUB: + case ProviderId.GITHUB: return new GithubAdditionalUserInfo(isNewUser, profile); - case externs.ProviderId.GOOGLE: + case ProviderId.GOOGLE: return new GoogleAdditionalUserInfo(isNewUser, profile); - case externs.ProviderId.TWITTER: + case ProviderId.TWITTER: return new TwitterAdditionalUserInfo( isNewUser, profile, idTokenResponse.screenName || null ); - case externs.ProviderId.CUSTOM: - case externs.ProviderId.ANONYMOUS: + case ProviderId.CUSTOM: + case ProviderId.ANONYMOUS: return new GenericAdditionalUserInfo(isNewUser, null); default: return new GenericAdditionalUserInfo(isNewUser, providerId, profile); } } -class GenericAdditionalUserInfo implements externs.AdditionalUserInfo { +class GenericAdditionalUserInfo implements AdditionalUserInfo { constructor( readonly isNewUser: boolean, - readonly providerId: externs.ProviderId | null, - readonly profile: externs.UserProfile = {} + readonly providerId: ProviderId | string | null, + readonly profile: Record = {} ) {} } class FederatedAdditionalUserInfoWithUsername extends GenericAdditionalUserInfo { constructor( isNewUser: boolean, - providerId: externs.ProviderId, - profile: externs.UserProfile, + providerId: ProviderId, + profile: Record, readonly username: string | null ) { super(isNewUser, providerId, profile); @@ -94,16 +99,16 @@ class FederatedAdditionalUserInfoWithUsername extends GenericAdditionalUserInfo } class FacebookAdditionalUserInfo extends GenericAdditionalUserInfo { - constructor(isNewUser: boolean, profile: externs.UserProfile) { - super(isNewUser, externs.ProviderId.FACEBOOK, profile); + constructor(isNewUser: boolean, profile: Record) { + super(isNewUser, ProviderId.FACEBOOK, profile); } } class GithubAdditionalUserInfo extends FederatedAdditionalUserInfoWithUsername { - constructor(isNewUser: boolean, profile: externs.UserProfile) { + constructor(isNewUser: boolean, profile: Record) { super( isNewUser, - externs.ProviderId.GITHUB, + ProviderId.GITHUB, profile, typeof profile?.login === 'string' ? profile?.login : null ); @@ -111,25 +116,41 @@ class GithubAdditionalUserInfo extends FederatedAdditionalUserInfoWithUsername { } class GoogleAdditionalUserInfo extends GenericAdditionalUserInfo { - constructor(isNewUser: boolean, profile: externs.UserProfile) { - super(isNewUser, externs.ProviderId.GOOGLE, profile); + constructor(isNewUser: boolean, profile: Record) { + super(isNewUser, ProviderId.GOOGLE, profile); } } class TwitterAdditionalUserInfo extends FederatedAdditionalUserInfoWithUsername { constructor( isNewUser: boolean, - profile: externs.UserProfile, + profile: Record, screenName: string | null ) { - super(isNewUser, externs.ProviderId.TWITTER, profile, screenName); + super(isNewUser, ProviderId.TWITTER, profile, screenName); } } +/** + * Extracts provider specific {@link AdditionalUserInfo} for the given credential. + * + * @param userCredential - The user credential. + * + * @public + */ export function getAdditionalUserInfo( - userCredential: externs.UserCredential -): externs.AdditionalUserInfo | null { - return _fromIdTokenResponse( - (userCredential as UserCredential)._tokenResponse - ); + userCredential: UserCredential +): AdditionalUserInfo | null { + const { user, _tokenResponse } = userCredential as UserCredentialInternal; + if (user.isAnonymous && !_tokenResponse) { + // Handle the special case where signInAnonymously() gets called twice. + // No network call is made so there's nothing to actually fill this in + return { + providerId: null, + isNewUser: false, + profile: null + }; + } + + return _fromIdTokenResponse(_tokenResponse); } diff --git a/packages-exp/auth-exp/src/core/user/id_token_result.test.ts b/packages-exp/auth-exp/src/core/user/id_token_result.test.ts index 240e0a4e364..82e5f70079b 100644 --- a/packages-exp/auth-exp/src/core/user/id_token_result.test.ts +++ b/packages-exp/auth-exp/src/core/user/id_token_result.test.ts @@ -19,12 +19,12 @@ import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import * as sinon from 'sinon'; -import { ProviderId } from '@firebase/auth-types-exp'; +import { ProviderId } from '../../model/public_types'; import { FirebaseError } from '@firebase/util'; import { makeJWT } from '../../../test/helpers/jwt'; import { testAuth, testUser } from '../../../test/helpers/mock_auth'; -import { User } from '../../model/user'; +import { UserInternal } from '../../model/user'; import { getIdTokenResult } from './id_token_result'; use(chaiAsPromised); @@ -33,8 +33,8 @@ const MAY_1 = new Date('May 1, 2020'); const MAY_2 = new Date('May 2, 2020'); const MAY_3 = new Date('May 3, 2020'); -describe('/core/user/id_token_result', () => { - let user: User; +describe('core/user/id_token_result', () => { + let user: UserInternal; beforeEach(async () => { user = testUser(await testAuth(), 'uid'); diff --git a/packages-exp/auth-exp/src/core/user/id_token_result.ts b/packages-exp/auth-exp/src/core/user/id_token_result.ts index aa672881081..1fa77f75324 100644 --- a/packages-exp/auth-exp/src/core/user/id_token_result.ts +++ b/packages-exp/auth-exp/src/core/user/id_token_result.ts @@ -15,34 +15,55 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; -import { base64Decode } from '@firebase/util'; +import { IdTokenResult, ParsedToken, User } from '../../model/public_types'; +import { base64Decode, getModularInstance } from '@firebase/util'; -import { User } from '../../model/user'; -import { assert } from '../util/assert'; +import { UserInternal } from '../../model/user'; +import { _assert } from '../util/assert'; import { _logError } from '../util/log'; import { utcTimestampToDateString } from '../util/time'; import { AuthErrorCode } from '../errors'; -export function getIdToken( - user: externs.User, - forceRefresh = false -): Promise { - return user.getIdToken(forceRefresh); +/** + * Returns a JSON Web Token (JWT) used to identify the user to a Firebase service. + * + * @remarks + * Returns the current token if it has not expired or if it will not expire in the next five + * minutes. Otherwise, this will refresh the token and return a new one. + * + * @param user - The user. + * @param forceRefresh - Force refresh regardless of token expiration. + * + * @public + */ +export function getIdToken(user: User, forceRefresh = false): Promise { + return getModularInstance(user).getIdToken(forceRefresh); } +/** + * Returns a deserialized JSON Web Token (JWT) used to identitfy the user to a Firebase service. + * + * @remarks + * Returns the current token if it has not expired or if it will not expire in the next five + * minutes. Otherwise, this will refresh the token and return a new one. + * + * @param user - The user. + * @param forceRefresh - Force refresh regardless of token expiration. + * + * @public + */ export async function getIdTokenResult( - externUser: externs.User, + user: User, forceRefresh = false -): Promise { - const user = externUser as User; - const token = await user.getIdToken(forceRefresh); +): Promise { + const userInternal = getModularInstance(user) as UserInternal; + const token = await userInternal.getIdToken(forceRefresh); const claims = _parseToken(token); - assert( + _assert( claims && claims.exp && claims.auth_time && claims.iat, - AuthErrorCode.INTERNAL_ERROR, - { appName: user.auth.name } + userInternal.auth, + AuthErrorCode.INTERNAL_ERROR ); const firebase = typeof claims.firebase === 'object' ? claims.firebase : undefined; @@ -70,7 +91,7 @@ function secondsStringToMilliseconds(seconds: string): number { return Number(seconds) * 1000; } -export function _parseToken(token: string): externs.ParsedToken | null { +export function _parseToken(token: string): ParsedToken | null { const [algorithm, payload, signature] = token.split('.'); if ( algorithm === undefined || @@ -93,3 +114,14 @@ export function _parseToken(token: string): externs.ParsedToken | null { return null; } } + +/** + * Extract expiresIn TTL from a token by subtracting the expiration from the issuance. + */ +export function _tokenExpiresIn(token: string): number { + const parsedToken = _parseToken(token); + _assert(parsedToken, AuthErrorCode.INTERNAL_ERROR); + _assert(typeof parsedToken.exp !== 'undefined', AuthErrorCode.INTERNAL_ERROR); + _assert(typeof parsedToken.iat !== 'undefined', AuthErrorCode.INTERNAL_ERROR); + return Number(parsedToken.exp) - Number(parsedToken.iat); +} diff --git a/packages-exp/auth-exp/src/core/user/invalidation.test.ts b/packages-exp/auth-exp/src/core/user/invalidation.test.ts index 2505f47628d..9d596bed448 100644 --- a/packages-exp/auth-exp/src/core/user/invalidation.test.ts +++ b/packages-exp/auth-exp/src/core/user/invalidation.test.ts @@ -21,25 +21,26 @@ import * as chaiAsPromised from 'chai-as-promised'; import { FirebaseError } from '@firebase/util'; import { testAuth, testUser } from '../../../test/helpers/mock_auth'; -import { Auth } from '../../model/auth'; -import { User } from '../../model/user'; -import { AUTH_ERROR_FACTORY, AuthErrorCode } from '../errors'; +import { AuthInternal } from '../../model/auth'; +import { UserInternal } from '../../model/user'; +import { AuthErrorCode } from '../errors'; import { _logoutIfInvalidated } from './invalidation'; +import { _createError } from '../util/assert'; use(chaiAsPromised); -describe('src/core/user/invalidation', () => { - let user: User; - let auth: Auth; +describe('core/user/invalidation', () => { + let user: UserInternal; + let auth: AuthInternal; beforeEach(async () => { auth = await testAuth(); user = testUser(auth, 'uid'); - await auth.updateCurrentUser(user); + await auth._updateCurrentUser(user); }); function makeError(code: AuthErrorCode): FirebaseError { - return AUTH_ERROR_FACTORY.create(code, { appName: auth.name }); + return _createError(auth, code); } it('leaves non-invalidation errors alone', async () => { @@ -63,6 +64,14 @@ describe('src/core/user/invalidation', () => { expect(auth.currentUser).to.be.null; }); + it('does not log out if bypass auth state is true', async () => { + const error = makeError(AuthErrorCode.USER_DISABLED); + try { + await _logoutIfInvalidated(user, Promise.reject(error), true); + } catch {} + expect(auth.currentUser).to.eq(user); + }); + it('logs out the user if the error is token_expired', async () => { const error = makeError(AuthErrorCode.TOKEN_EXPIRED); await expect( @@ -72,11 +81,11 @@ describe('src/core/user/invalidation', () => { }); context('with another logged in user', () => { - let user2: User; + let user2: UserInternal; beforeEach(async () => { user2 = testUser(auth, 'uid2'); - await auth.updateCurrentUser(user2); + await auth._updateCurrentUser(user2); }); it('does not log out user2 if the error is user_disabled', async () => { diff --git a/packages-exp/auth-exp/src/core/user/invalidation.ts b/packages-exp/auth-exp/src/core/user/invalidation.ts index 74d29dab455..718e800b2aa 100644 --- a/packages-exp/auth-exp/src/core/user/invalidation.ts +++ b/packages-exp/auth-exp/src/core/user/invalidation.ts @@ -17,13 +17,17 @@ import { FirebaseError } from '@firebase/util'; -import { User } from '../../model/user'; +import { UserInternal } from '../../model/user'; import { AuthErrorCode } from '../errors'; export async function _logoutIfInvalidated( - user: User, - promise: Promise + user: UserInternal, + promise: Promise, + bypassAuthState = false ): Promise { + if (bypassAuthState) { + return promise; + } try { return await promise; } catch (e) { diff --git a/packages-exp/auth-exp/src/core/user/link_unlink.test.ts b/packages-exp/auth-exp/src/core/user/link_unlink.test.ts index 45a80ce583a..a2bb6d271da 100644 --- a/packages-exp/auth-exp/src/core/user/link_unlink.test.ts +++ b/packages-exp/auth-exp/src/core/user/link_unlink.test.ts @@ -18,7 +18,7 @@ import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; -import { ProviderId } from '@firebase/auth-types-exp'; +import { ProviderId } from '../../model/public_types'; import { FirebaseError } from '@firebase/util'; import { mockEndpoint } from '../../../test/helpers/api/helper'; @@ -26,20 +26,20 @@ import { testAuth, TestAuth, testUser } from '../../../test/helpers/mock_auth'; import * as fetch from '../../../test/helpers/mock_fetch'; import { Endpoint } from '../../api'; import { APIUserInfo } from '../../api/account_management/account'; -import { User } from '../../model/user'; +import { UserInternal } from '../../model/user'; import { _assertLinkedStatus, unlink } from './link_unlink'; use(chaiAsPromised); describe('core/user/link_unlink', () => { - let user: User; + let user: UserInternal; let auth: TestAuth; let getAccountInfoEndpoint: fetch.Route; beforeEach(async () => { auth = await testAuth(); user = testUser(auth, 'uid', '', true); - await auth.updateCurrentUser(user); + await auth._updateCurrentUser(user); fetch.setUp(); }); diff --git a/packages-exp/auth-exp/src/core/user/link_unlink.ts b/packages-exp/auth-exp/src/core/user/link_unlink.ts index 3683f1a3f6f..756aa654015 100644 --- a/packages-exp/auth-exp/src/core/user/link_unlink.ts +++ b/packages-exp/auth-exp/src/core/user/link_unlink.ts @@ -15,67 +15,64 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { OperationType, ProviderId, User } from '../../model/public_types'; import { deleteLinkedAccounts } from '../../api/account_management/account'; -import { _processCredentialSavingMfaContextIfNecessary } from '../../mfa/mfa_error'; -import { User, UserCredential } from '../../model/user'; +import { UserInternal, UserCredentialInternal } from '../../model/user'; import { AuthCredential } from '../credentials'; import { AuthErrorCode } from '../errors'; -import { assert } from '../util/assert'; +import { _assert } from '../util/assert'; import { providerDataAsNames } from '../util/providers'; import { _logoutIfInvalidated } from './invalidation'; import { _reloadWithoutSaving } from './reload'; import { UserCredentialImpl } from './user_credential_impl'; +import { getModularInstance } from '@firebase/util'; /** - * This is the externally visible unlink function + * Unlinks a provider from a user account. + * + * @param user - The user. + * @param providerId - The provider to unlink. + * + * @public */ -export async function unlink( - userExtern: externs.User, - providerId: externs.ProviderId -): Promise { - const user = userExtern as User; - await _assertLinkedStatus(true, user, providerId); - const { providerUserInfo } = await deleteLinkedAccounts(user.auth, { - idToken: await user.getIdToken(), +export async function unlink(user: User, providerId: string): Promise { + const userInternal = getModularInstance(user) as UserInternal; + await _assertLinkedStatus(true, userInternal, providerId); + const { providerUserInfo } = await deleteLinkedAccounts(userInternal.auth, { + idToken: await userInternal.getIdToken(), deleteProvider: [providerId] }); const providersLeft = providerDataAsNames(providerUserInfo || []); - user.providerData = user.providerData.filter(pd => + userInternal.providerData = userInternal.providerData.filter(pd => providersLeft.has(pd.providerId) ); - if (!providersLeft.has(externs.ProviderId.PHONE)) { - user.phoneNumber = null; + if (!providersLeft.has(ProviderId.PHONE)) { + userInternal.phoneNumber = null; } - await user.auth._persistUserIfCurrent(user); - return user; + await userInternal.auth._persistUserIfCurrent(userInternal); + return userInternal; } -/** - * Internal-only link helper - */ export async function _link( - user: User, - credential: AuthCredential -): Promise { + user: UserInternal, + credential: AuthCredential, + bypassAuthState = false +): Promise { const response = await _logoutIfInvalidated( user, - credential._linkToIdToken(user.auth, await user.getIdToken()) - ); - return UserCredentialImpl._forOperation( - user, - externs.OperationType.LINK, - response + credential._linkToIdToken(user.auth, await user.getIdToken()), + bypassAuthState ); + return UserCredentialImpl._forOperation(user, OperationType.LINK, response); } export async function _assertLinkedStatus( expected: boolean, - user: User, + user: UserInternal, provider: string ): Promise { await _reloadWithoutSaving(user); @@ -85,7 +82,5 @@ export async function _assertLinkedStatus( expected === false ? AuthErrorCode.PROVIDER_ALREADY_LINKED : AuthErrorCode.NO_SUCH_PROVIDER; - assert(providerIds.has(provider) === expected, code, { - appName: user.auth.name - }); + _assert(providerIds.has(provider) === expected, user.auth, code); } diff --git a/packages-exp/auth-exp/src/core/user/proactive_refresh.test.ts b/packages-exp/auth-exp/src/core/user/proactive_refresh.test.ts index 474cfa195f1..abdb0f9ca8c 100644 --- a/packages-exp/auth-exp/src/core/user/proactive_refresh.test.ts +++ b/packages-exp/auth-exp/src/core/user/proactive_refresh.test.ts @@ -21,19 +21,16 @@ import * as sinon from 'sinon'; import * as sinonChai from 'sinon-chai'; import { testAuth, testUser } from '../../../test/helpers/mock_auth'; -import { User } from '../../model/user'; -import { AUTH_ERROR_FACTORY, AuthErrorCode } from '../errors'; -import { - _OFFSET_DURATION, - _RETRY_BACKOFF_MIN, - ProactiveRefresh -} from './proactive_refresh'; +import { UserInternal } from '../../model/user'; +import { AuthErrorCode } from '../errors'; +import { _createError } from '../util/assert'; +import { Duration, ProactiveRefresh } from './proactive_refresh'; use(chaiAsPromised); use(sinonChai); -describe('src/core/user/proactive_refresh', () => { - let user: User; +describe('core/user/proactive_refresh', () => { + let user: UserInternal; let proactiveRefresh: ProactiveRefresh; let getTokenStub: sinon.SinonStub; let clock: sinon.SinonFakeTimers; @@ -41,7 +38,7 @@ describe('src/core/user/proactive_refresh', () => { // Sets the expiration time in accordance with the offset in proactive refresh // This translates to the interval between updates function setExpirationTime(offset: number): void { - user.stsTokenManager.expirationTime = _OFFSET_DURATION + offset; + user.stsTokenManager.expirationTime = Duration.OFFSET + offset; } // clock.nextAsync() returns the number of milliseconds since the unix epoch. @@ -104,10 +101,9 @@ describe('src/core/user/proactive_refresh', () => { }); context('error backoff', () => { - const error = AUTH_ERROR_FACTORY.create( - AuthErrorCode.NETWORK_REQUEST_FAILED, - { appName: 'app' } - ); + const error = _createError(AuthErrorCode.NETWORK_REQUEST_FAILED, { + appName: 'app' + }); beforeEach(() => { getTokenStub.callsFake(() => Promise.reject(error)); }); @@ -116,41 +112,41 @@ describe('src/core/user/proactive_refresh', () => { setExpirationTime(1000); proactiveRefresh._start(); expect(await nextAsync()).to.eq(1000); - expect(await nextAsync()).to.eq(_RETRY_BACKOFF_MIN); + expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN); }); it('backoff continues to increase until the max', async () => { setExpirationTime(1000); proactiveRefresh._start(); expect(await nextAsync()).to.eq(1000); - expect(await nextAsync()).to.eq(_RETRY_BACKOFF_MIN); - expect(await nextAsync()).to.eq(_RETRY_BACKOFF_MIN * 2); - expect(await nextAsync()).to.eq(_RETRY_BACKOFF_MIN * 4); - expect(await nextAsync()).to.eq(_RETRY_BACKOFF_MIN * 8); - expect(await nextAsync()).to.eq(_RETRY_BACKOFF_MIN * 16); - expect(await nextAsync()).to.eq(_RETRY_BACKOFF_MIN * 32); - expect(await nextAsync()).to.eq(_RETRY_BACKOFF_MIN * 32); - expect(await nextAsync()).to.eq(_RETRY_BACKOFF_MIN * 32); + expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN); + expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN * 2); + expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN * 4); + expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN * 8); + expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN * 16); + expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN * 32); + expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN * 32); + expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN * 32); }); it('backoff resets after one success', async () => { setExpirationTime(1000); proactiveRefresh._start(); expect(await nextAsync()).to.eq(1000); - expect(await nextAsync()).to.eq(_RETRY_BACKOFF_MIN); - expect(await nextAsync()).to.eq(_RETRY_BACKOFF_MIN * 2); - expect(await nextAsync()).to.eq(_RETRY_BACKOFF_MIN * 4); - expect(await nextAsync()).to.eq(_RETRY_BACKOFF_MIN * 8); - expect(await nextAsync()).to.eq(_RETRY_BACKOFF_MIN * 16); - setExpirationTime(1000 + Date.now() + _RETRY_BACKOFF_MIN * 32); + expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN); + expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN * 2); + expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN * 4); + expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN * 8); + expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN * 16); + setExpirationTime(1000 + Date.now() + Duration.RETRY_BACKOFF_MIN * 32); getTokenStub.callsFake(() => Promise.resolve()); - expect(await nextAsync()).to.eq(_RETRY_BACKOFF_MIN * 32); + expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN * 32); expect(await nextAsync()).to.eq(1000); getTokenStub.callsFake(() => Promise.reject(error)); await nextAsync(); - expect(await nextAsync()).to.eq(_RETRY_BACKOFF_MIN); - expect(await nextAsync()).to.eq(_RETRY_BACKOFF_MIN * 2); - expect(await nextAsync()).to.eq(_RETRY_BACKOFF_MIN * 4); + expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN); + expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN * 2); + expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN * 4); }); }); }); diff --git a/packages-exp/auth-exp/src/core/user/proactive_refresh.ts b/packages-exp/auth-exp/src/core/user/proactive_refresh.ts index a94d3d8b8a4..1ba9a3efe2f 100644 --- a/packages-exp/auth-exp/src/core/user/proactive_refresh.ts +++ b/packages-exp/auth-exp/src/core/user/proactive_refresh.ts @@ -15,14 +15,15 @@ * limitations under the License. */ -import { User } from '../../model/user'; +import { UserInternal } from '../../model/user'; import { AuthErrorCode } from '../errors'; // Refresh the token five minutes before expiration -export const _OFFSET_DURATION = 5 * 1000 * 60; - -export const _RETRY_BACKOFF_MIN = 30 * 1000; -export const _RETRY_BACKOFF_MAX = 16 * 60 * 1000; +export const enum Duration { + OFFSET = 5 * 1000 * 60, + RETRY_BACKOFF_MIN = 30 * 1000, + RETRY_BACKOFF_MAX = 16 * 60 * 1000 +} export class ProactiveRefresh { private isRunning = false; @@ -32,9 +33,9 @@ export class ProactiveRefresh { // we can't cast properly in both environments. // eslint-disable-next-line @typescript-eslint/no-explicit-any private timerId: any | null = null; - private errorBackoff = _RETRY_BACKOFF_MIN; + private errorBackoff = Duration.RETRY_BACKOFF_MIN; - constructor(private readonly user: User) {} + constructor(private readonly user: UserInternal) {} _start(): void { if (this.isRunning) { @@ -59,13 +60,16 @@ export class ProactiveRefresh { private getInterval(wasError: boolean): number { if (wasError) { const interval = this.errorBackoff; - this.errorBackoff = Math.min(this.errorBackoff * 2, _RETRY_BACKOFF_MAX); + this.errorBackoff = Math.min( + this.errorBackoff * 2, + Duration.RETRY_BACKOFF_MAX + ); return interval; } else { // Reset the error backoff - this.errorBackoff = _RETRY_BACKOFF_MIN; + this.errorBackoff = Duration.RETRY_BACKOFF_MIN; const expTime = this.user.stsTokenManager.expirationTime ?? 0; - const interval = expTime - Date.now() - _OFFSET_DURATION; + const interval = expTime - Date.now() - Duration.OFFSET; return Math.max(0, interval); } diff --git a/packages-exp/auth-exp/src/core/user/reauthenticate.test.ts b/packages-exp/auth-exp/src/core/user/reauthenticate.test.ts index 89494090cf0..aaeb1423d4e 100644 --- a/packages-exp/auth-exp/src/core/user/reauthenticate.test.ts +++ b/packages-exp/auth-exp/src/core/user/reauthenticate.test.ts @@ -23,7 +23,7 @@ import { OperationType, ProviderId, SignInMethod -} from '@firebase/auth-types-exp'; +} from '../../model/public_types'; import { FirebaseError } from '@firebase/util'; import { mockEndpoint } from '../../../test/helpers/api/helper'; @@ -36,16 +36,17 @@ import { Endpoint } from '../../api'; import { IdTokenMfaResponse } from '../../api/authentication/mfa'; import { MultiFactorError } from '../../mfa/mfa_error'; import { IdTokenResponse } from '../../model/id_token'; -import { User, UserCredential } from '../../model/user'; +import { UserInternal, UserCredentialInternal } from '../../model/user'; import { AuthCredential } from '../credentials'; -import { AUTH_ERROR_FACTORY, AuthErrorCode } from '../errors'; +import { AuthErrorCode } from '../errors'; import { _reauthenticate } from './reauthenticate'; +import { _createError } from '../util/assert'; use(chaiAsPromised); -describe('src/core/user/reauthenticate', () => { +describe('core/user/reauthenticate', () => { let credential: AuthCredential; - let user: User; + let user: UserInternal; beforeEach(async () => { fetch.setUp(); @@ -105,7 +106,7 @@ describe('src/core/user/reauthenticate', () => { it('should switch a user deleted error to a mismatch error', async () => { stub(credential, '_getReauthenticationResolver').returns( Promise.reject( - AUTH_ERROR_FACTORY.create(AuthErrorCode.USER_DELETED, { + _createError(AuthErrorCode.USER_DELETED, { appName: '' }) ) @@ -120,7 +121,7 @@ describe('src/core/user/reauthenticate', () => { it('should not switch other errors to a mismatch error', async () => { stub(credential, '_getReauthenticationResolver').returns( Promise.reject( - AUTH_ERROR_FACTORY.create(AuthErrorCode.NETWORK_REQUEST_FAILED, { + _createError(AuthErrorCode.NETWORK_REQUEST_FAILED, { appName: '' }) ) @@ -128,7 +129,7 @@ describe('src/core/user/reauthenticate', () => { await expect(_reauthenticate(user, credential)).to.be.rejectedWith( FirebaseError, - 'Firebase: A network AuthError (such as timeout, interrupted connection or unreachable host) has occurred. (auth/network-request-failed).' + 'auth/network-request-failed' ); }); @@ -146,8 +147,7 @@ describe('src/core/user/reauthenticate', () => { }; stub(credential, '_getReauthenticationResolver').returns( Promise.reject( - AUTH_ERROR_FACTORY.create(AuthErrorCode.MFA_REQUIRED, { - appName: user.auth.name, + _createError(user.auth, AuthErrorCode.MFA_REQUIRED, { serverResponse }) ) @@ -155,7 +155,6 @@ describe('src/core/user/reauthenticate', () => { const error = await expect( _reauthenticate(user, credential) ).to.be.rejectedWith(MultiFactorError); - expect(error.credential).to.eq(credential); expect(error.operationType).to.eq(OperationType.REAUTHENTICATE); expect(error.serverResponse).to.eql(serverResponse); expect(error.user).to.eq(user); @@ -174,7 +173,10 @@ describe('src/core/user/reauthenticate', () => { users: [{ localId: 'uid' }] }); - const cred = (await _reauthenticate(user, credential)) as UserCredential; + const cred = (await _reauthenticate( + user, + credential + )) as UserCredentialInternal; expect(cred.operationType).to.eq(OperationType.REAUTHENTICATE); expect(cred._tokenResponse).to.eq(response); diff --git a/packages-exp/auth-exp/src/core/user/reauthenticate.ts b/packages-exp/auth-exp/src/core/user/reauthenticate.ts index 55982a749fd..71b901a03f9 100644 --- a/packages-exp/auth-exp/src/core/user/reauthenticate.ts +++ b/packages-exp/auth-exp/src/core/user/reauthenticate.ts @@ -15,46 +15,48 @@ * limitations under the License. */ -import { OperationType } from '@firebase/auth-types-exp'; +import { OperationType } from '../../model/public_types'; import { _processCredentialSavingMfaContextIfNecessary } from '../../mfa/mfa_error'; -import { User } from '../../model/user'; +import { UserInternal } from '../../model/user'; import { AuthCredential } from '../credentials'; import { AuthErrorCode } from '../errors'; -import { assert, fail } from '../util/assert'; +import { _assert, _fail } from '../util/assert'; import { _parseToken } from './id_token_result'; import { _logoutIfInvalidated } from './invalidation'; import { UserCredentialImpl } from './user_credential_impl'; export async function _reauthenticate( - user: User, - credential: AuthCredential + user: UserInternal, + credential: AuthCredential, + bypassAuthState = false ): Promise { - const appName = user.auth.name; + const { auth } = user; const operationType = OperationType.REAUTHENTICATE; try { const response = await _logoutIfInvalidated( user, _processCredentialSavingMfaContextIfNecessary( - user.auth, + auth, operationType, credential, user - ) + ), + bypassAuthState ); - assert(response.idToken, AuthErrorCode.INTERNAL_ERROR, { appName }); + _assert(response.idToken, auth, AuthErrorCode.INTERNAL_ERROR); const parsed = _parseToken(response.idToken); - assert(parsed, AuthErrorCode.INTERNAL_ERROR, { appName }); + _assert(parsed, auth, AuthErrorCode.INTERNAL_ERROR); const { sub: localId } = parsed; - assert(user.uid === localId, AuthErrorCode.USER_MISMATCH, { appName }); + _assert(user.uid === localId, auth, AuthErrorCode.USER_MISMATCH); return UserCredentialImpl._forOperation(user, operationType, response); } catch (e) { // Convert user deleted error into user mismatch if (e?.code === `auth/${AuthErrorCode.USER_DELETED}`) { - fail(AuthErrorCode.USER_MISMATCH, { appName }); + _fail(auth, AuthErrorCode.USER_MISMATCH); } throw e; } diff --git a/packages-exp/auth-exp/src/core/user/reload.test.ts b/packages-exp/auth-exp/src/core/user/reload.test.ts index f4983879b16..134847f3010 100644 --- a/packages-exp/auth-exp/src/core/user/reload.test.ts +++ b/packages-exp/auth-exp/src/core/user/reload.test.ts @@ -20,7 +20,7 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as sinon from 'sinon'; import * as sinonChai from 'sinon-chai'; -import { ProviderId, UserInfo } from '@firebase/auth-types-exp'; +import { ProviderId, UserInfo } from '../../model/public_types'; import { mockEndpoint } from '../../../test/helpers/api/helper'; import { testAuth, TestAuth, testUser } from '../../../test/helpers/mock_auth'; @@ -32,6 +32,7 @@ import { } from '../../api/account_management/account'; import { _reloadWithoutSaving, reload } from './reload'; import { UserMetadata } from './user_metadata'; +import { UserInternal } from '../../model/user'; use(chaiAsPromised); use(sinonChai); @@ -166,4 +167,55 @@ describe('core/user/reload', () => { expect(cb).to.have.been.calledWith(user); expect(auth.persistenceLayer.lastObjectSet).to.eql(user.toJSON()); }); + + context('anonymous carryover', () => { + let user: UserInternal; + beforeEach(() => { + user = testUser(auth, 'abc', '', true); + }); + function setup( + isAnonStart: boolean, + emailStart: string, + passwordHash: string, + providerData: Array<{ providerId: string }> + ): void { + // Get around readonly property + const mutUser = (user as unknown) as Record; + mutUser.isAnonymous = isAnonStart; + mutUser.email = emailStart; + + mockEndpoint(Endpoint.GET_ACCOUNT_INFO, { + users: [ + { + providerUserInfo: [...providerData], + passwordHash + } + ] + }); + } + + it('user stays not anonymous even if reload user is', async () => { + setup(false, '', '', []); // After reload the user would count as anon + await _reloadWithoutSaving(user); + expect(user.isAnonymous).to.be.false; + }); + + it('user stays anonymous if reload user is anonymous', async () => { + setup(true, '', '', []); // After reload the user would count as anon + await _reloadWithoutSaving(user); + expect(user.isAnonymous).to.be.true; + }); + + it('user becomes not anonymous if reload user is not', async () => { + setup(true, '', '', [{ providerId: 'google' }]); // After reload the user would count as anon + await _reloadWithoutSaving(user); + expect(user.isAnonymous).to.be.false; + }); + + it('user becomes not anonymous if password hash set', async () => { + setup(true, 'email', 'pass', [{ providerId: 'google' }]); // After reload the user would count as anon + await _reloadWithoutSaving(user); + expect(user.isAnonymous).to.be.false; + }); + }); }); diff --git a/packages-exp/auth-exp/src/core/user/reload.ts b/packages-exp/auth-exp/src/core/user/reload.ts index fe1a780494b..fc0a33b937a 100644 --- a/packages-exp/auth-exp/src/core/user/reload.ts +++ b/packages-exp/auth-exp/src/core/user/reload.ts @@ -15,19 +15,20 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { User, UserInfo } from '../../model/public_types'; import { getAccountInfo, ProviderUserInfo } from '../../api/account_management/account'; -import { User } from '../../model/user'; +import { UserInternal } from '../../model/user'; import { AuthErrorCode } from '../errors'; -import { assert } from '../util/assert'; +import { _assert } from '../util/assert'; import { _logoutIfInvalidated } from './invalidation'; import { UserMetadata } from './user_metadata'; +import { getModularInstance } from '@firebase/util'; -export async function _reloadWithoutSaving(user: User): Promise { +export async function _reloadWithoutSaving(user: UserInternal): Promise { const auth = user.auth; const idToken = await user.getIdToken(); const response = await _logoutIfInvalidated( @@ -35,9 +36,7 @@ export async function _reloadWithoutSaving(user: User): Promise { getAccountInfo(auth, { idToken }) ); - assert(response?.users.length, AuthErrorCode.INTERNAL_ERROR, { - appName: auth.name - }); + _assert(response?.users.length, auth, AuthErrorCode.INTERNAL_ERROR); const coreAccount = response.users[0]; @@ -46,7 +45,20 @@ export async function _reloadWithoutSaving(user: User): Promise { const newProviderData = coreAccount.providerUserInfo?.length ? extractProviderData(coreAccount.providerUserInfo) : []; - const updates: Partial = { + + const providerData = mergeProviderData(user.providerData, newProviderData); + + // Preserves the non-nonymous status of the stored user, even if no more + // credentials (federated or email/password) are linked to the user. If + // the user was previously anonymous, then use provider data to update. + // On the other hand, if it was not anonymous before, it should never be + // considered anonymous now. + const oldIsAnonymous = user.isAnonymous; + const newIsAnonymous = + !(user.email && coreAccount.passwordHash) && !providerData?.length; + const isAnonymous = !oldIsAnonymous ? false : newIsAnonymous; + + const updates: Partial = { uid: coreAccount.localId, displayName: coreAccount.displayName || null, photoURL: coreAccount.photoUrl || null, @@ -54,37 +66,43 @@ export async function _reloadWithoutSaving(user: User): Promise { emailVerified: coreAccount.emailVerified || false, phoneNumber: coreAccount.phoneNumber || null, tenantId: coreAccount.tenantId || null, - providerData: mergeProviderData(user.providerData, newProviderData), - metadata: new UserMetadata(coreAccount.createdAt, coreAccount.lastLoginAt) + providerData, + metadata: new UserMetadata(coreAccount.createdAt, coreAccount.lastLoginAt), + isAnonymous }; Object.assign(user, updates); } -export async function reload(externUser: externs.User): Promise { - const user: User = externUser as User; - await _reloadWithoutSaving(user); +/** + * Reloads user account data, if signed in. + * + * @param user - The user. + * + * @public + */ +export async function reload(user: User): Promise { + const userInternal: UserInternal = getModularInstance(user) as UserInternal; + await _reloadWithoutSaving(userInternal); // Even though the current user hasn't changed, update // current user will trigger a persistence update w/ the // new info. - await user.auth._persistUserIfCurrent(user); - user.auth._notifyListenersIfCurrent(user); + await userInternal.auth._persistUserIfCurrent(userInternal); + userInternal.auth._notifyListenersIfCurrent(userInternal); } function mergeProviderData( - original: externs.UserInfo[], - newData: externs.UserInfo[] -): externs.UserInfo[] { + original: UserInfo[], + newData: UserInfo[] +): UserInfo[] { const deduped = original.filter( o => !newData.some(n => n.providerId === o.providerId) ); return [...deduped, ...newData]; } -function extractProviderData( - providers: ProviderUserInfo[] -): externs.UserInfo[] { +function extractProviderData(providers: ProviderUserInfo[]): UserInfo[] { return providers.map(({ providerId, ...provider }) => { return { providerId, diff --git a/packages-exp/auth-exp/src/core/user/token_manager.test.ts b/packages-exp/auth-exp/src/core/user/token_manager.test.ts index e26c1823c26..8a20c2a30b6 100644 --- a/packages-exp/auth-exp/src/core/user/token_manager.test.ts +++ b/packages-exp/auth-exp/src/core/user/token_manager.test.ts @@ -23,9 +23,11 @@ import { FirebaseError } from '@firebase/util'; import { testAuth, TestAuth } from '../../../test/helpers/mock_auth'; import * as fetch from '../../../test/helpers/mock_fetch'; -import { _ENDPOINT } from '../../api/authentication/token'; +import { Endpoint } from '../../api/authentication/token'; import { IdTokenResponse } from '../../model/id_token'; -import { StsTokenManager, TOKEN_REFRESH_BUFFER_MS } from './token_manager'; +import { StsTokenManager, Buffer } from './token_manager'; +import { FinalizeMfaResponse } from '../../api/authentication/mfa'; +import { makeJWT } from '../../../test/helpers/jwt'; use(chaiAsPromised); @@ -52,12 +54,12 @@ describe('core/user/token_manager', () => { }); it('is true if exp is in future but within buffer', () => { - stsTokenManager.expirationTime = now + (TOKEN_REFRESH_BUFFER_MS - 10); + stsTokenManager.expirationTime = now + (Buffer.TOKEN_REFRESH - 10); expect(stsTokenManager.isExpired).to.eq(true); }); it('is fals if exp is far enough in future', () => { - stsTokenManager.expirationTime = now + (TOKEN_REFRESH_BUFFER_MS + 10); + stsTokenManager.expirationTime = now + (Buffer.TOKEN_REFRESH + 10); expect(stsTokenManager.isExpired).to.eq(false); }); }); @@ -74,6 +76,18 @@ describe('core/user/token_manager', () => { expect(stsTokenManager.accessToken).to.eq('id-token'); expect(stsTokenManager.refreshToken).to.eq('refresh-token'); }); + + it('falls back to exp and iat when expiresIn is ommited (ie: MFA)', () => { + const idToken = makeJWT({ 'exp': '180', 'iat': '120' }); + stsTokenManager.updateFromServerResponse({ + idToken, + refreshToken: 'refresh-token' + } as FinalizeMfaResponse); + + expect(stsTokenManager.expirationTime).to.eq(now + 60_000); + expect(stsTokenManager.accessToken).to.eq(idToken); + expect(stsTokenManager.refreshToken).to.eq('refresh-token'); + }); }); describe('#clearRefreshToken', () => { @@ -89,7 +103,7 @@ describe('core/user/token_manager', () => { let mock: fetch.Route; beforeEach(() => { const { apiKey, tokenApiHost, apiScheme } = auth.config; - const endpoint = `${apiScheme}://${tokenApiHost}${_ENDPOINT}?key=${apiKey}`; + const endpoint = `${apiScheme}://${tokenApiHost}${Endpoint.TOKEN}?key=${apiKey}`; mock = fetch.mock(endpoint, { 'access_token': 'new-access-token', 'refresh_token': 'new-refresh-token', @@ -158,9 +172,22 @@ describe('core/user/token_manager', () => { }); }); + describe('#_clone', () => { + it('copies the manager to a new object', () => { + Object.assign(stsTokenManager, { + accessToken: 'token', + refreshToken: 'refresh', + expirationTime: now + }); + + const copy = stsTokenManager._clone(); + expect(copy).not.to.eq(stsTokenManager); + expect(copy.toJSON()).to.eql(stsTokenManager.toJSON()); + }); + }); + describe('.fromJSON', () => { - const errorString = - 'Firebase: An internal AuthError has occurred. (auth/internal-error).'; + const errorString = 'auth/internal-error'; it('throws if refresh token is not a string', () => { expect(() => diff --git a/packages-exp/auth-exp/src/core/user/token_manager.ts b/packages-exp/auth-exp/src/core/user/token_manager.ts index 75c69e784a9..5f56f88afb6 100644 --- a/packages-exp/auth-exp/src/core/user/token_manager.ts +++ b/packages-exp/auth-exp/src/core/user/token_manager.ts @@ -17,18 +17,27 @@ import { FinalizeMfaResponse } from '../../api/authentication/mfa'; import { requestStsToken } from '../../api/authentication/token'; -import { Auth } from '../../model/auth'; +import { AuthInternal } from '../../model/auth'; import { IdTokenResponse } from '../../model/id_token'; import { AuthErrorCode } from '../errors'; import { PersistedBlob } from '../persistence'; -import { assert, debugFail } from '../util/assert'; +import { _assert, debugFail } from '../util/assert'; +import { _tokenExpiresIn } from './id_token_result'; /** * The number of milliseconds before the official expiration time of a token * to refresh that token, to provide a buffer for RPCs to complete. */ -export const TOKEN_REFRESH_BUFFER_MS = 30_000; +export const enum Buffer { + TOKEN_REFRESH = 30_000 +} +/** + * We need to mark this class as internal explicitly to exclude it in the public typings, because + * it references AuthInternal which has a circular dependency with UserInternal. + * + * @internal + */ export class StsTokenManager { refreshToken: string | null = null; accessToken: string | null = null; @@ -37,58 +46,79 @@ export class StsTokenManager { get isExpired(): boolean { return ( !this.expirationTime || - Date.now() > this.expirationTime - TOKEN_REFRESH_BUFFER_MS + Date.now() > this.expirationTime - Buffer.TOKEN_REFRESH ); } updateFromServerResponse( response: IdTokenResponse | FinalizeMfaResponse ): void { + _assert(response.idToken, AuthErrorCode.INTERNAL_ERROR); + _assert( + typeof response.idToken !== 'undefined', + AuthErrorCode.INTERNAL_ERROR + ); + _assert( + typeof response.refreshToken !== 'undefined', + AuthErrorCode.INTERNAL_ERROR + ); + const expiresIn = + 'expiresIn' in response && typeof response.expiresIn !== 'undefined' + ? Number(response.expiresIn) + : _tokenExpiresIn(response.idToken); this.updateTokensAndExpiration( response.idToken, response.refreshToken, - 'expiresIn' in response ? response.expiresIn : undefined + expiresIn ); } - async getToken(auth: Auth, forceRefresh = false): Promise { + async getToken( + auth: AuthInternal, + forceRefresh = false + ): Promise { + _assert( + !this.accessToken || this.refreshToken, + auth, + AuthErrorCode.TOKEN_EXPIRED + ); + if (!forceRefresh && this.accessToken && !this.isExpired) { return this.accessToken; } - if (!this.refreshToken) { - assert(!this.accessToken, AuthErrorCode.TOKEN_EXPIRED, { - appName: auth.name - }); - return null; + if (this.refreshToken) { + await this.refresh(auth, this.refreshToken!); + return this.accessToken; } - await this.refresh(auth, this.refreshToken); - return this.accessToken; + return null; } clearRefreshToken(): void { this.refreshToken = null; } - private async refresh(auth: Auth, oldToken: string): Promise { + private async refresh(auth: AuthInternal, oldToken: string): Promise { const { accessToken, refreshToken, expiresIn } = await requestStsToken( auth, oldToken ); - this.updateTokensAndExpiration(accessToken, refreshToken, expiresIn); + this.updateTokensAndExpiration( + accessToken, + refreshToken, + Number(expiresIn) + ); } private updateTokensAndExpiration( - accessToken?: string, - refreshToken?: string, - expiresInSec?: string + accessToken: string, + refreshToken: string, + expiresInSec: number ): void { this.refreshToken = refreshToken || null; this.accessToken = accessToken || null; - if (expiresInSec) { - this.expirationTime = Date.now() + Number(expiresInSec) * 1000; - } + this.expirationTime = Date.now() + expiresInSec * 1000; } static fromJSON(appName: string, object: PersistedBlob): StsTokenManager { @@ -96,21 +126,25 @@ export class StsTokenManager { const manager = new StsTokenManager(); if (refreshToken) { - assert(typeof refreshToken === 'string', AuthErrorCode.INTERNAL_ERROR, { + _assert(typeof refreshToken === 'string', AuthErrorCode.INTERNAL_ERROR, { appName }); manager.refreshToken = refreshToken; } if (accessToken) { - assert(typeof accessToken === 'string', AuthErrorCode.INTERNAL_ERROR, { + _assert(typeof accessToken === 'string', AuthErrorCode.INTERNAL_ERROR, { appName }); manager.accessToken = accessToken; } if (expirationTime) { - assert(typeof expirationTime === 'number', AuthErrorCode.INTERNAL_ERROR, { - appName - }); + _assert( + typeof expirationTime === 'number', + AuthErrorCode.INTERNAL_ERROR, + { + appName + } + ); manager.expirationTime = expirationTime; } return manager; @@ -124,12 +158,16 @@ export class StsTokenManager { }; } - _copy(stsTokenManager: StsTokenManager): void { + _assign(stsTokenManager: StsTokenManager): void { this.accessToken = stsTokenManager.accessToken; this.refreshToken = stsTokenManager.refreshToken; this.expirationTime = stsTokenManager.expirationTime; } + _clone(): StsTokenManager { + return Object.assign(new StsTokenManager(), this.toJSON()); + } + _performRefresh(): never { return debugFail('not implemented'); } diff --git a/packages-exp/auth-exp/src/core/user/user_credential_impl.test.ts b/packages-exp/auth-exp/src/core/user/user_credential_impl.test.ts index 7102393e8c3..4f864257b1c 100644 --- a/packages-exp/auth-exp/src/core/user/user_credential_impl.test.ts +++ b/packages-exp/auth-exp/src/core/user/user_credential_impl.test.ts @@ -20,7 +20,7 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as sinon from 'sinon'; import * as sinonChai from 'sinon-chai'; -import { OperationType, ProviderId } from '@firebase/auth-types-exp'; +import { OperationType, ProviderId } from '../../model/public_types'; import { mockEndpoint } from '../../../test/helpers/api/helper'; import { TEST_ID_TOKEN_RESPONSE } from '../../../test/helpers/id_token_response'; @@ -29,7 +29,7 @@ import * as mockFetch from '../../../test/helpers/mock_fetch'; import { Endpoint } from '../../api'; import { APIUserInfo } from '../../api/account_management/account'; import { IdTokenResponse, IdTokenResponseKind } from '../../model/id_token'; -import { User } from '../../model/user'; +import { UserInternal } from '../../model/user'; import { UserCredentialImpl } from './user_credential_impl'; use(chaiAsPromised); @@ -83,7 +83,7 @@ describe('core/user/user_credential_impl', () => { it('should not trigger callbacks', async () => { const cb = sinon.spy(); auth.onAuthStateChanged(cb); - await auth.updateCurrentUser(null); + await auth._updateCurrentUser(null); cb.resetHistory(); await UserCredentialImpl._fromIdTokenResponse( @@ -96,11 +96,11 @@ describe('core/user/user_credential_impl', () => { }); describe('forOperation', () => { - let user: User; + let user: UserInternal; beforeEach(async () => { user = testUser(auth, 'uid', 'email', true); - await auth.updateCurrentUser(user); + await auth._updateCurrentUser(user); }); it('gets a credential based on the response', async () => { diff --git a/packages-exp/auth-exp/src/core/user/user_credential_impl.ts b/packages-exp/auth-exp/src/core/user/user_credential_impl.ts index 4ca738b6cb2..9bea29772e8 100644 --- a/packages-exp/auth-exp/src/core/user/user_credential_impl.ts +++ b/packages-exp/auth-exp/src/core/user/user_credential_impl.ts @@ -15,27 +15,27 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { OperationType, ProviderId } from '../../model/public_types'; import { PhoneOrOauthTokenResponse } from '../../api/authentication/mfa'; import { IdTokenResponse } from '../../model/id_token'; -import { User, UserCredential } from '../../model/user'; +import { UserInternal, UserCredentialInternal } from '../../model/user'; import { UserImpl } from './user_impl'; -import { Auth } from '../../model/auth'; +import { AuthInternal } from '../../model/auth'; interface UserCredentialParams { - readonly user: User; - readonly providerId: externs.ProviderId | null; + readonly user: UserInternal; + readonly providerId: ProviderId | string | null; readonly _tokenResponse?: PhoneOrOauthTokenResponse; - readonly operationType: externs.OperationType; + readonly operationType: OperationType; } export class UserCredentialImpl - implements UserCredential, UserCredentialParams { - readonly user: User; - readonly providerId: externs.ProviderId | null; + implements UserCredentialInternal, UserCredentialParams { + readonly user: UserInternal; + readonly providerId: ProviderId | string | null; readonly _tokenResponse: PhoneOrOauthTokenResponse | undefined; - readonly operationType: externs.OperationType; + readonly operationType: OperationType; constructor(params: UserCredentialParams) { this.user = params.user; @@ -45,11 +45,11 @@ export class UserCredentialImpl } static async _fromIdTokenResponse( - auth: Auth, - operationType: externs.OperationType, + auth: AuthInternal, + operationType: OperationType, idTokenResponse: IdTokenResponse, isAnonymous: boolean = false - ): Promise { + ): Promise { const user = await UserImpl._fromIdTokenResponse( auth, idTokenResponse, @@ -66,8 +66,8 @@ export class UserCredentialImpl } static async _forOperation( - user: User, - operationType: externs.OperationType, + user: UserInternal, + operationType: OperationType, response: PhoneOrOauthTokenResponse ): Promise { await user._updateTokensIfNecessary(response, /* reload */ true); @@ -83,13 +83,13 @@ export class UserCredentialImpl function providerIdForResponse( response: IdTokenResponse -): externs.ProviderId | null { +): ProviderId | string | null { if (response.providerId) { return response.providerId; } if ('phoneNumber' in response) { - return externs.ProviderId.PHONE; + return ProviderId.PHONE; } return null; diff --git a/packages-exp/auth-exp/src/core/user/user_impl.test.ts b/packages-exp/auth-exp/src/core/user/user_impl.test.ts index c1e88d8ab43..8966cdb99f6 100644 --- a/packages-exp/auth-exp/src/core/user/user_impl.test.ts +++ b/packages-exp/auth-exp/src/core/user/user_impl.test.ts @@ -235,11 +235,40 @@ describe('core/user/user_impl', () => { it('should not trigger additional callbacks', async () => { const cb = sinon.spy(); auth.onAuthStateChanged(cb); - await auth.updateCurrentUser(null); + await auth._updateCurrentUser(null); cb.resetHistory(); await UserImpl._fromIdTokenResponse(auth, idTokenResponse); expect(cb).not.to.have.been.called; }); }); + + describe('_clone', () => { + it('copies the user to a new object', async () => { + const stsTokenManager = Object.assign(new StsTokenManager(), { + accessToken: 'access-token', + refreshToken: 'refresh-token', + expirationTime: 3 + }); + + const user = new UserImpl({ + auth, + uid: 'uid', + stsTokenManager, + displayName: 'name', + email: 'email', + phoneNumber: 'number', + photoURL: 'photo', + emailVerified: false, + isAnonymous: true + }); + + const newAuth = await testAuth(); + const copy = user._clone(newAuth); + expect(copy).not.to.eq(user); + expect(copy.stsTokenManager).not.to.eq(user.stsTokenManager); + expect(copy.toJSON()).to.eql(user.toJSON()); + expect(copy.auth).to.eq(newAuth); + }); + }); }); diff --git a/packages-exp/auth-exp/src/core/user/user_impl.ts b/packages-exp/auth-exp/src/core/user/user_impl.ts index df109c953ee..7a9aa0a8383 100644 --- a/packages-exp/auth-exp/src/core/user/user_impl.ts +++ b/packages-exp/auth-exp/src/core/user/user_impl.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { IdTokenResult, ProviderId } from '../../model/public_types'; import { NextFn } from '@firebase/util'; import { @@ -23,12 +23,16 @@ import { deleteAccount } from '../../api/account_management/account'; import { FinalizeMfaResponse } from '../../api/authentication/mfa'; -import { Auth } from '../../model/auth'; +import { AuthInternal } from '../../model/auth'; import { IdTokenResponse } from '../../model/id_token'; -import { MutableUserInfo, User, UserParameters } from '../../model/user'; +import { + MutableUserInfo, + UserInternal, + UserParameters +} from '../../model/user'; import { AuthErrorCode } from '../errors'; import { PersistedBlob } from '../persistence'; -import { assert } from '../util/assert'; +import { _assert } from '../util/assert'; import { getIdTokenResult } from './id_token_result'; import { _logoutIfInvalidated } from './invalidation'; import { ProactiveRefresh } from './proactive_refresh'; @@ -40,22 +44,22 @@ function assertStringOrUndefined( assertion: unknown, appName: string ): asserts assertion is string | undefined { - assert( + _assert( typeof assertion === 'string' || typeof assertion === 'undefined', AuthErrorCode.INTERNAL_ERROR, { appName } ); } -export class UserImpl implements User { +export class UserImpl implements UserInternal { // For the user object, provider is always Firebase. - readonly providerId = externs.ProviderId.FIREBASE; + readonly providerId = ProviderId.FIREBASE; stsTokenManager: StsTokenManager; // Last known accessToken so we know when it changes private accessToken: string | null; uid: string; - auth: Auth; + auth: AuthInternal; emailVerified = false; isAnonymous = false; tenantId: string | null = null; @@ -81,7 +85,10 @@ export class UserImpl implements User { this.phoneNumber = opt.phoneNumber || null; this.photoURL = opt.photoURL || null; this.isAnonymous = opt.isAnonymous || false; - this.metadata = new UserMetadata(opt.createdAt, opt.lastLoginAt); + this.metadata = new UserMetadata( + opt.createdAt || undefined, + opt.lastLoginAt || undefined + ); } async getIdToken(forceRefresh?: boolean): Promise { @@ -89,9 +96,7 @@ export class UserImpl implements User { this, this.stsTokenManager.getToken(this.auth, forceRefresh) ); - assert(accessToken, AuthErrorCode.INTERNAL_ERROR, { - appName: this.auth.name - }); + _assert(accessToken, this.auth, AuthErrorCode.INTERNAL_ERROR); if (this.accessToken !== accessToken) { this.accessToken = accessToken; @@ -102,7 +107,7 @@ export class UserImpl implements User { return accessToken; } - getIdTokenResult(forceRefresh?: boolean): Promise { + getIdTokenResult(forceRefresh?: boolean): Promise { return getIdTokenResult(this, forceRefresh); } @@ -113,13 +118,11 @@ export class UserImpl implements User { private reloadUserInfo: APIUserInfo | null = null; private reloadListener: NextFn | null = null; - _copy(user: User): void { + _assign(user: UserInternal): void { if (this === user) { return; } - assert(this.uid === user.uid, AuthErrorCode.INTERNAL_ERROR, { - appName: this.auth.name - }); + _assert(this.uid === user.uid, this.auth, AuthErrorCode.INTERNAL_ERROR); this.displayName = user.displayName; this.photoURL = user.photoURL; this.email = user.email; @@ -129,14 +132,20 @@ export class UserImpl implements User { this.tenantId = user.tenantId; this.providerData = user.providerData.map(userInfo => ({ ...userInfo })); this.metadata._copy(user.metadata); - this.stsTokenManager._copy(user.stsTokenManager); + this.stsTokenManager._assign(user.stsTokenManager); + } + + _clone(auth: AuthInternal): UserInternal { + return new UserImpl({ + ...this, + auth, + stsTokenManager: this.stsTokenManager._clone() + }); } _onReload(callback: NextFn): void { // There should only ever be one listener, and that is a single instance of MultiFactorUser - assert(!this.reloadListener, AuthErrorCode.INTERNAL_ERROR, { - appName: this.auth.name - }); + _assert(!this.reloadListener, this.auth, AuthErrorCode.INTERNAL_ERROR); this.reloadListener = callback; if (this.reloadUserInfo) { this._notifyReloadListener(this.reloadUserInfo); @@ -210,7 +219,13 @@ export class UserImpl implements User { // Redirect event ID must be maintained in case there is a pending // redirect event. _redirectEventId: this._redirectEventId, - ...this.metadata.toJSON() + ...this.metadata.toJSON(), + + // Required for compatibility with the legacy SDK (go/firebase-auth-sdk-persistence-parsing): + apiKey: this.auth.config.apiKey, + appName: this.auth.name + // Missing authDomain will be tolerated by the legacy SDK. + // stsTokenManager.apiKey isn't actually required (despite the legacy SDK persisting it). }; } @@ -218,7 +233,7 @@ export class UserImpl implements User { return this.stsTokenManager.refreshToken || ''; } - static _fromJSON(auth: Auth, object: PersistedBlob): User { + static _fromJSON(auth: AuthInternal, object: PersistedBlob): UserInternal { const displayName = object.displayName ?? undefined; const email = object.email ?? undefined; const phoneNumber = object.phoneNumber ?? undefined; @@ -235,26 +250,26 @@ export class UserImpl implements User { stsTokenManager: plainObjectTokenManager } = object; - assert(uid && plainObjectTokenManager, AuthErrorCode.INTERNAL_ERROR, { - appName: auth.name - }); + _assert(uid && plainObjectTokenManager, auth, AuthErrorCode.INTERNAL_ERROR); const stsTokenManager = StsTokenManager.fromJSON( this.name, plainObjectTokenManager as PersistedBlob ); - assert(typeof uid === 'string', AuthErrorCode.INTERNAL_ERROR, { - appName: auth.name - }); + _assert(typeof uid === 'string', auth, AuthErrorCode.INTERNAL_ERROR); assertStringOrUndefined(displayName, auth.name); assertStringOrUndefined(email, auth.name); - assert(typeof emailVerified === 'boolean', AuthErrorCode.INTERNAL_ERROR, { - appName: auth.name - }); - assert(typeof isAnonymous === 'boolean', AuthErrorCode.INTERNAL_ERROR, { - appName: auth.name - }); + _assert( + typeof emailVerified === 'boolean', + auth, + AuthErrorCode.INTERNAL_ERROR + ); + _assert( + typeof isAnonymous === 'boolean', + auth, + AuthErrorCode.INTERNAL_ERROR + ); assertStringOrUndefined(phoneNumber, auth.name); assertStringOrUndefined(photoURL, auth.name); assertStringOrUndefined(tenantId, auth.name); @@ -293,10 +308,10 @@ export class UserImpl implements User { * @param idTokenResponse */ static async _fromIdTokenResponse( - auth: Auth, + auth: AuthInternal, idTokenResponse: IdTokenResponse, isAnonymous: boolean = false - ): Promise { + ): Promise { const stsTokenManager = new StsTokenManager(); stsTokenManager.updateFromServerResponse(idTokenResponse); diff --git a/packages-exp/auth-exp/src/core/user/user_metadata.ts b/packages-exp/auth-exp/src/core/user/user_metadata.ts index ffd9fd4ee9d..7dccb3281ce 100644 --- a/packages-exp/auth-exp/src/core/user/user_metadata.ts +++ b/packages-exp/auth-exp/src/core/user/user_metadata.ts @@ -15,11 +15,11 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { UserMetadata as UserMetadataType } from '../../model/public_types'; import { utcTimestampToDateString } from '../util/time'; -export class UserMetadata implements externs.UserMetadata { +export class UserMetadata implements UserMetadataType { creationTime?: string; lastSignInTime?: string; diff --git a/packages-exp/auth-exp/src/core/util/assert.ts b/packages-exp/auth-exp/src/core/util/assert.ts index e4e10a0d787..71d755b0b6e 100644 --- a/packages-exp/auth-exp/src/core/util/assert.ts +++ b/packages-exp/auth-exp/src/core/util/assert.ts @@ -15,35 +15,115 @@ * limitations under the License. */ -import { AUTH_ERROR_FACTORY, AuthErrorCode, AuthErrorParams } from '../errors'; +import { Auth } from '../../model/public_types'; +import { FirebaseError } from '@firebase/util'; +import { AuthInternal } from '../../model/auth'; +import { + _DEFAULT_AUTH_ERROR_FACTORY, + AuthErrorCode, + AuthErrorParams +} from '../errors'; import { _logError } from './log'; +type AuthErrorListParams = K extends keyof AuthErrorParams + ? [AuthErrorParams[K]] + : []; +type LessAppName = Omit; + /** * Unconditionally fails, throwing a developer facing INTERNAL_ERROR * + * @example + * ```javascript + * fail(auth, AuthErrorCode.MFA_REQUIRED); // Error: the MFA_REQUIRED error needs more params than appName + * fail(auth, AuthErrorCode.MFA_REQUIRED, {serverResponse}); // Compiles + * fail(AuthErrorCode.INTERNAL_ERROR); // Compiles; internal error does not need appName + * fail(AuthErrorCode.USER_DELETED); // Error: USER_DELETED requires app name + * fail(auth, AuthErrorCode.USER_DELETED); // Compiles; USER_DELETED _only_ needs app name + * ``` + * * @param appName App name for tagging the error * @throws FirebaseError */ -export function fail( +export function _fail( + code: K, + ...data: {} extends AuthErrorParams[K] + ? [AuthErrorParams[K]?] + : [AuthErrorParams[K]] +): never; +export function _fail( + auth: Auth, code: K, - ...data: K extends keyof AuthErrorParams ? [AuthErrorParams[K]] : [] + ...data: {} extends LessAppName ? [LessAppName?] : [LessAppName] +): never; +export function _fail( + authOrCode: Auth | K, + ...rest: unknown[] ): never { - throw AUTH_ERROR_FACTORY.create(code, ...data); + throw createErrorInternal(authOrCode, ...rest); } -/** - * Verifies the given condition and fails if false, throwing a developer facing error - * - * @param assertion - * @param appName - */ -export function assert( +export function _createError( + code: K, + ...data: {} extends AuthErrorParams[K] + ? [AuthErrorParams[K]?] + : [AuthErrorParams[K]] +): FirebaseError; +export function _createError( + auth: Auth, + code: K, + ...data: {} extends LessAppName ? [LessAppName?] : [LessAppName] +): FirebaseError; +export function _createError( + authOrCode: Auth | K, + ...rest: unknown[] +): FirebaseError { + return createErrorInternal(authOrCode, ...rest); +} + +function createErrorInternal( + authOrCode: Auth | K, + ...rest: unknown[] +): FirebaseError { + if (typeof authOrCode !== 'string') { + const code = rest[0] as K; + const fullParams = [...rest.slice(1)] as AuthErrorListParams; + if (fullParams[0]) { + fullParams[0].appName = authOrCode.name; + } + + return (authOrCode as AuthInternal)._errorFactory.create( + code, + ...fullParams + ); + } + + return _DEFAULT_AUTH_ERROR_FACTORY.create( + authOrCode, + ...(rest as AuthErrorListParams) + ); +} + +export function _assert( assertion: unknown, code: K, - ...data: K extends keyof AuthErrorParams ? [AuthErrorParams[K]] : [] + ...data: {} extends AuthErrorParams[K] + ? [AuthErrorParams[K]?] + : [AuthErrorParams[K]] +): asserts assertion; +export function _assert( + assertion: unknown, + auth: Auth, + code: K, + ...data: {} extends LessAppName ? [LessAppName?] : [LessAppName] +): asserts assertion; +export function _assert( + assertion: unknown, + authOrCode: Auth | K, + ...rest: unknown[] ): asserts assertion { if (!assertion) { - fail(code, ...data); + throw createErrorInternal(authOrCode, ...rest); } } @@ -91,7 +171,7 @@ export function assertTypes( ...expected: Array ): void { if (args.length > expected.length) { - fail(AuthErrorCode.ARGUMENT_ERROR, {}); + _fail(AuthErrorCode.ARGUMENT_ERROR, {}); } for (let i = 0; i < expected.length; i++) { @@ -114,7 +194,7 @@ export function assertTypes( } const required = expect.split('|'); - assert(required.includes(typeof arg), AuthErrorCode.ARGUMENT_ERROR, {}); + _assert(required.includes(typeof arg), AuthErrorCode.ARGUMENT_ERROR, {}); } else if (typeof expect === 'object') { // Recursively check record arguments const record = arg as Record; @@ -126,7 +206,7 @@ export function assertTypes( ...keys.map(k => map[k]) ); } else { - assert(arg instanceof expect, AuthErrorCode.ARGUMENT_ERROR, {}); + _assert(arg instanceof expect, AuthErrorCode.ARGUMENT_ERROR, {}); } } } diff --git a/packages-exp/auth-exp/src/core/util/browser.ts b/packages-exp/auth-exp/src/core/util/browser.ts index 2754269dcc7..e2b8fe5b87a 100644 --- a/packages-exp/auth-exp/src/core/util/browser.ts +++ b/packages-exp/auth-exp/src/core/util/browser.ts @@ -28,7 +28,7 @@ interface Document { /** * Enums for Browser name. */ -export enum BrowserName { +export const enum BrowserName { ANDROID = 'Android', BLACKBERRY = 'Blackberry', EDGE = 'Edge', @@ -88,11 +88,11 @@ export function _getBrowserName(userAgent: string): BrowserName | string { return BrowserName.OTHER; } -export function _isFirefox(ua: string): boolean { +export function _isFirefox(ua = getUA()): boolean { return /firefox\//i.test(ua); } -export function _isSafari(userAgent: string): boolean { +export function _isSafari(userAgent = getUA()): boolean { const ua = userAgent.toLowerCase(); return ( ua.includes('safari/') && @@ -102,31 +102,38 @@ export function _isSafari(userAgent: string): boolean { ); } -export function _isChromeIOS(ua: string): boolean { +export function _isChromeIOS(ua = getUA()): boolean { return /crios\//i.test(ua); } -export function _isIEMobile(ua: string): boolean { +export function _isIEMobile(ua = getUA()): boolean { return /iemobile/i.test(ua); } -export function _isAndroid(ua: string): boolean { +export function _isAndroid(ua = getUA()): boolean { return /android/i.test(ua); } -export function _isBlackBerry(ua: string): boolean { +export function _isBlackBerry(ua = getUA()): boolean { return /blackberry/i.test(ua); } -export function _isWebOS(ua: string): boolean { +export function _isWebOS(ua = getUA()): boolean { return /webos/i.test(ua); } -export function _isIOS(ua: string): boolean { +export function _isIOS(ua = getUA()): boolean { return /iphone|ipad|ipod/i.test(ua); } -export function _isIOSStandalone(ua: string): boolean { +export function _isIOS7Or8(ua = getUA()): boolean { + return ( + /(iPad|iPhone|iPod).*OS 7_\d/i.test(ua) || + /(iPad|iPhone|iPod).*OS 8_\d/i.test(ua) + ); +} + +export function _isIOSStandalone(ua = getUA()): boolean { return _isIOS(ua) && !!(window.navigator as NavigatorStandalone)?.standalone; } diff --git a/packages-exp/auth-exp/src/core/util/delay.test.ts b/packages-exp/auth-exp/src/core/util/delay.test.ts index 939ac98c588..54ce00ab9b1 100644 --- a/packages-exp/auth-exp/src/core/util/delay.test.ts +++ b/packages-exp/auth-exp/src/core/util/delay.test.ts @@ -18,7 +18,7 @@ import * as util from '@firebase/util'; import { expect } from 'chai'; import { restore, stub } from 'sinon'; -import { Delay, _OFFLINE_DELAY_MS } from './delay'; +import { Delay, DelayMin } from './delay'; import * as navigator from './navigator'; describe('core/util/delay', () => { @@ -50,6 +50,6 @@ describe('core/util/delay', () => { const mock = stub(navigator, '_isOnline'); mock.callsFake(() => false); const delay = new Delay(SHORT_DELAY, LONG_DELAY); - expect(delay.get()).to.eq(_OFFLINE_DELAY_MS); + expect(delay.get()).to.eq(DelayMin.OFFLINE); }); }); diff --git a/packages-exp/auth-exp/src/core/util/delay.ts b/packages-exp/auth-exp/src/core/util/delay.ts index dae81f2d8c7..d5d189d64b4 100644 --- a/packages-exp/auth-exp/src/core/util/delay.ts +++ b/packages-exp/auth-exp/src/core/util/delay.ts @@ -19,7 +19,9 @@ import { isMobileCordova, isReactNative } from '@firebase/util'; import { _isOnline } from './navigator'; import { debugAssert } from './assert'; -export const _OFFLINE_DELAY_MS = 5000; +export const enum DelayMin { + OFFLINE = 5000 +} /** * A structure to help pick between a range of long and short delay durations @@ -45,7 +47,7 @@ export class Delay { get(): number { if (!_isOnline()) { // Pick the shorter timeout. - return Math.min(_OFFLINE_DELAY_MS, this.shortDelay); + return Math.min(DelayMin.OFFLINE, this.shortDelay); } // If running in a mobile environment, return the long delay, otherwise // return the short delay. diff --git a/packages-exp/auth-exp/src/core/util/emulator.test.ts b/packages-exp/auth-exp/src/core/util/emulator.test.ts index e9d9a88bc8d..65337681d5b 100644 --- a/packages-exp/auth-exp/src/core/util/emulator.test.ts +++ b/packages-exp/auth-exp/src/core/util/emulator.test.ts @@ -23,7 +23,7 @@ import { _emulatorUrl } from './emulator'; describe('core/util/emulator', () => { const config: ConfigInternal = { emulator: { - url: 'http://localhost:4000' + url: 'http://localhost:4000/' } } as ConfigInternal; diff --git a/packages-exp/auth-exp/src/core/util/emulator.ts b/packages-exp/auth-exp/src/core/util/emulator.ts index 4d56687bbee..fb42011045b 100644 --- a/packages-exp/auth-exp/src/core/util/emulator.ts +++ b/packages-exp/auth-exp/src/core/util/emulator.ts @@ -21,11 +21,10 @@ import { debugAssert } from './assert'; export function _emulatorUrl(config: ConfigInternal, path?: string): string { debugAssert(config.emulator, 'Emulator should always be set here'); const { url } = config.emulator; - const emulatorHost = new URL(url).toString(); if (!path) { - return emulatorHost; + return url; } - return `${emulatorHost}${path.startsWith('/') ? path.slice(1) : path}`; + return `${url}${path.startsWith('/') ? path.slice(1) : path}`; } diff --git a/packages-exp/auth-exp/src/core/util/event_id.test.ts b/packages-exp/auth-exp/src/core/util/event_id.test.ts new file mode 100644 index 00000000000..510b66b5c25 --- /dev/null +++ b/packages-exp/auth-exp/src/core/util/event_id.test.ts @@ -0,0 +1,33 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from 'chai'; +import { _generateEventId } from './event_id'; + +describe('core/util/event_id', () => { + it('sub-15 digit id', () => { + expect(_generateEventId('', 10)).to.have.length(10); + }); + + it('15 digit id', () => { + expect(_generateEventId('', 15)).to.have.length(15); + }); + + it('above-15 digit id', () => { + expect(_generateEventId('', 20)).to.have.length(20); + }); +}); diff --git a/packages-exp/auth-exp/src/core/util/event_id.ts b/packages-exp/auth-exp/src/core/util/event_id.ts index ef5abe1839c..6e2e02fca63 100644 --- a/packages-exp/auth-exp/src/core/util/event_id.ts +++ b/packages-exp/auth-exp/src/core/util/event_id.ts @@ -15,6 +15,10 @@ * limitations under the License. */ -export function _generateEventId(prefix?: string): string { - return `${prefix ? prefix : ''}${Math.floor(Math.random() * 1000000000)}`; +export function _generateEventId(prefix = '', digits = 10): string { + let random = ''; + for (let i = 0; i < digits; i++) { + random += Math.floor(Math.random() * 10); + } + return prefix + random; } diff --git a/packages-exp/auth-exp/src/core/util/handler.ts b/packages-exp/auth-exp/src/core/util/handler.ts new file mode 100644 index 00000000000..ce8935b0c99 --- /dev/null +++ b/packages-exp/auth-exp/src/core/util/handler.ts @@ -0,0 +1,119 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SDK_VERSION } from '@firebase/app-exp'; +import { AuthProvider } from '../../model/public_types'; +import { ApiKey, AppName, AuthInternal } from '../../model/auth'; +import { AuthEventType } from '../../model/popup_redirect'; +import { AuthErrorCode } from '../errors'; +import { _assert } from './assert'; +import { isEmpty, querystring } from '@firebase/util'; +import { _emulatorUrl } from './emulator'; +import { FederatedAuthProvider } from '../providers/federated'; +import { BaseOAuthProvider } from '../providers/oauth'; + +/** + * URL for Authentication widget which will initiate the OAuth handshake + * + * @internal + */ +const WIDGET_PATH = '__/auth/handler'; + +/** + * URL for emulated environment + * + * @internal + */ +const EMULATOR_WIDGET_PATH = 'emulator/auth/handler'; + +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions +type WidgetParams = { + apiKey: ApiKey; + appName: AppName; + authType: AuthEventType; + redirectUrl?: string; + v: string; + providerId?: string; + scopes?: string; + customParameters?: string; + eventId?: string; + tid?: string; +} & { [key: string]: string | undefined }; + +export function _getRedirectUrl( + auth: AuthInternal, + provider: AuthProvider, + authType: AuthEventType, + redirectUrl?: string, + eventId?: string, + additionalParams?: Record +): string { + _assert(auth.config.authDomain, auth, AuthErrorCode.MISSING_AUTH_DOMAIN); + _assert(auth.config.apiKey, auth, AuthErrorCode.INVALID_API_KEY); + + const params: WidgetParams = { + apiKey: auth.config.apiKey, + appName: auth.name, + authType, + redirectUrl, + v: SDK_VERSION, + eventId + }; + + if (provider instanceof FederatedAuthProvider) { + provider.setDefaultLanguage(auth.languageCode); + params.providerId = provider.providerId || ''; + if (!isEmpty(provider.getCustomParameters())) { + params.customParameters = JSON.stringify(provider.getCustomParameters()); + } + + // TODO set additionalParams from the provider as well? + for (const [key, value] of Object.entries(additionalParams || {})) { + params[key] = value; + } + } + + if (provider instanceof BaseOAuthProvider) { + const scopes = provider.getScopes().filter(scope => scope !== ''); + if (scopes.length > 0) { + params.scopes = scopes.join(','); + } + } + + if (auth.tenantId) { + params.tid = auth.tenantId; + } + + // TODO: maybe set eid as endipointId + // TODO: maybe set fw as Frameworks.join(",") + + const paramsDict = params as Record; + for (const key of Object.keys(paramsDict)) { + if (paramsDict[key] === undefined) { + delete paramsDict[key]; + } + } + return `${getHandlerBase(auth)}?${querystring(paramsDict).slice(1)}`; +} + +function getHandlerBase({ config }: AuthInternal): string { + if (!config.emulator) { + return `https://${config.authDomain}/${WIDGET_PATH}`; + } + + return _emulatorUrl(config, EMULATOR_WIDGET_PATH); +} diff --git a/packages-exp/auth-exp/src/core/util/instantiator.test.ts b/packages-exp/auth-exp/src/core/util/instantiator.test.ts index f1c1ba603ad..b93e02d72b7 100644 --- a/packages-exp/auth-exp/src/core/util/instantiator.test.ts +++ b/packages-exp/auth-exp/src/core/util/instantiator.test.ts @@ -19,7 +19,7 @@ import { expect } from 'chai'; import { _getInstance } from './instantiator'; -describe('src/core/util/instantiator', () => { +describe('core/util/instantiator', () => { context('_getInstance', () => { // All tests define their own classes since the Class object is used in the // global map. diff --git a/packages-exp/auth-exp/src/core/util/instantiator.ts b/packages-exp/auth-exp/src/core/util/instantiator.ts index 90ebc9411d8..92f40baaafe 100644 --- a/packages-exp/auth-exp/src/core/util/instantiator.ts +++ b/packages-exp/auth-exp/src/core/util/instantiator.ts @@ -46,3 +46,7 @@ export function _getInstance(cls: unknown): T { instanceCache.set(cls, instance); return instance; } + +export function _clearInstanceMap(): void { + instanceCache.clear(); +} diff --git a/packages-exp/auth-exp/src/core/util/resolver.ts b/packages-exp/auth-exp/src/core/util/resolver.ts new file mode 100644 index 00000000000..2e7d8422bd1 --- /dev/null +++ b/packages-exp/auth-exp/src/core/util/resolver.ts @@ -0,0 +1,41 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { PopupRedirectResolver } from '../../model/public_types'; +import { AuthInternal } from '../../model/auth'; +import { PopupRedirectResolverInternal } from '../../model/popup_redirect'; +import { AuthErrorCode } from '../errors'; +import { _assert } from './assert'; +import { _getInstance } from './instantiator'; + +/** + * Chooses a popup/redirect resolver to use. This prefers the override (which + * is directly passed in), and falls back to the property set on the auth + * object. If neither are available, this function errors w/ an argument error. + */ +export function _withDefaultResolver( + auth: AuthInternal, + resolverOverride: PopupRedirectResolver | undefined +): PopupRedirectResolverInternal { + if (resolverOverride) { + return _getInstance(resolverOverride); + } + + _assert(auth._popupRedirectResolver, auth, AuthErrorCode.ARGUMENT_ERROR); + + return auth._popupRedirectResolver; +} diff --git a/packages-exp/auth-exp/src/core/util/validate_origin.test.ts b/packages-exp/auth-exp/src/core/util/validate_origin.test.ts index b87774460a2..efc3795e1a6 100644 --- a/packages-exp/auth-exp/src/core/util/validate_origin.test.ts +++ b/packages-exp/auth-exp/src/core/util/validate_origin.test.ts @@ -25,14 +25,14 @@ import { mockEndpoint } from '../../../test/helpers/api/helper'; import { testAuth } from '../../../test/helpers/mock_auth'; import * as fetch from '../../../test/helpers/mock_fetch'; import { Endpoint } from '../../api'; -import { Auth } from '../../model/auth'; +import { AuthInternal } from '../../model/auth'; import * as location from './location'; import { _validateOrigin } from './validate_origin'; use(chaiAsPromised); describe('core/util/validate_origin', () => { - let auth: Auth; + let auth: AuthInternal; let authorizedDomains: string[]; let currentUrl: string; beforeEach(async () => { diff --git a/packages-exp/auth-exp/src/core/util/validate_origin.ts b/packages-exp/auth-exp/src/core/util/validate_origin.ts index d0e722c0c22..a377a49c857 100644 --- a/packages-exp/auth-exp/src/core/util/validate_origin.ts +++ b/packages-exp/auth-exp/src/core/util/validate_origin.ts @@ -16,15 +16,15 @@ */ import { _getProjectConfig } from '../../api/project_config/get_project_config'; -import { Auth } from '../../model/auth'; +import { AuthInternal } from '../../model/auth'; import { AuthErrorCode } from '../errors'; -import { fail } from './assert'; +import { _fail } from './assert'; import { _getCurrentUrl } from './location'; const IP_ADDRESS_REGEX = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/; const HTTP_REGEX = /^https?/; -export async function _validateOrigin(auth: Auth): Promise { +export async function _validateOrigin(auth: AuthInternal): Promise { // Skip origin validation if we are in an emulated environment if (auth.config.emulator) { return; @@ -43,7 +43,7 @@ export async function _validateOrigin(auth: Auth): Promise { } // In the old SDK, this error also provides helpful messages. - fail(AuthErrorCode.INVALID_ORIGIN, { appName: auth.name }); + _fail(auth, AuthErrorCode.INVALID_ORIGIN); } function matchDomain(expected: string): boolean { diff --git a/packages-exp/auth-exp/src/core/util/version.ts b/packages-exp/auth-exp/src/core/util/version.ts index ccd43b58094..e8aecd48f17 100644 --- a/packages-exp/auth-exp/src/core/util/version.ts +++ b/packages-exp/auth-exp/src/core/util/version.ts @@ -19,29 +19,25 @@ import { SDK_VERSION } from '@firebase/app-exp'; import { _getBrowserName } from './browser'; import { getUA } from '@firebase/util'; -const CLIENT_IMPLEMENTATION = 'JsCore'; +export const enum ClientImplementation { + CORE = 'JsCore' +} -export enum ClientPlatform { +export const enum ClientPlatform { BROWSER = 'Browser', NODE = 'Node', REACT_NATIVE = 'ReactNative', + CORDOVA = 'Cordova', WORKER = 'Worker' } -enum ClientFramework { - // No other framework used. - DEFAULT = 'FirebaseCore-web', - // Firebase Auth used with FirebaseUI-web. - // TODO: Pass this in when used in conjunction with FirebaseUI - FIREBASEUI = 'FirebaseUI-web' -} - /* * Determine the SDK version string - * - * TODO: This should be set on the Auth object during initialization */ -export function _getClientVersion(clientPlatform: ClientPlatform): string { +export function _getClientVersion( + clientPlatform: ClientPlatform, + frameworks: readonly string[] = [] +): string { let reportedPlatform: string; switch (clientPlatform) { case ClientPlatform.BROWSER: @@ -57,5 +53,8 @@ export function _getClientVersion(clientPlatform: ClientPlatform): string { default: reportedPlatform = clientPlatform; } - return `${reportedPlatform}/${CLIENT_IMPLEMENTATION}/${SDK_VERSION}/${ClientFramework.DEFAULT}`; + const reportedFrameworks = frameworks.length + ? frameworks.join(',') + : 'FirebaseCore-web'; /* default value if no other framework is used */ + return `${reportedPlatform}/${ClientImplementation.CORE}/${SDK_VERSION}/${reportedFrameworks}`; } diff --git a/packages-exp/auth-exp/src/mfa/assertions/index.ts b/packages-exp/auth-exp/src/mfa/assertions/index.ts deleted file mode 100644 index 1e4ef24ea63..00000000000 --- a/packages-exp/auth-exp/src/mfa/assertions/index.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import * as externs from '@firebase/auth-types-exp'; -import { debugFail } from '../../core/util/assert'; -import { MultiFactorSession, MultiFactorSessionType } from '../mfa_session'; -import { FinalizeMfaResponse } from '../../api/authentication/mfa'; -import { Auth } from '../../model/auth'; - -export abstract class MultiFactorAssertion - implements externs.MultiFactorAssertion { - protected constructor(readonly factorId: string) {} - - _process( - auth: Auth, - session: MultiFactorSession, - displayName?: string | null - ): Promise { - switch (session.type) { - case MultiFactorSessionType.ENROLL: - return this._finalizeEnroll(auth, session.credential, displayName); - case MultiFactorSessionType.SIGN_IN: - return this._finalizeSignIn(auth, session.credential); - default: - return debugFail('unexpected MultiFactorSessionType'); - } - } - - abstract _finalizeEnroll( - auth: Auth, - idToken: string, - displayName?: string | null - ): Promise; - abstract _finalizeSignIn( - auth: Auth, - mfaPendingCredential: string - ): Promise; -} diff --git a/packages-exp/auth-exp/src/mfa/mfa_assertion.ts b/packages-exp/auth-exp/src/mfa/mfa_assertion.ts new file mode 100644 index 00000000000..ea22545ac9f --- /dev/null +++ b/packages-exp/auth-exp/src/mfa/mfa_assertion.ts @@ -0,0 +1,50 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { FactorId, MultiFactorAssertion } from '../model/public_types'; +import { debugFail } from '../core/util/assert'; +import { MultiFactorSessionImpl, MultiFactorSessionType } from './mfa_session'; +import { FinalizeMfaResponse } from '../api/authentication/mfa'; +import { AuthInternal } from '../model/auth'; + +export abstract class MultiFactorAssertionImpl implements MultiFactorAssertion { + protected constructor(readonly factorId: FactorId) {} + + _process( + auth: AuthInternal, + session: MultiFactorSessionImpl, + displayName?: string | null + ): Promise { + switch (session.type) { + case MultiFactorSessionType.ENROLL: + return this._finalizeEnroll(auth, session.credential, displayName); + case MultiFactorSessionType.SIGN_IN: + return this._finalizeSignIn(auth, session.credential); + default: + return debugFail('unexpected MultiFactorSessionType'); + } + } + + abstract _finalizeEnroll( + auth: AuthInternal, + idToken: string, + displayName?: string | null + ): Promise; + abstract _finalizeSignIn( + auth: AuthInternal, + mfaPendingCredential: string + ): Promise; +} diff --git a/packages-exp/auth-exp/src/mfa/mfa_error.ts b/packages-exp/auth-exp/src/mfa/mfa_error.ts index 6fbc5985060..d0af68a3bf1 100644 --- a/packages-exp/auth-exp/src/mfa/mfa_error.ts +++ b/packages-exp/auth-exp/src/mfa/mfa_error.ts @@ -15,18 +15,21 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { + MultiFactorError as MultiFactorErrorPublic, + OperationType +} from '../model/public_types'; import { FirebaseError } from '@firebase/util'; -import { Auth } from '../model/auth'; +import { AuthInternal } from '../model/auth'; import { IdTokenResponse } from '../model/id_token'; import { AuthErrorCode } from '../core/errors'; -import { User } from '../model/user'; +import { UserInternal } from '../model/user'; import { AuthCredential } from '../core/credentials'; import { IdTokenMfaResponse } from '../api/authentication/mfa'; export class MultiFactorError extends FirebaseError - implements externs.MultiFactorError { + implements MultiFactorErrorPublic { readonly name = 'FirebaseError'; readonly code: string; readonly appName: string; @@ -35,50 +38,48 @@ export class MultiFactorError readonly tenantId?: string; private constructor( - auth: Auth, + auth: AuthInternal, error: FirebaseError, - readonly operationType: externs.OperationType, - readonly credential: AuthCredential, - readonly user?: User + readonly operationType: OperationType, + readonly user?: UserInternal ) { super(error.code, error.message); // https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work Object.setPrototypeOf(this, MultiFactorError.prototype); this.appName = auth.name; this.code = error.code; - this.tenantid = auth.tenantId; - this.serverResponse = error.serverResponse as IdTokenMfaResponse; + this.tenantId = auth.tenantId ?? undefined; + this.serverResponse = error.customData! + .serverResponse as IdTokenMfaResponse; } - static _fromErrorAndCredential( - auth: Auth, + static _fromErrorAndOperation( + auth: AuthInternal, error: FirebaseError, - operationType: externs.OperationType, - credential: AuthCredential, - user?: User + operationType: OperationType, + user?: UserInternal ): MultiFactorError { - return new MultiFactorError(auth, error, operationType, credential, user); + return new MultiFactorError(auth, error, operationType, user); } } export function _processCredentialSavingMfaContextIfNecessary( - auth: Auth, - operationType: externs.OperationType, + auth: AuthInternal, + operationType: OperationType, credential: AuthCredential, - user?: User + user?: UserInternal ): Promise { const idTokenProvider = - operationType === externs.OperationType.REAUTHENTICATE + operationType === OperationType.REAUTHENTICATE ? credential._getReauthenticationResolver(auth) : credential._getIdTokenResponse(auth); return idTokenProvider.catch(error => { if (error.code === `auth/${AuthErrorCode.MFA_REQUIRED}`) { - throw MultiFactorError._fromErrorAndCredential( + throw MultiFactorError._fromErrorAndOperation( auth, error, operationType, - credential, user ); } diff --git a/packages-exp/auth-exp/src/mfa/mfa_info.test.ts b/packages-exp/auth-exp/src/mfa/mfa_info.test.ts index d3ab67158fe..59d292f1e18 100644 --- a/packages-exp/auth-exp/src/mfa/mfa_info.test.ts +++ b/packages-exp/auth-exp/src/mfa/mfa_info.test.ts @@ -18,12 +18,12 @@ import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; -import { ProviderId } from '@firebase/auth-types-exp'; +import { ProviderId } from '../model/public_types'; import { FirebaseError } from '@firebase/util'; import { testAuth, TestAuth } from '../../test/helpers/mock_auth'; import { PhoneMfaEnrollment } from '../api/account_management/mfa'; -import { MultiFactorInfo } from './mfa_info'; +import { MultiFactorInfoImpl } from './mfa_info'; use(chaiAsPromised); @@ -45,7 +45,7 @@ describe('core/mfa/mfa_info/MultiFactorInfo', () => { }; it('should create a valid MfaInfo', () => { - const mfaInfo = MultiFactorInfo._fromServerResponse( + const mfaInfo = MultiFactorInfoImpl._fromServerResponse( auth, enrollmentInfo ); @@ -65,7 +65,7 @@ describe('core/mfa/mfa_info/MultiFactorInfo', () => { it('should throw an error', () => { expect(() => - MultiFactorInfo._fromServerResponse( + MultiFactorInfoImpl._fromServerResponse( auth, enrollmentInfo as PhoneMfaEnrollment ) diff --git a/packages-exp/auth-exp/src/mfa/mfa_info.ts b/packages-exp/auth-exp/src/mfa/mfa_info.ts index 98e8088618d..f89bcf990b4 100644 --- a/packages-exp/auth-exp/src/mfa/mfa_info.ts +++ b/packages-exp/auth-exp/src/mfa/mfa_info.ts @@ -15,50 +15,47 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { FactorId, MultiFactorInfo } from '../model/public_types'; import { PhoneMfaEnrollment, MfaEnrollment } from '../api/account_management/mfa'; import { AuthErrorCode } from '../core/errors'; -import { fail } from '../core/util/assert'; -import { Auth } from '../model/auth'; +import { _fail } from '../core/util/assert'; +import { AuthInternal } from '../model/auth'; -export abstract class MultiFactorInfo implements externs.MultiFactorInfo { +export abstract class MultiFactorInfoImpl implements MultiFactorInfo { readonly uid: string; readonly displayName?: string | null; readonly enrollmentTime: string; - protected constructor( - readonly factorId: externs.ProviderId, - response: MfaEnrollment - ) { + protected constructor(readonly factorId: FactorId, response: MfaEnrollment) { this.uid = response.mfaEnrollmentId; this.enrollmentTime = new Date(response.enrolledAt).toUTCString(); this.displayName = response.displayName; } static _fromServerResponse( - auth: Auth, + auth: AuthInternal, enrollment: MfaEnrollment - ): MultiFactorInfo { + ): MultiFactorInfoImpl { if ('phoneInfo' in enrollment) { return PhoneMultiFactorInfo._fromServerResponse(auth, enrollment); } - return fail(AuthErrorCode.INTERNAL_ERROR, { appName: auth.name }); + return _fail(auth, AuthErrorCode.INTERNAL_ERROR); } } -export class PhoneMultiFactorInfo extends MultiFactorInfo { +export class PhoneMultiFactorInfo extends MultiFactorInfoImpl { readonly phoneNumber: string; private constructor(response: PhoneMfaEnrollment) { - super(externs.ProviderId.PHONE, response); + super(FactorId.PHONE, response); this.phoneNumber = response.phoneInfo; } static _fromServerResponse( - _auth: Auth, + _auth: AuthInternal, enrollment: MfaEnrollment ): PhoneMultiFactorInfo { return new PhoneMultiFactorInfo(enrollment); diff --git a/packages-exp/auth-exp/src/mfa/mfa_resolver.test.ts b/packages-exp/auth-exp/src/mfa/mfa_resolver.test.ts index 491faea6c5d..73eadafc084 100644 --- a/packages-exp/auth-exp/src/mfa/mfa_resolver.test.ts +++ b/packages-exp/auth-exp/src/mfa/mfa_resolver.test.ts @@ -17,8 +17,9 @@ import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; +import * as sinon from 'sinon'; -import { OperationType, ProviderId } from '@firebase/auth-types-exp'; +import { FactorId, OperationType, ProviderId } from '../model/public_types'; import { FirebaseError } from '@firebase/util'; import { mockEndpoint } from '../../test/helpers/api/helper'; @@ -26,52 +27,55 @@ import { testAuth, testUser, TestAuth } from '../../test/helpers/mock_auth'; import * as mockFetch from '../../test/helpers/mock_fetch'; import { Endpoint } from '../api'; import { APIUserInfo } from '../api/account_management/account'; -import { AuthCredential } from '../core/credentials'; import { PhoneAuthCredential } from '../core/credentials/phone'; -import { AUTH_ERROR_FACTORY, AuthErrorCode } from '../core/errors'; -import { EmailAuthProvider } from '../core/providers/email'; -import { User, UserCredential } from '../model/user'; -import { MultiFactorAssertion } from './assertions'; -import { PhoneMultiFactorAssertion } from '../platform_browser/mfa/assertions/phone'; +import { AuthErrorCode } from '../core/errors'; +import { UserInternal, UserCredentialInternal } from '../model/user'; +import { MultiFactorAssertionImpl } from './mfa_assertion'; +import { PhoneMultiFactorAssertionImpl } from '../platform_browser/mfa/assertions/phone'; import { MultiFactorError } from './mfa_error'; -import { getMultiFactorResolver, MultiFactorResolver } from './mfa_resolver'; - +import { + getMultiFactorResolver, + MultiFactorResolverImpl +} from './mfa_resolver'; +import { _createError } from '../core/util/assert'; +import { makeJWT } from '../../test/helpers/jwt'; use(chaiAsPromised); describe('core/mfa/mfa_resolver/MultiFactorResolver', () => { + const finalIdToken = makeJWT({ 'exp': '3600', 'iat': '1200' }); let auth: TestAuth; let underlyingError: FirebaseError; let error: MultiFactorError; - let primaryFactorCredential: AuthCredential; + let clock: sinon.SinonFakeTimers; beforeEach(async () => { + clock = sinon.useFakeTimers(); auth = await testAuth(); auth.tenantId = 'tenant-id'; - primaryFactorCredential = EmailAuthProvider.credential( - 'email', - 'password' - ) as AuthCredential; - underlyingError = AUTH_ERROR_FACTORY.create(AuthErrorCode.MFA_REQUIRED, { - appName: auth.name, + underlyingError = _createError(auth, AuthErrorCode.MFA_REQUIRED, { serverResponse: { localId: 'local-id', - expiresIn: '3600', mfaPendingCredential: 'mfa-pending-credential', mfaInfo: [ { mfaEnrollmentId: 'mfa-enrollment-id', enrolledAt: Date.now(), - phoneInfo: 'phone-info' + phoneInfo: '+*******1234', + displayName: '' } ] } }); }); + afterEach(() => { + sinon.restore(); + }); + describe('MultiFactorResolver', () => { - let assertion: MultiFactorAssertion; + let assertion: MultiFactorAssertionImpl; let secondFactorCredential: PhoneAuthCredential; - let resolver: MultiFactorResolver; + let resolver: MultiFactorResolverImpl; beforeEach(() => { mockFetch.setUp(); @@ -79,7 +83,7 @@ describe('core/mfa/mfa_resolver/MultiFactorResolver', () => { 'verification-id', 'verification-code' ); - assertion = PhoneMultiFactorAssertion._fromCredential( + assertion = PhoneMultiFactorAssertionImpl._fromCredential( secondFactorCredential ); }); @@ -110,7 +114,7 @@ describe('core/mfa/mfa_resolver/MultiFactorResolver', () => { beforeEach(() => { mock = mockEndpoint(Endpoint.FINALIZE_PHONE_MFA_SIGN_IN, { - idToken: 'final-id-token', + idToken: finalIdToken, refreshToken: 'final-refresh-token' }); @@ -121,27 +125,26 @@ describe('core/mfa/mfa_resolver/MultiFactorResolver', () => { context('sign in', () => { beforeEach(() => { - error = MultiFactorError._fromErrorAndCredential( + error = MultiFactorError._fromErrorAndOperation( auth, underlyingError, - OperationType.SIGN_IN, - primaryFactorCredential + OperationType.SIGN_IN ); - resolver = MultiFactorResolver._fromError(auth, error); + resolver = MultiFactorResolverImpl._fromError(auth, error); }); it('finalizes the sign in flow', async () => { const userCredential = (await resolver.resolveSignIn( assertion - )) as UserCredential; + )) as UserCredentialInternal; expect(userCredential.user.uid).to.eq('local-id'); - expect(await userCredential.user.getIdToken()).to.eq( - 'final-id-token' + expect(await userCredential.user.getIdToken()).to.eq(finalIdToken); + expect(userCredential.user.stsTokenManager.expirationTime).to.eq( + clock.now + 2400 * 1000 ); expect(userCredential._tokenResponse).to.eql({ localId: 'local-id', - expiresIn: '3600', - idToken: 'final-id-token', + idToken: finalIdToken, refreshToken: 'final-refresh-token' }); expect(mock.calls[0].request).to.eql({ @@ -156,32 +159,31 @@ describe('core/mfa/mfa_resolver/MultiFactorResolver', () => { }); context('reauthentication', () => { - let user: User; + let user: UserInternal; beforeEach(() => { user = testUser(auth, 'local-id', undefined, true); - error = MultiFactorError._fromErrorAndCredential( + error = MultiFactorError._fromErrorAndOperation( auth, underlyingError, OperationType.REAUTHENTICATE, - primaryFactorCredential, user ); - resolver = MultiFactorResolver._fromError(auth, error); + resolver = MultiFactorResolverImpl._fromError(auth, error); }); it('finalizes the reauth flow', async () => { const userCredential = (await resolver.resolveSignIn( assertion - )) as UserCredential; + )) as UserCredentialInternal; expect(userCredential.user).to.eq(user); - expect(await userCredential.user.getIdToken()).to.eq( - 'final-id-token' + expect(await userCredential.user.getIdToken()).to.eq(finalIdToken); + expect(userCredential.user.stsTokenManager.expirationTime).to.eq( + clock.now + 2400 * 1000 ); expect(userCredential._tokenResponse).to.eql({ localId: 'local-id', - expiresIn: '3600', - idToken: 'final-id-token', + idToken: finalIdToken, refreshToken: 'final-refresh-token' }); expect(mock.calls[0].request).to.eql({ @@ -200,11 +202,10 @@ describe('core/mfa/mfa_resolver/MultiFactorResolver', () => { describe('getMultiFactorResolver', () => { context('sign in', () => { beforeEach(() => { - error = MultiFactorError._fromErrorAndCredential( + error = MultiFactorError._fromErrorAndOperation( auth, underlyingError, - OperationType.SIGN_IN, - primaryFactorCredential + OperationType.SIGN_IN ); }); it('can be used to obtain a resolver', () => { @@ -214,22 +215,21 @@ describe('core/mfa/mfa_resolver/MultiFactorResolver', () => { }); context('reauthentication', () => { - let user: User; + let user: UserInternal; beforeEach(() => { user = testUser(auth, 'local-id', undefined, true); - error = MultiFactorError._fromErrorAndCredential( + error = MultiFactorError._fromErrorAndOperation( auth, underlyingError, OperationType.REAUTHENTICATE, - primaryFactorCredential, user ); }); it('can be used to obtain a resolver', () => { const resolver = getMultiFactorResolver(auth, error); - expect(resolver.hints[0].factorId).to.eq(ProviderId.PHONE); + expect(resolver.hints[0].factorId).to.eq(FactorId.PHONE); }); }); }); diff --git a/packages-exp/auth-exp/src/mfa/mfa_resolver.ts b/packages-exp/auth-exp/src/mfa/mfa_resolver.ts index 253d798ed00..52bed8c5e6f 100644 --- a/packages-exp/auth-exp/src/mfa/mfa_resolver.ts +++ b/packages-exp/auth-exp/src/mfa/mfa_resolver.ts @@ -15,49 +15,60 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { + Auth, + MultiFactorResolver, + OperationType, + UserCredential, + MultiFactorError +} from '../model/public_types'; import { _castAuth } from '../core/auth/auth_impl'; import { AuthErrorCode } from '../core/errors'; import { UserCredentialImpl } from '../core/user/user_credential_impl'; -import { assert, fail } from '../core/util/assert'; -import { UserCredential } from '../model/user'; -import { MultiFactorAssertion } from './assertions'; -import { MultiFactorError } from './mfa_error'; -import { MultiFactorInfo } from './mfa_info'; -import { MultiFactorSession } from './mfa_session'; +import { _assert, _fail } from '../core/util/assert'; +import { UserCredentialInternal } from '../model/user'; +import { MultiFactorAssertionImpl } from './mfa_assertion'; +import { MultiFactorError as MultiFactorErrorInternal } from './mfa_error'; +import { MultiFactorInfoImpl } from './mfa_info'; +import { MultiFactorSessionImpl } from './mfa_session'; +import { getModularInstance } from '@firebase/util'; -export class MultiFactorResolver implements externs.MultiFactorResolver { +export class MultiFactorResolverImpl implements MultiFactorResolver { private constructor( - readonly session: MultiFactorSession, - readonly hints: MultiFactorInfo[], + readonly session: MultiFactorSessionImpl, + readonly hints: MultiFactorInfoImpl[], private readonly signInResolver: ( - assertion: MultiFactorAssertion - ) => Promise + assertion: MultiFactorAssertionImpl + ) => Promise ) {} + /** @internal */ static _fromError( - auth: externs.Auth, - error: MultiFactorError - ): MultiFactorResolver { + authExtern: Auth, + error: MultiFactorErrorInternal + ): MultiFactorResolverImpl { + const auth = _castAuth(authExtern); const hints = (error.serverResponse.mfaInfo || []).map(enrollment => - MultiFactorInfo._fromServerResponse(_castAuth(auth), enrollment) + MultiFactorInfoImpl._fromServerResponse(auth, enrollment) ); - assert( + _assert( error.serverResponse.mfaPendingCredential, - AuthErrorCode.INTERNAL_ERROR, - { appName: auth.name } + auth, + AuthErrorCode.INTERNAL_ERROR ); - const session = MultiFactorSession._fromMfaPendingCredential( + const session = MultiFactorSessionImpl._fromMfaPendingCredential( error.serverResponse.mfaPendingCredential ); - return new MultiFactorResolver( + return new MultiFactorResolverImpl( session, hints, - async (assertion: MultiFactorAssertion): Promise => { - const mfaResponse = await assertion._process(_castAuth(auth), session); + async ( + assertion: MultiFactorAssertionImpl + ): Promise => { + const mfaResponse = await assertion._process(auth, session); // Clear out the unneeded fields from the old login response delete error.serverResponse.mfaInfo; delete error.serverResponse.mfaPendingCredential; @@ -71,54 +82,58 @@ export class MultiFactorResolver implements externs.MultiFactorResolver { // TODO: we should collapse this switch statement into UserCredentialImpl._forOperation and have it support the SIGN_IN case switch (error.operationType) { - case externs.OperationType.SIGN_IN: + case OperationType.SIGN_IN: const userCredential = await UserCredentialImpl._fromIdTokenResponse( - _castAuth(auth), + auth, error.operationType, idTokenResponse ); - await auth.updateCurrentUser(userCredential.user); + await auth._updateCurrentUser(userCredential.user); return userCredential; - case externs.OperationType.REAUTHENTICATE: - assert(error.user, AuthErrorCode.INTERNAL_ERROR, { - appName: auth.name - }); + case OperationType.REAUTHENTICATE: + _assert(error.user, auth, AuthErrorCode.INTERNAL_ERROR); return UserCredentialImpl._forOperation( error.user, error.operationType, idTokenResponse ); default: - fail(AuthErrorCode.INTERNAL_ERROR, { appName: auth.name }); + _fail(auth, AuthErrorCode.INTERNAL_ERROR); } } ); } async resolveSignIn( - assertionExtern: externs.MultiFactorAssertion - ): Promise { - const assertion = assertionExtern as MultiFactorAssertion; + assertionExtern: MultiFactorAssertionImpl + ): Promise { + const assertion = assertionExtern as MultiFactorAssertionImpl; return this.signInResolver(assertion); } } +/** + * Provides a {@link MultiFactorResolver} suitable for completion of a + * multi-factor flow. + * + * @param auth - The auth instance. + * @param error - The {@link MultiFactorError} raised during a sign-in, or + * reauthentication operation. + * + * @public + */ export function getMultiFactorResolver( - auth: externs.Auth, - errorExtern: externs.MultiFactorError -): externs.MultiFactorResolver { - const error = errorExtern as MultiFactorError; - assert(error.operationType, AuthErrorCode.ARGUMENT_ERROR, { - appName: auth.name - }); - assert(error.credential, AuthErrorCode.ARGUMENT_ERROR, { - appName: auth.name - }); - assert( - error.serverResponse?.mfaPendingCredential, - AuthErrorCode.ARGUMENT_ERROR, - { appName: auth.name } + auth: Auth, + error: MultiFactorError +): MultiFactorResolver { + const authModular = getModularInstance(auth); + const errorInternal = error as MultiFactorErrorInternal; + _assert(error.operationType, authModular, AuthErrorCode.ARGUMENT_ERROR); + _assert( + errorInternal.serverResponse?.mfaPendingCredential, + authModular, + AuthErrorCode.ARGUMENT_ERROR ); - return MultiFactorResolver._fromError(auth, error); + return MultiFactorResolverImpl._fromError(authModular, errorInternal); } diff --git a/packages-exp/auth-exp/src/mfa/mfa_session.test.ts b/packages-exp/auth-exp/src/mfa/mfa_session.test.ts index fd4701399d4..ec603fd5f83 100644 --- a/packages-exp/auth-exp/src/mfa/mfa_session.test.ts +++ b/packages-exp/auth-exp/src/mfa/mfa_session.test.ts @@ -17,7 +17,7 @@ import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; -import { MultiFactorSession, MultiFactorSessionType } from './mfa_session'; +import { MultiFactorSessionImpl, MultiFactorSessionType } from './mfa_session'; use(chaiAsPromised); @@ -25,7 +25,7 @@ describe('core/mfa/mfa_session/MultiFactorSession', () => { describe('toJSON', () => { context('ENROLL', () => { it('should serialize correctly', () => { - const mfaSession = MultiFactorSession._fromIdtoken('id-token'); + const mfaSession = MultiFactorSessionImpl._fromIdtoken('id-token'); expect(mfaSession.toJSON()).to.eql({ multiFactorSession: { idToken: 'id-token' } }); @@ -34,7 +34,7 @@ describe('core/mfa/mfa_session/MultiFactorSession', () => { context('SIGN_IN', () => { it('should serialize correctly', () => { - const mfaSession = MultiFactorSession._fromMfaPendingCredential( + const mfaSession = MultiFactorSessionImpl._fromMfaPendingCredential( 'mfa-pending-credential' ); expect(mfaSession.toJSON()).to.eql({ @@ -47,10 +47,10 @@ describe('core/mfa/mfa_session/MultiFactorSession', () => { describe('.fromJSON', () => { context('ENROLL', () => { it('should deserialize correctly', () => { - const mfaSession = MultiFactorSession.fromJSON({ + const mfaSession = MultiFactorSessionImpl.fromJSON({ multiFactorSession: { idToken: 'id-token' } }); - expect(mfaSession).to.be.instanceOf(MultiFactorSession); + expect(mfaSession).to.be.instanceOf(MultiFactorSessionImpl); expect(mfaSession!.type).to.eq(MultiFactorSessionType.ENROLL); expect(mfaSession!.credential).to.eq('id-token'); }); @@ -58,10 +58,10 @@ describe('core/mfa/mfa_session/MultiFactorSession', () => { context('SIGN_IN', () => { it('should deserialize correctly', () => { - const mfaSession = MultiFactorSession.fromJSON({ + const mfaSession = MultiFactorSessionImpl.fromJSON({ multiFactorSession: { pendingCredential: 'mfa-pending-credential' } }); - expect(mfaSession).to.be.instanceOf(MultiFactorSession); + expect(mfaSession).to.be.instanceOf(MultiFactorSessionImpl); expect(mfaSession!.type).to.eq(MultiFactorSessionType.SIGN_IN); expect(mfaSession!.credential).to.eq('mfa-pending-credential'); }); @@ -69,8 +69,8 @@ describe('core/mfa/mfa_session/MultiFactorSession', () => { context('invalid', () => { it('should return null', () => { - expect(MultiFactorSession.fromJSON({ multiFactorSession: {} })).to.be - .null; + expect(MultiFactorSessionImpl.fromJSON({ multiFactorSession: {} })).to + .be.null; }); }); }); diff --git a/packages-exp/auth-exp/src/mfa/mfa_session.ts b/packages-exp/auth-exp/src/mfa/mfa_session.ts index 7de06f8ff6d..f9b8452d06d 100644 --- a/packages-exp/auth-exp/src/mfa/mfa_session.ts +++ b/packages-exp/auth-exp/src/mfa/mfa_session.ts @@ -14,9 +14,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { MultiFactorSession } from '../model/public_types'; -export enum MultiFactorSessionType { +export const enum MultiFactorSessionType { ENROLL = 'enroll', SIGN_IN = 'signin' } @@ -28,20 +28,20 @@ interface SerializedMultiFactorSession { }; } -export class MultiFactorSession implements externs.MultiFactorSession { +export class MultiFactorSessionImpl implements MultiFactorSession { private constructor( readonly type: MultiFactorSessionType, readonly credential: string ) {} - static _fromIdtoken(idToken: string): MultiFactorSession { - return new MultiFactorSession(MultiFactorSessionType.ENROLL, idToken); + static _fromIdtoken(idToken: string): MultiFactorSessionImpl { + return new MultiFactorSessionImpl(MultiFactorSessionType.ENROLL, idToken); } static _fromMfaPendingCredential( mfaPendingCredential: string - ): MultiFactorSession { - return new MultiFactorSession( + ): MultiFactorSessionImpl { + return new MultiFactorSessionImpl( MultiFactorSessionType.SIGN_IN, mfaPendingCredential ); @@ -61,14 +61,16 @@ export class MultiFactorSession implements externs.MultiFactorSession { static fromJSON( obj: Partial - ): MultiFactorSession | null { + ): MultiFactorSessionImpl | null { if (obj?.multiFactorSession) { if (obj.multiFactorSession?.pendingCredential) { - return MultiFactorSession._fromMfaPendingCredential( + return MultiFactorSessionImpl._fromMfaPendingCredential( obj.multiFactorSession.pendingCredential ); } else if (obj.multiFactorSession?.idToken) { - return MultiFactorSession._fromIdtoken(obj.multiFactorSession.idToken); + return MultiFactorSessionImpl._fromIdtoken( + obj.multiFactorSession.idToken + ); } } return null; diff --git a/packages-exp/auth-exp/src/mfa/mfa_user.test.ts b/packages-exp/auth-exp/src/mfa/mfa_user.test.ts index d8ae82eb8be..44188171e66 100644 --- a/packages-exp/auth-exp/src/mfa/mfa_user.test.ts +++ b/packages-exp/auth-exp/src/mfa/mfa_user.test.ts @@ -17,8 +17,9 @@ import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; +import * as sinon from 'sinon'; -import { ProviderId } from '@firebase/auth-types-exp'; +import { FactorId } from '../model/public_types'; import { mockEndpoint } from '../../test/helpers/api/helper'; import { testAuth, testUser, TestAuth } from '../../test/helpers/mock_auth'; @@ -27,29 +28,30 @@ import { Endpoint } from '../api'; import { APIUserInfo } from '../api/account_management/account'; import { FinalizeMfaResponse } from '../api/authentication/mfa'; import { ServerError } from '../api/errors'; -import { User } from '../model/user'; -import { MultiFactorInfo } from './mfa_info'; -import { MultiFactorSession, MultiFactorSessionType } from './mfa_session'; -import { multiFactor, MultiFactorUser } from './mfa_user'; -import { MultiFactorAssertion } from './assertions'; -import { Auth } from '../model/auth'; +import { UserInternal } from '../model/user'; +import { MultiFactorInfoImpl } from './mfa_info'; +import { MultiFactorSessionImpl, MultiFactorSessionType } from './mfa_session'; +import { multiFactor, MultiFactorUserImpl } from './mfa_user'; +import { MultiFactorAssertionImpl } from './mfa_assertion'; +import { AuthInternal } from '../model/auth'; +import { makeJWT } from '../../test/helpers/jwt'; use(chaiAsPromised); -class MockMultiFactorAssertion extends MultiFactorAssertion { +class MockMultiFactorAssertion extends MultiFactorAssertionImpl { constructor(readonly response: FinalizeMfaResponse) { - super(ProviderId.PHONE); + super(FactorId.PHONE); } async _finalizeEnroll( - _auth: Auth, + _auth: AuthInternal, _idToken: string, _displayName?: string | null ): Promise { return this.response; } async _finalizeSignIn( - _auth: Auth, + _auth: AuthInternal, _mfaPendingCredential: string ): Promise { return this.response; @@ -57,27 +59,35 @@ class MockMultiFactorAssertion extends MultiFactorAssertion { } describe('core/mfa/mfa_user/MultiFactorUser', () => { + const idToken = makeJWT({ 'exp': '3600', 'iat': '1200' }); let auth: TestAuth; - let mfaUser: MultiFactorUser; + let mfaUser: MultiFactorUserImpl; + let clock: sinon.SinonFakeTimers; beforeEach(async () => { auth = await testAuth(); mockFetch.setUp(); - mfaUser = MultiFactorUser._fromUser(testUser(auth, 'uid', undefined, true)); + clock = sinon.useFakeTimers(); + mfaUser = MultiFactorUserImpl._fromUser( + testUser(auth, 'uid', undefined, true) + ); }); - afterEach(mockFetch.tearDown); + afterEach(() => { + mockFetch.tearDown(); + sinon.restore(); + }); describe('getSession', () => { it('should return the id token', async () => { - const mfaSession = (await mfaUser.getSession()) as MultiFactorSession; + const mfaSession = (await mfaUser.getSession()) as MultiFactorSessionImpl; expect(mfaSession.type).to.eq(MultiFactorSessionType.ENROLL); expect(mfaSession.credential).to.eq('access-token'); }); }); describe('enroll', () => { - let assertion: MultiFactorAssertion; + let assertion: MultiFactorAssertionImpl; const serverUser: APIUserInfo = { localId: 'local-id', @@ -99,7 +109,7 @@ describe('core/mfa/mfa_user/MultiFactorUser', () => { }; const serverResponse: FinalizeMfaResponse = { - idToken: 'final-id-token', + idToken, refreshToken: 'refresh-token' }; @@ -114,7 +124,10 @@ describe('core/mfa/mfa_user/MultiFactorUser', () => { it('should update the tokens', async () => { await mfaUser.enroll(assertion); - expect(await mfaUser.user.getIdToken()).to.eq('final-id-token'); + expect(await mfaUser.user.getIdToken()).to.eq(idToken); + expect(mfaUser.user.stsTokenManager.expirationTime).to.eq( + clock.now + 2400 * 1000 + ); }); it('should update the enrolled Factors', async () => { @@ -122,7 +135,7 @@ describe('core/mfa/mfa_user/MultiFactorUser', () => { expect(mfaUser.enrolledFactors.length).to.eq(1); const enrolledFactor = mfaUser.enrolledFactors[0]; - expect(enrolledFactor.factorId).to.eq(ProviderId.PHONE); + expect(enrolledFactor.factorId).to.eq(FactorId.PHONE); expect(enrolledFactor.uid).to.eq('mfa-id'); }); }); @@ -131,17 +144,17 @@ describe('core/mfa/mfa_user/MultiFactorUser', () => { let withdrawMfaEnrollmentMock: mockFetch.Route; const serverResponse: FinalizeMfaResponse = { - idToken: 'final-id-token', + idToken, refreshToken: 'refresh-token' }; - const mfaInfo = MultiFactorInfo._fromServerResponse(auth, { + const mfaInfo = MultiFactorInfoImpl._fromServerResponse(auth, { mfaEnrollmentId: 'mfa-id', enrolledAt: Date.now(), phoneInfo: 'phone-info' }); - const otherMfaInfo = MultiFactorInfo._fromServerResponse(auth, { + const otherMfaInfo = MultiFactorInfoImpl._fromServerResponse(auth, { mfaEnrollmentId: 'other-mfa-id', enrolledAt: Date.now(), phoneInfo: 'other-phone-info' @@ -199,7 +212,10 @@ describe('core/mfa/mfa_user/MultiFactorUser', () => { it('should update the tokens', async () => { await mfaUser.unenroll(mfaInfo); - expect(await mfaUser.user.getIdToken()).to.eq('final-id-token'); + expect(await mfaUser.user.getIdToken()).to.eq(idToken); + expect(mfaUser.user.stsTokenManager.expirationTime).to.eq( + clock.now + 2400 * 1000 + ); }); context('token revoked by backend', () => { @@ -224,7 +240,7 @@ describe('core/mfa/mfa_user/MultiFactorUser', () => { describe('core/mfa/mfa_user/multiFactor', () => { let auth: TestAuth; - let user: User; + let user: UserInternal; beforeEach(async () => { auth = await testAuth(); @@ -233,7 +249,7 @@ describe('core/mfa/mfa_user/multiFactor', () => { it('can be used to a create a MultiFactorUser', () => { const mfaUser = multiFactor(user); - expect((mfaUser as MultiFactorUser).user).to.eq(user); + expect((mfaUser as MultiFactorUserImpl).user).to.eq(user); }); it('should only create one instance of an MFA user per User', () => { @@ -284,7 +300,7 @@ describe('core/mfa/mfa_user/multiFactor', () => { expect(mfaUser.enrolledFactors.length).to.eq(1); const mfaInfo = mfaUser.enrolledFactors[0]; expect(mfaInfo.uid).to.eq('enrollment-id'); - expect(mfaInfo.factorId).to.eq(ProviderId.PHONE); + expect(mfaInfo.factorId).to.eq(FactorId.PHONE); }); it('should update the enrolled factors if the user is reloaded', async () => { diff --git a/packages-exp/auth-exp/src/mfa/mfa_user.ts b/packages-exp/auth-exp/src/mfa/mfa_user.ts index cc6a0a7f7f6..3b8540f9126 100644 --- a/packages-exp/auth-exp/src/mfa/mfa_user.ts +++ b/packages-exp/auth-exp/src/mfa/mfa_user.ts @@ -14,43 +14,50 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { + MultiFactorAssertion, + MultiFactorInfo, + MultiFactorSession, + MultiFactorUser, + User +} from '../model/public_types'; import { withdrawMfa } from '../api/account_management/mfa'; import { AuthErrorCode } from '../core/errors'; import { _logoutIfInvalidated } from '../core/user/invalidation'; -import { User } from '../model/user'; -import { MultiFactorAssertion } from './assertions'; -import { MultiFactorInfo } from './mfa_info'; -import { MultiFactorSession } from './mfa_session'; +import { UserInternal } from '../model/user'; +import { MultiFactorAssertionImpl } from './mfa_assertion'; +import { MultiFactorInfoImpl } from './mfa_info'; +import { MultiFactorSessionImpl } from './mfa_session'; +import { getModularInstance } from '@firebase/util'; -export class MultiFactorUser implements externs.MultiFactorUser { - enrolledFactors: externs.MultiFactorInfo[] = []; +export class MultiFactorUserImpl implements MultiFactorUser { + enrolledFactors: MultiFactorInfo[] = []; - private constructor(readonly user: User) { + private constructor(readonly user: UserInternal) { user._onReload(userInfo => { if (userInfo.mfaInfo) { this.enrolledFactors = userInfo.mfaInfo.map(enrollment => - MultiFactorInfo._fromServerResponse(user.auth, enrollment) + MultiFactorInfoImpl._fromServerResponse(user.auth, enrollment) ); } }); } - static _fromUser(user: User): MultiFactorUser { - return new MultiFactorUser(user); + static _fromUser(user: UserInternal): MultiFactorUserImpl { + return new MultiFactorUserImpl(user); } - async getSession(): Promise { - return MultiFactorSession._fromIdtoken(await this.user.getIdToken()); + async getSession(): Promise { + return MultiFactorSessionImpl._fromIdtoken(await this.user.getIdToken()); } async enroll( - assertionExtern: externs.MultiFactorAssertion, + assertionExtern: MultiFactorAssertion, displayName?: string | null ): Promise { - const assertion = assertionExtern as MultiFactorAssertion; - const session = (await this.getSession()) as MultiFactorSession; + const assertion = assertionExtern as MultiFactorAssertionImpl; + const session = (await this.getSession()) as MultiFactorSessionImpl; const finalizeMfaResponse = await _logoutIfInvalidated( this.user, assertion._process(this.user.auth, session, displayName) @@ -64,7 +71,7 @@ export class MultiFactorUser implements externs.MultiFactorUser { return this.user.reload(); } - async unenroll(infoOrUid: externs.MultiFactorInfo | string): Promise { + async unenroll(infoOrUid: MultiFactorInfo | string): Promise { const mfaEnrollmentId = typeof infoOrUid === 'string' ? infoOrUid : infoOrUid.uid; const idToken = await this.user.getIdToken(); @@ -94,14 +101,25 @@ export class MultiFactorUser implements externs.MultiFactorUser { } } -const multiFactorUserCache = new WeakMap< - externs.User, - externs.MultiFactorUser ->(); +const multiFactorUserCache = new WeakMap(); -export function multiFactor(user: externs.User): externs.MultiFactorUser { - if (!multiFactorUserCache.has(user)) { - multiFactorUserCache.set(user, MultiFactorUser._fromUser(user as User)); +/** + * The {@link MultiFactorUser} corresponding to the user. + * + * @remarks + * This is used to access all multi-factor properties and operations related to the user. + * + * @param user - The user. + * + * @public + */ +export function multiFactor(user: User): MultiFactorUser { + const userModular = getModularInstance(user); + if (!multiFactorUserCache.has(userModular)) { + multiFactorUserCache.set( + userModular, + MultiFactorUserImpl._fromUser(userModular as UserInternal) + ); } - return multiFactorUserCache.get(user)!; + return multiFactorUserCache.get(userModular)!; } diff --git a/packages-exp/auth-exp/src/model/application_verifier.ts b/packages-exp/auth-exp/src/model/application_verifier.ts index 595ff025e05..d23f625ee11 100644 --- a/packages-exp/auth-exp/src/model/application_verifier.ts +++ b/packages-exp/auth-exp/src/model/application_verifier.ts @@ -15,8 +15,11 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { ApplicationVerifier } from './public_types'; -export interface ApplicationVerifier extends externs.ApplicationVerifier { +export interface ApplicationVerifierInternal extends ApplicationVerifier { + /** + * @internal + */ _reset(): void; } diff --git a/packages-exp/auth-exp/src/model/auth.ts b/packages-exp/auth-exp/src/model/auth.ts index 91bd7f20b72..ccb538ad124 100644 --- a/packages-exp/auth-exp/src/model/auth.ts +++ b/packages-exp/auth-exp/src/model/auth.ts @@ -15,53 +15,78 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { + Auth, + AuthSettings, + Config, + EmulatorConfig, + PopupRedirectResolver, + User +} from './public_types'; +import { ErrorFactory } from '@firebase/util'; +import { AuthErrorCode, AuthErrorParams } from '../core/errors'; -import { PopupRedirectResolver } from './popup_redirect'; -import { User } from './user'; +import { PopupRedirectResolverInternal } from './popup_redirect'; +import { UserInternal } from './user'; +import { ClientPlatform } from '../core/util/version'; export type AppName = string; export type ApiKey = string; export type AuthDomain = string; -export interface ConfigInternal extends externs.Config { +export interface ConfigInternal extends Config { + /** + * @readonly + */ emulator?: { url: string; }; + + /** + * @readonly + */ + clientPlatform: ClientPlatform; } -export interface Auth extends externs.Auth { - currentUser: externs.User | null; +/** + * UserInternal and AuthInternal reference each other, so both of them are included in the public typings. + * In order to exclude them, we mark them as internal explicitly. + * + * @internal + */ +export interface AuthInternal extends Auth { + currentUser: User | null; + emulatorConfig: EmulatorConfig | null; _canInitEmulator: boolean; _isInitialized: boolean; _initializationPromise: Promise | null; - updateCurrentUser(user: User | null): Promise; + _updateCurrentUser(user: UserInternal | null): Promise; _onStorageEvent(): void; - _notifyListenersIfCurrent(user: User): void; - _persistUserIfCurrent(user: User): Promise; + _notifyListenersIfCurrent(user: UserInternal): void; + _persistUserIfCurrent(user: UserInternal): Promise; _setRedirectUser( - user: User | null, - popupRedirectResolver?: externs.PopupRedirectResolver + user: UserInternal | null, + popupRedirectResolver?: PopupRedirectResolver ): Promise; - _redirectUserForId(id: string): Promise; - _popupRedirectResolver: PopupRedirectResolver | null; + _redirectUserForId(id: string): Promise; + _popupRedirectResolver: PopupRedirectResolverInternal | null; _key(): string; _startProactiveRefresh(): void; _stopProactiveRefresh(): void; + _getPersistence(): string; + _logFramework(framework: string): void; + _getFrameworks(): readonly string[]; + _getSdkClientVersion(): string; readonly name: AppName; readonly config: ConfigInternal; languageCode: string | null; tenantId: string | null; - readonly settings: externs.AuthSettings; + readonly settings: AuthSettings; + _errorFactory: ErrorFactory; useDeviceLanguage(): void; signOut(): Promise; } - -export interface Dependencies { - persistence?: externs.Persistence | externs.Persistence[]; - popupRedirectResolver?: externs.PopupRedirectResolver; -} diff --git a/packages-exp/auth-exp/src/model/enum_maps.ts b/packages-exp/auth-exp/src/model/enum_maps.ts new file mode 100644 index 00000000000..4d3e3f4a59c --- /dev/null +++ b/packages-exp/auth-exp/src/model/enum_maps.ts @@ -0,0 +1,102 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * An enum of factors that may be used for multifactor authentication. + * + * @public + */ +export const FactorId = { + /** Phone as second factor */ + PHONE: 'phone' +} as const; + +/** + * Enumeration of supported providers. + * + * @public + */ +export const ProviderId = { + /** Facebook provider ID */ + FACEBOOK: 'facebook.com', + /** GitHub provider ID */ + GITHUB: 'github.com', + /** Google provider ID */ + GOOGLE: 'google.com', + /** Password provider */ + PASSWORD: 'password', + /** Phone provider */ + PHONE: 'phone', + /** Twitter provider ID */ + TWITTER: 'twitter.com' +} as const; + +/** + * Enumeration of supported sign-in methods. + * + * @public + */ +export const SignInMethod = { + /** Email link sign in method */ + EMAIL_LINK: 'emailLink', + /** Email/password sign in method */ + EMAIL_PASSWORD: 'password', + /** Facebook sign in method */ + FACEBOOK: 'facebook.com', + /** GitHub sign in method */ + GITHUB: 'github.com', + /** Google sign in method */ + GOOGLE: 'google.com', + /** Phone sign in method */ + PHONE: 'phone', + /** Twitter sign in method */ + TWITTER: 'twitter.com' +} as const; + +/** + * Enumeration of supported operation types. + * + * @public + */ +export const OperationType = { + /** Operation involving linking an additional provider to an already signed-in user. */ + LINK: 'link', + /** Operation involving using a provider to reauthenticate an already signed-in user. */ + REAUTHENTICATE: 'reauthenticate', + /** Operation involving signing in a user. */ + SIGN_IN: 'signIn' +} as const; + +/** + * An enumeration of the possible email action types. + * + * @public + */ +export const ActionCodeOperation = { + /** The email link sign-in action. */ + EMAIL_SIGNIN: 'EMAIL_SIGNIN', + /** The password reset action. */ + PASSWORD_RESET: 'PASSWORD_RESET', + /** The email revocation action. */ + RECOVER_EMAIL: 'RECOVER_EMAIL', + /** The revert second factor addition email action. */ + REVERT_SECOND_FACTOR_ADDITION: 'REVERT_SECOND_FACTOR_ADDITION', + /** The revert second factor addition email action. */ + VERIFY_AND_CHANGE_EMAIL: 'VERIFY_AND_CHANGE_EMAIL', + /** The email verification action. */ + VERIFY_EMAIL: 'VERIFY_EMAIL' +} as const; diff --git a/packages-exp/auth-exp/src/model/id_token.ts b/packages-exp/auth-exp/src/model/id_token.ts index e2bcd84ac6f..d66aacd1498 100644 --- a/packages-exp/auth-exp/src/model/id_token.ts +++ b/packages-exp/auth-exp/src/model/id_token.ts @@ -15,17 +15,19 @@ * limitations under the License. */ -import { ProviderId } from '@firebase/auth-types-exp'; +import { ProviderId } from './public_types'; import { PhoneOrOauthTokenResponse } from '../api/authentication/mfa'; /** * Raw encoded JWT + * */ export type IdToken = string; /** * Raw parsed JWT + * */ export interface ParsedIdToken { iss: string; @@ -46,13 +48,14 @@ export interface ParsedIdToken { /** * IdToken as returned by the API + * */ export interface IdTokenResponse { localId: string; idToken?: IdToken; refreshToken?: string; expiresIn?: string; - providerId?: ProviderId; + providerId?: ProviderId | string; // Used in AdditionalUserInfo displayName?: string | null; @@ -65,6 +68,7 @@ export interface IdTokenResponse { /** * The possible types of the `IdTokenResponse` + * */ export const enum IdTokenResponseKind { CreateAuthUri = 'identitytoolkit#CreateAuthUriResponse', diff --git a/packages-exp/auth-exp/src/model/popup_redirect.ts b/packages-exp/auth-exp/src/model/popup_redirect.ts index 23908c3e25f..a0b6ba6b3a9 100644 --- a/packages-exp/auth-exp/src/model/popup_redirect.ts +++ b/packages-exp/auth-exp/src/model/popup_redirect.ts @@ -15,11 +15,17 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { + Auth, + AuthProvider, + Persistence, + PopupRedirectResolver, + UserCredential +} from './public_types'; import { FirebaseError } from '@firebase/util'; import { AuthPopup } from '../platform_browser/util/popup'; -import { Auth } from './auth'; +import { AuthInternal } from './auth'; export const enum EventFilter { POPUP, @@ -73,23 +79,40 @@ export interface EventManager { unregisterConsumer(authEventConsumer: AuthEventConsumer): void; } -export interface PopupRedirectResolver extends externs.PopupRedirectResolver { - _initialize(auth: Auth): Promise; +/** + * We need to mark this interface as internal explicitly to exclude it in the public typings, because + * it references AuthInternal which has a circular dependency with UserInternal. + * + * @internal + */ +export interface PopupRedirectResolverInternal extends PopupRedirectResolver { + // Whether or not to initialize the event manager early + _shouldInitProactively: boolean; + + _initialize(auth: AuthInternal): Promise; _openPopup( - auth: Auth, - provider: externs.AuthProvider, + auth: AuthInternal, + provider: AuthProvider, authType: AuthEventType, eventId?: string ): Promise; _openRedirect( - auth: Auth, - provider: externs.AuthProvider, + auth: AuthInternal, + provider: AuthProvider, authType: AuthEventType, eventId?: string - ): Promise; + ): Promise; _isIframeWebStorageSupported( - auth: Auth, + auth: AuthInternal, cb: (support: boolean) => unknown ): void; - _redirectPersistence: externs.Persistence; + _redirectPersistence: Persistence; + _originValidation(auth: Auth): Promise; + + // This is needed so that auth does not have a hard dependency on redirect + _completeRedirectFn: ( + auth: Auth, + resolver: PopupRedirectResolver, + bypassAuthState: boolean + ) => Promise; } diff --git a/packages-exp/auth-exp/src/model/public_types.ts b/packages-exp/auth-exp/src/model/public_types.ts new file mode 100644 index 00000000000..afaf89de2a2 --- /dev/null +++ b/packages-exp/auth-exp/src/model/public_types.ts @@ -0,0 +1,1239 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + CompleteFn, + ErrorFn, + FirebaseError, + NextFn, + Observer, + Unsubscribe +} from '@firebase/util'; + +import { + FactorId as FactorIdMap, + OperationType as OperationTypeMap, + ActionCodeOperation as ActionCodeOperationMap +} from './enum_maps'; + +export { CompleteFn, ErrorFn, NextFn, Unsubscribe }; +/** + * Enumeration of supported providers. + * + * @internal + */ +export const enum ProviderId { + /** @internal */ + ANONYMOUS = 'anonymous', + /** @internal */ + CUSTOM = 'custom', + /** Facebook provider ID */ + FACEBOOK = 'facebook.com', + /** @internal */ + FIREBASE = 'firebase', + /** GitHub provider ID */ + GITHUB = 'github.com', + /** Google provider ID */ + GOOGLE = 'google.com', + /** Password provider */ + PASSWORD = 'password', + /** Phone provider */ + PHONE = 'phone', + /** Twitter provider ID */ + TWITTER = 'twitter.com' +} + +/** + * Enumeration of supported sign-in methods. + * + * @internal + */ +export const enum SignInMethod { + /** @internal */ + ANONYMOUS = 'anonymous', + /** Email link sign in method */ + EMAIL_LINK = 'emailLink', + /** Email/password sign in method */ + EMAIL_PASSWORD = 'password', + /** Facebook sign in method */ + FACEBOOK = 'facebook.com', + /** GitHub sign in method */ + GITHUB = 'github.com', + /** Google sign in method */ + GOOGLE = 'google.com', + /** Phone sign in method */ + PHONE = 'phone', + /** Twitter sign in method */ + TWITTER = 'twitter.com' +} + +/** + * Enumeration of supported operation types. + * + * @internal + */ +export const enum OperationType { + /** Operation involving linking an additional provider to an already signed-in user. */ + LINK = 'link', + /** Operation involving using a provider to reauthenticate an already signed-in user. */ + REAUTHENTICATE = 'reauthenticate', + /** Operation involving signing in a user. */ + SIGN_IN = 'signIn' +} + +/** + * Interface representing the Auth config. + * + * @public + */ +export interface Config { + /** + * The API Key used to communicate with the Firebase Auth backend. + */ + apiKey: string; + /** + * The host at which the Firebase Auth backend is running. + */ + apiHost: string; + /** + * The scheme used to communicate with the Firebase Auth backend. + */ + apiScheme: string; + /** + * The host at which the Secure Token API is running. + */ + tokenApiHost: string; + /** + * The SDK Client Version. + */ + sdkClientVersion: string; + /** + * The domain at which the web widgets are hosted (provided via Firebase Config). + */ + authDomain?: string; +} + +/** + * Interface representing a parsed ID token. + * + * @privateRemarks TODO(avolkovi): consolidate with parsed_token in implementation. + * + * @public + */ +export interface ParsedToken { + /** Expiration time of the token. */ + 'exp'?: string; + /** UID of the user. */ + 'sub'?: string; + /** Time at which authentication was performed. */ + 'auth_time'?: string; + /** Issuance time of the token. */ + 'iat'?: string; + /** Firebase specific claims, containing the provider(s) used to authenticate the user. */ + 'firebase'?: { + 'sign_in_provider'?: string; + 'sign_in_second_factor'?: string; + }; + /** Map of any additional custom claims. */ + [key: string]: string | object | undefined; +} + +/** + * Type definition for an event callback. + * + * @privateRemarks TODO(avolkovi): should we consolidate with Subscribe since we're changing the API anyway? + * + * @public + */ +export type NextOrObserver = NextFn | Observer; + +/** + * Interface for an Auth error. + * + * @public + */ +export interface AuthError extends FirebaseError { + /** The name of the Firebase App which triggered this error. */ + readonly appName: string; + /** The email of the user's account, used for sign-in/linking. */ + readonly email?: string; + /** The phone number of the user's account, used for sign-in/linking. */ + readonly phoneNumber?: string; + /** + * The tenant ID being used for sign-in/linking. + * + * @remarks + * If you use {@link signInWithRedirect} to sign in, + * you have to set the tenant ID on {@link Auth} instance again as the tenant ID is not persisted + * after redirection. + */ + readonly tenantid?: string; +} + +/** + * Interface representing an Auth instance's settings. + * + * @remarks Currently used for enabling/disabling app verification for phone Auth testing. + * + * @public + */ +export interface AuthSettings { + /** + * When set, this property disables app verification for the purpose of testing phone + * authentication. For this property to take effect, it needs to be set before rendering a + * reCAPTCHA app verifier. When this is disabled, a mock reCAPTCHA is rendered instead. This is + * useful for manual testing during development or for automated integration tests. + * + * In order to use this feature, you will need to + * {@link https://firebase.google.com/docs/auth/web/phone-auth#test-with-whitelisted-phone-numbers | whitelist your phone number} + * via the Firebase Console. + * + * The default value is false (app verification is enabled). + */ + appVerificationDisabledForTesting: boolean; +} + +/** + * Interface representing Firebase Auth service. + * + * @remarks + * See {@link https://firebase.google.com/docs/auth/ | Firebase Authentication} for a full guide + * on how to use the Firebase Auth service. + * + * @public + */ +export interface Auth { + /** The name of the app associated with the Auth service instance. */ + readonly name: string; + /** The {@link Config} used to initialize this instance. */ + readonly config: Config; + /** + * Changes the type of persistence on the Auth instance. + * + * @remarks + * This will affect the currently saved Auth session and applies this type of persistence for + * future sign-in requests, including sign-in with redirect requests. + * + * This makes it easy for a user signing in to specify whether their session should be + * remembered or not. It also makes it easier to never persist the Auth state for applications + * that are shared by other users or have sensitive data. + * + * @example + * ```javascript + * auth.setPersistence(browserSessionPersistence); + * ``` + * + * @param persistence - The {@link Persistence} to use. + */ + setPersistence(persistence: Persistence): Promise; + /** + * The Auth instance's language code. + * + * @remarks + * This is a readable/writable property. When set to null, the default Firebase Console language + * setting is applied. The language code will propagate to email action templates (password + * reset, email verification and email change revocation), SMS templates for phone authentication, + * reCAPTCHA verifier and OAuth popup/redirect operations provided the specified providers support + * localization with the language code specified. + */ + languageCode: string | null; + /** + * The Auth instance's tenant ID. + * + * @remarks + * This is a readable/writable property. When you set the tenant ID of an Auth instance, all + * future sign-in/sign-up operations will pass this tenant ID and sign in or sign up users to + * the specified tenant project. When set to null, users are signed in to the parent project. + * + * @example + * ```javascript + * // Set the tenant ID on Auth instance. + * auth.tenantId = 'TENANT_PROJECT_ID'; + * + * // All future sign-in request now include tenant ID. + * const result = await signInWithEmailAndPassword(auth, email, password); + * // result.user.tenantId should be 'TENANT_PROJECT_ID'. + * ``` + * + * @defaultValue null + */ + tenantId: string | null; + /** + * The Auth instance's settings. + * + * @remarks + * This is used to edit/read configuration related options such as app verification mode for + * phone authentication. + */ + readonly settings: AuthSettings; + /** + * Adds an observer for changes to the user's sign-in state. + * + * @remarks + * To keep the old behavior, see {@link Auth.onIdTokenChanged}. + * + * @param nextOrObserver - callback triggered on change. + * @param error - callback triggered on error. + * @param completed - callback triggered when observer is removed. + */ + onAuthStateChanged( + nextOrObserver: NextOrObserver, + error?: ErrorFn, + completed?: CompleteFn + ): Unsubscribe; + /** + * Adds an observer for changes to the signed-in user's ID token. + * + * @remarks + * This includes sign-in, sign-out, and token refresh events. + * + * @param nextOrObserver - callback triggered on change. + * @param error - callback triggered on error. + * @param completed - callback triggered when observer is removed. + */ + onIdTokenChanged( + nextOrObserver: NextOrObserver, + error?: ErrorFn, + completed?: CompleteFn + ): Unsubscribe; + /** The currently signed-in user (or null). */ + readonly currentUser: User | null; + /** The current emulator configuration (or null). */ + readonly emulatorConfig: EmulatorConfig | null; + /** + * Asynchronously sets the provided user as {@link Auth.currentUser} on the {@link Auth} instance. + * + * @remarks + * A new instance copy of the user provided will be made and set as currentUser. + * + * This will trigger {@link Auth.onAuthStateChanged} and {@link Auth.onIdTokenChanged} listeners + * like other sign in methods. + * + * The operation fails with an error if the user to be updated belongs to a different Firebase + * project. + * + * @param user - The new {@link User}. + */ + updateCurrentUser(user: User | null): Promise; + /** + * Sets the current language to the default device/browser preference. + */ + useDeviceLanguage(): void; + /** + * Signs out the current user. + */ + signOut(): Promise; +} + +/** + * An interface covering the possible persistence mechanism types. + * + * @public + */ +export interface Persistence { + /** + * Type of Persistence. + * - 'SESSION' is used for temporary persistence such as `sessionStorage`. + * - 'LOCAL' is used for long term persistence such as `localStorage` or `IndexedDB`. + * - 'NONE' is used for in-memory, or no persistence. + */ + readonly type: 'SESSION' | 'LOCAL' | 'NONE'; +} + +/** + * Interface representing ID token result obtained from {@link User.getIdTokenResult}. + * + * @remarks + * It contains the ID token JWT string and other helper properties for getting different data + * associated with the token as well as all the decoded payload claims. + * + * Note that these claims are not to be trusted as they are parsed client side. Only server side + * verification can guarantee the integrity of the token claims. + * + * @public + */ +export interface IdTokenResult { + /** + * The authentication time formatted as a UTC string. + * + * @remarks + * This is the time the user authenticated (signed in) and not the time the token was refreshed. + */ + authTime: string; + /** The ID token expiration time formatted as a UTC string. */ + expirationTime: string; + /** The ID token issuance time formatted as a UTC string. */ + issuedAtTime: string; + /** + * The sign-in provider through which the ID token was obtained (anonymous, custom, phone, + * password, etc). + * + * @remarks + * Note, this does not map to provider IDs. + */ + signInProvider: string | null; + /** + * The type of second factor associated with this session, provided the user was multi-factor + * authenticated (eg. phone, etc). + */ + signInSecondFactor: string | null; + /** The Firebase Auth ID token JWT string. */ + token: string; + /** + * The entire payload claims of the ID token including the standard reserved claims as well as + * the custom claims. + */ + claims: ParsedToken; +} + +/** + * A response from {@link checkActionCode}. + * + * @public + */ +export interface ActionCodeInfo { + /** + * The data associated with the action code. + * + * @remarks + * For the {@link ActionCodeOperation}.PASSWORD_RESET, {@link ActionCodeOperation}.VERIFY_EMAIL, and + * {@link ActionCodeOperation}.RECOVER_EMAIL actions, this object contains an email field with the address + * the email was sent to. + * + * For the {@link ActionCodeOperation}.RECOVER_EMAIL action, which allows a user to undo an email address + * change, this object also contains a `previousEmail` field with the user account's current + * email address. After the action completes, the user's email address will revert to the value + * in the `email` field from the value in `previousEmail` field. + * + * For the {@link ActionCodeOperation}.VERIFY_AND_CHANGE_EMAIL action, which allows a user to verify the + * email before updating it, this object contains a `previousEmail` field with the user account's + * email address before updating. After the action completes, the user's email address will be + * updated to the value in the `email` field from the value in `previousEmail` field. + * + * For the {@link ActionCodeOperation}.REVERT_SECOND_FACTOR_ADDITION action, which allows a user to + * unenroll a newly added second factor, this object contains a `multiFactorInfo` field with + * the information about the second factor. For phone second factor, the `multiFactorInfo` + * is a {@link MultiFactorInfo} object, which contains the phone number. + */ + data: { + email?: string | null; + multiFactorInfo?: MultiFactorInfo | null; + previousEmail?: string | null; + }; + /** + * The type of operation that generated the action code. + */ + operation: typeof ActionCodeOperationMap[keyof typeof ActionCodeOperationMap]; +} + +/** + * An enumeration of the possible email action types. + * + * @internal + */ +export const enum ActionCodeOperation { + /** The email link sign-in action. */ + EMAIL_SIGNIN = 'EMAIL_SIGNIN', + /** The password reset action. */ + PASSWORD_RESET = 'PASSWORD_RESET', + /** The email revocation action. */ + RECOVER_EMAIL = 'RECOVER_EMAIL', + /** The revert second factor addition email action. */ + REVERT_SECOND_FACTOR_ADDITION = 'REVERT_SECOND_FACTOR_ADDITION', + /** The revert second factor addition email action. */ + VERIFY_AND_CHANGE_EMAIL = 'VERIFY_AND_CHANGE_EMAIL', + /** The email verification action. */ + VERIFY_EMAIL = 'VERIFY_EMAIL' +} + +/** + * An interface that defines the required continue/state URL with optional Android and iOS + * bundle identifiers. + * + * @public + */ +export interface ActionCodeSettings { + /** + * Sets the Android package name. + * + * @remarks + * 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?: { + installApp?: boolean; + minimumVersion?: string; + packageName: string; + }; + /** + * When set to true, the action code link will be be sent as a Universal Link or Android App + * Link and will be opened by the app if installed. + * + * @remarks + * In the false case, the code will be sent to the web widget first and then on continue will + * redirect to the app if installed. + * + * @defaultValue false + */ + handleCodeInApp?: boolean; + /** + * Sets the iOS bundle ID. + * + * @remarks + * This will try to open the link in an iOS app if it is installed. + * + * App installation is not supported for iOS. + */ + iOS?: { + bundleId: string; + }; + /** + * Sets the link continue/state URL. + * + * @remarks + * This 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; + /** + * When multiple custom dynamic link domains are defined for a project, specify which one to use + * when the link is to be opened via a specified mobile app (for example, `example.page.link`). + * + * + * @defaultValue The first domain is automatically selected. + */ + dynamicLinkDomain?: string; +} + +/** + * A verifier for domain verification and abuse prevention. + * + * @remarks + * Currently, the only implementation is {@link RecaptchaVerifier}. + * + * @public + */ +export interface ApplicationVerifier { + /** + * Identifies the type of application verifier (e.g. "recaptcha"). + */ + readonly type: string; + /** + * Executes the verification process. + * + * @returns A Promise for a token that can be used to assert the validity of a request. + */ + verify(): Promise; +} + +/** + * Interface that represents an auth provider, used to facilitate creating {@link AuthCredential}. + * + * @public + */ +export interface AuthProvider { + /** + * Provider for which credentials can be constructed. + */ + readonly providerId: string; +} + +/** + * An enum of factors that may be used for multifactor authentication. + * + * @internal + */ +export const enum FactorId { + /** Phone as second factor */ + PHONE = 'phone' +} + +/** + * A result from a phone number sign-in, link, or reauthenticate call. + * + * @public + */ +export interface ConfirmationResult { + /** + * The phone number authentication operation's verification ID. + * + * @remarks + * This can be used along with the verification code to initialize a + * {@link PhoneAuthCredential}. + */ + readonly verificationId: string; + /** + * Finishes a phone number sign-in, link, or reauthentication. + * + * @example + * ```javascript + * const confirmationResult = await signInWithPhoneNumber(auth, phoneNumber, applicationVerifier); + * // Obtain verificationCode from the user. + * const userCredential = await confirmationResult.confirm(verificationCode); + * ``` + * + * @param verificationCode - The code that was sent to the user's mobile device. + */ + confirm(verificationCode: string): Promise; +} + +/** + * The base class for asserting ownership of a second factor. + * + * @remarks + * This is used to facilitate enrollment of a second factor on an existing user or sign-in of a + * user who already verified the first factor. + * + * @public + */ +export interface MultiFactorAssertion { + /** The identifier of the second factor. */ + readonly factorId: typeof FactorIdMap[keyof typeof FactorIdMap]; +} + +/** + * The error thrown when the user needs to provide a second factor to sign in successfully. + * + * @remarks + * The error code for this error is `auth/multi-factor-auth-required`. + * + * @example + * ```javascript + * let resolver; + * let multiFactorHints; + * + * signInWithEmailAndPassword(auth, email, password) + * .then((result) => { + * // User signed in. No 2nd factor challenge is needed. + * }) + * .catch((error) => { + * if (error.code == 'auth/multi-factor-auth-required') { + * resolver = getMultiFactorResolver(auth, error); + * multiFactorHints = resolver.hints; + * } else { + * // Handle other errors. + * } + * }); + * + * // Obtain a multiFactorAssertion by verifying the second factor. + * + * const userCredential = await resolver.resolveSignIn(multiFactorAssertion); + * ``` + * + * @public + */ +export interface MultiFactorError extends AuthError { + /** + * The type of operation (e.g., sign-in, link, or reauthenticate) during which the error was raised. + */ + readonly operationType: typeof OperationTypeMap[keyof typeof OperationTypeMap]; +} + +/** + * A structure containing the information of a second factor entity. + * + * @public + */ +export interface MultiFactorInfo { + /** The multi-factor enrollment ID. */ + readonly uid: string; + /** The user friendly name of the current second factor. */ + readonly displayName?: string | null; + /** The enrollment date of the second factor formatted as a UTC string. */ + readonly enrollmentTime: string; + /** The identifier of the second factor. */ + readonly factorId: typeof FactorIdMap[keyof typeof FactorIdMap]; +} + +/** + * The class used to facilitate recovery from {@link MultiFactorError} when a user needs to + * provide a second factor to sign in. + * + * @example + * ```javascript + * let resolver; + * let multiFactorHints; + * + * signInWithEmailAndPassword(auth, email, password) + * .then((result) => { + * // User signed in. No 2nd factor challenge is needed. + * }) + * .catch((error) => { + * if (error.code == 'auth/multi-factor-auth-required') { + * resolver = getMultiFactorResolver(auth, error); + * // Show UI to let user select second factor. + * multiFactorHints = resolver.hints; + * } else { + * // Handle other errors. + * } + * }); + * + * // The enrolled second factors that can be used to complete + * // sign-in are returned in the `MultiFactorResolver.hints` list. + * // UI needs to be presented to allow the user to select a second factor + * // from that list. + * + * const selectedHint = // ; selected from multiFactorHints + * const phoneAuthProvider = new PhoneAuthProvider(auth); + * const phoneInfoOptions = { + * multiFactorHint: selectedHint, + * session: resolver.session + * }; + * const verificationId = phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, appVerifier); + * // Store `verificationId` and show UI to let user enter verification code. + * + * // UI to enter verification code and continue. + * // Continue button click handler + * const phoneAuthCredential = PhoneAuthProvider.credential(verificationId, verificationCode); + * const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(phoneAuthCredential); + * const userCredential = await resolver.resolveSignIn(multiFactorAssertion); + * ``` + * + * @public + */ +export interface MultiFactorResolver { + /** + * The list of hints for the second factors needed to complete the sign-in for the current + * session. + */ + readonly hints: MultiFactorInfo[]; + /** + * The session identifier for the current sign-in flow, which can be used to complete the second + * factor sign-in. + */ + readonly session: MultiFactorSession; + /** + * A helper function to help users complete sign in with a second factor using an + * {@link MultiFactorAssertion} confirming the user successfully completed the second factor + * challenge. + * + * @example + * ```javascript + * const phoneAuthCredential = PhoneAuthProvider.credential(verificationId, verificationCode); + * const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(phoneAuthCredential); + * const userCredential = await resolver.resolveSignIn(multiFactorAssertion); + * ``` + * + * @param assertion - The multi-factor assertion to resolve sign-in with. + * @returns The promise that resolves with the user credential object. + */ + resolveSignIn(assertion: MultiFactorAssertion): Promise; +} + +/** + * An interface defining the multi-factor session object used for enrolling a second factor on a + * user or helping sign in an enrolled user with a second factor. + * + * @public + */ +export interface MultiFactorSession {} + +/** + * An interface that defines the multi-factor related properties and operations pertaining + * to a {@link User}. + * + * @public + */ +export interface MultiFactorUser { + /** Returns a list of the user's enrolled second factors. */ + readonly enrolledFactors: MultiFactorInfo[]; + /** + * Returns the session identifier for a second factor enrollment operation. This is used to + * identify the user trying to enroll a second factor. + * + * @example + * ```javascript + * const multiFactorUser = multiFactor(auth.currentUser); + * const multiFactorSession = await multiFactorUser.getSession(); + * + * // Send verification code. + * const phoneAuthProvider = new PhoneAuthProvider(auth); + * const phoneInfoOptions = { + * phoneNumber: phoneNumber, + * session: multiFactorSession + * }; + * const verificationId = await phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, appVerifier); + * + * // Obtain verification code from user. + * const phoneAuthCredential = PhoneAuthProvider.credential(verificationId, verificationCode); + * const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(phoneAuthCredential); + * await multiFactorUser.enroll(multiFactorAssertion); + * ``` + * + * @returns The promise that resolves with the {@link MultiFactorSession}. + */ + getSession(): Promise; + /** + * + * Enrolls a second factor as identified by the {@link MultiFactorAssertion} for the + * user. + * + * @remarks + * On resolution, the user tokens are updated to reflect the change in the JWT payload. + * Accepts an additional display name parameter used to identify the second factor to the end + * user. Recent re-authentication is required for this operation to succeed. On successful + * enrollment, existing Firebase sessions (refresh tokens) are revoked. When a new factor is + * enrolled, an email notification is sent to the user’s email. + * + * @example + * ```javascript + * const multiFactorUser = multiFactor(auth.currentUser); + * const multiFactorSession = await multiFactorUser.getSession(); + * + * // Send verification code. + * const phoneAuthProvider = new PhoneAuthProvider(auth); + * const phoneInfoOptions = { + * phoneNumber: phoneNumber, + * session: multiFactorSession + * }; + * const verificationId = await phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, appVerifier); + * + * // Obtain verification code from user. + * const phoneAuthCredential = PhoneAuthProvider.credential(verificationId, verificationCode); + * const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(phoneAuthCredential); + * await multiFactorUser.enroll(multiFactorAssertion); + * // Second factor enrolled. + * ``` + * + * @param assertion - The multi-factor assertion to enroll with. + * @param displayName - The display name of the second factor. + */ + enroll( + assertion: MultiFactorAssertion, + displayName?: string | null + ): Promise; + /** + * Unenrolls the specified second factor. + * + * @remarks + * To specify the factor to remove, pass a {@link MultiFactorInfo} object (retrieved from + * {@link MultiFactorUser.enrolledFactors}) or the + * factor's UID string. Sessions are not revoked when the account is unenrolled. An email + * notification is likely to be sent to the user notifying them of the change. Recent + * re-authentication is required for this operation to succeed. When an existing factor is + * unenrolled, an email notification is sent to the user’s email. + * + * @example + * ```javascript + * const multiFactorUser = multiFactor(auth.currentUser); + * // Present user the option to choose which factor to unenroll. + * await multiFactorUser.unenroll(multiFactorUser.enrolledFactors[i]) + * ``` + * + * @param option - The multi-factor option to unenroll. + * @returns - A promise which resolves when the unenroll operation is complete. + */ + unenroll(option: MultiFactorInfo | string): Promise; +} + +/** + * The class for asserting ownership of a phone second factor. Provided by + * {@link PhoneMultiFactorGenerator.assertion}. + * + * @public + */ +export interface PhoneMultiFactorAssertion extends MultiFactorAssertion {} + +/** + * The information required to verify the ownership of a phone number. + * + * @remarks + * The information that's required depends on whether you are doing single-factor sign-in, + * multi-factor enrollment or multi-factor sign-in. + * + * @public + */ +export type PhoneInfoOptions = + | PhoneSingleFactorInfoOptions + | PhoneMultiFactorEnrollInfoOptions + | PhoneMultiFactorSignInInfoOptions; + +/** + * Options used for single-factor sign-in. + * + * @public + */ +export interface PhoneSingleFactorInfoOptions { + /** Phone number to send a verification code to. */ + phoneNumber: string; +} + +/** + * Options used for enrolling a second factor. + * + * @public + */ +export interface PhoneMultiFactorEnrollInfoOptions { + /** Phone number to send a verification code to. */ + phoneNumber: string; + /** The {@link MultiFactorSession} obtained via {@link MultiFactorUser.getSession}. */ + session: MultiFactorSession; +} +/** + * Options used for signing-in with a second factor. + * + * @public + */ +export interface PhoneMultiFactorSignInInfoOptions { + /** + * The {@link MultiFactorInfo} obtained via {@link MultiFactorResolver.hints}. + * + * One of `multiFactorHint` or `multiFactorUid` is required. + */ + multiFactorHint?: MultiFactorInfo; + /** + * The uid of the second factor. + * + * One of `multiFactorHint` or `multiFactorUid` is required. + */ + multiFactorUid?: string; + /** The {@link MultiFactorSession} obtained via {@link MultiFactorResolver.session}. */ + session: MultiFactorSession; +} + +/** + * Interface for a supplied AsyncStorage. + * + * @public + */ +export interface ReactNativeAsyncStorage { + /** + * Persist an item in storage. + * + * @param key - storage key. + * @param value - storage value. + */ + setItem(key: string, value: string): Promise; + /** + * Retrieve an item from storage. + * + * @param key - storage key. + */ + getItem(key: string): Promise; + /** + * Remove an item from storage. + * + * @param key - storage key. + */ + removeItem(key: string): Promise; +} + +/** + * A user account. + * + * @public + */ +export interface User extends UserInfo { + /** + * Whether the email has been verified with {@link sendEmailVerification} and + * {@link applyActionCode}. + */ + readonly emailVerified: boolean; + /** + * Whether the user is authenticated using the {@link ProviderId}.ANONYMOUS provider. + */ + readonly isAnonymous: boolean; + /** + * Additional metadata around user creation and sign-in times. + */ + readonly metadata: UserMetadata; + /** + * Additional per provider such as displayName and profile information. + */ + readonly providerData: UserInfo[]; + /** + * Refresh token used to reauthenticate the user. Avoid using this directly and prefer + * {@link User.getIdToken} to refresh the ID token instead. + */ + readonly refreshToken: string; + /** + * The user's tenant ID. + * + * @remarks + * This is a read-only property, which indicates the tenant ID + * used to sign in the user. This is null if the user is signed in from the parent + * project. + * + * @example + * ```javascript + * // Set the tenant ID on Auth instance. + * auth.tenantId = 'TENANT_PROJECT_ID'; + * + * // All future sign-in request now include tenant ID. + * const result = await signInWithEmailAndPassword(auth, email, password); + * // result.user.tenantId should be 'TENANT_PROJECT_ID'. + * ``` + */ + readonly tenantId: string | null; + /** + * Deletes and signs out the user. + * + * @remarks + * Important: this is a security-sensitive operation that requires the user to have recently + * signed in. If this requirement isn't met, ask the user to authenticate again and then call + * one of the reauthentication methods like {@link reauthenticateWithCredential}. + */ + delete(): Promise; + /** + * Returns a JSON Web Token (JWT) used to identify the user to a Firebase service. + * + * @remarks + * Returns the current token if it has not expired or if it will not expire in the next five + * minutes. Otherwise, this will refresh the token and return a new one. + * + * @param forceRefresh - Force refresh regardless of token expiration. + */ + getIdToken(forceRefresh?: boolean): Promise; + /** + * Returns a deserialized JSON Web Token (JWT) used to identitfy the user to a Firebase service. + * + * @remarks + * Returns the current token if it has not expired or if it will not expire in the next five + * minutes. Otherwise, this will refresh the token and return a new one. + * + * @param forceRefresh - Force refresh regardless of token expiration. + */ + getIdTokenResult(forceRefresh?: boolean): Promise; + /** + * Refreshes the user, if signed in. + */ + reload(): Promise; + /** + * Returns a JSON-serializable representation of this object. + * + * @returns A JSON-serializable representation of this object. + */ + toJSON(): object; +} + +/** + * A structure containing a {@link User}, an {@link AuthCredential}, the {@link OperationType}, + * and any additional user information that was returned from the identity provider. + * + * @remarks + * `operationType` could be {@link OperationType}.SIGN_IN for a sign-in operation, + * {@link OperationType}.LINK for a linking operation and {@link OperationType}.REAUTHENTICATE for + * a reauthentication operation. + * + * @public + */ +export interface UserCredential { + /** + * The user authenticated by this credential. + */ + user: User; + /** + * The provider which was used to authenticate the user. + */ + providerId: string | null; + /** + * The type of operation which was used to authenticate the user (such as sign-in or link). + */ + operationType: typeof OperationTypeMap[keyof typeof OperationTypeMap]; +} + +/** + * User profile information, visible only to the Firebase project's apps. + * + * @public + */ +export interface UserInfo { + /** + * The display name of the user. + */ + readonly displayName: string | null; + /** + * The email of the user. + */ + readonly email: string | null; + /** + * The phone number normalized based on the E.164 standard (e.g. +16505550101) for the + * user. + * + * @remarks + * This is null if the user has no phone credential linked to the account. + */ + readonly phoneNumber: string | null; + /** + * The profile photo URL of the user. + */ + readonly photoURL: string | null; + /** + * The provider used to authenticate the user. + */ + readonly providerId: string; + /** + * The user's unique ID, scoped to the project. + */ + readonly uid: string; +} + +/** + * Interface representing a user's metadata. + * + * @public + */ +export interface UserMetadata { + /** Time the user was created. */ + readonly creationTime?: string; + /** Time the user last signed in. */ + readonly lastSignInTime?: string; +} + +/** + * A structure containing additional user information from a federated identity provider. + * + * @public + */ +export interface AdditionalUserInfo { + /** + * Whether the user is new (created via sign-up) or existing (authenticated using sign-in). + */ + readonly isNewUser: boolean; + /** + * Map containing IDP-specific user data. + */ + readonly profile: Record | null; + /** + * Identifier for the provider used to authenticate this user. + */ + readonly providerId: string | null; + /** + * The username if the provider is GitHub or Twitter. + */ + readonly username?: string | null; +} + +/** + * User profile used in {@link AdditionalUserInfo}. + * + * @public + */ +export type UserProfile = Record; + +/** + * A resolver used for handling DOM specific operations like {@link signInWithPopup} + * or {@link signInWithRedirect}. + * + * @public + */ +export interface PopupRedirectResolver {} + +declare module '@firebase/component' { + interface NameServiceMapping { + 'auth-exp': Auth; + } +} + +/** + * Configuration of Firebase Authentication Emulator. + */ +export interface EmulatorConfig { + /** + * The protocol used to communicate with the emulator ("http"/"https"). + */ + readonly protocol: string; + /** + * The hostname of the emulator, which may be a domain ("localhost"), IPv4 address ("127.0.0.1") + * or quoted IPv6 address ("[::1]"). + */ + readonly host: string; + /** + * The port of the emulator, or null if port isn't specified (i.e. protocol default). + */ + readonly port: number | null; + /** + * The emulator-specific options. + */ + readonly options: { + /** + * Whether the warning banner attached to the DOM was disabled. + */ + readonly disableWarnings: boolean; + }; +} + +/** + * A mapping of error codes to error messages. + * + * @remarks + * + * While error messages are useful for debugging (providing verbose textual + * context around what went wrong), these strings take up a lot of space in the + * compiled code. When deploying code in production, using {@link prodErrorMap} + * will save you roughly 10k compressed/gzipped over {@link debugErrorMap}. You + * can select the error map during initialization: + * + * ```javascript + * initializeAuth(app, {errorMap: debugErrorMap}) + * ``` + * + * When initializing Auth, {@link prodErrorMap} is default. + * + * @public + */ +export interface AuthErrorMap {} + +/** + * The dependencies that can be used to initialize an Auth instance. + * + * @remarks + * + * The modular SDK enables tree shaking by allowing explicit declarations of + * dependencies. For example, a web app does not need to include code that + * enables Cordova redirect sign in. That functionality is therefore split into + * {@link browserPopupRedirectResolver} and + * {@link cordovaPopupRedirectResolver}. The dependencies object is how Auth is + * configured to reduce bundle sizes. + * + * There are two ways to initialize an auth instance: {@link getAuth} and + * {@link initializeAuth}. `getAuth` initializes everything using + * platform-specific configurations, while `initializeAuth` takes a + * `Dependencies` object directly, giving you more control over what is used. + * + * @public + */ +export interface Dependencies { + /** + * Which {@link Persistence} to use. If this is an array, the first + * `Persistence` that the device supports is used. The SDK searches for an + * existing account in order and, if one is found in a secondary + * `Persistence`, the account is moved to the primary `Persistence`. + * + * If no persistence is provided, the SDK falls back on + * {@link inMemoryPersistence}. + */ + persistence?: Persistence | Persistence[]; + /** + * The {@link PopupRedirectResolver} to use. This value depends on the + * platform. Options are {@link browserPopupRedirectResolver} and + * {@link cordovaPopupRedirectResolver}. This field is optional if neither + * {@link signInWithPopup} or {@link signInWithRedirect} are being used. + */ + popupRedirectResolver?: PopupRedirectResolver; + /** + * Which {@link AuthErrorMap} to use. + */ + errorMap?: AuthErrorMap; +} diff --git a/packages-exp/auth-exp/src/model/user.ts b/packages-exp/auth-exp/src/model/user.ts index 3e21ac9a928..9f0802a2423 100644 --- a/packages-exp/auth-exp/src/model/user.ts +++ b/packages-exp/auth-exp/src/model/user.ts @@ -15,45 +15,57 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { + IdTokenResult, + ProviderId, + User, + UserCredential, + UserInfo +} from './public_types'; import { NextFn } from '@firebase/util'; import { APIUserInfo } from '../api/account_management/account'; import { FinalizeMfaResponse } from '../api/authentication/mfa'; import { PersistedBlob } from '../core/persistence'; import { StsTokenManager } from '../core/user/token_manager'; import { UserMetadata } from '../core/user/user_metadata'; -import { Auth } from './auth'; +import { AuthInternal } from './auth'; import { IdTokenResponse, TaggedWithTokenResponse } from './id_token'; export type MutableUserInfo = { - -readonly [K in keyof externs.UserInfo]: externs.UserInfo[K]; + -readonly [K in keyof UserInfo]: UserInfo[K]; }; export interface UserParameters { uid: string; - auth: Auth; + auth: AuthInternal; stsTokenManager: StsTokenManager; - displayName?: string; - email?: string; - phoneNumber?: string; - photoURL?: string; - isAnonymous?: boolean; - emailVerified?: boolean; - tenantId?: string; + displayName?: string | null; + email?: string | null; + phoneNumber?: string | null; + photoURL?: string | null; + isAnonymous?: boolean | null; + emailVerified?: boolean | null; + tenantId?: string | null; - createdAt?: string; - lastLoginAt?: string; + createdAt?: string | null; + lastLoginAt?: string | null; } -export interface User extends externs.User { +/** + * UserInternal and AuthInternal reference each other, so both of them are included in the public typings. + * In order to exclude them, we mark them as internal explicitly. + * + * @internal + */ +export interface UserInternal extends User { displayName: string | null; email: string | null; phoneNumber: string | null; photoURL: string | null; - auth: Auth; - providerId: externs.ProviderId.FIREBASE; + auth: AuthInternal; + providerId: ProviderId.FIREBASE; refreshToken: string; emailVerified: boolean; tenantId: string | null; @@ -68,21 +80,22 @@ export interface User extends externs.User { reload?: boolean ): Promise; - _copy(user: User): void; + _assign(user: UserInternal): void; + _clone(auth: AuthInternal): UserInternal; _onReload: (cb: NextFn) => void; _notifyReloadListener: NextFn; _startProactiveRefresh: () => void; _stopProactiveRefresh: () => void; getIdToken(forceRefresh?: boolean): Promise; - getIdTokenResult(forceRefresh?: boolean): Promise; + getIdTokenResult(forceRefresh?: boolean): Promise; reload(): Promise; delete(): Promise; toJSON(): PersistedBlob; } -export interface UserCredential - extends externs.UserCredential, +export interface UserCredentialInternal + extends UserCredential, TaggedWithTokenResponse { - user: User; + user: UserInternal; } diff --git a/packages-exp/auth-exp/src/platform_browser/auth.test.ts b/packages-exp/auth-exp/src/platform_browser/auth.test.ts index 6e836a30da6..072e60b6f49 100644 --- a/packages-exp/auth-exp/src/platform_browser/auth.test.ts +++ b/packages-exp/auth-exp/src/platform_browser/auth.test.ts @@ -20,20 +20,19 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as sinon from 'sinon'; import * as sinonChai from 'sinon-chai'; -import { FirebaseApp } from '@firebase/app-types-exp'; -import * as externs from '@firebase/auth-types-exp'; +import { FirebaseApp } from '@firebase/app-exp'; +import { + Auth, + OperationType, + Persistence, + PopupRedirectResolver +} from '../model/public_types'; import { testAuth, testUser } from '../../test/helpers/mock_auth'; -import { - _castAuth, - AuthImpl, - DEFAULT_API_HOST, - DEFAULT_API_SCHEME, - DEFAULT_TOKEN_API_HOST -} from '../core/auth/auth_impl'; +import { AuthImpl, DefaultConfig } from '../core/auth/auth_impl'; import { _initializeAuthInstance } from '../core/auth/initialize'; -import { AUTH_ERROR_FACTORY, AuthErrorCode } from '../core/errors'; -import { Persistence } from '../core/persistence'; +import { AuthErrorCode } from '../core/errors'; +import { PersistenceInternal } from '../core/persistence'; import { browserLocalPersistence } from './persistence/local_storage'; import { browserSessionPersistence } from './persistence/session_storage'; import { inMemoryPersistence } from '../core/persistence/in_memory'; @@ -41,8 +40,13 @@ import { PersistenceUserManager } from '../core/persistence/persistence_user_man import * as reload from '../core/user/reload'; import { _getInstance } from '../core/util/instantiator'; import { _getClientVersion, ClientPlatform } from '../core/util/version'; -import { Auth } from '../model/auth'; +import { AuthInternal } from '../model/auth'; import { browserPopupRedirectResolver } from './popup_redirect'; +import { PopupRedirectResolverInternal } from '../model/popup_redirect'; +import { UserCredentialImpl } from '../core/user/user_credential_impl'; +import { UserInternal } from '../model/user'; +import { _createError } from '../core/util/assert'; +import { makeMockPopupRedirectResolver } from '../../test/helpers/mock_popup_redirect_resolver'; use(sinonChai); use(chaiAsPromised); @@ -57,16 +61,17 @@ const FAKE_APP: FirebaseApp = { }; describe('core/auth/auth_impl', () => { - let auth: Auth; - let persistenceStub: sinon.SinonStubbedInstance; + let auth: AuthInternal; + let persistenceStub: sinon.SinonStubbedInstance; beforeEach(async () => { persistenceStub = sinon.stub(_getInstance(inMemoryPersistence)); const authImpl = new AuthImpl(FAKE_APP, { apiKey: FAKE_APP.options.apiKey!, - apiHost: DEFAULT_API_HOST, - apiScheme: DEFAULT_API_SCHEME, - tokenApiHost: DEFAULT_TOKEN_API_HOST, + apiHost: DefaultConfig.API_HOST, + apiScheme: DefaultConfig.API_SCHEME, + tokenApiHost: DefaultConfig.TOKEN_API_HOST, + clientPlatform: ClientPlatform.BROWSER, sdkClientVersion: 'v' }); @@ -79,7 +84,9 @@ describe('core/auth/auth_impl', () => { describe('#setPersistence', () => { it('swaps underlying persistence', async () => { const newPersistence = browserLocalPersistence; - const newStub = sinon.stub(_getInstance(newPersistence)); + const newStub = sinon.stub( + _getInstance(newPersistence) + ); persistenceStub._get.returns( Promise.resolve(testUser(auth, 'test').toJSON()) ); @@ -101,7 +108,8 @@ describe('core/auth/initializeAuth', () => { describe('persistence manager creation', () => { let createManagerStub: sinon.SinonSpy; let reloadStub: sinon.SinonStub; - let oldAuth: Auth; + let oldAuth: AuthInternal; + let completeRedirectFnStub: sinon.SinonStub; beforeEach(async () => { oldAuth = await testAuth(); @@ -109,18 +117,28 @@ describe('core/auth/initializeAuth', () => { reloadStub = sinon .stub(reload, '_reloadWithoutSaving') .returns(Promise.resolve()); + completeRedirectFnStub = sinon + .stub( + _getInstance( + browserPopupRedirectResolver + ), + '_completeRedirectFn' + ) + .returns(Promise.resolve(null)); }); async function initAndWait( - persistence: externs.Persistence | externs.Persistence[], - popupRedirectResolver?: externs.PopupRedirectResolver - ): Promise { + persistence: Persistence | Persistence[], + popupRedirectResolver?: PopupRedirectResolver, + authDomain = FAKE_APP.options.authDomain + ): Promise { const auth = new AuthImpl(FAKE_APP, { apiKey: FAKE_APP.options.apiKey!, - apiHost: DEFAULT_API_HOST, - apiScheme: DEFAULT_API_SCHEME, - tokenApiHost: DEFAULT_TOKEN_API_HOST, - authDomain: FAKE_APP.options.authDomain, + apiHost: DefaultConfig.API_HOST, + apiScheme: DefaultConfig.API_SCHEME, + tokenApiHost: DefaultConfig.TOKEN_API_HOST, + authDomain, + clientPlatform: ClientPlatform.BROWSER, sdkClientVersion: _getClientVersion(ClientPlatform.BROWSER) }); @@ -143,7 +161,7 @@ describe('core/auth/initializeAuth', () => { it('pulls the user from storage', async () => { sinon - .stub(_getInstance(inMemoryPersistence), '_get') + .stub(_getInstance(inMemoryPersistence), '_get') .returns(Promise.resolve(testUser(oldAuth, 'uid').toJSON())); const auth = await initAndWait(inMemoryPersistence); expect(auth.currentUser!.uid).to.eq('uid'); @@ -164,21 +182,49 @@ describe('core/auth/initializeAuth', () => { const user = testUser(oldAuth, 'uid'); user._redirectEventId = 'event-id'; sinon - .stub(_getInstance(inMemoryPersistence), '_get') + .stub(_getInstance(inMemoryPersistence), '_get') .returns(Promise.resolve(user.toJSON())); sinon - .stub(_getInstance(browserSessionPersistence), '_get') + .stub( + _getInstance(browserSessionPersistence), + '_get' + ) .returns(Promise.resolve(user.toJSON())); await initAndWait(inMemoryPersistence); expect(reload._reloadWithoutSaving).not.to.have.been.called; }); + it('does not early-initialize the resolver if _shouldInitProactively is false', async () => { + const popupRedirectResolver = makeMockPopupRedirectResolver(); + const resolverInternal: PopupRedirectResolverInternal = _getInstance( + popupRedirectResolver + ); + sinon.stub(resolverInternal, '_shouldInitProactively').value(false); + sinon.spy(resolverInternal, '_initialize'); + await initAndWait(inMemoryPersistence, popupRedirectResolver); + expect(resolverInternal._initialize).not.to.have.been.called; + }); + + it('early-initializes the resolver if _shouldInitProactively is true', async () => { + const popupRedirectResolver = makeMockPopupRedirectResolver(); + const resolverInternal: PopupRedirectResolverInternal = _getInstance( + popupRedirectResolver + ); + sinon.stub(resolverInternal, '_shouldInitProactively').value(true); + sinon.spy(resolverInternal, '_initialize'); + await initAndWait(inMemoryPersistence, popupRedirectResolver); + expect(resolverInternal._initialize).to.have.been.called; + }); + it('reloads non-redirect users', async () => { sinon - .stub(_getInstance(inMemoryPersistence), '_get') + .stub(_getInstance(inMemoryPersistence), '_get') .returns(Promise.resolve(testUser(oldAuth, 'uid').toJSON())); sinon - .stub(_getInstance(browserSessionPersistence), '_get') + .stub( + _getInstance(browserSessionPersistence), + '_get' + ) .returns(Promise.resolve(null)); await initAndWait(inMemoryPersistence); @@ -190,10 +236,13 @@ describe('core/auth/initializeAuth', () => { user._redirectEventId = 'event-id'; sinon - .stub(_getInstance(inMemoryPersistence), '_get') + .stub(_getInstance(inMemoryPersistence), '_get') .returns(Promise.resolve(user.toJSON())); sinon - .stub(_getInstance(browserSessionPersistence), '_get') + .stub( + _getInstance(browserSessionPersistence), + '_get' + ) .returns(Promise.resolve(user.toJSON())); await initAndWait(inMemoryPersistence, browserPopupRedirectResolver); @@ -205,12 +254,15 @@ describe('core/auth/initializeAuth', () => { user._redirectEventId = 'event-id'; sinon - .stub(_getInstance(inMemoryPersistence), '_get') + .stub(_getInstance(inMemoryPersistence), '_get') .returns(Promise.resolve(user.toJSON())); user._redirectEventId = 'some-other-id'; sinon - .stub(_getInstance(browserSessionPersistence), '_get') + .stub( + _getInstance(browserSessionPersistence), + '_get' + ) .returns(Promise.resolve(user.toJSON())); await initAndWait(inMemoryPersistence, browserPopupRedirectResolver); @@ -218,12 +270,14 @@ describe('core/auth/initializeAuth', () => { }); it('Nulls out the current user if reload fails', async () => { - const stub = sinon.stub(_getInstance(inMemoryPersistence)); + const stub = sinon.stub( + _getInstance(inMemoryPersistence) + ); stub._get.returns(Promise.resolve(testUser(oldAuth, 'uid').toJSON())); stub._remove.returns(Promise.resolve()); reloadStub.returns( Promise.reject( - AUTH_ERROR_FACTORY.create(AuthErrorCode.TOKEN_EXPIRED, { + _createError(AuthErrorCode.TOKEN_EXPIRED, { appName: 'app' }) ) @@ -234,12 +288,14 @@ describe('core/auth/initializeAuth', () => { }); it('Keeps current user if reload fails with network error', async () => { - const stub = sinon.stub(_getInstance(inMemoryPersistence)); + const stub = sinon.stub( + _getInstance(inMemoryPersistence) + ); stub._get.returns(Promise.resolve(testUser(oldAuth, 'uid').toJSON())); stub._remove.returns(Promise.resolve()); reloadStub.returns( Promise.reject( - AUTH_ERROR_FACTORY.create(AuthErrorCode.NETWORK_REQUEST_FAILED, { + _createError(AuthErrorCode.NETWORK_REQUEST_FAILED, { appName: 'app' }) ) @@ -255,11 +311,55 @@ describe('core/auth/initializeAuth', () => { expect(auth.config).to.eql({ apiKey: FAKE_APP.options.apiKey, authDomain: FAKE_APP.options.authDomain, - apiHost: DEFAULT_API_HOST, - apiScheme: DEFAULT_API_SCHEME, - tokenApiHost: DEFAULT_TOKEN_API_HOST, + apiHost: DefaultConfig.API_HOST, + apiScheme: DefaultConfig.API_SCHEME, + tokenApiHost: DefaultConfig.TOKEN_API_HOST, + clientPlatform: ClientPlatform.BROWSER, sdkClientVersion: _getClientVersion(ClientPlatform.BROWSER) }); }); + + context('#tryRedirectSignIn', () => { + it('returns null and clears the redirect user in case of error', async () => { + const stub = sinon.stub( + _getInstance(browserSessionPersistence) + ); + stub._remove.returns(Promise.resolve()); + completeRedirectFnStub.returns(Promise.reject(new Error('no'))); + + await initAndWait([inMemoryPersistence], browserPopupRedirectResolver); + expect(stub._remove).to.have.been.called; + }); + + it('does not run redirect sign in attempt if authDomain not set', async () => { + await initAndWait( + [inMemoryPersistence], + browserPopupRedirectResolver, + '' + ); + expect(completeRedirectFnStub).not.to.have.been.called; + }); + + it('signs in the redirect user if found', async () => { + let user: UserInternal | null = null; + completeRedirectFnStub.callsFake((auth: AuthInternal) => { + user = testUser(auth, 'uid', 'redirectUser@test.com'); + return Promise.resolve( + new UserCredentialImpl({ + operationType: OperationType.SIGN_IN, + user, + providerId: null + }) + ); + }); + + const auth = await initAndWait( + [inMemoryPersistence], + browserPopupRedirectResolver + ); + expect(user).not.to.be.null; + expect(auth.currentUser).to.eq(user); + }); + }); }); }); diff --git a/packages-exp/auth-exp/src/platform_browser/iframe/gapi.iframes.ts b/packages-exp/auth-exp/src/platform_browser/iframe/gapi.iframes.ts index 45027432be8..c312e7a6596 100644 --- a/packages-exp/auth-exp/src/platform_browser/iframe/gapi.iframes.ts +++ b/packages-exp/auth-exp/src/platform_browser/iframe/gapi.iframes.ts @@ -15,9 +15,9 @@ * limitations under the License. */ - // For some reason, the linter doesn't recognize that these are used elsewhere - // in the SDK - /* eslint-disable @typescript-eslint/no-unused-vars */ +// For some reason, the linter doesn't recognize that these are used elsewhere +// in the SDK +/* eslint-disable @typescript-eslint/no-unused-vars */ declare namespace gapi { type LoadCallback = () => void; diff --git a/packages-exp/auth-exp/src/platform_browser/iframe/gapi.test.ts b/packages-exp/auth-exp/src/platform_browser/iframe/gapi.test.ts index 785a9f0e7a8..af7dc30210d 100644 --- a/packages-exp/auth-exp/src/platform_browser/iframe/gapi.test.ts +++ b/packages-exp/auth-exp/src/platform_browser/iframe/gapi.test.ts @@ -30,7 +30,7 @@ import { _loadGapi, _resetLoader } from './gapi'; use(sinonChai); use(chaiAsPromised); -describe('src/platform_browser/iframe/gapi', () => { +describe('platform_browser/iframe/gapi', () => { let library: typeof gapi; let auth: TestAuth; function onJsLoad(globalLoadFnName: string): void { diff --git a/packages-exp/auth-exp/src/platform_browser/iframe/gapi.ts b/packages-exp/auth-exp/src/platform_browser/iframe/gapi.ts index 40a558110e3..cc637546544 100644 --- a/packages-exp/auth-exp/src/platform_browser/iframe/gapi.ts +++ b/packages-exp/auth-exp/src/platform_browser/iframe/gapi.ts @@ -15,14 +15,14 @@ * limitations under the License. */ -import { AUTH_ERROR_FACTORY, AuthErrorCode } from '../../core/errors'; +import { AuthErrorCode } from '../../core/errors'; +import { _createError } from '../../core/util/assert'; import { Delay } from '../../core/util/delay'; -import { Auth } from '../../model/auth'; +import { AuthInternal } from '../../model/auth'; import { _window } from '../auth_window'; import * as js from '../load_js'; const NETWORK_TIMEOUT = new Delay(30000, 60000); -const LOADJS_CALLBACK_PREFIX = 'iframefcb'; /** * Reset unlaoded GApi modules. If gapi.load fails due to a network error, @@ -54,7 +54,7 @@ function resetUnloadedGapiModules(): void { } } -function loadGapi(auth: Auth): Promise { +function loadGapi(auth: AuthInternal): Promise { return new Promise((resolve, reject) => { // Function to run when gapi.load is ready. function loadGapiIframe(): void { @@ -73,11 +73,7 @@ function loadGapi(auth: Auth): Promise { // failed attempt. // Timeout when gapi.iframes.Iframe not loaded. resetUnloadedGapiModules(); - reject( - AUTH_ERROR_FACTORY.create(AuthErrorCode.NETWORK_REQUEST_FAILED, { - appName: auth.name - }) - ); + reject(_createError(auth, AuthErrorCode.NETWORK_REQUEST_FAILED)); }, timeout: NETWORK_TIMEOUT.get() }); @@ -95,7 +91,7 @@ function loadGapi(auth: Auth): Promise { // multiple times in parallel and could result in the later callback // overwriting the previous one. This would end up with a iframe // timeout. - const cbName = js._generateCallbackName(LOADJS_CALLBACK_PREFIX); + const cbName = js._generateCallbackName('iframefcb'); // GApi loader not available, dynamically load platform.js. _window()[cbName] = () => { // GApi loader should be ready. @@ -103,11 +99,7 @@ function loadGapi(auth: Auth): Promise { loadGapiIframe(); } else { // Gapi loader failed, throw error. - reject( - AUTH_ERROR_FACTORY.create(AuthErrorCode.NETWORK_REQUEST_FAILED, { - appName: auth.name - }) - ); + reject(_createError(auth, AuthErrorCode.NETWORK_REQUEST_FAILED)); } }; // Load GApi loader. @@ -121,7 +113,7 @@ function loadGapi(auth: Auth): Promise { } let cachedGApiLoader: Promise | null = null; -export function _loadGapi(auth: Auth): Promise { +export function _loadGapi(auth: AuthInternal): Promise { cachedGApiLoader = cachedGApiLoader || loadGapi(auth); return cachedGApiLoader; } diff --git a/packages-exp/auth-exp/src/platform_browser/iframe/iframe.test.ts b/packages-exp/auth-exp/src/platform_browser/iframe/iframe.test.ts index 96ef34f3247..60a2d296406 100644 --- a/packages-exp/auth-exp/src/platform_browser/iframe/iframe.test.ts +++ b/packages-exp/auth-exp/src/platform_browser/iframe/iframe.test.ts @@ -39,7 +39,7 @@ use(chaiAsPromised); type IframesCallback = (iframesLib: unknown) => Promise; -describe('src/platform_browser/iframe/iframe', () => { +describe('platform_browser/iframe/iframe', () => { let auth: TestAuth; let iframeSettings: Record; let libraryLoadedCallback: IframesCallback; @@ -89,6 +89,25 @@ describe('src/platform_browser/iframe/iframe', () => { expect(iframeSettings.dontclear).to.be.true; }); + it('sets a single framework if logged', async () => { + auth._logFramework('Magical'); + await _openIframe(auth); + expect(iframeSettings.url).to.eq( + `https://${TEST_AUTH_DOMAIN}/__/auth/iframe?apiKey=${TEST_KEY}&appName=test-app&v=${SDK_VERSION}&fw=Magical` + ); + }); + + it('sets multiple frameworks comma-separated if logged', async () => { + auth._logFramework('Mythical'); + auth._logFramework('Magical'); + auth._logFramework('Magical'); // Duplicate, should be ignored + await _openIframe(auth); + expect(iframeSettings.url).to.eq( + // fw should be a comma-separated list sorted alphabetically: + `https://${TEST_AUTH_DOMAIN}/__/auth/iframe?apiKey=${TEST_KEY}&appName=test-app&v=${SDK_VERSION}&fw=Magical%2CMythical` + ); + }); + context('on load callback', () => { let iframe: sinon.SinonStubbedInstance; let clearTimeoutStub: sinon.SinonStub; diff --git a/packages-exp/auth-exp/src/platform_browser/iframe/iframe.ts b/packages-exp/auth-exp/src/platform_browser/iframe/iframe.ts index 1877f51a2a3..9e634519a81 100644 --- a/packages-exp/auth-exp/src/platform_browser/iframe/iframe.ts +++ b/packages-exp/auth-exp/src/platform_browser/iframe/iframe.ts @@ -17,12 +17,13 @@ import { SDK_VERSION } from '@firebase/app-exp'; import { querystring } from '@firebase/util'; +import { DefaultConfig } from '../../../internal'; -import { AUTH_ERROR_FACTORY, AuthErrorCode } from '../../core/errors'; -import { assert } from '../../core/util/assert'; +import { AuthErrorCode } from '../../core/errors'; +import { _assert, _createError } from '../../core/util/assert'; import { Delay } from '../../core/util/delay'; import { _emulatorUrl } from '../../core/util/emulator'; -import { Auth } from '../../model/auth'; +import { AuthInternal } from '../../model/auth'; import { _window } from '../auth_window'; import * as gapiLoader from './gapi'; @@ -39,27 +40,43 @@ const IFRAME_ATTRIBUTES = { } }; -function getIframeUrl(auth: Auth): string { +// Map from apiHost to endpoint ID for passing into iframe. In current SDK, apiHost can be set to +// anything (not from a list of endpoints with IDs as in legacy), so this is the closest we can get. +const EID_FROM_APIHOST = new Map([ + [DefaultConfig.API_HOST, 'p'], // production + ['staging-identitytoolkit.sandbox.googleapis.com', 's'], // staging + ['test-identitytoolkit.sandbox.googleapis.com', 't'] // test +]); + +function getIframeUrl(auth: AuthInternal): string { const config = auth.config; + _assert(config.authDomain, auth, AuthErrorCode.MISSING_AUTH_DOMAIN); const url = config.emulator ? _emulatorUrl(config, EMULATED_IFRAME_PATH) - : `https://${auth.config.authDomain!}/${IFRAME_PATH}`; + : `https://${auth.config.authDomain}/${IFRAME_PATH}`; - const params = { + const params: Record = { apiKey: config.apiKey, appName: auth.name, v: SDK_VERSION }; - // Can pass 'eid' as one of 'p' (production), 's' (staging), or 't' (test) - // TODO: do we care about frameworks? pass them as fw= - + const eid = EID_FROM_APIHOST.get(auth.config.apiHost); + if (eid) { + params.eid = eid; + } + const frameworks = auth._getFrameworks(); + if (frameworks.length) { + params.fw = frameworks.join(','); + } return `${url}?${querystring(params).slice(1)}`; } -export async function _openIframe(auth: Auth): Promise { +export async function _openIframe( + auth: AuthInternal +): Promise { const context = await gapiLoader._loadGapi(auth); const gapi = _window().gapi; - assert(gapi, AuthErrorCode.INTERNAL_ERROR, { appName: auth.name }); + _assert(gapi, auth, AuthErrorCode.INTERNAL_ERROR); return context.open( { where: document.body, @@ -75,11 +92,9 @@ export async function _openIframe(auth: Auth): Promise { setHideOnLeave: false }); - const networkError = AUTH_ERROR_FACTORY.create( - AuthErrorCode.NETWORK_REQUEST_FAILED, - { - appName: auth.name - } + const networkError = _createError( + auth, + AuthErrorCode.NETWORK_REQUEST_FAILED ); // Confirm iframe is correctly loaded. // To fallback on failure, set a timeout. diff --git a/packages-exp/auth-exp/src/platform_browser/messagechannel/index.ts b/packages-exp/auth-exp/src/platform_browser/messagechannel/index.ts new file mode 100644 index 00000000000..8417c9ead5b --- /dev/null +++ b/packages-exp/auth-exp/src/platform_browser/messagechannel/index.ts @@ -0,0 +1,109 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { PromiseSettledResult } from './promise'; + +export const enum _TimeoutDuration { + ACK = 50, + COMPLETION = 3000, + // Used when a handler is confirmed to be available on the other side. + LONG_ACK = 800 +} + +/** + * Enumeration of possible response types from the Receiver. + */ +export const enum _Status { + ACK = 'ack', + DONE = 'done' +} + +export const enum _MessageError { + CONNECTION_CLOSED = 'connection_closed', + CONNECTION_UNAVAILABLE = 'connection_unavailable', + INVALID_RESPONSE = 'invalid_response', + TIMEOUT = 'timeout', + UNKNOWN = 'unknown_error', + UNSUPPORTED_EVENT = 'unsupported_event' +} + +/** + * Enumeration of possible events sent by the Sender. + */ +export const enum _EventType { + KEY_CHANGED = 'keyChanged', + PING = 'ping' +} + +/** + * Response to a {@link EventType.KEY_CHANGED} event. + */ +export interface KeyChangedResponse { + keyProcessed: boolean; +} + +/** + * Response to a {@link EventType.PING} event. + */ +export type _PingResponse = _EventType[]; + +export type _ReceiverResponse = KeyChangedResponse | _PingResponse; + +interface MessageEvent { + eventType: _EventType; + eventId: string; +} + +/** + * Request for a {@link EventType.KEY_CHANGED} event. + */ +export interface KeyChangedRequest { + key: string; +} + +/** + * Request for a {@link EventType.PING} event. + */ +export interface PingRequest {} + +/** Data sent by Sender */ +export type _SenderRequest = KeyChangedRequest | PingRequest; + +/** Receiver handler to process events sent by the Sender */ +export interface ReceiverHandler< + T extends _ReceiverResponse, + S extends _SenderRequest +> { + (origin: string, data: S): T | Promise; +} + +/** Full message sent by Sender */ +export interface SenderMessageEvent + extends MessageEvent { + data: T; +} + +export type _ReceiverMessageResponse = Array< + PromiseSettledResult +> | null; + +/** Full message sent by Receiver */ +export interface ReceiverMessageEvent + extends MessageEvent { + status: _Status; + response: _ReceiverMessageResponse; +} diff --git a/packages-exp/auth-exp/src/platform_browser/messagechannel/promise.test.ts b/packages-exp/auth-exp/src/platform_browser/messagechannel/promise.test.ts new file mode 100644 index 00000000000..0bf17f50772 --- /dev/null +++ b/packages-exp/auth-exp/src/platform_browser/messagechannel/promise.test.ts @@ -0,0 +1,60 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from 'chai'; +import { _allSettled } from './promise'; + +describe('platform_browser/messagechannel/promise', () => { + describe('_allSettled', () => { + it('should work with a single successfull promise', async () => { + const result = await _allSettled([Promise.resolve('foo')]); + expect(result).to.have.deep.members([ + { + fulfilled: true, + value: 'foo' + } + ]); + }); + + it('should work with a failed promise', async () => { + const result = await _allSettled([Promise.reject('bar')]); + expect(result).to.have.deep.members([ + { + fulfilled: false, + reason: 'bar' + } + ]); + }); + + it('should work with mixed promises', async () => { + const result = await _allSettled([ + Promise.resolve('foo'), + Promise.reject('bar') + ]); + expect(result).to.have.deep.members([ + { + fulfilled: true, + value: 'foo' + }, + { + fulfilled: false, + reason: 'bar' + } + ]); + }); + }); +}); diff --git a/packages-exp/auth-exp/src/platform_browser/messagechannel/promise.ts b/packages-exp/auth-exp/src/platform_browser/messagechannel/promise.ts new file mode 100644 index 00000000000..2469a3d9c99 --- /dev/null +++ b/packages-exp/auth-exp/src/platform_browser/messagechannel/promise.ts @@ -0,0 +1,58 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** TODO: remove this once tslib has a polyfill for Promise.allSettled */ +interface PromiseFulfilledResult { + fulfilled: true; + value: T; +} + +interface PromiseRejectedResult { + fulfilled: false; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + reason: any; +} + +export type PromiseSettledResult = + | PromiseFulfilledResult + | PromiseRejectedResult; + +/** + * Shim for Promise.allSettled, note the slightly different format of `fulfilled` vs `status`. + * + * @param promises - Array of promises to wait on. + */ +export function _allSettled( + promises: Array> +): Promise>> { + return Promise.all( + promises.map(async promise => { + try { + const value = await promise; + return { + fulfilled: true, + value + } as PromiseFulfilledResult; + } catch (reason) { + return { + fulfilled: false, + reason + } as PromiseRejectedResult; + } + }) + ); +} diff --git a/packages-exp/auth-exp/src/platform_browser/messagechannel/receiver.test.ts b/packages-exp/auth-exp/src/platform_browser/messagechannel/receiver.test.ts new file mode 100644 index 00000000000..820b9223e26 --- /dev/null +++ b/packages-exp/auth-exp/src/platform_browser/messagechannel/receiver.test.ts @@ -0,0 +1,178 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect, use } from 'chai'; +import * as sinon from 'sinon'; +import * as sinonChai from 'sinon-chai'; +import { + _EventType, + PingRequest, + _PingResponse, + ReceiverMessageEvent, + SenderMessageEvent, + _Status +} from '.'; +import { FakeServiceWorker } from '../../../test/helpers/fake_service_worker'; +import { Receiver } from './receiver'; + +use(sinonChai); + +describe('platform_browser/messagechannel/receiver', () => { + let receiver: Receiver; + let serviceWorker: ServiceWorker; + let messageChannel: MessageChannel; + + beforeEach(() => { + serviceWorker = (new FakeServiceWorker() as unknown) as ServiceWorker; + receiver = Receiver._getInstance(serviceWorker); + messageChannel = new MessageChannel(); + }); + + describe('_getInstance', () => { + it('should memoize the instances', () => { + expect(Receiver._getInstance(serviceWorker)).to.eq(receiver); + }); + }); + + describe('_subscribe', () => { + it('should not respond to events that arent subscribed to', () => { + messageChannel.port1.onmessage = sinon.spy(); + serviceWorker.postMessage( + { + eventType: _EventType.PING, + eventId: '12345', + data: {} + } as SenderMessageEvent, + [messageChannel.port2] + ); + expect(messageChannel.port1.onmessage).to.not.have.been.called; + }); + + it('should return the handlers response to the caller', done => { + const response = [_EventType.KEY_CHANGED]; + let ackReceived = false; + receiver._subscribe<_PingResponse, PingRequest>( + _EventType.PING, + (_origin: string, data: PingRequest) => { + expect(data).to.eql({}); + return response; + } + ); + messageChannel.port1.onmessage = (event: Event) => { + const messageEvent = event as MessageEvent< + ReceiverMessageEvent<_PingResponse> + >; + if (!ackReceived) { + expect(messageEvent.data.eventId).to.eq('12345'); + expect(messageEvent.data.eventType).to.eq(_EventType.PING); + expect(messageEvent.data.status).to.eq(_Status.ACK); + ackReceived = true; + } else { + expect(messageEvent.data.eventId).to.eq('12345'); + expect(messageEvent.data.eventType).to.eq(_EventType.PING); + expect(messageEvent.data.status).to.eq(_Status.DONE); + expect(messageEvent.data.response).to.have.deep.members([ + { + fulfilled: true, + value: response + } + ]); + expect(ackReceived).to.be.true; + done(); + } + }; + serviceWorker.postMessage( + { + eventType: _EventType.PING, + eventId: '12345', + data: {} + } as SenderMessageEvent, + [messageChannel.port2] + ); + }); + + it('should handle multiple subscribers, even if one fails', done => { + const response = [_EventType.KEY_CHANGED]; + let ackReceived = false; + receiver._subscribe( + _EventType.PING, + (_origin: string, data: PingRequest) => { + expect(data).to.eql({}); + return response; + } + ); + receiver._subscribe( + _EventType.PING, + (_origin: string, _data: PingRequest) => Promise.reject('fail') + ); + messageChannel.port1.onmessage = (event: Event) => { + const messageEvent = event as MessageEvent< + ReceiverMessageEvent<_PingResponse> + >; + if (!ackReceived) { + expect(messageEvent.data.eventId).to.eq('12345'); + expect(messageEvent.data.eventType).to.eq(_EventType.PING); + expect(messageEvent.data.status).to.eq(_Status.ACK); + ackReceived = true; + } else { + expect(messageEvent.data.eventId).to.eq('12345'); + expect(messageEvent.data.eventType).to.eq(_EventType.PING); + expect(messageEvent.data.status).to.eq(_Status.DONE); + expect(messageEvent.data.response).to.have.deep.members([ + { + fulfilled: true, + value: response + }, + { + fulfilled: false, + reason: 'fail' + } + ]); + done(); + } + }; + serviceWorker.postMessage( + { + eventType: _EventType.PING, + eventId: '12345', + data: {} + } as SenderMessageEvent, + [messageChannel.port2] + ); + }); + }); + + describe('_unsubscribe', () => { + it('should remove the handlers', () => { + messageChannel.port1.onmessage = sinon.spy(); + const handler = (_origin: string, _data: PingRequest): _EventType[] => { + return [_EventType.KEY_CHANGED]; + }; + receiver._subscribe(_EventType.PING, handler); + receiver._unsubscribe(_EventType.PING, handler); + serviceWorker.postMessage( + { + eventType: _EventType.PING, + eventId: '12345', + data: {} + } as SenderMessageEvent, + [messageChannel.port2] + ); + expect(messageChannel.port1.onmessage).to.not.have.been.called; + }); + }); +}); diff --git a/packages-exp/auth-exp/src/platform_browser/messagechannel/receiver.ts b/packages-exp/auth-exp/src/platform_browser/messagechannel/receiver.ts new file mode 100644 index 00000000000..770e67ab2d2 --- /dev/null +++ b/packages-exp/auth-exp/src/platform_browser/messagechannel/receiver.ts @@ -0,0 +1,157 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + ReceiverHandler, + _EventType, + _ReceiverResponse, + SenderMessageEvent, + _Status, + _SenderRequest +} from './index'; +import { _allSettled } from './promise'; + +/** + * Interface class for receiving messages. + * + */ +export class Receiver { + private static readonly receivers: Receiver[] = []; + private readonly boundEventHandler: EventListener; + + private readonly handlersMap: { + // Typescript doesn't have existential types :( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [eventType: string]: Set>; + } = {}; + + constructor(private readonly eventTarget: EventTarget) { + this.boundEventHandler = this.handleEvent.bind(this); + } + + /** + * Obtain an instance of a Receiver for a given event target, if none exists it will be created. + * + * @param eventTarget - An event target (such as window or self) through which the underlying + * messages will be received. + */ + static _getInstance(eventTarget: EventTarget): Receiver { + // The results are stored in an array since objects can't be keys for other + // objects. In addition, setting a unique property on an event target as a + // hash map key may not be allowed due to CORS restrictions. + const existingInstance = this.receivers.find(receiver => + receiver.isListeningto(eventTarget) + ); + if (existingInstance) { + return existingInstance; + } + const newInstance = new Receiver(eventTarget); + this.receivers.push(newInstance); + return newInstance; + } + + private isListeningto(eventTarget: EventTarget): boolean { + return this.eventTarget === eventTarget; + } + + /** + * Fans out a MessageEvent to the appropriate listeners. + * + * @remarks + * Sends an {@link Status.ACK} upon receipt and a {@link Status.DONE} once all handlers have + * finished processing. + * + * @param event - The MessageEvent. + * + */ + private async handleEvent< + T extends _ReceiverResponse, + S extends _SenderRequest + >(event: Event): Promise { + const messageEvent = event as MessageEvent>; + const { eventId, eventType, data } = messageEvent.data; + + const handlers: Set> | undefined = this.handlersMap[ + eventType + ]; + if (!handlers?.size) { + return; + } + + messageEvent.ports[0].postMessage({ + status: _Status.ACK, + eventId, + eventType + }); + + const promises = Array.from(handlers).map(async handler => + handler(messageEvent.origin, data) + ); + const response = await _allSettled(promises); + messageEvent.ports[0].postMessage({ + status: _Status.DONE, + eventId, + eventType, + response + }); + } + + /** + * Subscribe an event handler for a particular event. + * + * @param eventType - Event name to subscribe to. + * @param eventHandler - The event handler which should receive the events. + * + */ + _subscribe( + eventType: _EventType, + eventHandler: ReceiverHandler + ): void { + if (Object.keys(this.handlersMap).length === 0) { + this.eventTarget.addEventListener('message', this.boundEventHandler); + } + + if (!this.handlersMap[eventType]) { + this.handlersMap[eventType] = new Set(); + } + + this.handlersMap[eventType].add(eventHandler); + } + + /** + * Unsubscribe an event handler from a particular event. + * + * @param eventType - Event name to unsubscribe from. + * @param eventHandler - Optinoal event handler, if none provided, unsubscribe all handlers on this event. + * + */ + _unsubscribe( + eventType: _EventType, + eventHandler?: ReceiverHandler + ): void { + if (this.handlersMap[eventType] && eventHandler) { + this.handlersMap[eventType].delete(eventHandler); + } + if (!eventHandler || this.handlersMap[eventType].size === 0) { + delete this.handlersMap[eventType]; + } + + if (Object.keys(this.handlersMap).length === 0) { + this.eventTarget.removeEventListener('message', this.boundEventHandler); + } + } +} diff --git a/packages-exp/auth-exp/src/platform_browser/messagechannel/sender.test.ts b/packages-exp/auth-exp/src/platform_browser/messagechannel/sender.test.ts new file mode 100644 index 00000000000..a2883265dba --- /dev/null +++ b/packages-exp/auth-exp/src/platform_browser/messagechannel/sender.test.ts @@ -0,0 +1,166 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect, use } from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import * as sinon from 'sinon'; +import * as sinonChai from 'sinon-chai'; +import { + _EventType, + _MessageError, + PingRequest, + _PingResponse, + ReceiverMessageEvent, + SenderMessageEvent, + _Status, + _TimeoutDuration +} from '.'; +import { FakeServiceWorker } from '../../../test/helpers/fake_service_worker'; +import { stubTimeouts, TimerMap } from '../../../test/helpers/timeout_stub'; +import { Sender } from './sender'; + +use(sinonChai); +use(chaiAsPromised); + +const oldSetTimeout = setTimeout; + +describe('platform_browser/messagechannel/sender', () => { + describe('_send', () => { + let sender: Sender; + let serviceWorker: ServiceWorker; + let pendingTimeouts: TimerMap; + + beforeEach(() => { + serviceWorker = (new FakeServiceWorker() as unknown) as ServiceWorker; + sender = new Sender(serviceWorker); + pendingTimeouts = stubTimeouts(); + sinon.stub(window, 'clearTimeout'); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('should send an event and wait for a response', async () => { + const response = [ + { + fulfilled: true, + value: [_EventType.KEY_CHANGED] + } + ]; + serviceWorker.addEventListener('message', (event: Event) => { + const messageEvent = event as MessageEvent< + SenderMessageEvent + >; + messageEvent.ports[0].postMessage({ + status: _Status.ACK, + eventId: messageEvent.data.eventId, + eventType: messageEvent.data.eventType, + response: null + } as ReceiverMessageEvent<_PingResponse>); + messageEvent.ports[0].postMessage({ + status: _Status.DONE, + eventId: messageEvent.data.eventId, + eventType: messageEvent.data.eventType, + response + } as ReceiverMessageEvent<_PingResponse>); + }); + const result = await sender._send<_PingResponse, PingRequest>( + _EventType.PING, + {}, + _TimeoutDuration.ACK + ); + expect(result).to.have.deep.members(response); + }); + + it('should timeout if it doesnt see an ACK', async () => { + serviceWorker.addEventListener('message', (_event: Event) => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + Promise.resolve().then(() => { + pendingTimeouts[_TimeoutDuration.ACK](); + }); + }); + await expect( + sender._send<_PingResponse, PingRequest>( + _EventType.PING, + {}, + _TimeoutDuration.ACK + ) + ).to.be.rejectedWith(Error, _MessageError.UNSUPPORTED_EVENT); + }); + + it('should work with a long ACK', async () => { + const response = [ + { + fulfilled: true, + value: [_EventType.KEY_CHANGED] + } + ]; + serviceWorker.addEventListener('message', (event: Event) => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + Promise.resolve().then(() => { + pendingTimeouts[_TimeoutDuration.ACK](); + }); + const messageEvent = event as MessageEvent< + SenderMessageEvent + >; + messageEvent.ports[0].postMessage({ + status: _Status.ACK, + eventId: messageEvent.data.eventId, + eventType: messageEvent.data.eventType, + response: null + } as ReceiverMessageEvent<_PingResponse>); + messageEvent.ports[0].postMessage({ + status: _Status.DONE, + eventId: messageEvent.data.eventId, + eventType: messageEvent.data.eventType, + response + } as ReceiverMessageEvent<_PingResponse>); + }); + const result = await sender._send<_PingResponse, PingRequest>( + _EventType.PING, + {}, + _TimeoutDuration.LONG_ACK + ); + expect(result).to.have.deep.members(response); + }); + + it('it should timeout if it gets an ACK but not a DONE', async () => { + serviceWorker.addEventListener('message', (event: Event) => { + oldSetTimeout(() => { + pendingTimeouts[_TimeoutDuration.COMPLETION](); + }, 5); + const messageEvent = event as MessageEvent< + SenderMessageEvent + >; + messageEvent.ports[0].postMessage({ + status: _Status.ACK, + eventId: messageEvent.data.eventId, + eventType: messageEvent.data.eventType, + response: null + } as ReceiverMessageEvent<_PingResponse>); + }); + await expect( + sender._send<_PingResponse, PingRequest>( + _EventType.PING, + {}, + _TimeoutDuration.ACK + ) + ).to.be.rejectedWith(Error, _MessageError.TIMEOUT); + }); + }); +}); diff --git a/packages-exp/auth-exp/src/platform_browser/messagechannel/sender.ts b/packages-exp/auth-exp/src/platform_browser/messagechannel/sender.ts new file mode 100644 index 00000000000..1452c9857f7 --- /dev/null +++ b/packages-exp/auth-exp/src/platform_browser/messagechannel/sender.ts @@ -0,0 +1,140 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { _generateEventId } from '../../core/util/event_id'; +import { + _SenderRequest, + _EventType, + ReceiverMessageEvent, + _MessageError, + SenderMessageEvent, + _Status, + _ReceiverMessageResponse, + _ReceiverResponse, + _TimeoutDuration +} from './index'; + +interface MessageHandler { + messageChannel: MessageChannel; + onMessage: EventListenerOrEventListenerObject; +} + +/** + * Interface for sending messages and waiting for a completion response. + * + */ +export class Sender { + private readonly handlers = new Set(); + + constructor(private readonly target: ServiceWorker) {} + + /** + * Unsubscribe the handler and remove it from our tracking Set. + * + * @param handler - The handler to unsubscribe. + */ + private removeMessageHandler(handler: MessageHandler): void { + if (handler.messageChannel) { + handler.messageChannel.port1.removeEventListener( + 'message', + handler.onMessage + ); + handler.messageChannel.port1.close(); + } + this.handlers.delete(handler); + } + + /** + * Send a message to the Receiver located at {@link target}. + * + * @remarks + * We'll first wait a bit for an ACK , if we get one we will wait significantly longer until the + * receiver has had a chance to fully process the event. + * + * @param eventType - Type of event to send. + * @param data - The payload of the event. + * @param timeout - Timeout for waiting on an ACK from the receiver. + * + * @returns An array of settled promises from all the handlers that were listening on the receiver. + */ + async _send( + eventType: _EventType, + data: S, + timeout = _TimeoutDuration.ACK + ): Promise<_ReceiverMessageResponse> { + const messageChannel = + typeof MessageChannel !== 'undefined' ? new MessageChannel() : null; + if (!messageChannel) { + throw new Error(_MessageError.CONNECTION_UNAVAILABLE); + } + // Node timers and browser timers return fundamentally different types. + // We don't actually care what the value is but TS won't accept unknown and + // we can't cast properly in both environments. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let completionTimer: any; + let handler: MessageHandler; + return new Promise<_ReceiverMessageResponse>((resolve, reject) => { + const eventId = _generateEventId('', 20); + messageChannel.port1.start(); + const ackTimer = setTimeout(() => { + reject(new Error(_MessageError.UNSUPPORTED_EVENT)); + }, timeout); + handler = { + messageChannel, + onMessage(event: Event): void { + const messageEvent = event as MessageEvent>; + if (messageEvent.data.eventId !== eventId) { + return; + } + switch (messageEvent.data.status) { + case _Status.ACK: + // The receiver should ACK first. + clearTimeout(ackTimer); + completionTimer = setTimeout(() => { + reject(new Error(_MessageError.TIMEOUT)); + }, _TimeoutDuration.COMPLETION); + break; + case _Status.DONE: + // Once the receiver's handlers are finished we will get the results. + clearTimeout(completionTimer); + resolve(messageEvent.data.response); + break; + default: + clearTimeout(ackTimer); + clearTimeout(completionTimer); + reject(new Error(_MessageError.INVALID_RESPONSE)); + break; + } + } + }; + this.handlers.add(handler); + messageChannel.port1.addEventListener('message', handler.onMessage); + this.target.postMessage( + { + eventType, + eventId, + data + } as SenderMessageEvent, + [messageChannel.port2] + ); + }).finally(() => { + if (handler) { + this.removeMessageHandler(handler); + } + }); + } +} diff --git a/packages-exp/auth-exp/src/platform_browser/mfa/assertions/phone.test.ts b/packages-exp/auth-exp/src/platform_browser/mfa/assertions/phone.test.ts index aafa2aec130..285934e12f1 100644 --- a/packages-exp/auth-exp/src/platform_browser/mfa/assertions/phone.test.ts +++ b/packages-exp/auth-exp/src/platform_browser/mfa/assertions/phone.test.ts @@ -15,28 +15,30 @@ * limitations under the License. */ +import { ProviderId } from '../../../model/public_types'; import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; -import { ProviderId } from '@firebase/auth-types-exp'; - import { mockEndpoint } from '../../../../test/helpers/api/helper'; import { testAuth, TestAuth } from '../../../../test/helpers/mock_auth'; import * as mockFetch from '../../../../test/helpers/mock_fetch'; import { Endpoint } from '../../../api'; import { FinalizeMfaResponse } from '../../../api/authentication/mfa'; import { PhoneAuthCredential } from '../../../core/credentials/phone'; +import { MultiFactorSessionImpl } from '../../../mfa/mfa_session'; import { PhoneAuthProvider } from '../../providers/phone'; -import { MultiFactorSession } from '../../../mfa/mfa_session'; -import { PhoneMultiFactorAssertion, PhoneMultiFactorGenerator } from './phone'; +import { + PhoneMultiFactorAssertionImpl, + PhoneMultiFactorGenerator +} from './phone'; use(chaiAsPromised); -describe('core/mfa/phone/PhoneMultiFactorAssertion', () => { +describe('platform_browser/mfa/phone', () => { let auth: TestAuth; let credential: PhoneAuthCredential; - let assertion: PhoneMultiFactorAssertion; - let session: MultiFactorSession; + let assertion: PhoneMultiFactorAssertionImpl; + let session: MultiFactorSessionImpl; const serverResponse: FinalizeMfaResponse = { idToken: 'final-id-token', @@ -50,13 +52,13 @@ describe('core/mfa/phone/PhoneMultiFactorAssertion', () => { 'verification-id', 'verification-code' ); - assertion = PhoneMultiFactorAssertion._fromCredential(credential); + assertion = PhoneMultiFactorAssertionImpl._fromCredential(credential); }); afterEach(mockFetch.tearDown); describe('enroll', () => { beforeEach(() => { - session = MultiFactorSession._fromIdtoken('enrollment-id-token'); + session = MultiFactorSessionImpl._fromIdtoken('enrollment-id-token'); }); it('should finalize the MFA enrollment', async () => { @@ -103,7 +105,7 @@ describe('core/mfa/phone/PhoneMultiFactorAssertion', () => { describe('sign_in', () => { beforeEach(() => { - session = MultiFactorSession._fromMfaPendingCredential( + session = MultiFactorSessionImpl._fromMfaPendingCredential( 'mfa-pending-credential' ); }); diff --git a/packages-exp/auth-exp/src/platform_browser/mfa/assertions/phone.ts b/packages-exp/auth-exp/src/platform_browser/mfa/assertions/phone.ts index 4f7a7d5aa68..91633f82cc9 100644 --- a/packages-exp/auth-exp/src/platform_browser/mfa/assertions/phone.ts +++ b/packages-exp/auth-exp/src/platform_browser/mfa/assertions/phone.ts @@ -14,10 +14,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { + FactorId, + PhoneMultiFactorAssertion +} from '../../../model/public_types'; -import { MultiFactorAssertion } from '../../../mfa/assertions'; -import { Auth } from '../../../model/auth'; +import { MultiFactorAssertionImpl } from '../../../mfa/mfa_assertion'; +import { AuthInternal } from '../../../model/auth'; import { finalizeEnrollPhoneMfa } from '../../../api/account_management/mfa'; import { PhoneAuthCredential } from '../../../core/credentials/phone'; import { @@ -25,21 +28,28 @@ import { FinalizeMfaResponse } from '../../../api/authentication/mfa'; -export class PhoneMultiFactorAssertion - extends MultiFactorAssertion - implements externs.PhoneMultiFactorAssertion { +/** + * {@inheritdoc PhoneMultiFactorAssertion} + * + * @public + */ +export class PhoneMultiFactorAssertionImpl + extends MultiFactorAssertionImpl + implements PhoneMultiFactorAssertion { private constructor(private readonly credential: PhoneAuthCredential) { - super(credential.providerId); + super(FactorId.PHONE); } + /** @internal */ static _fromCredential( credential: PhoneAuthCredential - ): PhoneMultiFactorAssertion { - return new PhoneMultiFactorAssertion(credential); + ): PhoneMultiFactorAssertionImpl { + return new PhoneMultiFactorAssertionImpl(credential); } + /** @internal */ _finalizeEnroll( - auth: Auth, + auth: AuthInternal, idToken: string, displayName?: string | null ): Promise { @@ -50,8 +60,9 @@ export class PhoneMultiFactorAssertion }); } + /** @internal */ _finalizeSignIn( - auth: Auth, + auth: AuthInternal, mfaPendingCredential: string ): Promise { return finalizeSignInPhoneMfa(auth, { @@ -61,15 +72,22 @@ export class PhoneMultiFactorAssertion } } -export class PhoneMultiFactorGenerator - implements externs.PhoneMultiFactorGenerator { +/** + * Provider for generating a {@link PhoneMultiFactorAssertion}. + * + * @public + */ +export class PhoneMultiFactorGenerator { private constructor() {} - static assertion( - credential: externs.PhoneAuthCredential - ): externs.PhoneMultiFactorAssertion { - return PhoneMultiFactorAssertion._fromCredential( - credential as PhoneAuthCredential - ); + /** + * Provides a {@link PhoneMultiFactorAssertion} to confirm ownership of the phone second factor. + * + * @param phoneAuthCredential - A credential provided by {@link PhoneAuthProvider.credential}. + * @returns A {@link PhoneMultiFactorAssertion} which can be used with + * {@link MultiFactorResolver.resolveSignIn} + */ + static assertion(credential: PhoneAuthCredential): PhoneMultiFactorAssertion { + return PhoneMultiFactorAssertionImpl._fromCredential(credential); } } diff --git a/packages-exp/auth-exp/src/platform_browser/persistence/indexed_db.test.ts b/packages-exp/auth-exp/src/platform_browser/persistence/indexed_db.test.ts index 461dc6d0dca..7bdc7f8cf68 100644 --- a/packages-exp/auth-exp/src/platform_browser/persistence/indexed_db.test.ts +++ b/packages-exp/auth-exp/src/platform_browser/persistence/indexed_db.test.ts @@ -18,22 +18,40 @@ import { expect, use } from 'chai'; import * as sinon from 'sinon'; import * as sinonChai from 'sinon-chai'; - -import { testUser, testAuth } from '../../../test/helpers/mock_auth'; -import { _getInstance } from '../../core/util/instantiator'; -import { Persistence, PersistenceType } from '../../core/persistence'; +import { FakeServiceWorker } from '../../../test/helpers/fake_service_worker'; +import { testAuth, testUser } from '../../../test/helpers/mock_auth'; +import { PersistenceInternal, PersistenceType } from '../../core/persistence'; +import { + SingletonInstantiator, + _getInstance +} from '../../core/util/instantiator'; import { + _EventType, + KeyChangedRequest, + _TimeoutDuration +} from '../messagechannel/index'; +import { Receiver } from '../messagechannel/receiver'; +import { Sender } from '../messagechannel/sender'; +import * as workerUtil from '../util/worker'; +import { + _deleteObject, indexedDBLocalPersistence, - _POLLING_INTERVAL_MS, - _putObject, + _clearDatabase, _openDatabase, - _clearDatabase + _POLLING_INTERVAL_MS, + _putObject } from './indexed_db'; use(sinonChai); -describe('core/persistence/indexed_db', () => { - const persistence: Persistence = _getInstance(indexedDBLocalPersistence); +interface TestPersistence extends PersistenceInternal { + _workerInitializationPromise: Promise; +} + +describe('platform_browser/persistence/indexed_db', () => { + const persistence: PersistenceInternal = _getInstance( + indexedDBLocalPersistence + ); afterEach(sinon.restore); @@ -110,6 +128,11 @@ describe('core/persistence/indexed_db', () => { clock.restore(); }); + it('should not trigger a listener when there are no changes', async () => { + await waitUntilPoll(clock); + expect(callback).not.to.have.been.called; + }); + it('should trigger a listener when the key changes', async () => { await _putObject(db, key, newValue); @@ -118,6 +141,18 @@ describe('core/persistence/indexed_db', () => { expect(callback).to.have.been.calledWith(newValue); }); + it('should trigger the listener when the key is removed', async () => { + await _putObject(db, key, newValue); + await waitUntilPoll(clock); + callback.resetHistory(); + + await _deleteObject(db, key); + + await waitUntilPoll(clock); + + expect(callback).to.have.been.calledOnceWith(null); + }); + it('should not trigger the listener when a different key changes', async () => { await _putObject(db, 'other-key', newValue); @@ -158,4 +193,176 @@ describe('core/persistence/indexed_db', () => { }); }); }); + + context('service worker integration', () => { + let serviceWorker: ServiceWorker; + let persistence: TestPersistence; + + beforeEach(() => { + serviceWorker = (new FakeServiceWorker() as unknown) as ServiceWorker; + }); + + afterEach(() => { + sinon.restore(); + }); + + context('as a service worker', () => { + let sender: Sender; + let db: IDBDatabase; + + beforeEach(async () => { + sender = new Sender(serviceWorker); + sinon.stub(workerUtil, '_isWorker').returns(true); + sinon.stub(workerUtil, '_getWorkerGlobalScope').returns(serviceWorker); + persistence = new ((indexedDBLocalPersistence as unknown) as SingletonInstantiator)(); + db = await _openDatabase(); + }); + + it('should respond to pings', async () => { + await persistence._workerInitializationPromise; + const response = await sender._send( + _EventType.PING, + {}, + _TimeoutDuration.ACK + ); + + expect(response).to.have.deep.members([ + { + fulfilled: true, + value: [_EventType.KEY_CHANGED] + } + ]); + }); + + it('should let us know if the key didnt actually change on a key changed event', async () => { + await persistence._workerInitializationPromise; + const response = await sender._send( + _EventType.KEY_CHANGED, + { + key: 'foo' + }, + _TimeoutDuration.LONG_ACK + ); + + expect(response).to.have.deep.members([ + { + fulfilled: true, + value: { + keyProcessed: false + } + } + ]); + }); + + it('should refresh on key changed events when a key has changed', async () => { + await persistence._workerInitializationPromise; + await _putObject(db, 'foo', 'bar'); + const response = await sender._send( + _EventType.KEY_CHANGED, + { + key: 'foo' + }, + _TimeoutDuration.LONG_ACK + ); + + expect(response).to.have.deep.members([ + { + fulfilled: true, + value: { + keyProcessed: true + } + } + ]); + }); + }); + + context('as a service worker controller', () => { + let receiver: Receiver; + + beforeEach(() => { + receiver = Receiver._getInstance(serviceWorker); + sinon.stub(workerUtil, '_isWorker').returns(false); + sinon + .stub(workerUtil, '_getActiveServiceWorker') + .returns(Promise.resolve(serviceWorker)); + sinon + .stub(workerUtil, '_getServiceWorkerController') + .returns(serviceWorker); + persistence = new ((indexedDBLocalPersistence as unknown) as SingletonInstantiator)(); + }); + + it('should send a ping on init', async () => { + return new Promise(resolve => { + receiver._subscribe(_EventType.PING, () => { + resolve(); + return [_EventType.KEY_CHANGED]; + }); + return persistence._workerInitializationPromise; + }); + }); + + it('should send a key changed event when a key is set', async () => { + return new Promise(async resolve => { + await persistence._workerInitializationPromise; + receiver._subscribe( + _EventType.KEY_CHANGED, + (_origin: string, data: KeyChangedRequest) => { + expect(data.key).to.eq('foo'); + resolve(); + return { + keyProcessed: true + }; + } + ); + return persistence._set('foo', 'bar'); + }); + }); + + it('should send a key changed event when a key is removed', async () => { + return new Promise(async resolve => { + receiver._subscribe( + _EventType.KEY_CHANGED, + async (_origin: string, data: KeyChangedRequest) => { + expect(data.key).to.eq('foo'); + const persistedValue = await persistence._get('foo'); + if (!persistedValue) { + resolve(); + } + return { + keyProcessed: true + }; + } + ); + await persistence._workerInitializationPromise; + await persistence._set('foo', 'bar'); + return persistence._remove('foo'); + }); + }); + }); + }); + + describe('closed IndexedDB connection', () => { + it('should retry by reopening the connection', async () => { + const closeDb = async (): Promise => { + const db = await ((persistence as unknown) as { + _openDb(): Promise; + })._openDb(); + db.close(); + }; + const key = 'my-super-special-persistence-type'; + const value = PersistenceType.LOCAL; + + expect(await persistence._get(key)).to.be.null; + + await closeDb(); + await persistence._set(key, value); + + await closeDb(); + expect(await persistence._get(key)).to.be.eq(value); + + await closeDb(); + await persistence._remove(key); + expect(await persistence._get(key)).to.be.null; + }); + }); }); diff --git a/packages-exp/auth-exp/src/platform_browser/persistence/indexed_db.ts b/packages-exp/auth-exp/src/platform_browser/persistence/indexed_db.ts index 0987d73e09c..7ebd5397096 100644 --- a/packages-exp/auth-exp/src/platform_browser/persistence/indexed_db.ts +++ b/packages-exp/auth-exp/src/platform_browser/persistence/indexed_db.ts @@ -15,15 +15,31 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { Persistence } from '../../model/public_types'; import { PersistedBlob, - Persistence, + PersistenceInternal as InternalPersistence, PersistenceType, PersistenceValue, StorageEventListener, STORAGE_AVAILABLE_KEY } from '../../core/persistence/'; +import { + _EventType, + _PingResponse, + KeyChangedResponse, + KeyChangedRequest, + PingRequest, + _TimeoutDuration +} from '../messagechannel/index'; +import { Receiver } from '../messagechannel/receiver'; +import { Sender } from '../messagechannel/sender'; +import { + _isWorker, + _getActiveServiceWorker, + _getServiceWorkerController, + _getWorkerGlobalScope +} from '../util/worker'; export const DB_NAME = 'firebaseLocalStorageDb'; const DB_VERSION = 1; @@ -39,6 +55,7 @@ interface DBObject { * Promise wrapper for IDBRequest * * Unfortunately we can't cleanly extend Promise since promises are not callable in ES6 + * */ class DBPromise { constructor(private readonly request: IDBRequest) {} @@ -96,8 +113,10 @@ export function _openDatabase(): Promise { // https://github.com/firebase/firebase-js-sdk/issues/634 if (!db.objectStoreNames.contains(DB_OBJECTSTORE_NAME)) { + // Need to close the database or else you get a `blocked` event + db.close(); await _deleteDatabase(); - return _openDatabase(); + resolve(await _openDatabase()); } else { resolve(db); } @@ -110,20 +129,11 @@ export async function _putObject( key: string, value: PersistenceValue | string ): Promise { - const getRequest = getObjectStore(db, false).get(key); - const data = await new DBPromise(getRequest).toPromise(); - if (data) { - // Force an index signature on the user object - data.value = value as PersistedBlob; - const request = getObjectStore(db, true).put(data); - return new DBPromise(request).toPromise(); - } else { - const request = getObjectStore(db, true).add({ - [DB_DATA_KEYPATH]: key, - value - }); - return new DBPromise(request).toPromise(); - } + const request = getObjectStore(db, true).put({ + [DB_DATA_KEYPATH]: key, + value + }); + return new DBPromise(request).toPromise(); } async function getObject( @@ -135,14 +145,15 @@ async function getObject( return data === undefined ? null : data.value; } -function deleteObject(db: IDBDatabase, key: string): Promise { +export function _deleteObject(db: IDBDatabase, key: string): Promise { const request = getObjectStore(db, true).delete(key); return new DBPromise(request).toPromise(); } export const _POLLING_INTERVAL_MS = 800; +export const _TRANSACTION_RETRY_COUNT = 3; -class IndexedDBLocalPersistence implements Persistence { +class IndexedDBLocalPersistence implements InternalPersistence { static type: 'LOCAL' = 'LOCAL'; type = PersistenceType.LOCAL; @@ -155,7 +166,23 @@ class IndexedDBLocalPersistence implements Persistence { private pollTimer: any | null = null; private pendingWrites = 0; - private async initialize(): Promise { + private receiver: Receiver | null = null; + private sender: Sender | null = null; + private serviceWorkerReceiverAvailable = false; + private activeServiceWorker: ServiceWorker | null = null; + // Visible for testing only + readonly _workerInitializationPromise: Promise; + + constructor() { + // Fire & forget the service worker registration as it may never resolve + this._workerInitializationPromise = + this.initializeServiceWorkerMessaging().then( + () => {}, + () => {} + ); + } + + async _openDb(): Promise { if (this.db) { return this.db; } @@ -163,6 +190,120 @@ class IndexedDBLocalPersistence implements Persistence { return this.db; } + async _withRetries(op: (db: IDBDatabase) => Promise): Promise { + let numAttempts = 0; + + while (true) { + try { + const db = await this._openDb(); + return await op(db); + } catch (e) { + if (numAttempts++ > _TRANSACTION_RETRY_COUNT) { + throw e; + } + if (this.db) { + this.db.close(); + this.db = undefined; + } + // TODO: consider adding exponential backoff + } + } + } + + /** + * IndexedDB events do not propagate from the main window to the worker context. We rely on a + * postMessage interface to send these events to the worker ourselves. + */ + private async initializeServiceWorkerMessaging(): Promise { + return _isWorker() ? this.initializeReceiver() : this.initializeSender(); + } + + /** + * As the worker we should listen to events from the main window. + */ + private async initializeReceiver(): Promise { + this.receiver = Receiver._getInstance(_getWorkerGlobalScope()!); + // Refresh from persistence if we receive a KeyChanged message. + this.receiver._subscribe( + _EventType.KEY_CHANGED, + async (_origin: string, data: KeyChangedRequest) => { + const keys = await this._poll(); + return { + keyProcessed: keys.includes(data.key) + }; + } + ); + // Let the sender know that we are listening so they give us more timeout. + this.receiver._subscribe( + _EventType.PING, + async (_origin: string, _data: PingRequest) => { + return [_EventType.KEY_CHANGED]; + } + ); + } + + /** + * As the main window, we should let the worker know when keys change (set and remove). + * + * @remarks + * {@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/ready | ServiceWorkerContainer.ready} + * may not resolve. + */ + private async initializeSender(): Promise { + // Check to see if there's an active service worker. + this.activeServiceWorker = await _getActiveServiceWorker(); + if (!this.activeServiceWorker) { + return; + } + this.sender = new Sender(this.activeServiceWorker); + // Ping the service worker to check what events they can handle. + const results = await this.sender._send<_PingResponse, PingRequest>( + _EventType.PING, + {}, + _TimeoutDuration.LONG_ACK + ); + if (!results) { + return; + } + if ( + results[0]?.fulfilled && + results[0]?.value.includes(_EventType.KEY_CHANGED) + ) { + this.serviceWorkerReceiverAvailable = true; + } + } + + /** + * Let the worker know about a changed key, the exact key doesn't technically matter since the + * worker will just trigger a full sync anyway. + * + * @remarks + * For now, we only support one service worker per page. + * + * @param key - Storage key which changed. + */ + private async notifyServiceWorker(key: string): Promise { + if ( + !this.sender || + !this.activeServiceWorker || + _getServiceWorkerController() !== this.activeServiceWorker + ) { + return; + } + try { + await this.sender._send( + _EventType.KEY_CHANGED, + { key }, + // Use long timeout if receiver has previously responded to a ping from us. + this.serviceWorkerReceiverAvailable + ? _TimeoutDuration.LONG_ACK + : _TimeoutDuration.ACK + ); + } catch { + // This is a best effort approach. Ignore errors. + } + } + async _isAvailable(): Promise { try { if (!indexedDB) { @@ -170,7 +311,7 @@ class IndexedDBLocalPersistence implements Persistence { } const db = await _openDatabase(); await _putObject(db, STORAGE_AVAILABLE_KEY, '1'); - await deleteObject(db, STORAGE_AVAILABLE_KEY); + await _deleteObject(db, STORAGE_AVAILABLE_KEY); return true; } catch {} return false; @@ -186,63 +327,74 @@ class IndexedDBLocalPersistence implements Persistence { } async _set(key: string, value: PersistenceValue): Promise { - const db = await this.initialize(); return this._withPendingWrite(async () => { - await _putObject(db, key, value); + await this._withRetries((db: IDBDatabase) => _putObject(db, key, value)); this.localCache[key] = value; + return this.notifyServiceWorker(key); }); } async _get(key: string): Promise { - const db = await this.initialize(); - const obj = (await getObject(db, key)) as T; + const obj = (await this._withRetries((db: IDBDatabase) => + getObject(db, key) + )) as T; this.localCache[key] = obj; return obj; } async _remove(key: string): Promise { - const db = await this.initialize(); return this._withPendingWrite(async () => { - await deleteObject(db, key); + await this._withRetries((db: IDBDatabase) => _deleteObject(db, key)); delete this.localCache[key]; + return this.notifyServiceWorker(key); }); } - private async _poll(): Promise { - const db = await _openDatabase(); - + private async _poll(): Promise { // TODO: check if we need to fallback if getAll is not supported - const getAllRequest = getObjectStore(db, false).getAll(); - const result = await new DBPromise( - getAllRequest - ).toPromise(); + const result = await this._withRetries((db: IDBDatabase) => { + const getAllRequest = getObjectStore(db, false).getAll(); + return new DBPromise(getAllRequest).toPromise(); + }); if (!result) { - return; + return []; } // If we have pending writes in progress abort, we'll get picked up on the next poll if (this.pendingWrites !== 0) { - return; + return []; } + const keys = []; + const keysInResult = new Set(); for (const { fbase_key: key, value } of result) { + keysInResult.add(key); if (JSON.stringify(this.localCache[key]) !== JSON.stringify(value)) { this.notifyListeners(key, value as PersistenceValue); + keys.push(key); + } + } + for (const localKey of Object.keys(this.localCache)) { + if (this.localCache[localKey] && !keysInResult.has(localKey)) { + // Deleted + this.notifyListeners(localKey, null); + keys.push(localKey); } } + return keys; } private notifyListeners( key: string, newValue: PersistenceValue | null ): void { - if (!this.listeners[key]) { - return; - } this.localCache[key] = newValue; - for (const listener of Array.from(this.listeners[key])) { - listener(newValue); + const listeners = this.listeners[key]; + if (listeners) { + for (const listener of Array.from(listeners)) { + listener(newValue); + } } } @@ -266,7 +418,11 @@ class IndexedDBLocalPersistence implements Persistence { if (Object.keys(this.listeners).length === 0) { this.startPolling(); } - this.listeners[key] = this.listeners[key] || new Set(); + if (!this.listeners[key]) { + this.listeners[key] = new Set(); + // Populate the cache to avoid spuriously triggering on first poll. + void this._get(key); // This can happen in the background async and we can return immediately. + } this.listeners[key].add(listener); } @@ -276,7 +432,6 @@ class IndexedDBLocalPersistence implements Persistence { if (this.listeners[key].size === 0) { delete this.listeners[key]; - delete this.localCache[key]; } } @@ -286,4 +441,10 @@ class IndexedDBLocalPersistence implements Persistence { } } -export const indexedDBLocalPersistence: externs.Persistence = IndexedDBLocalPersistence; +/** + * An implementation of {@link Persistence} of type 'LOCAL' using `indexedDB` + * for the underlying storage. + * + * @public + */ +export const indexedDBLocalPersistence: Persistence = IndexedDBLocalPersistence; diff --git a/packages-exp/auth-exp/src/platform_browser/persistence/local_storage.test.ts b/packages-exp/auth-exp/src/platform_browser/persistence/local_storage.test.ts index 8e138ce2e3f..dd5b713eded 100644 --- a/packages-exp/auth-exp/src/platform_browser/persistence/local_storage.test.ts +++ b/packages-exp/auth-exp/src/platform_browser/persistence/local_storage.test.ts @@ -21,7 +21,7 @@ import * as sinonChai from 'sinon-chai'; import { testAuth, testUser } from '../../../test/helpers/mock_auth'; import { PersistedBlob, - Persistence, + PersistenceInternal, PersistenceType } from '../../core/persistence'; import { _getInstance } from '../../core/util/instantiator'; @@ -29,8 +29,10 @@ import { browserLocalPersistence, _POLLING_INTERVAL_MS } from './local_storage'; use(sinonChai); -describe('browserLocalPersistence', () => { - const persistence: Persistence = _getInstance(browserLocalPersistence); +describe('platform_browser/persistence/local_storage', () => { + const persistence: PersistenceInternal = _getInstance( + browserLocalPersistence + ); beforeEach(() => { localStorage.clear(); diff --git a/packages-exp/auth-exp/src/platform_browser/persistence/local_storage.ts b/packages-exp/auth-exp/src/platform_browser/persistence/local_storage.ts index 60509787ad5..49b32c86a5d 100644 --- a/packages-exp/auth-exp/src/platform_browser/persistence/local_storage.ts +++ b/packages-exp/auth-exp/src/platform_browser/persistence/local_storage.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { Persistence } from '../../model/public_types'; import { getUA } from '@firebase/util'; import { @@ -26,8 +26,9 @@ import { _isIE10 } from '../../core/util/browser'; import { - Persistence, + PersistenceInternal as InternalPersistence, PersistenceType, + PersistenceValue, StorageEventListener } from '../../core/persistence'; import { BrowserPersistenceClass } from './browser'; @@ -45,13 +46,18 @@ const IE10_LOCAL_STORAGE_SYNC_DELAY = 10; class BrowserLocalPersistence extends BrowserPersistenceClass - implements Persistence { + implements InternalPersistence { static type: 'LOCAL' = 'LOCAL'; constructor() { - super(localStorage, PersistenceType.LOCAL); + super(window.localStorage, PersistenceType.LOCAL); + this.boundEventHandler = this.onStorageEvent.bind(this); } + private readonly boundEventHandler: ( + event: StorageEvent, + poll?: boolean + ) => void; private readonly listeners: Record> = {}; private readonly localCache: Record = {}; // setTimeout return value is platform specific @@ -93,11 +99,6 @@ class BrowserLocalPersistence const key = event.key; - // Ignore keys that have no listeners. - if (!this.listeners[key]) { - return; - } - // Check the mechanism how this event was detected. // The first event will dictate the mechanism to be used. if (poll) { @@ -159,12 +160,12 @@ class BrowserLocalPersistence } private notifyListeners(key: string, value: string | null): void { - if (!this.listeners[key]) { - return; - } this.localCache[key] = value; - for (const listener of Array.from(this.listeners[key])) { - listener(value ? JSON.parse(value) : value); + const listeners = this.listeners[key]; + if (listeners) { + for (const listener of Array.from(listeners)) { + listener(value ? JSON.parse(value) : value); + } } } @@ -195,15 +196,14 @@ class BrowserLocalPersistence } private attachListener(): void { - window.addEventListener('storage', this.onStorageEvent.bind(this)); + window.addEventListener('storage', this.boundEventHandler); } private detachListener(): void { - window.removeEventListener('storage', this.onStorageEvent); + window.removeEventListener('storage', this.boundEventHandler); } _addListener(key: string, listener: StorageEventListener): void { - this.localCache[key] = this.storage.getItem(key); if (Object.keys(this.listeners).length === 0) { // Whether browser can detect storage event when it had already been pushed to the background. // This may happen in some mobile browsers. A localStorage change in the foreground window @@ -215,7 +215,11 @@ class BrowserLocalPersistence this.attachListener(); } } - this.listeners[key] = this.listeners[key] || new Set(); + if (!this.listeners[key]) { + this.listeners[key] = new Set(); + // Populate the cache to avoid spuriously triggering on first poll. + this.localCache[key] = this.storage.getItem(key); + } this.listeners[key].add(listener); } @@ -225,7 +229,6 @@ class BrowserLocalPersistence if (this.listeners[key].size === 0) { delete this.listeners[key]; - delete this.localCache[key]; } } @@ -234,6 +237,30 @@ class BrowserLocalPersistence this.stopPolling(); } } + + // Update local cache on base operations: + + async _set(key: string, value: PersistenceValue): Promise { + await super._set(key, value); + this.localCache[key] = JSON.stringify(value); + } + + async _get(key: string): Promise { + const value = await super._get(key); + this.localCache[key] = JSON.stringify(value); + return value; + } + + async _remove(key: string): Promise { + await super._remove(key); + delete this.localCache[key]; + } } -export const browserLocalPersistence: externs.Persistence = BrowserLocalPersistence; +/** + * An implementation of {@link Persistence} of type 'LOCAL' using `localStorage` + * for the underlying storage. + * + * @public + */ +export const browserLocalPersistence: Persistence = BrowserLocalPersistence; diff --git a/packages-exp/auth-exp/src/platform_browser/persistence/session_storage.test.ts b/packages-exp/auth-exp/src/platform_browser/persistence/session_storage.test.ts index 7eae761311b..2ea2dea6039 100644 --- a/packages-exp/auth-exp/src/platform_browser/persistence/session_storage.test.ts +++ b/packages-exp/auth-exp/src/platform_browser/persistence/session_storage.test.ts @@ -20,13 +20,13 @@ import * as sinon from 'sinon'; import { testAuth, testUser } from '../../../test/helpers/mock_auth'; import { PersistedBlob, - Persistence, + PersistenceInternal, PersistenceType } from '../../core/persistence'; import { _getInstance } from '../../core/util/instantiator'; import { browserSessionPersistence } from './session_storage'; -describe('core/persistence/browser', () => { +describe('platform_browser/persistence/session_storage', () => { beforeEach(() => { localStorage.clear(); sessionStorage.clear(); @@ -34,7 +34,9 @@ describe('core/persistence/browser', () => { afterEach(() => sinon.restore()); describe('browserSessionPersistence', () => { - const persistence: Persistence = _getInstance(browserSessionPersistence); + const persistence: PersistenceInternal = _getInstance( + browserSessionPersistence + ); it('should work with persistence type', async () => { const key = 'my-super-special-persistence-type'; diff --git a/packages-exp/auth-exp/src/platform_browser/persistence/session_storage.ts b/packages-exp/auth-exp/src/platform_browser/persistence/session_storage.ts index b585f658b0d..ddce0fd414e 100644 --- a/packages-exp/auth-exp/src/platform_browser/persistence/session_storage.ts +++ b/packages-exp/auth-exp/src/platform_browser/persistence/session_storage.ts @@ -15,10 +15,10 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { Persistence } from '../../model/public_types'; import { - Persistence, + PersistenceInternal as InternalPersistence, PersistenceType, StorageEventListener } from '../../core/persistence'; @@ -26,11 +26,11 @@ import { BrowserPersistenceClass } from './browser'; class BrowserSessionPersistence extends BrowserPersistenceClass - implements Persistence { + implements InternalPersistence { static type: 'SESSION' = 'SESSION'; constructor() { - super(sessionStorage, PersistenceType.SESSION); + super(window.sessionStorage, PersistenceType.SESSION); } _addListener(_key: string, _listener: StorageEventListener): void { @@ -44,4 +44,10 @@ class BrowserSessionPersistence } } -export const browserSessionPersistence: externs.Persistence = BrowserSessionPersistence; +/** + * An implementation of {@link Persistence} of 'SESSION' using `sessionStorage` + * for the underlying storage. + * + * @public + */ +export const browserSessionPersistence: Persistence = BrowserSessionPersistence; diff --git a/packages-exp/auth-exp/src/platform_browser/popup_redirect.test.ts b/packages-exp/auth-exp/src/platform_browser/popup_redirect.test.ts index ae8db8590f1..07724657e1e 100644 --- a/packages-exp/auth-exp/src/platform_browser/popup_redirect.test.ts +++ b/packages-exp/auth-exp/src/platform_browser/popup_redirect.test.ts @@ -21,7 +21,7 @@ import * as sinon from 'sinon'; import * as sinonChai from 'sinon-chai'; import { SDK_VERSION } from '@firebase/app-exp'; -import { Config, ProviderId } from '@firebase/auth-types-exp'; +import { Config, ProviderId } from '../model/public_types'; import { FirebaseError } from '@firebase/util'; import { @@ -38,7 +38,7 @@ import { AuthEvent, AuthEventType, GapiAuthEvent, - PopupRedirectResolver + PopupRedirectResolverInternal } from '../model/popup_redirect'; import * as authWindow from './auth_window'; import * as gapiLoader from './iframe/gapi'; @@ -47,17 +47,15 @@ import { browserPopupRedirectResolver } from './popup_redirect'; use(chaiAsPromised); use(sinonChai); -describe('src/platform_browser/popup_redirect', () => { - let resolver: PopupRedirectResolver; +describe('platform_browser/popup_redirect', () => { + let resolver: PopupRedirectResolverInternal; let auth: TestAuth; let onIframeMessage: (event: GapiAuthEvent) => Promise; let iframeSendStub: sinon.SinonStub; beforeEach(async () => { auth = await testAuth(); - resolver = new (browserPopupRedirectResolver as SingletonInstantiator< - PopupRedirectResolver - >)(); + resolver = new (browserPopupRedirectResolver as SingletonInstantiator)(); sinon.stub(validateOrigin, '_validateOrigin').returns(Promise.resolve()); iframeSendStub = sinon.stub(); @@ -74,6 +72,12 @@ describe('src/platform_browser/popup_redirect', () => { }) } as unknown) as gapi.iframes.Context) ); + + sinon.stub(authWindow._window(), 'gapi').value({ + iframes: { + CROSS_ORIGIN_IFRAMES_FILTER: 'cross-origin-iframes-filter' + } + }); }); afterEach(() => { @@ -113,15 +117,6 @@ describe('src/platform_browser/popup_redirect', () => { ); }); - it('throws an error if authDomain is unspecified', async () => { - delete auth.config.authDomain; - await resolver._initialize(auth); - - await expect( - resolver._openPopup(auth, provider, event) - ).to.be.rejectedWith(FirebaseError, 'auth/auth-domain-config-required'); - }); - it('throws an error if apiKey is unspecified', async () => { delete (auth.config as Partial).apiKey; await resolver._initialize(auth); @@ -130,17 +125,6 @@ describe('src/platform_browser/popup_redirect', () => { resolver._openPopup(auth, provider, event) ).to.be.rejectedWith(FirebaseError, 'auth/invalid-api-key'); }); - - it('rejects immediately if origin validation fails', async () => { - await resolver._initialize(auth); - (validateOrigin._validateOrigin as sinon.SinonStub).returns( - Promise.reject(new Error('invalid-origin')) - ); - - await expect( - resolver._openPopup(auth, provider, event) - ).to.be.rejectedWith(Error, 'invalid-origin'); - }); }); context('#_openRedirect', () => { @@ -211,6 +195,27 @@ describe('src/platform_browser/popup_redirect', () => { }); }); + context('#_originValidation', () => { + it('validates the origin', async () => { + await resolver._initialize(auth); + + await resolver._originValidation(auth); + expect(validateOrigin._validateOrigin).to.have.been.calledWith(auth); + }); + + it('rejects if origin validation fails', async () => { + await resolver._initialize(auth); + (validateOrigin._validateOrigin as sinon.SinonStub).returns( + Promise.reject(new Error('invalid-origin')) + ); + + await expect(resolver._originValidation(auth)).to.be.rejectedWith( + Error, + 'invalid-origin' + ); + }); + }); + context('#_initialize', () => { it('returns different manager for a different auth', async () => { const manager = await resolver._initialize(auth); @@ -312,7 +317,7 @@ describe('src/platform_browser/popup_redirect', () => { expect(args[1]).to.eql({ type: 'webStorageSupport' }); - expect(args[3]).to.eq(gapi.iframes.CROSS_ORIGIN_IFRAMES_FILTER); + expect(args[3]).to.eq('cross-origin-iframes-filter'); }); it('passes through true value from the response to the callback', done => { diff --git a/packages-exp/auth-exp/src/platform_browser/popup_redirect.ts b/packages-exp/auth-exp/src/platform_browser/popup_redirect.ts index 698bb225f6c..26a4deb2ec9 100644 --- a/packages-exp/auth-exp/src/platform_browser/popup_redirect.ts +++ b/packages-exp/auth-exp/src/platform_browser/popup_redirect.ts @@ -15,44 +15,33 @@ * limitations under the License. */ -import { SDK_VERSION } from '@firebase/app-exp'; -import * as externs from '@firebase/auth-types-exp'; -import { isEmpty, querystring } from '@firebase/util'; -import { _getInstance } from '../core/util/instantiator'; +import { AuthProvider, PopupRedirectResolver } from '../model/public_types'; import { AuthEventManager } from '../core/auth/auth_event_manager'; import { AuthErrorCode } from '../core/errors'; -import { OAuthProvider } from '../core/providers/oauth'; -import { assert, debugAssert, fail } from '../core/util/assert'; -import { _emulatorUrl } from '../core/util/emulator'; +import { _assert, debugAssert, _fail } from '../core/util/assert'; import { _generateEventId } from '../core/util/event_id'; import { _getCurrentUrl } from '../core/util/location'; import { _validateOrigin } from '../core/util/validate_origin'; -import { ApiKey, AppName, Auth } from '../model/auth'; +import { AuthInternal } from '../model/auth'; import { AuthEventType, EventManager, GapiAuthEvent, GapiOutcome, - PopupRedirectResolver + PopupRedirectResolverInternal } from '../model/popup_redirect'; import { _setWindowLocation } from './auth_window'; import { _openIframe } from './iframe/iframe'; import { browserSessionPersistence } from './persistence/session_storage'; import { _open, AuthPopup } from './util/popup'; - -/** - * URL for Authentication widget which will initiate the OAuth handshake - */ -const WIDGET_PATH = '__/auth/handler'; - -/** - * URL for emulated environment - */ -const EMULATOR_WIDGET_PATH = 'emulator/auth/handler'; +import { _getRedirectResult } from './strategies/redirect'; +import { _getRedirectUrl } from '../core/util/handler'; +import { _isIOS, _isMobileBrowser, _isSafari } from '../core/util/browser'; /** * The special web storage event + * */ const WEB_STORAGE_SUPPORT_KEY = 'webStorageSupport'; @@ -65,27 +54,7 @@ interface ManagerOrPromise { promise?: Promise; } -/** - * Chooses a popup/redirect resolver to use. This prefers the override (which - * is directly passed in), and falls back to the property set on the auth - * object. If neither are available, this function errors w/ an argument error. - */ -export function _withDefaultResolver( - auth: Auth, - resolverOverride: externs.PopupRedirectResolver | undefined -): PopupRedirectResolver { - if (resolverOverride) { - return _getInstance(resolverOverride); - } - - assert(auth._popupRedirectResolver, AuthErrorCode.ARGUMENT_ERROR, { - appName: auth.name - }); - - return auth._popupRedirectResolver; -} - -class BrowserPopupRedirectResolver implements PopupRedirectResolver { +class BrowserPopupRedirectResolver implements PopupRedirectResolverInternal { private readonly eventManagers: Record = {}; private readonly iframes: Record = {}; private readonly originValidationPromises: Record> = {}; @@ -95,8 +64,8 @@ class BrowserPopupRedirectResolver implements PopupRedirectResolver { // Wrapping in async even though we don't await anywhere in order // to make sure errors are raised as promise rejections async _openPopup( - auth: Auth, - provider: externs.AuthProvider, + auth: AuthInternal, + provider: AuthProvider, authType: AuthEventType, eventId?: string ): Promise { @@ -105,23 +74,30 @@ class BrowserPopupRedirectResolver implements PopupRedirectResolver { '_initialize() not called before _openPopup()' ); - await this.originValidation(auth); - const url = getRedirectUrl(auth, provider, authType, eventId); - return _open(auth.name, url, _generateEventId()); + const url = _getRedirectUrl( + auth, + provider, + authType, + _getCurrentUrl(), + eventId + ); + return _open(auth, url, _generateEventId()); } async _openRedirect( - auth: Auth, - provider: externs.AuthProvider, + auth: AuthInternal, + provider: AuthProvider, authType: AuthEventType, eventId?: string ): Promise { - await this.originValidation(auth); - _setWindowLocation(getRedirectUrl(auth, provider, authType, eventId)); + await this._originValidation(auth); + _setWindowLocation( + _getRedirectUrl(auth, provider, authType, _getCurrentUrl(), eventId) + ); return new Promise(() => {}); } - _initialize(auth: Auth): Promise { + _initialize(auth: AuthInternal): Promise { const key = auth._key(); if (this.eventManagers[key]) { const { manager, promise } = this.eventManagers[key]; @@ -138,15 +114,13 @@ class BrowserPopupRedirectResolver implements PopupRedirectResolver { return promise; } - private async initAndGetManager(auth: Auth): Promise { + private async initAndGetManager(auth: AuthInternal): Promise { const iframe = await _openIframe(auth); - const manager = new AuthEventManager(auth.name); + const manager = new AuthEventManager(auth); iframe.register( 'authEvent', (iframeEvent: GapiAuthEvent | null) => { - assert(iframeEvent?.authEvent, AuthErrorCode.INVALID_AUTH_EVENT, { - appName: auth.name - }); + _assert(iframeEvent?.authEvent, auth, AuthErrorCode.INVALID_AUTH_EVENT); // TODO: Consider splitting redirect and popup events earlier on const handled = manager.onEvent(iframeEvent.authEvent); @@ -161,7 +135,7 @@ class BrowserPopupRedirectResolver implements PopupRedirectResolver { } _isIframeWebStorageSupported( - auth: Auth, + auth: AuthInternal, cb: (supported: boolean) => unknown ): void { const iframe = this.iframes[auth._key()]; @@ -174,15 +148,13 @@ class BrowserPopupRedirectResolver implements PopupRedirectResolver { cb(!!isSupported); } - fail(AuthErrorCode.INTERNAL_ERROR, { - appName: auth.name - }); + _fail(auth, AuthErrorCode.INTERNAL_ERROR); }, gapi.iframes.CROSS_ORIGIN_IFRAMES_FILTER ); } - private originValidation(auth: Auth): Promise { + _originValidation(auth: AuthInternal): Promise { const key = auth._key(); if (!this.originValidationPromises[key]) { this.originValidationPromises[key] = _validateOrigin(auth); @@ -190,91 +162,19 @@ class BrowserPopupRedirectResolver implements PopupRedirectResolver { return this.originValidationPromises[key]; } -} - -export const browserPopupRedirectResolver: externs.PopupRedirectResolver = BrowserPopupRedirectResolver; - -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -type WidgetParams = { - apiKey: ApiKey; - appName: AppName; - authType: AuthEventType; - redirectUrl: string; - v: string; - providerId?: string; - scopes?: string; - customParameters?: string; - eventId?: string; - tid?: string; -}; - -function getRedirectUrl( - auth: Auth, - provider: externs.AuthProvider, - authType: AuthEventType, - eventId?: string -): string { - assert(auth.config.authDomain, AuthErrorCode.MISSING_AUTH_DOMAIN, { - appName: auth.name - }); - assert(auth.config.apiKey, AuthErrorCode.INVALID_API_KEY, { - appName: auth.name - }); - - const params: WidgetParams = { - apiKey: auth.config.apiKey, - appName: auth.name, - authType, - redirectUrl: _getCurrentUrl(), - v: SDK_VERSION, - eventId - }; - - if (provider instanceof OAuthProvider) { - provider.setDefaultLanguage(auth.languageCode); - params.providerId = provider.providerId || ''; - if (!isEmpty(provider.getCustomParameters())) { - params.customParameters = JSON.stringify(provider.getCustomParameters()); - } - const scopes = provider.getScopes().filter(scope => scope !== ''); - if (scopes.length > 0) { - params.scopes = scopes.join(','); - } - // TODO set additionalParams? - // let additionalParams = provider.getAdditionalParams(); - // for (let key in additionalParams) { - // if (!params.hasOwnProperty(key)) { - // params[key] = additionalParams[key] - // } - // } - } - - if (auth.tenantId) { - params.tid = auth.tenantId; - } - for (const key of Object.keys(params)) { - if ((params as Record)[key] === undefined) { - delete (params as Record)[key]; - } + get _shouldInitProactively(): boolean { + // Mobile browsers and Safari need to optimistically initialize + return _isMobileBrowser() || _isSafari() || _isIOS(); } - // TODO: maybe set eid as endipointId - // TODO: maybe set fw as Frameworks.join(",") - - const url = new URL( - `${getHandlerBase(auth)}?${querystring( - params as Record - ).slice(1)}` - ); - - return url.toString(); + _completeRedirectFn = _getRedirectResult; } -function getHandlerBase({ config }: Auth): string { - if (!config.emulator) { - return `https://${config.authDomain}/${WIDGET_PATH}`; - } - - return _emulatorUrl(config, EMULATOR_WIDGET_PATH); -} +/** + * An implementation of {@link PopupRedirectResolver} suitable for browser + * based applications. + * + * @public + */ +export const browserPopupRedirectResolver: PopupRedirectResolver = BrowserPopupRedirectResolver; diff --git a/packages-exp/auth-exp/src/platform_browser/providers/phone.test.ts b/packages-exp/auth-exp/src/platform_browser/providers/phone.test.ts index 884d284308b..25383a0deb4 100644 --- a/packages-exp/auth-exp/src/platform_browser/providers/phone.test.ts +++ b/packages-exp/auth-exp/src/platform_browser/providers/phone.test.ts @@ -25,7 +25,7 @@ import { Endpoint } from '../../api'; import { RecaptchaVerifier } from '../../platform_browser/recaptcha/recaptcha_verifier'; import { PhoneAuthProvider } from './phone'; -describe('core/providers/phone', () => { +describe('platform_browser/providers/phone', () => { let auth: TestAuth; beforeEach(async () => { diff --git a/packages-exp/auth-exp/src/platform_browser/providers/phone.ts b/packages-exp/auth-exp/src/platform_browser/providers/phone.ts index 160bf7b95ff..86103423c45 100644 --- a/packages-exp/auth-exp/src/platform_browser/providers/phone.ts +++ b/packages-exp/auth-exp/src/platform_browser/providers/phone.ts @@ -15,40 +15,129 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { + Auth, + PhoneInfoOptions, + ProviderId, + SignInMethod, + ApplicationVerifier, + UserCredential +} from '../../model/public_types'; import { SignInWithPhoneNumberResponse } from '../../api/authentication/sms'; -import { ApplicationVerifier } from '../../model/application_verifier'; -import { Auth } from '../../model/auth'; -import { UserCredential } from '../../model/user'; +import { ApplicationVerifierInternal as ApplicationVerifierInternal } from '../../model/application_verifier'; +import { AuthInternal as AuthInternal } from '../../model/auth'; +import { UserCredentialInternal as UserCredentialInternal } from '../../model/user'; import { PhoneAuthCredential } from '../../core/credentials/phone'; -import { AuthErrorCode } from '../../core/errors'; import { _verifyPhoneNumber } from '../strategies/phone'; -import { assert, fail } from '../../core/util/assert'; import { _castAuth } from '../../core/auth/auth_impl'; +import { AuthCredential } from '../../core'; +import { FirebaseError, getModularInstance } from '@firebase/util'; +import { TaggedWithTokenResponse } from '../../model/id_token'; -export class PhoneAuthProvider implements externs.PhoneAuthProvider { - static readonly PROVIDER_ID = externs.ProviderId.PHONE; - static readonly PHONE_SIGN_IN_METHOD = externs.SignInMethod.PHONE; +/** + * Provider for generating an {@link PhoneAuthCredential}. + * + * @example + * ```javascript + * // 'recaptcha-container' is the ID of an element in the DOM. + * const applicationVerifier = new RecaptchaVerifier('recaptcha-container'); + * const provider = new PhoneAuthProvider(auth); + * const verificationId = await provider.verifyPhoneNumber('+16505550101', applicationVerifier); + * // Obtain the verificationCode from the user. + * const phoneCredential = PhoneAuthProvider.credential(verificationId, verificationCode); + * const userCredential = await signInWithCredential(auth, phoneCredential); + * ``` + * + * @public + */ +export class PhoneAuthProvider { + /** Always set to {@link ProviderId}.PHONE. */ + static readonly PROVIDER_ID = ProviderId.PHONE; + /** Always set to {@link SignInMethod}.PHONE. */ + static readonly PHONE_SIGN_IN_METHOD = SignInMethod.PHONE; + /** Always set to {@link ProviderId}.PHONE. */ readonly providerId = PhoneAuthProvider.PROVIDER_ID; - private readonly auth: Auth; + private readonly auth: AuthInternal; - constructor(auth: externs.Auth) { + /** + * @param auth - The Firebase Auth instance in which sign-ins should occur. + * + */ + constructor(auth: Auth) { this.auth = _castAuth(auth); } + /** + * + * Starts a phone number authentication flow by sending a verification code to the given phone + * number. + * + * @example + * ```javascript + * const provider = new PhoneAuthProvider(auth); + * const verificationId = await provider.verifyPhoneNumber(phoneNumber, applicationVerifier); + * // Obtain verificationCode from the user. + * const authCredential = PhoneAuthProvider.credential(verificationId, verificationCode); + * const userCredential = await signInWithCredential(auth, authCredential); + * ``` + * + * @example + * An alternative flow is provided using the `signInWithPhoneNumber` method. + * ```javascript + * const confirmationResult = signInWithPhoneNumber(auth, phoneNumber, applicationVerifier); + * // Obtain verificationCode from the user. + * const userCredential = confirmationResult.confirm(verificationCode); + * ``` + * + * @param phoneInfoOptions - The user's {@link PhoneInfoOptions}. The phone number should be in + * E.164 format (e.g. +16505550101). + * @param applicationVerifier - For abuse prevention, this method also requires a + * {@link ApplicationVerifier}. This SDK includes a reCAPTCHA-based implementation, + * {@link RecaptchaVerifier}. + * + * @returns A Promise for a verification ID that can be passed to + * {@link PhoneAuthProvider.credential} to identify this flow.. + */ verifyPhoneNumber( - phoneOptions: externs.PhoneInfoOptions | string, - applicationVerifier: externs.ApplicationVerifier + phoneOptions: PhoneInfoOptions | string, + applicationVerifier: ApplicationVerifier ): Promise { return _verifyPhoneNumber( this.auth, phoneOptions, - applicationVerifier as ApplicationVerifier + getModularInstance(applicationVerifier as ApplicationVerifierInternal) ); } + /** + * Creates a phone auth credential, given the verification ID from + * {@link PhoneAuthProvider.verifyPhoneNumber} and the code that was sent to the user's + * mobile device. + * + * @example + * ```javascript + * const provider = new PhoneAuthProvider(auth); + * const verificationId = provider.verifyPhoneNumber(phoneNumber, applicationVerifier); + * // Obtain verificationCode from the user. + * const authCredential = PhoneAuthProvider.credential(verificationId, verificationCode); + * const userCredential = signInWithCredential(auth, authCredential); + * ``` + * + * @example + * An alternative flow is provided using the `signInWithPhoneNumber` method. + * ```javascript + * const confirmationResult = await signInWithPhoneNumber(auth, phoneNumber, applicationVerifier); + * // Obtain verificationCode from the user. + * const userCredential = await confirmationResult.confirm(verificationCode); + * ``` + * + * @param verificationId - The verification ID returned from {@link PhoneAuthProvider.verifyPhoneNumber}. + * @param verificationCode - The verification code sent to the user's mobile device. + * + * @returns The auth provider credential. + */ static credential( verificationId: string, verificationCode: string @@ -59,24 +148,69 @@ export class PhoneAuthProvider implements externs.PhoneAuthProvider { ); } + /** + * Generates an {@link AuthCredential} from a {@link UserCredential}. + * @param userCredential + */ static credentialFromResult( - userCredential: externs.UserCredential - ): externs.AuthCredential | null { - const credential = userCredential as UserCredential; - assert(credential._tokenResponse, AuthErrorCode.ARGUMENT_ERROR, { - appName: credential.user.auth.name - }); - const { - phoneNumber, - temporaryProof - } = credential._tokenResponse as SignInWithPhoneNumberResponse; + userCredential: UserCredential + ): AuthCredential | null { + const credential = userCredential as UserCredentialInternal; + return PhoneAuthProvider.credentialFromTaggedObject(credential); + } + + /** + * Returns an {@link AuthCredential} when passed an error. + * + * @remarks + * + * This method works for errors like + * `auth/account-exists-with-different-credentials`. This is useful for + * recovering when attempting to set a user's phone number but the number + * in question is already tied to another account. For example, the following + * code tries to update the current user's phone number, and if that + * fails, links the user with the account associated with that number: + * + * ```js + * const provider = new PhoneAuthProvider(auth); + * const verificationId = await provider.verifyPhoneNumber(number, verifier); + * try { + * const code = ''; // Prompt the user for the verification code + * await updatePhoneNumber( + * auth.currentUser, + * PhoneAuthProvider.credential(verificationId, code)); + * } catch (e) { + * if (e.code === 'auth/account-exists-with-different-credential') { + * const cred = PhoneAuthProvider.credentialFromError(e); + * await linkWithCredential(auth.currentUser, cred); + * } + * } + * + * // At this point, auth.currentUser.phoneNumber === number. + * ``` + * + * @param error + */ + static credentialFromError(error: FirebaseError): AuthCredential | null { + return PhoneAuthProvider.credentialFromTaggedObject( + (error.customData || {}) as TaggedWithTokenResponse + ); + } + + private static credentialFromTaggedObject({ + _tokenResponse: tokenResponse + }: TaggedWithTokenResponse): AuthCredential | null { + if (!tokenResponse) { + return null; + } + const { phoneNumber, temporaryProof } = + tokenResponse as SignInWithPhoneNumberResponse; if (phoneNumber && temporaryProof) { return PhoneAuthCredential._fromTokenResponse( phoneNumber, temporaryProof ); } - - fail(AuthErrorCode.ARGUMENT_ERROR, { appName: credential.user.auth.name }); + return null; } } diff --git a/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_loader.test.ts b/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_loader.test.ts index 94bbe0c6e1a..b1323c55e63 100644 --- a/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_loader.test.ts +++ b/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_loader.test.ts @@ -37,7 +37,7 @@ import { MockReCaptcha } from './recaptcha_mock'; use(chaiAsPromised); use(sinonChai); -describe('platform-browser/recaptcha/recaptcha_loader', () => { +describe('platform_browser/recaptcha/recaptcha_loader', () => { let auth: TestAuth; beforeEach(async () => { @@ -67,9 +67,9 @@ describe('platform-browser/recaptcha/recaptcha_loader', () => { triggerNetworkTimeout = stubSingleTimeout(networkTimeoutId); sinon.stub(jsHelpers, '_loadJS').callsFake(() => { - return new Promise((resolve, reject) => { + return (new Promise((resolve, reject) => { jsLoader = { resolve, reject }; - }); + }) as unknown) as Promise; }); loader = new ReCaptchaLoaderImpl(); diff --git a/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_loader.ts b/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_loader.ts index 8db31cb8c08..212c81b0d1f 100644 --- a/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_loader.ts +++ b/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_loader.ts @@ -17,10 +17,10 @@ import { querystring } from '@firebase/util'; -import { AUTH_ERROR_FACTORY, AuthErrorCode } from '../../core/errors'; -import { assert } from '../../core/util/assert'; +import { AuthErrorCode } from '../../core/errors'; +import { _assert, _createError } from '../../core/util/assert'; import { Delay } from '../../core/util/delay'; -import { Auth } from '../../model/auth'; +import { AuthInternal } from '../../model/auth'; import { _window } from '../auth_window'; import * as jsHelpers from '../load_js'; import { Recaptcha } from './recaptcha'; @@ -32,8 +32,14 @@ export const _JSLOAD_CALLBACK = jsHelpers._generateCallbackName('rcb'); const NETWORK_TIMEOUT_DELAY = new Delay(30000, 60000); const RECAPTCHA_BASE = 'https://www.google.com/recaptcha/api.js?'; +/** + * We need to mark this interface as internal explicitly to exclude it in the public typings, because + * it references AuthInternal which has a circular dependency with UserInternal. + * + * @internal + */ export interface ReCaptchaLoader { - load(auth: Auth, hl?: string): Promise; + load(auth: AuthInternal, hl?: string): Promise; clearedOneInstance(): void; } @@ -45,21 +51,15 @@ export class ReCaptchaLoaderImpl implements ReCaptchaLoader { private counter = 0; private readonly librarySeparatelyLoaded = !!_window().grecaptcha; - load(auth: Auth, hl = ''): Promise { - assert(isHostLanguageValid(hl), AuthErrorCode.ARGUMENT_ERROR, { - appName: auth.name - }); + load(auth: AuthInternal, hl = ''): Promise { + _assert(isHostLanguageValid(hl), auth, AuthErrorCode.ARGUMENT_ERROR); if (this.shouldResolveImmediately(hl)) { return Promise.resolve(_window().grecaptcha!); } return new Promise((resolve, reject) => { const networkTimeout = _window().setTimeout(() => { - reject( - AUTH_ERROR_FACTORY.create(AuthErrorCode.NETWORK_REQUEST_FAILED, { - appName: auth.name - }) - ); + reject(_createError(auth, AuthErrorCode.NETWORK_REQUEST_FAILED)); }, NETWORK_TIMEOUT_DELAY.get()); _window()[_JSLOAD_CALLBACK] = () => { @@ -69,11 +69,7 @@ export class ReCaptchaLoaderImpl implements ReCaptchaLoader { const recaptcha = _window().grecaptcha; if (!recaptcha) { - reject( - AUTH_ERROR_FACTORY.create(AuthErrorCode.INTERNAL_ERROR, { - appName: auth.name - }) - ); + reject(_createError(auth, AuthErrorCode.INTERNAL_ERROR)); return; } @@ -98,11 +94,7 @@ export class ReCaptchaLoaderImpl implements ReCaptchaLoader { jsHelpers._loadJS(url).catch(() => { clearTimeout(networkTimeout); - reject( - AUTH_ERROR_FACTORY.create(AuthErrorCode.INTERNAL_ERROR, { - appName: auth.name - }) - ); + reject(_createError(auth, AuthErrorCode.INTERNAL_ERROR)); }); }); } @@ -133,7 +125,7 @@ function isHostLanguageValid(hl: string): boolean { } export class MockReCaptchaLoaderImpl implements ReCaptchaLoader { - async load(auth: Auth): Promise { + async load(auth: AuthInternal): Promise { return new MockReCaptcha(auth); } diff --git a/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_mock.test.ts b/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_mock.test.ts index e39040aca49..0dff03cac04 100644 --- a/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_mock.test.ts +++ b/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_mock.test.ts @@ -36,7 +36,7 @@ import { use(sinonChai); use(chaiAsPromised); -describe('platform-browser/recaptcha/recaptcha_mock', () => { +describe('platform_browser/recaptcha/recaptcha_mock', () => { let container: HTMLElement; let auth: TestAuth; diff --git a/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_mock.ts b/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_mock.ts index b883c0aafde..8839d511014 100644 --- a/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_mock.ts +++ b/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_mock.ts @@ -16,8 +16,8 @@ */ import { AuthErrorCode } from '../../core/errors'; -import { assert } from '../../core/util/assert'; -import { Auth } from '../../model/auth'; +import { _assert } from '../../core/util/assert'; +import { AuthInternal } from '../../model/auth'; import { Parameters, Recaptcha } from './recaptcha'; export const _SOLVE_TIME_MS = 500; @@ -34,7 +34,7 @@ export class MockReCaptcha implements Recaptcha { private counter = _WIDGET_ID_START; _widgets = new Map(); - constructor(private readonly auth: Auth) {} + constructor(private readonly auth: AuthInternal) {} render(container: string | HTMLElement, parameters?: Parameters): number { const id = this.counter; @@ -83,7 +83,7 @@ export class MockWidget { typeof containerOrId === 'string' ? document.getElementById(containerOrId) : containerOrId; - assert(container, AuthErrorCode.ARGUMENT_ERROR, { appName }); + _assert(container, AuthErrorCode.ARGUMENT_ERROR, { appName }); this.container = container; this.isVisible = this.params.size !== 'invisible'; diff --git a/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_verifier.test.ts b/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_verifier.test.ts index 89e8a9aa238..170adb0f278 100644 --- a/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_verifier.test.ts +++ b/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_verifier.test.ts @@ -28,14 +28,14 @@ import * as fetch from '../../../test/helpers/mock_fetch'; import { Endpoint } from '../../api'; import { _window } from '../auth_window'; import { Parameters, Recaptcha } from './recaptcha'; -import { _JSLOAD_CALLBACK, ReCaptchaLoader } from './recaptcha_loader'; +import { ReCaptchaLoader } from './recaptcha_loader'; import { MockReCaptcha } from './recaptcha_mock'; import { RecaptchaVerifier } from './recaptcha_verifier'; use(chaiAsPromised); use(sinonChai); -describe('platform_browser/recaptcha/recaptcha_verifier.ts', () => { +describe('platform_browser/recaptcha/recaptcha_verifier', () => { let auth: TestAuth; let container: HTMLElement; let verifier: RecaptchaVerifier; diff --git a/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_verifier.ts b/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_verifier.ts index 407f4628d97..c68c0b3fd78 100644 --- a/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_verifier.ts +++ b/packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_verifier.ts @@ -15,15 +15,16 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { Auth } from '../../model/public_types'; import { getRecaptchaParams } from '../../api/authentication/recaptcha'; import { _castAuth } from '../../core/auth/auth_impl'; import { AuthErrorCode } from '../../core/errors'; -import { assert } from '../../core/util/assert'; +import { _assert } from '../../core/util/assert'; import { _isHttpOrHttps } from '../../core/util/location'; -import { ApplicationVerifier } from '../../model/application_verifier'; -import { Auth } from '../../model/auth'; +import { ApplicationVerifierInternal } from '../../model/application_verifier'; +import { AuthInternal } from '../../model/auth'; import { _window } from '../auth_window'; +import { _isWorker } from '../util/worker'; import { Parameters, Recaptcha } from './recaptcha'; import { MockReCaptchaLoaderImpl, @@ -40,41 +41,74 @@ const DEFAULT_PARAMS: Parameters = { type TokenCallback = (token: string) => void; -export class RecaptchaVerifier - implements externs.RecaptchaVerifier, ApplicationVerifier { +/** + * An {@link https://www.google.com/recaptcha/ | reCAPTCHA}-based application verifier. + * + * @public + */ +export class RecaptchaVerifier implements ApplicationVerifierInternal { + /** + * The application verifier type. + * + * @remarks + * For a reCAPTCHA verifier, this is 'recaptcha'. + */ readonly type = RECAPTCHA_VERIFIER_TYPE; - private readonly appName: string; private destroyed = false; private widgetId: number | null = null; private readonly container: HTMLElement; private readonly isInvisible: boolean; private readonly tokenChangeListeners = new Set(); private renderPromise: Promise | null = null; - private readonly auth: Auth; + private readonly auth: AuthInternal; + /** @internal */ readonly _recaptchaLoader: ReCaptchaLoader; private recaptcha: Recaptcha | null = null; + /** + * + * @param containerOrId - The reCAPTCHA container parameter. + * + * @remarks + * This has different meaning depending on whether the reCAPTCHA is hidden or visible. For a + * visible reCAPTCHA the container must be empty. If a string is used, it has to correspond to + * an element ID. The corresponding element must also must be in the DOM at the time of + * initialization. + * + * @param parameters - The optional reCAPTCHA parameters. + * + * @remarks + * Check the reCAPTCHA docs for a comprehensive list. All parameters are accepted except for + * the sitekey. Firebase Auth backend provisions a reCAPTCHA for each project and will + * configure this upon rendering. For an invisible reCAPTCHA, a size key must have the value + * 'invisible'. + * + * @param authExtern - The corresponding Firebase Auth instance. + * + * @remarks + * If none is provided, the default Firebase Auth instance is used. A Firebase Auth instance + * must be initialized with an API key, otherwise an error will be thrown. + */ constructor( containerOrId: HTMLElement | string, private readonly parameters: Parameters = { ...DEFAULT_PARAMS }, - authExtern: externs.Auth + authExtern: Auth ) { this.auth = _castAuth(authExtern); - this.appName = this.auth.name; this.isInvisible = this.parameters.size === 'invisible'; - assert( + _assert( typeof document !== 'undefined', - AuthErrorCode.OPERATION_NOT_SUPPORTED, - { appName: this.appName } + this.auth, + AuthErrorCode.OPERATION_NOT_SUPPORTED ); const container = typeof containerOrId === 'string' ? document.getElementById(containerOrId) : containerOrId; - assert(container, AuthErrorCode.ARGUMENT_ERROR, { appName: this.appName }); + _assert(container, this.auth, AuthErrorCode.ARGUMENT_ERROR); this.container = container; this.parameters.callback = this.makeTokenCallback(this.parameters.callback); @@ -87,6 +121,11 @@ export class RecaptchaVerifier // TODO: Figure out if sdk version is needed } + /** + * Waits for the user to solve the reCAPTCHA and resolves with the reCAPTCHA token. + * + * @returns A Promise for the reCAPTCHA token. + */ async verify(): Promise { this.assertNotDestroyed(); const id = await this.render(); @@ -113,6 +152,11 @@ export class RecaptchaVerifier }); } + /** + * Renders the reCAPTCHA widget on the page. + * + * @returns A Promise that resolves with the reCAPTCHA widget ID. + */ render(): Promise { try { this.assertNotDestroyed(); @@ -135,6 +179,7 @@ export class RecaptchaVerifier return this.renderPromise; } + /** @internal */ _reset(): void { this.assertNotDestroyed(); if (this.widgetId !== null) { @@ -142,6 +187,9 @@ export class RecaptchaVerifier } } + /** + * Clears the reCAPTCHA widget from the page and destroys the instance. + */ clear(): void { this.assertNotDestroyed(); this.destroyed = true; @@ -154,13 +202,16 @@ export class RecaptchaVerifier } private validateStartingState(): void { - assert(!this.parameters.sitekey, AuthErrorCode.ARGUMENT_ERROR, { - appName: this.appName - }); - assert( + _assert(!this.parameters.sitekey, this.auth, AuthErrorCode.ARGUMENT_ERROR); + _assert( this.isInvisible || !this.container.hasChildNodes(), - AuthErrorCode.ARGUMENT_ERROR, - { appName: this.appName } + this.auth, + AuthErrorCode.ARGUMENT_ERROR + ); + _assert( + typeof document !== 'undefined', + this.auth, + AuthErrorCode.OPERATION_NOT_SUPPORTED ); } @@ -181,9 +232,7 @@ export class RecaptchaVerifier } private assertNotDestroyed(): void { - assert(!this.destroyed, AuthErrorCode.INTERNAL_ERROR, { - appName: this.appName - }); + _assert(!this.destroyed, this.auth, AuthErrorCode.INTERNAL_ERROR); } private async makeRenderPromise(): Promise { @@ -206,9 +255,11 @@ export class RecaptchaVerifier } private async init(): Promise { - assert(_isHttpOrHttps() && !isWorker(), AuthErrorCode.INTERNAL_ERROR, { - appName: this.appName - }); + _assert( + _isHttpOrHttps() && !_isWorker(), + this.auth, + AuthErrorCode.INTERNAL_ERROR + ); await domReady(); this.recaptcha = await this._recaptchaLoader.load( @@ -217,25 +268,16 @@ export class RecaptchaVerifier ); const siteKey = await getRecaptchaParams(this.auth); - assert(siteKey, AuthErrorCode.INTERNAL_ERROR, { appName: this.appName }); + _assert(siteKey, this.auth, AuthErrorCode.INTERNAL_ERROR); this.parameters.sitekey = siteKey; } private getAssertedRecaptcha(): Recaptcha { - assert(this.recaptcha, AuthErrorCode.INTERNAL_ERROR, { - appName: this.appName - }); + _assert(this.recaptcha, this.auth, AuthErrorCode.INTERNAL_ERROR); return this.recaptcha; } } -function isWorker(): boolean { - return ( - typeof _window()['WorkerGlobalScope'] !== 'undefined' && - typeof _window()['importScripts'] === 'function' - ); -} - function domReady(): Promise { let resolver: (() => void) | null = null; return new Promise(resolve => { diff --git a/packages-exp/auth-exp/src/platform_browser/strategies/abstract_popup_redirect_operation.ts b/packages-exp/auth-exp/src/platform_browser/strategies/abstract_popup_redirect_operation.ts deleted file mode 100644 index 9a9260e066d..00000000000 --- a/packages-exp/auth-exp/src/platform_browser/strategies/abstract_popup_redirect_operation.ts +++ /dev/null @@ -1,146 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { FirebaseError } from '@firebase/util'; - -import { - AuthEvent, - AuthEventConsumer, - AuthEventType, - EventManager, - PopupRedirectResolver -} from '../../model/popup_redirect'; -import { User, UserCredential } from '../../model/user'; -import { AuthErrorCode } from '../../core/errors'; -import { debugAssert, fail } from '../../core/util/assert'; -import { - _link, - _reauth, - _signIn, - IdpTask, - IdpTaskParams -} from '../../core/strategies/idp'; -import { Auth } from '../../model/auth'; - -interface PendingPromise { - resolve: (cred: UserCredential | null) => void; - reject: (error: Error) => void; -} - -/** - * Popup event manager. Handles the popup's entire lifecycle; listens to auth - * events - */ -export abstract class AbstractPopupRedirectOperation - implements AuthEventConsumer { - private pendingPromise: PendingPromise | null = null; - private eventManager: EventManager | null = null; - readonly filter: AuthEventType[]; - - abstract eventId: string | null; - - constructor( - protected readonly auth: Auth, - filter: AuthEventType | AuthEventType[], - protected readonly resolver: PopupRedirectResolver, - protected user?: User - ) { - this.filter = Array.isArray(filter) ? filter : [filter]; - } - - abstract onExecution(): Promise; - - execute(): Promise { - return new Promise(async (resolve, reject) => { - this.pendingPromise = { resolve, reject }; - - try { - this.eventManager = await this.resolver._initialize(this.auth); - await this.onExecution(); - this.eventManager.registerConsumer(this); - } catch (e) { - this.reject(e); - } - }); - } - - async onAuthEvent(event: AuthEvent): Promise { - const { urlResponse, sessionId, postBody, tenantId, error, type } = event; - if (error) { - this.reject(error); - return; - } - - const params: IdpTaskParams = { - auth: this.auth, - requestUri: urlResponse!, - sessionId: sessionId!, - tenantId: tenantId || undefined, - postBody: postBody || undefined, - user: this.user - }; - - try { - this.resolve(await this.getIdpTask(type)(params)); - } catch (e) { - this.reject(e); - } - } - - onError(error: FirebaseError): void { - this.reject(error); - } - - private getIdpTask(type: AuthEventType): IdpTask { - switch (type) { - case AuthEventType.SIGN_IN_VIA_POPUP: - case AuthEventType.SIGN_IN_VIA_REDIRECT: - return _signIn; - case AuthEventType.LINK_VIA_POPUP: - case AuthEventType.LINK_VIA_REDIRECT: - return _link; - case AuthEventType.REAUTH_VIA_POPUP: - case AuthEventType.REAUTH_VIA_REDIRECT: - return _reauth; - default: - fail(AuthErrorCode.INTERNAL_ERROR, { appName: this.auth.name }); - } - } - - protected resolve(cred: UserCredential | null): void { - debugAssert(this.pendingPromise, 'Pending promise was never set'); - this.pendingPromise.resolve(cred); - this.unregisterAndCleanUp(); - } - - protected reject(error: Error): void { - debugAssert(this.pendingPromise, 'Pending promise was never set'); - this.pendingPromise.reject(error); - this.unregisterAndCleanUp(); - } - - private unregisterAndCleanUp(): void { - if (this.eventManager) { - this.eventManager.unregisterConsumer(this); - } - - this.pendingPromise = null; - this.cleanUp(); - } - - abstract cleanUp(): void; -} diff --git a/packages-exp/auth-exp/src/platform_browser/strategies/phone.test.ts b/packages-exp/auth-exp/src/platform_browser/strategies/phone.test.ts index edfbda8ec81..da7ae45be29 100644 --- a/packages-exp/auth-exp/src/platform_browser/strategies/phone.test.ts +++ b/packages-exp/auth-exp/src/platform_browser/strategies/phone.test.ts @@ -20,7 +20,7 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as sinon from 'sinon'; import * as sinonChai from 'sinon-chai'; -import { OperationType, ProviderId } from '@firebase/auth-types-exp'; +import { OperationType, ProviderId } from '../../model/public_types'; import { FirebaseError } from '@firebase/util'; import { mockEndpoint } from '../../../test/helpers/api/helper'; @@ -28,12 +28,12 @@ import { makeJWT } from '../../../test/helpers/jwt'; import { testAuth, testUser, TestAuth } from '../../../test/helpers/mock_auth'; import * as fetch from '../../../test/helpers/mock_fetch'; import { Endpoint } from '../../api'; -import { MultiFactorInfo } from '../../mfa/mfa_info'; -import { MultiFactorSession } from '../../mfa/mfa_session'; -import { multiFactor, MultiFactorUser } from '../../mfa/mfa_user'; -import { ApplicationVerifier } from '../../model/application_verifier'; +import { MultiFactorInfoImpl } from '../../mfa/mfa_info'; +import { MultiFactorSessionImpl } from '../../mfa/mfa_session'; +import { multiFactor, MultiFactorUserImpl } from '../../mfa/mfa_user'; +import { ApplicationVerifierInternal } from '../../model/application_verifier'; import { IdTokenResponse, IdTokenResponseKind } from '../../model/id_token'; -import { User } from '../../model/user'; +import { UserInternal } from '../../model/user'; import { RecaptchaVerifier } from '../../platform_browser/recaptcha/recaptcha_verifier'; import { PhoneAuthCredential } from '../../core/credentials/phone'; import { @@ -47,9 +47,9 @@ import { use(chaiAsPromised); use(sinonChai); -describe('core/strategies/phone', () => { +describe('platform_browser/strategies/phone', () => { let auth: TestAuth; - let verifier: ApplicationVerifier; + let verifier: ApplicationVerifierInternal; let sendCodeEndpoint: fetch.Route; beforeEach(async () => { @@ -118,7 +118,7 @@ describe('core/strategies/phone', () => { describe('linkWithPhoneNumber', () => { let getAccountInfoEndpoint: fetch.Route; - let user: User; + let user: UserInternal; beforeEach(() => { getAccountInfoEndpoint = mockEndpoint(Endpoint.GET_ACCOUNT_INFO, { @@ -196,7 +196,7 @@ describe('core/strategies/phone', () => { }); describe('reauthenticateWithPhoneNumber', () => { - let user: User; + let user: UserInternal; beforeEach(() => { mockEndpoint(Endpoint.GET_ACCOUNT_INFO, { @@ -310,8 +310,8 @@ describe('core/strategies/phone', () => { }); context('MFA', () => { - let user: User; - let mfaUser: MultiFactorUser; + let user: UserInternal; + let mfaUser: MultiFactorUserImpl; beforeEach(() => { mockEndpoint(Endpoint.GET_ACCOUNT_INFO, { @@ -319,7 +319,7 @@ describe('core/strategies/phone', () => { }); user = testUser(auth, 'uid', 'email', true); - mfaUser = multiFactor(user) as MultiFactorUser; + mfaUser = multiFactor(user) as MultiFactorUserImpl; }); it('works with an enrollment flow', async () => { @@ -328,7 +328,7 @@ describe('core/strategies/phone', () => { sessionInfo: 'session-info' } }); - const session = (await mfaUser.getSession()) as MultiFactorSession; + const session = (await mfaUser.getSession()) as MultiFactorSessionImpl; const sessionInfo = await _verifyPhoneNumber( auth, { phoneNumber: 'number', session }, @@ -351,10 +351,10 @@ describe('core/strategies/phone', () => { sessionInfo: 'session-info' } }); - const session = MultiFactorSession._fromMfaPendingCredential( + const session = MultiFactorSessionImpl._fromMfaPendingCredential( 'mfa-pending-credential' ); - const mfaInfo = MultiFactorInfo._fromServerResponse(auth, { + const mfaInfo = MultiFactorInfoImpl._fromServerResponse(auth, { mfaEnrollmentId: 'mfa-enrollment-id', enrolledAt: Date.now(), phoneInfo: 'phone-number-from-enrollment' @@ -388,7 +388,7 @@ describe('core/strategies/phone', () => { it('throws if the verifier type is not recaptcha', async () => { const mutVerifier: { - -readonly [K in keyof ApplicationVerifier]: ApplicationVerifier[K]; + -readonly [K in keyof ApplicationVerifierInternal]: ApplicationVerifierInternal[K]; } = verifier; mutVerifier.type = 'not-recaptcha-thats-for-sure'; await expect( @@ -414,17 +414,20 @@ describe('core/strategies/phone', () => { }); describe('updatePhoneNumber', () => { - let user: User; + let user: UserInternal; let reloadMock: fetch.Route; let signInMock: fetch.Route; let credential: PhoneAuthCredential; + let idToken: string; beforeEach(() => { + idToken = makeJWT({ exp: '200', iat: '100' }); reloadMock = mockEndpoint(Endpoint.GET_ACCOUNT_INFO, { users: [{ uid: 'uid' }] }); signInMock = mockEndpoint(Endpoint.SIGN_IN_WITH_PHONE_NUMBER, { - idToken: 'new-access-token' + idToken, + refreshToken: 'refresh-token' }); credential = PhoneAuthCredential._fromVerification( 'session-info', @@ -446,7 +449,7 @@ describe('core/strategies/phone', () => { it('should update the access token', async () => { await updatePhoneNumber(user, credential); const idToken = await user.getIdToken(); - expect(idToken).to.eq('new-access-token'); + expect(idToken).to.eq(idToken); }); it('should reload the user', async () => { diff --git a/packages-exp/auth-exp/src/platform_browser/strategies/phone.ts b/packages-exp/auth-exp/src/platform_browser/strategies/phone.ts index a6c85f5f951..45affbe8039 100644 --- a/packages-exp/auth-exp/src/platform_browser/strategies/phone.ts +++ b/packages-exp/auth-exp/src/platform_browser/strategies/phone.ts @@ -15,41 +15,50 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { + ApplicationVerifier, + Auth, + ConfirmationResult, + PhoneInfoOptions, + ProviderId, + User, + UserCredential +} from '../../model/public_types'; import { startEnrollPhoneMfa } from '../../api/account_management/mfa'; import { startSignInPhoneMfa } from '../../api/authentication/mfa'; import { sendPhoneVerificationCode } from '../../api/authentication/sms'; -import { ApplicationVerifier } from '../../model/application_verifier'; +import { ApplicationVerifierInternal } from '../../model/application_verifier'; import { PhoneAuthCredential } from '../../core/credentials/phone'; import { AuthErrorCode } from '../../core/errors'; import { _assertLinkedStatus, _link } from '../../core/user/link_unlink'; -import { assert } from '../../core/util/assert'; -import { Auth } from '../../model/auth'; +import { _assert } from '../../core/util/assert'; +import { AuthInternal } from '../../model/auth'; import { linkWithCredential, reauthenticateWithCredential, signInWithCredential } from '../../core/strategies/credential'; import { - MultiFactorSession, + MultiFactorSessionImpl, MultiFactorSessionType } from '../../mfa/mfa_session'; -import { User } from '../../model/user'; +import { UserInternal } from '../../model/user'; import { RECAPTCHA_VERIFIER_TYPE } from '../recaptcha/recaptcha_verifier'; import { _castAuth } from '../../core/auth/auth_impl'; +import { getModularInstance } from '@firebase/util'; interface OnConfirmationCallback { - (credential: PhoneAuthCredential): Promise; + (credential: PhoneAuthCredential): Promise; } -class ConfirmationResult implements externs.ConfirmationResult { +class ConfirmationResultImpl implements ConfirmationResult { constructor( readonly verificationId: string, private readonly onConfirmation: OnConfirmationCallback ) {} - confirm(verificationCode: string): Promise { + confirm(verificationCode: string): Promise { const authCredential = PhoneAuthCredential._fromVerification( this.verificationId, verificationCode @@ -58,76 +67,126 @@ class ConfirmationResult implements externs.ConfirmationResult { } } +/** + * Asynchronously signs in using a phone number. + * + * @remarks + * This method sends a code via SMS to the given + * phone number, and returns a {@link ConfirmationResult}. After the user + * provides the code sent to their phone, call {@link ConfirmationResult.confirm} + * with the code to sign the user in. + * + * For abuse prevention, this method also requires a {@link ApplicationVerifier}. + * This SDK includes a reCAPTCHA-based implementation, {@link RecaptchaVerifier}. + * + * @example + * ```javascript + * // 'recaptcha-container' is the ID of an element in the DOM. + * const applicationVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container'); + * const confirmationResult = await signInWithPhoneNumber(auth, phoneNumber, applicationVerifier); + * // Obtain a verificationCode from the user. + * const credential = await confirmationResult.confirm(verificationCode); + * ``` + * + * @param auth - The Auth instance. + * @param phoneNumber - The user's phone number in E.164 format (e.g. +16505550101). + * @param appVerifier - The {@link ApplicationVerifier}. + * + * @public + */ export async function signInWithPhoneNumber( - auth: externs.Auth, + auth: Auth, phoneNumber: string, - appVerifier: externs.ApplicationVerifier -): Promise { + appVerifier: ApplicationVerifier +): Promise { + const authInternal = _castAuth(auth); const verificationId = await _verifyPhoneNumber( - _castAuth(auth), + authInternal, phoneNumber, - appVerifier as ApplicationVerifier + getModularInstance(appVerifier as ApplicationVerifierInternal) ); - return new ConfirmationResult(verificationId, cred => - signInWithCredential(auth, cred) + return new ConfirmationResultImpl(verificationId, cred => + signInWithCredential(authInternal, cred) ); } +/** + * Links the user account with the given phone number. + * + * @param user - The user. + * @param phoneNumber - The user's phone number in E.164 format (e.g. +16505550101). + * @param appVerifier - The {@link ApplicationVerifier}. + * + * @public + */ export async function linkWithPhoneNumber( - userExtern: externs.User, + user: User, phoneNumber: string, - appVerifier: externs.ApplicationVerifier -): Promise { - const user = userExtern as User; - await _assertLinkedStatus(false, user, externs.ProviderId.PHONE); + appVerifier: ApplicationVerifier +): Promise { + const userInternal = getModularInstance(user) as UserInternal; + await _assertLinkedStatus(false, userInternal, ProviderId.PHONE); const verificationId = await _verifyPhoneNumber( - user.auth, + userInternal.auth, phoneNumber, - appVerifier as ApplicationVerifier + getModularInstance(appVerifier as ApplicationVerifierInternal) ); - return new ConfirmationResult(verificationId, cred => - linkWithCredential(user, cred) + return new ConfirmationResultImpl(verificationId, cred => + linkWithCredential(userInternal, cred) ); } +/** + * Re-authenticates a user using a fresh phne credential. + * + * @remarks Use before operations such as {@link updatePassword} that require tokens from recent sign-in attempts. + * + * @param user - The user. + * @param phoneNumber - The user's phone number in E.164 format (e.g. +16505550101). + * @param appVerifier - The {@link ApplicationVerifier}. + * + * @public + */ export async function reauthenticateWithPhoneNumber( - userExtern: externs.User, + user: User, phoneNumber: string, - appVerifier: externs.ApplicationVerifier -): Promise { - const user = userExtern as User; + appVerifier: ApplicationVerifier +): Promise { + const userInternal = getModularInstance(user) as UserInternal; const verificationId = await _verifyPhoneNumber( - user.auth, + userInternal.auth, phoneNumber, - appVerifier as ApplicationVerifier + getModularInstance(appVerifier as ApplicationVerifierInternal) ); - return new ConfirmationResult(verificationId, cred => - reauthenticateWithCredential(user, cred) + return new ConfirmationResultImpl(verificationId, cred => + reauthenticateWithCredential(userInternal, cred) ); } /** - * Returns a verification ID to be used in conjunction with the SMS code that - * is sent. + * Returns a verification ID to be used in conjunction with the SMS code that is sent. + * */ export async function _verifyPhoneNumber( - auth: Auth, - options: externs.PhoneInfoOptions | string, - verifier: ApplicationVerifier + auth: AuthInternal, + options: PhoneInfoOptions | string, + verifier: ApplicationVerifierInternal ): Promise { const recaptchaToken = await verifier.verify(); try { - assert(typeof recaptchaToken === 'string', AuthErrorCode.ARGUMENT_ERROR, { - appName: auth.name - }); - assert( + _assert( + typeof recaptchaToken === 'string', + auth, + AuthErrorCode.ARGUMENT_ERROR + ); + _assert( verifier.type === RECAPTCHA_VERIFIER_TYPE, - AuthErrorCode.ARGUMENT_ERROR, - { appName: auth.name } + auth, + AuthErrorCode.ARGUMENT_ERROR ); - let phoneInfoOptions: externs.PhoneInfoOptions; + let phoneInfoOptions: PhoneInfoOptions; if (typeof options === 'string') { phoneInfoOptions = { @@ -138,13 +197,13 @@ export async function _verifyPhoneNumber( } if ('session' in phoneInfoOptions) { - const session = phoneInfoOptions.session as MultiFactorSession; + const session = phoneInfoOptions.session as MultiFactorSessionImpl; if ('phoneNumber' in phoneInfoOptions) { - assert( + _assert( session.type === MultiFactorSessionType.ENROLL, - AuthErrorCode.INTERNAL_ERROR, - { appName: auth.name } + auth, + AuthErrorCode.INTERNAL_ERROR ); const response = await startEnrollPhoneMfa(auth, { idToken: session.credential, @@ -155,17 +214,15 @@ export async function _verifyPhoneNumber( }); return response.phoneSessionInfo.sessionInfo; } else { - assert( + _assert( session.type === MultiFactorSessionType.SIGN_IN, - AuthErrorCode.INTERNAL_ERROR, - { appName: auth.name } + auth, + AuthErrorCode.INTERNAL_ERROR ); const mfaEnrollmentId = phoneInfoOptions.multiFactorHint?.uid || phoneInfoOptions.multiFactorUid; - assert(mfaEnrollmentId, AuthErrorCode.MISSING_MFA_INFO, { - appName: auth.name - }); + _assert(mfaEnrollmentId, auth, AuthErrorCode.MISSING_MFA_INFO); const response = await startSignInPhoneMfa(auth, { mfaPendingCredential: session.credential, mfaEnrollmentId, @@ -187,9 +244,28 @@ export async function _verifyPhoneNumber( } } +/** + * Updates the user's phone number. + * + * @example + * ``` + * // 'recaptcha-container' is the ID of an element in the DOM. + * const applicationVerifier = new RecaptchaVerifier('recaptcha-container'); + * const provider = new PhoneAuthProvider(auth); + * const verificationId = await provider.verifyPhoneNumber('+16505550101', applicationVerifier); + * // Obtain the verificationCode from the user. + * const phoneCredential = PhoneAuthProvider.credential(verificationId, verificationCode); + * await updatePhoneNumber(user, phoneCredential); + * ``` + * + * @param user - The user. + * @param credential - A credential authenticating the new phone number. + * + * @public + */ export async function updatePhoneNumber( - user: externs.User, - credential: externs.PhoneAuthCredential + user: User, + credential: PhoneAuthCredential ): Promise { - await _link(user as User, credential as PhoneAuthCredential); + await _link(getModularInstance(user) as UserInternal, credential); } diff --git a/packages-exp/auth-exp/src/platform_browser/strategies/popup.test.ts b/packages-exp/auth-exp/src/platform_browser/strategies/popup.test.ts index 0dd0020bfc2..1954d0844f7 100644 --- a/packages-exp/auth-exp/src/platform_browser/strategies/popup.test.ts +++ b/packages-exp/auth-exp/src/platform_browser/strategies/popup.test.ts @@ -24,7 +24,7 @@ import { OperationType, PopupRedirectResolver, ProviderId -} from '@firebase/auth-types-exp'; +} from '../../model/public_types'; import { FirebaseError } from '@firebase/util'; import { delay } from '../../../test/helpers/delay'; @@ -33,22 +33,23 @@ import { testAuth, testUser, TestAuth } from '../../../test/helpers/mock_auth'; import { makeMockPopupRedirectResolver } from '../../../test/helpers/mock_popup_redirect_resolver'; import { stubTimeouts, TimerMap } from '../../../test/helpers/timeout_stub'; import { AuthEvent, AuthEventType } from '../../model/popup_redirect'; -import { User } from '../../model/user'; +import { UserInternal } from '../../model/user'; import { AuthEventManager } from '../../core/auth/auth_event_manager'; -import { AUTH_ERROR_FACTORY, AuthErrorCode } from '../../core/errors'; +import { AuthErrorCode } from '../../core/errors'; import { OAuthProvider } from '../../core/providers/oauth'; import { UserCredentialImpl } from '../../core/user/user_credential_impl'; import * as eid from '../../core/util/event_id'; import { AuthPopup } from '../util/popup'; import * as idpTasks from '../../core/strategies/idp'; import { - _AUTH_EVENT_TIMEOUT, + _Timeout, _POLL_WINDOW_CLOSE_TIMEOUT, linkWithPopup, reauthenticateWithPopup, signInWithPopup } from './popup'; import { _getInstance } from '../../../internal'; +import { _createError } from '../../core/util/assert'; use(sinonChai); use(chaiAsPromised); @@ -56,7 +57,7 @@ use(chaiAsPromised); const MATCHING_EVENT_ID = 'matching-event-id'; const OTHER_EVENT_ID = 'wrong-id'; -describe('src/core/strategies/popup', () => { +describe('platform_browser/strategies/popup', () => { let resolver: PopupRedirectResolver; let provider: OAuthProvider; let eventManager: AuthEventManager; @@ -68,7 +69,7 @@ describe('src/core/strategies/popup', () => { beforeEach(async () => { auth = await testAuth(); - eventManager = new AuthEventManager(auth.name); + eventManager = new AuthEventManager(auth); underlyingWindow = { closed: false }; authPopup = new AuthPopup(underlyingWindow as Window); provider = new OAuthProvider(ProviderId.GOOGLE); @@ -215,7 +216,7 @@ describe('src/core/strategies/popup', () => { delay(() => { underlyingWindow.closed = true; pendingTimeouts[_POLL_WINDOW_CLOSE_TIMEOUT.get()](); - pendingTimeouts[_AUTH_EVENT_TIMEOUT](); + pendingTimeouts[_Timeout.AUTH_EVENT](); }); iframeEvent({ type: AuthEventType.SIGN_IN_VIA_POPUP @@ -235,11 +236,7 @@ describe('src/core/strategies/popup', () => { it('passes any errors from idp task', async () => { idpStubs._signIn.returns( - Promise.reject( - AUTH_ERROR_FACTORY.create(AuthErrorCode.INVALID_APP_ID, { - appName: auth.name - }) - ) + Promise.reject(_createError(auth, AuthErrorCode.INVALID_APP_ID)) ); const promise = signInWithPopup(auth, provider, resolver); iframeEvent({ @@ -274,7 +271,7 @@ describe('src/core/strategies/popup', () => { }); context('linkWithPopup', () => { - let user: User; + let user: UserInternal; beforeEach(() => { user = testUser(auth, 'uid'); }); @@ -399,7 +396,7 @@ describe('src/core/strategies/popup', () => { delay(() => { underlyingWindow.closed = true; pendingTimeouts[_POLL_WINDOW_CLOSE_TIMEOUT.get()](); - pendingTimeouts[_AUTH_EVENT_TIMEOUT](); + pendingTimeouts[_Timeout.AUTH_EVENT](); }); iframeEvent({ type: AuthEventType.LINK_VIA_POPUP @@ -420,11 +417,7 @@ describe('src/core/strategies/popup', () => { it('passes any errors from idp task', async () => { idpStubs._link.returns( - Promise.reject( - AUTH_ERROR_FACTORY.create(AuthErrorCode.INVALID_APP_ID, { - appName: auth.name - }) - ) + Promise.reject(_createError(auth, AuthErrorCode.INVALID_APP_ID)) ); const promise = linkWithPopup(user, provider, resolver); iframeEvent({ @@ -459,7 +452,7 @@ describe('src/core/strategies/popup', () => { }); context('reauthenticateWithPopup', () => { - let user: User; + let user: UserInternal; beforeEach(() => { user = testUser(auth, 'uid'); }); @@ -591,7 +584,7 @@ describe('src/core/strategies/popup', () => { delay(() => { underlyingWindow.closed = true; pendingTimeouts[_POLL_WINDOW_CLOSE_TIMEOUT.get()](); - pendingTimeouts[_AUTH_EVENT_TIMEOUT](); + pendingTimeouts[_Timeout.AUTH_EVENT](); }); iframeEvent({ type: AuthEventType.REAUTH_VIA_POPUP @@ -604,11 +597,7 @@ describe('src/core/strategies/popup', () => { it('passes any errors from idp task', async () => { idpStubs._reauth.returns( - Promise.reject( - AUTH_ERROR_FACTORY.create(AuthErrorCode.INVALID_APP_ID, { - appName: auth.name - }) - ) + Promise.reject(_createError(auth, AuthErrorCode.INVALID_APP_ID)) ); const promise = reauthenticateWithPopup(user, provider, resolver); iframeEvent({ diff --git a/packages-exp/auth-exp/src/platform_browser/strategies/popup.ts b/packages-exp/auth-exp/src/platform_browser/strategies/popup.ts index f53013364e9..b62801efb3e 100644 --- a/packages-exp/auth-exp/src/platform_browser/strategies/popup.ts +++ b/packages-exp/auth-exp/src/platform_browser/strategies/popup.ts @@ -15,88 +15,182 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { + Auth, + AuthProvider, + PopupRedirectResolver, + User, + UserCredential +} from '../../model/public_types'; import { _castAuth } from '../../core/auth/auth_impl'; -import { AUTH_ERROR_FACTORY, AuthErrorCode } from '../../core/errors'; -import { OAuthProvider } from '../../core/providers/oauth'; -import { assert, debugAssert } from '../../core/util/assert'; +import { AuthErrorCode } from '../../core/errors'; +import { _assert, debugAssert, _createError } from '../../core/util/assert'; import { Delay } from '../../core/util/delay'; import { _generateEventId } from '../../core/util/event_id'; -import { _getInstance } from '../../core/util/instantiator'; -import { Auth } from '../../model/auth'; +import { AuthInternal } from '../../model/auth'; import { AuthEventType, - PopupRedirectResolver + PopupRedirectResolverInternal } from '../../model/popup_redirect'; -import { User } from '../../model/user'; -import { _withDefaultResolver } from '../popup_redirect'; +import { UserInternal } from '../../model/user'; +import { _withDefaultResolver } from '../../core/util/resolver'; import { AuthPopup } from '../util/popup'; -import { AbstractPopupRedirectOperation } from './abstract_popup_redirect_operation'; +import { AbstractPopupRedirectOperation } from '../../core/strategies/abstract_popup_redirect_operation'; +import { FederatedAuthProvider } from '../../core/providers/federated'; +import { getModularInstance } from '@firebase/util'; -// The event timeout is the same on mobile and desktop, no need for Delay. -export const _AUTH_EVENT_TIMEOUT = 2020; +/* + * The event timeout is the same on mobile and desktop, no need for Delay. + */ +export const enum _Timeout { + AUTH_EVENT = 2000 +} export const _POLL_WINDOW_CLOSE_TIMEOUT = new Delay(2000, 10000); +/** + * Authenticates a Firebase client using a popup-based OAuth authentication flow. + * + * @remarks + * If succeeds, returns the signed in user along with the provider's credential. If sign in was + * unsuccessful, returns an error object containing additional information about the error. + * + * @example + * ```javascript + * // Sign in using a popup. + * const provider = new FacebookAuthProvider(); + * const result = await signInWithPopup(auth, provider); + * + * // The signed-in user info. + * const user = result.user; + * // This gives you a Facebook Access Token. + * const credential = provider.credentialFromResult(auth, result); + * const token = credential.accessToken; + * ``` + * + * @param auth - The Auth instance. + * @param provider - The provider to authenticate. The provider has to be an {@link OAuthProvider}. + * Non-OAuth providers like {@link EmailAuthProvider} will throw an error. + * @param resolver - An instance of {@link PopupRedirectResolver}, optional + * if already supplied to {@link initializeAuth} or provided by {@link getAuth}. + * + * + * @public + */ export async function signInWithPopup( - authExtern: externs.Auth, - provider: externs.AuthProvider, - resolverExtern?: externs.PopupRedirectResolver -): Promise { - const auth = _castAuth(authExtern); - assert(provider instanceof OAuthProvider, AuthErrorCode.ARGUMENT_ERROR, { - appName: auth.name - }); - - const resolver = _withDefaultResolver(auth, resolverExtern); - const action = new PopupOperation( + auth: Auth, + provider: AuthProvider, + resolver?: PopupRedirectResolver +): Promise { + const authInternal = _castAuth(auth); + _assert( + provider instanceof FederatedAuthProvider, auth, + AuthErrorCode.ARGUMENT_ERROR + ); + + const resolverInternal = _withDefaultResolver(authInternal, resolver); + const action = new PopupOperation( + authInternal, AuthEventType.SIGN_IN_VIA_POPUP, provider, - resolver + resolverInternal ); return action.executeNotNull(); } +/** + * Reauthenticates the current user with the specified {@link OAuthProvider} using a pop-up based + * OAuth flow. + * + * @remarks + * If the reauthentication is successful, the returned result will contain the user and the + * provider's credential. + * + * @example + * ```javascript + * // Sign in using a popup. + * const provider = new FacebookAuthProvider(); + * const result = await signInWithPopup(auth, provider); + * // Reauthenticate using a popup. + * await reauthenticateWithPopup(result.user, provider); + * ``` + * + * @param user - The user. + * @param provider - The provider to authenticate. The provider has to be an {@link OAuthProvider}. + * Non-OAuth providers like {@link EmailAuthProvider} will throw an error. + * @param resolver - An instance of {@link PopupRedirectResolver}, optional + * if already supplied to {@link initializeAuth} or provided by {@link getAuth}. + * + * @public + */ export async function reauthenticateWithPopup( - userExtern: externs.User, - provider: externs.AuthProvider, - resolverExtern?: externs.PopupRedirectResolver -): Promise { - const user = userExtern as User; - assert(provider instanceof OAuthProvider, AuthErrorCode.ARGUMENT_ERROR, { - appName: user.auth.name - }); - - const resolver = _withDefaultResolver(user.auth, resolverExtern); + user: User, + provider: AuthProvider, + resolver?: PopupRedirectResolver +): Promise { + const userInternal = getModularInstance(user) as UserInternal; + _assert( + provider instanceof FederatedAuthProvider, + userInternal.auth, + AuthErrorCode.ARGUMENT_ERROR + ); + + const resolverInternal = _withDefaultResolver(userInternal.auth, resolver); const action = new PopupOperation( - user.auth, + userInternal.auth, AuthEventType.REAUTH_VIA_POPUP, provider, - resolver, - user + resolverInternal, + userInternal ); return action.executeNotNull(); } +/** + * Links the authenticated provider to the user account using a pop-up based OAuth flow. + * + * @remarks + * If the linking is successful, the returned result will contain the user and the provider's credential. + * + * + * @example + * ```javascript + * // Sign in using some other provider. + * const result = await signInWithEmailAndPassword(auth, email, password); + * // Link using a popup. + * const provider = new FacebookAuthProvider(); + * await linkWithPopup(result.user, provider); + * ``` + * + * @param user - The user. + * @param provider - The provider to authenticate. The provider has to be an {@link OAuthProvider}. + * Non-OAuth providers like {@link EmailAuthProvider} will throw an error. + * @param resolver - An instance of {@link PopupRedirectResolver}, optional + * if already supplied to {@link initializeAuth} or provided by {@link getAuth}. + * + * @public + */ export async function linkWithPopup( - userExtern: externs.User, - provider: externs.AuthProvider, - resolverExtern?: externs.PopupRedirectResolver -): Promise { - const user = userExtern as User; - assert(provider instanceof OAuthProvider, AuthErrorCode.ARGUMENT_ERROR, { - appName: user.auth.name - }); + user: User, + provider: AuthProvider, + resolver?: PopupRedirectResolver +): Promise { + const userInternal = getModularInstance(user) as UserInternal; + _assert( + provider instanceof FederatedAuthProvider, + userInternal.auth, + AuthErrorCode.ARGUMENT_ERROR + ); - const resolver = _withDefaultResolver(user.auth, resolverExtern); + const resolverInternal = _withDefaultResolver(userInternal.auth, resolver); const action = new PopupOperation( - user.auth, + userInternal.auth, AuthEventType.LINK_VIA_POPUP, provider, - resolver, - user + resolverInternal, + userInternal ); return action.executeNotNull(); } @@ -104,6 +198,7 @@ export async function linkWithPopup( /** * Popup event manager. Handles the popup's entire lifecycle; listens to auth * events + * */ class PopupOperation extends AbstractPopupRedirectOperation { // Only one popup is ever shown at once. The lifecycle of the current popup @@ -113,11 +208,11 @@ class PopupOperation extends AbstractPopupRedirectOperation { private pollId: number | null = null; constructor( - auth: Auth, + auth: AuthInternal, filter: AuthEventType, - private readonly provider: externs.AuthProvider, - resolver: PopupRedirectResolver, - user?: User + private readonly provider: AuthProvider, + resolver: PopupRedirectResolverInternal, + user?: UserInternal ) { super(auth, filter, resolver, user); if (PopupOperation.currentPopupAction) { @@ -127,9 +222,9 @@ class PopupOperation extends AbstractPopupRedirectOperation { PopupOperation.currentPopupAction = this; } - async executeNotNull(): Promise { + async executeNotNull(): Promise { const result = await this.execute(); - assert(result, AuthErrorCode.INTERNAL_ERROR, { appName: this.auth.name }); + _assert(result, this.auth, AuthErrorCode.INTERNAL_ERROR); return result; } @@ -147,25 +242,27 @@ class PopupOperation extends AbstractPopupRedirectOperation { ); this.authWindow.associatedEvent = eventId; - // Check for web storage support _after_ the popup is loaded. Checking for - // web storage is slow (on the order of a second or so). Rather than - // waiting on that before opening the window, optimistically open the popup + // Check for web storage support and origin validation _after_ the popup is + // loaded. These operations are slow (~1 second or so) Rather than + // waiting on them before opening the window, optimistically open the popup // and check for storage support at the same time. If storage support is // not available, this will cause the whole thing to reject properly. It // will also close the popup, but since the promise has already rejected, // the popup closed by user poll will reject into the void. + this.resolver._originValidation(this.auth).catch(e => { + this.reject(e); + }); + this.resolver._isIframeWebStorageSupported(this.auth, isSupported => { if (!isSupported) { this.reject( - AUTH_ERROR_FACTORY.create(AuthErrorCode.WEB_STORAGE_UNSUPPORTED, { - appName: this.auth.name - }) + _createError(this.auth, AuthErrorCode.WEB_STORAGE_UNSUPPORTED) ); } }); // Handle user closure. Notice this does *not* use await - this.pollUserCancellation(this.auth.name); + this.pollUserCancellation(); } get eventId(): string | null { @@ -173,11 +270,7 @@ class PopupOperation extends AbstractPopupRedirectOperation { } cancel(): void { - this.reject( - AUTH_ERROR_FACTORY.create(AuthErrorCode.EXPIRED_POPUP_REQUEST, { - appName: this.auth.name - }) - ); + this.reject(_createError(this.auth, AuthErrorCode.EXPIRED_POPUP_REQUEST)); } cleanUp(): void { @@ -194,7 +287,7 @@ class PopupOperation extends AbstractPopupRedirectOperation { PopupOperation.currentPopupAction = null; } - private pollUserCancellation(appName: string): void { + private pollUserCancellation(): void { const poll = (): void => { if (this.authWindow?.window?.closed) { // Make sure that there is sufficient time for whatever action to @@ -203,11 +296,9 @@ class PopupOperation extends AbstractPopupRedirectOperation { this.pollId = window.setTimeout(() => { this.pollId = null; this.reject( - AUTH_ERROR_FACTORY.create(AuthErrorCode.POPUP_CLOSED_BY_USER, { - appName - }) + _createError(this.auth, AuthErrorCode.POPUP_CLOSED_BY_USER) ); - }, _AUTH_EVENT_TIMEOUT); + }, _Timeout.AUTH_EVENT); return; } diff --git a/packages-exp/auth-exp/src/platform_browser/strategies/redirect.test.ts b/packages-exp/auth-exp/src/platform_browser/strategies/redirect.test.ts index 9b1fc3e623f..a2892c89e4a 100644 --- a/packages-exp/auth-exp/src/platform_browser/strategies/redirect.test.ts +++ b/packages-exp/auth-exp/src/platform_browser/strategies/redirect.test.ts @@ -20,7 +20,12 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as sinon from 'sinon'; import * as sinonChai from 'sinon-chai'; -import * as externs from '@firebase/auth-types-exp'; +import { + AuthError, + OperationType, + PopupRedirectResolver, + ProviderId +} from '../../model/public_types'; import { delay } from '../../../test/helpers/delay'; import { BASE_AUTH_EVENT } from '../../../test/helpers/iframe_event'; @@ -34,26 +39,27 @@ import { makeMockPopupRedirectResolver } from '../../../test/helpers/mock_popup_ import { AuthEvent, AuthEventType, - PopupRedirectResolver + PopupRedirectResolverInternal } from '../../model/popup_redirect'; -import { User } from '../../model/user'; +import { UserInternal } from '../../model/user'; import { AuthEventManager } from '../../core/auth/auth_event_manager'; import { AuthErrorCode } from '../../core/errors'; -import { Persistence } from '../../core/persistence'; -import { InMemoryPersistence } from '../../core/persistence/in_memory'; +import { PersistenceInternal } from '../../core/persistence'; import { OAuthProvider } from '../../core/providers/oauth'; import * as link from '../../core/user/link_unlink'; import { UserCredentialImpl } from '../../core/user/user_credential_impl'; -import { _getInstance } from '../../core/util/instantiator'; +import { _clearInstanceMap, _getInstance } from '../../core/util/instantiator'; import * as idpTasks from '../../core/strategies/idp'; import { - _clearOutcomes, getRedirectResult, linkWithRedirect, reauthenticateWithRedirect, - signInWithRedirect + signInWithRedirect, + _getRedirectResult } from './redirect'; import { FirebaseError } from '@firebase/util'; +import { _clearRedirectOutcomes } from '../../core/strategies/redirect'; +import { RedirectPersistence } from '../../../test/helpers/redirect_persistence'; use(sinonChai); use(chaiAsPromised); @@ -61,35 +67,37 @@ use(chaiAsPromised); const MATCHING_EVENT_ID = 'matching-event-id'; const OTHER_EVENT_ID = 'wrong-id'; -class RedirectPersistence extends InMemoryPersistence {} - -describe('src/core/strategies/redirect', () => { +describe('platform_browser/strategies/redirect', () => { let auth: TestAuth; let eventManager: AuthEventManager; let provider: OAuthProvider; - let resolver: externs.PopupRedirectResolver; + let resolver: PopupRedirectResolver; let idpStubs: sinon.SinonStubbedInstance; beforeEach(async () => { - eventManager = new AuthEventManager('test-app'); - provider = new OAuthProvider(externs.ProviderId.GOOGLE); + eventManager = new AuthEventManager(({} as unknown) as TestAuth); + provider = new OAuthProvider(ProviderId.GOOGLE); resolver = makeMockPopupRedirectResolver(eventManager); - _getInstance( + _getInstance( resolver )._redirectPersistence = RedirectPersistence; auth = await testAuth(resolver); idpStubs = sinon.stub(idpTasks); + _getInstance( + RedirectPersistence + ).hasPendingRedirect = true; }); afterEach(() => { sinon.restore(); - _clearOutcomes(); + _clearRedirectOutcomes(); + _clearInstanceMap(); }); context('signInWithRedirect', () => { it('redirects the window', async () => { const spy = sinon.spy( - _getInstance(resolver), + _getInstance(resolver), '_openRedirect' ); await signInWithRedirect(auth, provider, resolver); @@ -102,7 +110,7 @@ describe('src/core/strategies/redirect', () => { it('redirects the window with auth fallback resolver', async () => { const spy = sinon.spy( - _getInstance(resolver), + _getInstance(resolver), '_openRedirect' ); await signInWithRedirect(auth, provider); @@ -123,17 +131,17 @@ describe('src/core/strategies/redirect', () => { }); context('linkWithRedirect', () => { - let user: User; + let user: UserInternal; beforeEach(async () => { user = testUser(auth, 'uid', 'email', true); - await auth.updateCurrentUser(user); + await auth._updateCurrentUser(user); sinon.stub(link, '_assertLinkedStatus').returns(Promise.resolve()); }); it('redirects the window', async () => { const spy = sinon.spy( - _getInstance(resolver), + _getInstance(resolver), '_openRedirect' ); await linkWithRedirect(user, provider, resolver); @@ -146,7 +154,7 @@ describe('src/core/strategies/redirect', () => { it('redirects the window with auth fallback resolver', async () => { const spy = sinon.spy( - _getInstance(resolver), + _getInstance(resolver), '_openRedirect' ); await linkWithRedirect(user, provider); @@ -166,7 +174,7 @@ describe('src/core/strategies/redirect', () => { }); it('persists the redirect user and current user', async () => { - const redirectPersistence: Persistence = _getInstance( + const redirectPersistence: PersistenceInternal = _getInstance( RedirectPersistence ); sinon.spy(redirectPersistence, '_set'); @@ -185,8 +193,8 @@ describe('src/core/strategies/redirect', () => { }); it('persists the redirect user but not current user if diff currentUser', async () => { - await auth.updateCurrentUser(testUser(auth, 'not-uid', 'email', true)); - const redirectPersistence: Persistence = _getInstance( + await auth._updateCurrentUser(testUser(auth, 'not-uid', 'email', true)); + const redirectPersistence: PersistenceInternal = _getInstance( RedirectPersistence ); sinon.spy(redirectPersistence, '_set'); @@ -203,16 +211,16 @@ describe('src/core/strategies/redirect', () => { }); context('reauthenticateWithRedirect', () => { - let user: User; + let user: UserInternal; beforeEach(async () => { user = testUser(auth, 'uid', 'email', true); - await auth.updateCurrentUser(user); + await auth._updateCurrentUser(user); }); it('redirects the window', async () => { const spy = sinon.spy( - _getInstance(resolver), + _getInstance(resolver), '_openRedirect' ); await reauthenticateWithRedirect(user, provider, resolver); @@ -225,7 +233,7 @@ describe('src/core/strategies/redirect', () => { it('redirects the window with auth fallback resolver', async () => { const spy = sinon.spy( - _getInstance(resolver), + _getInstance(resolver), '_openRedirect' ); await reauthenticateWithRedirect(user, provider); @@ -244,7 +252,7 @@ describe('src/core/strategies/redirect', () => { }); it('persists the redirect user and current user', async () => { - const redirectPersistence: Persistence = _getInstance( + const redirectPersistence: PersistenceInternal = _getInstance( RedirectPersistence ); sinon.spy(redirectPersistence, '_set'); @@ -263,8 +271,8 @@ describe('src/core/strategies/redirect', () => { }); it('persists the redirect user but not current user if diff currentUser', async () => { - await auth.updateCurrentUser(testUser(auth, 'not-uid', 'email', true)); - const redirectPersistence: Persistence = _getInstance( + await auth._updateCurrentUser(testUser(auth, 'not-uid', 'email', true)); + const redirectPersistence: PersistenceInternal = _getInstance( RedirectPersistence ); sinon.spy(redirectPersistence, '_set'); @@ -293,16 +301,14 @@ describe('src/core/strategies/redirect', () => { } async function reInitAuthWithRedirectUser(eventId: string): Promise { - const redirectPersistence: Persistence = _getInstance( + const redirectPersistence: RedirectPersistence = _getInstance( RedirectPersistence ); const mainPersistence = new MockPersistenceLayer(); const oldAuth = await testAuth(); const user = testUser(oldAuth, 'uid'); user._redirectEventId = eventId; - sinon - .stub(redirectPersistence, '_get') - .returns(Promise.resolve(user.toJSON())); + redirectPersistence.redirectUser = user.toJSON(); sinon .stub(mainPersistence, '_get') .returns(Promise.resolve(user.toJSON())); @@ -313,8 +319,8 @@ describe('src/core/strategies/redirect', () => { it('completes the proper flow', async () => { const cred = new UserCredentialImpl({ user: testUser(auth, 'uid'), - providerId: externs.ProviderId.GOOGLE, - operationType: externs.OperationType.SIGN_IN + providerId: ProviderId.GOOGLE, + operationType: OperationType.SIGN_IN }); idpStubs._signIn.returns(Promise.resolve(cred)); const promise = getRedirectResult(auth, resolver); @@ -327,8 +333,8 @@ describe('src/core/strategies/redirect', () => { it('returns the same value if called multiple times', async () => { const cred = new UserCredentialImpl({ user: testUser(auth, 'uid'), - providerId: externs.ProviderId.GOOGLE, - operationType: externs.OperationType.SIGN_IN + providerId: ProviderId.GOOGLE, + operationType: OperationType.SIGN_IN }); idpStubs._signIn.returns(Promise.resolve(cred)); const promise = getRedirectResult(auth, resolver); @@ -346,8 +352,8 @@ describe('src/core/strategies/redirect', () => { const cred = new UserCredentialImpl({ user: testUser(auth, 'uid'), - providerId: externs.ProviderId.GOOGLE, - operationType: externs.OperationType.LINK + providerId: ProviderId.GOOGLE, + operationType: OperationType.LINK }); idpStubs._link.returns(Promise.resolve(cred)); const promise = getRedirectResult(auth, resolver); @@ -364,8 +370,8 @@ describe('src/core/strategies/redirect', () => { const cred = new UserCredentialImpl({ user: testUser(auth, 'uid'), - providerId: externs.ProviderId.GOOGLE, - operationType: externs.OperationType.LINK + providerId: ProviderId.GOOGLE, + operationType: OperationType.LINK }); idpStubs._link.returns(Promise.resolve(cred)); const promise = getRedirectResult(auth, resolver); @@ -381,7 +387,7 @@ describe('src/core/strategies/redirect', () => { type: AuthEventType.UNKNOWN, error: { code: `auth/${AuthErrorCode.NO_AUTH_EVENT}` - } as externs.AuthError + } as AuthError }); expect(await promise).to.be.null; }); @@ -391,8 +397,8 @@ describe('src/core/strategies/redirect', () => { const cred = new UserCredentialImpl({ user: testUser(auth, 'uid'), - providerId: externs.ProviderId.GOOGLE, - operationType: externs.OperationType.REAUTHENTICATE + providerId: ProviderId.GOOGLE, + operationType: OperationType.REAUTHENTICATE }); idpStubs._reauth.returns(Promise.resolve(cred)); const promise = getRedirectResult(auth, resolver); @@ -405,15 +411,15 @@ describe('src/core/strategies/redirect', () => { it('removes the redirect user and clears eventId from currentuser', async () => { await reInitAuthWithRedirectUser(MATCHING_EVENT_ID); - const redirectPersistence: Persistence = _getInstance( + const redirectPersistence: PersistenceInternal = _getInstance( RedirectPersistence ); sinon.spy(redirectPersistence, '_remove'); const cred = new UserCredentialImpl({ user: auth._currentUser!, - providerId: externs.ProviderId.GOOGLE, - operationType: externs.OperationType.LINK + providerId: ProviderId.GOOGLE, + operationType: OperationType.LINK }); idpStubs._link.returns(Promise.resolve(cred)); const promise = getRedirectResult(auth, resolver); @@ -421,10 +427,38 @@ describe('src/core/strategies/redirect', () => { type: AuthEventType.LINK_VIA_REDIRECT }); expect(await promise).to.eq(cred); - expect(redirectPersistence._remove).to.have.been.called; + expect(redirectPersistence._remove).to.have.been.calledWith( + 'firebase:redirectUser:test-api-key:test-app' + ); expect(auth._currentUser?._redirectEventId).to.be.undefined; expect(auth.persistenceLayer.lastObjectSet?._redirectEventId).to.be .undefined; }); + + it('does not mutate authstate if bypassAuthState is true', async () => { + await reInitAuthWithRedirectUser(MATCHING_EVENT_ID); + const redirectPersistence: PersistenceInternal = _getInstance( + RedirectPersistence + ); + sinon.spy(redirectPersistence, '_remove'); + + const cred = new UserCredentialImpl({ + user: auth._currentUser!, + providerId: ProviderId.GOOGLE, + operationType: OperationType.LINK + }); + idpStubs._link.returns(Promise.resolve(cred)); + const promise = _getRedirectResult(auth, resolver, true); + iframeEvent({ + type: AuthEventType.LINK_VIA_REDIRECT + }); + expect(await promise).to.eq(cred); + expect(redirectPersistence._remove).not.to.have.been.calledWith( + 'firebase:redirectUser:test-api-key:test-app' + ); + expect(auth._currentUser?._redirectEventId).not.to.be.undefined; + expect(auth.persistenceLayer.lastObjectSet?._redirectEventId).not.to.be + .undefined; + }); }); }); diff --git a/packages-exp/auth-exp/src/platform_browser/strategies/redirect.ts b/packages-exp/auth-exp/src/platform_browser/strategies/redirect.ts index a2d919d16fa..d06eba48cf1 100644 --- a/packages-exp/auth-exp/src/platform_browser/strategies/redirect.ts +++ b/packages-exp/auth-exp/src/platform_browser/strategies/redirect.ts @@ -15,181 +15,290 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { + Auth, + AuthProvider, + PopupRedirectResolver, + User, + UserCredential +} from '../../model/public_types'; -import { OAuthProvider } from '../../core'; import { _castAuth } from '../../core/auth/auth_impl'; import { AuthErrorCode } from '../../core/errors'; import { _assertLinkedStatus } from '../../core/user/link_unlink'; -import { assert } from '../../core/util/assert'; +import { _assert } from '../../core/util/assert'; import { _generateEventId } from '../../core/util/event_id'; -import { _getInstance } from '../../core/util/instantiator'; -import { Auth } from '../../model/auth'; +import { AuthEventType } from '../../model/popup_redirect'; +import { UserInternal } from '../../model/user'; +import { _withDefaultResolver } from '../../core/util/resolver'; import { - AuthEvent, - AuthEventType, - PopupRedirectResolver -} from '../../model/popup_redirect'; -import { User, UserCredential } from '../../model/user'; -import { _withDefaultResolver } from '../popup_redirect'; -import { AbstractPopupRedirectOperation } from './abstract_popup_redirect_operation'; - -export async function signInWithRedirect( - authExtern: externs.Auth, - provider: externs.AuthProvider, - resolverExtern?: externs.PopupRedirectResolver + RedirectAction, + _setPendingRedirectStatus +} from '../../core/strategies/redirect'; +import { FederatedAuthProvider } from '../../core/providers/federated'; +import { getModularInstance } from '@firebase/util'; + +/** + * Authenticates a Firebase client using a full-page redirect flow. + * + * @remarks + * To handle the results and errors for this operation, refer to {@link getRedirectResult}. + * + * @example + * ```javascript + * // Sign in using a redirect. + * const provider = new FacebookAuthProvider(); + * // You can add additional scopes to the provider: + * provider.addScope('user_birthday'); + * // Start a sign in process for an unauthenticated user. + * await signInWithRedirect(auth, provider); + * // This will trigger a full page redirect away from your app + * + * // After returning from the redirect when your app initializes you can obtain the result + * const result = await getRedirectResult(auth); + * if (result) { + * // This is the signed-in user + * const user = result.user; + * // This gives you a Facebook Access Token. + * const credential = provider.credentialFromResult(auth, result); + * const token = credential.accessToken; + * } + * // As this API can be used for sign-in, linking and reauthentication, + * // check the operationType to determine what triggered this redirect + * // operation. + * const operationType = result.operationType; + * ``` + * + * @param auth - The Auth instance. + * @param provider - The provider to authenticate. The provider has to be an {@link OAuthProvider}. + * Non-OAuth providers like {@link EmailAuthProvider} will throw an error. + * @param resolver - An instance of {@link PopupRedirectResolver}, optional + * if already supplied to {@link initializeAuth} or provided by {@link getAuth}. + * + * @public + */ +export function signInWithRedirect( + auth: Auth, + provider: AuthProvider, + resolver?: PopupRedirectResolver ): Promise { - const auth = _castAuth(authExtern); - assert(provider instanceof OAuthProvider, AuthErrorCode.ARGUMENT_ERROR, { - appName: auth.name - }); + return _signInWithRedirect(auth, provider, resolver) as Promise; +} - return _withDefaultResolver(auth, resolverExtern)._openRedirect( +export async function _signInWithRedirect( + auth: Auth, + provider: AuthProvider, + resolver?: PopupRedirectResolver +): Promise { + const authInternal = _castAuth(auth); + _assert( + provider instanceof FederatedAuthProvider, auth, + AuthErrorCode.ARGUMENT_ERROR + ); + + const resolverInternal = _withDefaultResolver(authInternal, resolver); + await _setPendingRedirectStatus(resolverInternal, authInternal); + + return resolverInternal._openRedirect( + authInternal, provider, AuthEventType.SIGN_IN_VIA_REDIRECT ); } -export async function reauthenticateWithRedirect( - userExtern: externs.User, - provider: externs.AuthProvider, - resolverExtern?: externs.PopupRedirectResolver +/** + * Reauthenticates the current user with the specified {@link OAuthProvider} using a full-page redirect flow. + * + * @example + * ```javascript + * // Sign in using a redirect. + * const provider = new FacebookAuthProvider(); + * const result = await signInWithRedirect(auth, provider); + * // This will trigger a full page redirect away from your app + * + * // After returning from the redirect when your app initializes you can obtain the result + * const result = await getRedirectResult(auth); + * // Link using a redirect. + * await linkWithRedirect(result.user, provider); + * // This will again trigger a full page redirect away from your app + * + * // After returning from the redirect when your app initializes you can obtain the result + * const result = await getRedirectResult(auth); + * ``` + * + * @param user - The user. + * @param provider - The provider to authenticate. The provider has to be an {@link OAuthProvider}. + * Non-OAuth providers like {@link EmailAuthProvider} will throw an error. + * @param resolver - An instance of {@link PopupRedirectResolver}, optional + * if already supplied to {@link initializeAuth} or provided by {@link getAuth}. + * + * @public + */ +export function reauthenticateWithRedirect( + user: User, + provider: AuthProvider, + resolver?: PopupRedirectResolver ): Promise { - const user = userExtern as User; - assert(provider instanceof OAuthProvider, AuthErrorCode.ARGUMENT_ERROR, { - appName: user.auth.name - }); + return _reauthenticateWithRedirect( + user, + provider, + resolver + ) as Promise; +} +export async function _reauthenticateWithRedirect( + user: User, + provider: AuthProvider, + resolver?: PopupRedirectResolver +): Promise { + const userInternal = getModularInstance(user) as UserInternal; + _assert( + provider instanceof FederatedAuthProvider, + userInternal.auth, + AuthErrorCode.ARGUMENT_ERROR + ); // Allow the resolver to error before persisting the redirect user - const resolver = _withDefaultResolver(user.auth, resolverExtern); + const resolverInternal = _withDefaultResolver(userInternal.auth, resolver); + await _setPendingRedirectStatus(resolverInternal, userInternal.auth); - const eventId = await prepareUserForRedirect(user.auth, user); - return resolver._openRedirect( - user.auth, + const eventId = await prepareUserForRedirect(userInternal); + return resolverInternal._openRedirect( + userInternal.auth, provider, AuthEventType.REAUTH_VIA_REDIRECT, eventId ); } -export async function linkWithRedirect( - userExtern: externs.User, - provider: externs.AuthProvider, - resolverExtern?: externs.PopupRedirectResolver +/** + * Links the {@link OAuthProvider} to the user account using a full-page redirect flow. + * + * @example + * ```javascript + * // Sign in using some other provider. + * const result = await signInWithEmailAndPassword(auth, email, password); + * // Link using a redirect. + * const provider = new FacebookAuthProvider(); + * await linkWithRedirect(result.user, provider); + * // This will trigger a full page redirect away from your app + * + * // After returning from the redirect when your app initializes you can obtain the result + * const result = await getRedirectResult(auth); + * ``` + * + * @param user - The user. + * @param provider - The provider to authenticate. The provider has to be an {@link OAuthProvider}. + * Non-OAuth providers like {@link EmailAuthProvider} will throw an error. + * @param resolver - An instance of {@link PopupRedirectResolver}, optional + * if already supplied to {@link initializeAuth} or provided by {@link getAuth}. + * + * + * @public + */ +export function linkWithRedirect( + user: User, + provider: AuthProvider, + resolver?: PopupRedirectResolver ): Promise { - const user = userExtern as User; - assert(provider instanceof OAuthProvider, AuthErrorCode.ARGUMENT_ERROR, { - appName: user.auth.name - }); + return _linkWithRedirect(user, provider, resolver) as Promise; +} +export async function _linkWithRedirect( + user: User, + provider: AuthProvider, + resolver?: PopupRedirectResolver +): Promise { + const userInternal = getModularInstance(user) as UserInternal; + _assert( + provider instanceof FederatedAuthProvider, + userInternal.auth, + AuthErrorCode.ARGUMENT_ERROR + ); // Allow the resolver to error before persisting the redirect user - const resolver = _withDefaultResolver(user.auth, resolverExtern); + const resolverInternal = _withDefaultResolver(userInternal.auth, resolver); + await _assertLinkedStatus(false, userInternal, provider.providerId); + await _setPendingRedirectStatus(resolverInternal, userInternal.auth); - await _assertLinkedStatus(false, user, provider.providerId); - const eventId = await prepareUserForRedirect(user.auth, user); - return resolver._openRedirect( - user.auth, + const eventId = await prepareUserForRedirect(userInternal); + return resolverInternal._openRedirect( + userInternal.auth, provider, AuthEventType.LINK_VIA_REDIRECT, eventId ); } +/** + * Returns a {@link UserCredential} from the redirect-based sign-in flow. + * + * @remarks + * If sign-in succeeded, returns the signed in user. If sign-in was unsuccessful, fails with an + * error. If no redirect operation was called, returns a {@link UserCredential} + * with a null `user`. + * + * @example + * ```javascript + * // Sign in using a redirect. + * const provider = new FacebookAuthProvider(); + * // You can add additional scopes to the provider: + * provider.addScope('user_birthday'); + * // Start a sign in process for an unauthenticated user. + * await signInWithRedirect(auth, provider); + * // This will trigger a full page redirect away from your app + * + * // After returning from the redirect when your app initializes you can obtain the result + * const result = await getRedirectResult(auth); + * if (result) { + * // This is the signed-in user + * const user = result.user; + * // This gives you a Facebook Access Token. + * const credential = provider.credentialFromResult(auth, result); + * const token = credential.accessToken; + * } + * // As this API can be used for sign-in, linking and reauthentication, + * // check the operationType to determine what triggered this redirect + * // operation. + * const operationType = result.operationType; + * ``` + * + * @param auth - The Auth instance. + * @param resolver - An instance of {@link PopupRedirectResolver}, optional + * if already supplied to {@link initializeAuth} or provided by {@link getAuth}. + * + * @public + */ export async function getRedirectResult( - authExtern: externs.Auth, - resolverExtern?: externs.PopupRedirectResolver -): Promise { - const auth = _castAuth(authExtern); - const resolver = _withDefaultResolver(auth, resolverExtern); - const action = new RedirectAction(auth, resolver); + auth: Auth, + resolver?: PopupRedirectResolver +): Promise { + await _castAuth(auth)._initializationPromise; + return _getRedirectResult(auth, resolver, false); +} + +export async function _getRedirectResult( + auth: Auth, + resolverExtern?: PopupRedirectResolver, + bypassAuthState = false +): Promise { + const authInternal = _castAuth(auth); + const resolver = _withDefaultResolver(authInternal, resolverExtern); + const action = new RedirectAction(authInternal, resolver, bypassAuthState); const result = await action.execute(); - if (result) { + if (result && !bypassAuthState) { delete result.user._redirectEventId; - await auth._persistUserIfCurrent(result.user as User); - await auth._setRedirectUser(null, resolverExtern); + await authInternal._persistUserIfCurrent(result.user as UserInternal); + await authInternal._setRedirectUser(null, resolverExtern); } return result; } -async function prepareUserForRedirect(auth: Auth, user: User): Promise { +async function prepareUserForRedirect(user: UserInternal): Promise { const eventId = _generateEventId(`${user.uid}:::`); user._redirectEventId = eventId; await user.auth._setRedirectUser(user); - await auth._persistUserIfCurrent(user); + await user.auth._persistUserIfCurrent(user); return eventId; } - -// We only get one redirect outcome for any one auth, so just store it -// in here. -const redirectOutcomeMap: Map< - string, - () => Promise -> = new Map(); - -class RedirectAction extends AbstractPopupRedirectOperation { - eventId = null; - - constructor(auth: Auth, resolver: PopupRedirectResolver) { - super( - auth, - [ - AuthEventType.SIGN_IN_VIA_REDIRECT, - AuthEventType.LINK_VIA_REDIRECT, - AuthEventType.REAUTH_VIA_REDIRECT, - AuthEventType.UNKNOWN - ], - resolver - ); - } - - /** - * Override the execute function; if we already have a redirect result, then - * just return it. - */ - async execute(): Promise { - let readyOutcome = redirectOutcomeMap.get(this.auth._key()); - if (!readyOutcome) { - try { - const result = await super.execute(); - readyOutcome = () => Promise.resolve(result); - } catch (e) { - readyOutcome = () => Promise.reject(e); - } - - redirectOutcomeMap.set(this.auth._key(), readyOutcome); - } - - return readyOutcome(); - } - - async onAuthEvent(event: AuthEvent): Promise { - if (event.type === AuthEventType.SIGN_IN_VIA_REDIRECT) { - return super.onAuthEvent(event); - } else if (event.type === AuthEventType.UNKNOWN) { - // This is a sentinel value indicating there's no pending redirect - this.resolve(null); - return; - } - - if (event.eventId) { - const user = await this.auth._redirectUserForId(event.eventId); - if (user) { - this.user = user; - return super.onAuthEvent(event); - } else { - this.resolve(null); - } - } - } - - async onExecution(): Promise {} - - cleanUp(): void {} -} - -export function _clearOutcomes(): void { - redirectOutcomeMap.clear(); -} diff --git a/packages-exp/auth-exp/src/platform_browser/util/popup.test.ts b/packages-exp/auth-exp/src/platform_browser/util/popup.test.ts index d097e8088d4..33e75b5feea 100644 --- a/packages-exp/auth-exp/src/platform_browser/util/popup.test.ts +++ b/packages-exp/auth-exp/src/platform_browser/util/popup.test.ts @@ -23,11 +23,14 @@ import { FirebaseError } from '@firebase/util'; import * as utils from '@firebase/util'; import { _open, AuthPopup } from './popup'; +import { AuthInternal } from '../../model/auth'; +import { testAuth } from '../../../test/helpers/mock_auth'; use(sinonChai); -describe('src/core/util/popup', () => { +describe('platform_browser/util/popup', () => { let windowOpenStub: sinon.SinonStub; + let auth: AuthInternal; let popupStub: sinon.SinonStubbedInstance; function setUA(ua: string): void { @@ -46,13 +49,14 @@ describe('src/core/util/popup', () => { return windowOpenStub.firstCall.args[2]; } - beforeEach(() => { + beforeEach(async () => { windowOpenStub = sinon.stub(window, 'open'); popupStub = sinon.stub(({ focus: () => {}, close: () => {} } as unknown) as Window); windowOpenStub.returns(popupStub); + auth = await testAuth(); }); afterEach(() => { @@ -61,53 +65,50 @@ describe('src/core/util/popup', () => { it('sets target to name param if not chrome UA', () => { setUA('notchrome'); - _open('appName', 'url', 'name'); + _open(auth, 'url', 'name'); expect(windowTarget()).to.eq('name'); }); it('sets target to _blank if on chrome IOS', () => { setUA('crios/'); - _open('appName', 'url', 'name'); + _open(auth, 'url', 'name'); expect(windowTarget()).to.eq('_blank'); }); it('sets the firefox url to a default if not provided', () => { setUA('firefox/'); - _open('appName'); + _open(auth); expect(windowURL()).to.eq('http://localhost'); }); it('sets the firefox url to the value provided', () => { setUA('firefox/'); - _open('appName', 'url'); + _open(auth, 'url'); expect(windowURL()).to.eq('url'); }); it('sets non-firefox url to empty if not provided', () => { setUA('not-ff/'); - _open('appName'); + _open(auth); expect(windowURL()).to.eq(''); }); it('sets non-firefox url to url if not provided', () => { setUA('not-ff/'); - _open('appName', 'url'); + _open(auth, 'url'); expect(windowURL()).to.eq('url'); }); it('sets scrollbars to yes in popup', () => { setUA('firefox/'); - _open('appName'); + _open(auth); expect(windowOptions()).to.include('scrollbars=yes'); }); it('errors if the popup is blocked', () => { setUA(''); windowOpenStub.returns(undefined); - expect(() => _open('appName')).to.throw( - FirebaseError, - 'auth/popup-blocked' - ); + expect(() => _open(auth)).to.throw(FirebaseError, 'auth/popup-blocked'); }); it('builds the proper options string', () => { @@ -119,7 +120,7 @@ describe('src/core/util/popup', () => { }; setUA(''); - _open('appName'); + _open(auth); const options = windowOptions() .split(',') .filter(s => !!s) @@ -146,21 +147,21 @@ describe('src/core/util/popup', () => { it('calls focus on the new popup', () => { setUA(''); - _open('appName'); + _open(auth); expect(popupStub.focus).to.have.been.called; }); it('does not fail if window.focus errors', () => { popupStub.focus.throws(new Error('lol no')); setUA(''); - expect(() => _open('appName')).not.to.throw(Error); + expect(() => _open(auth)).not.to.throw(Error); }); context('resulting popup object', () => { let authPopup: AuthPopup; beforeEach(() => { setUA(''); - authPopup = _open('appName'); + authPopup = _open(auth); }); it('has a window object', () => { diff --git a/packages-exp/auth-exp/src/platform_browser/util/popup.ts b/packages-exp/auth-exp/src/platform_browser/util/popup.ts index fc8704da72a..13e324b30be 100644 --- a/packages-exp/auth-exp/src/platform_browser/util/popup.ts +++ b/packages-exp/auth-exp/src/platform_browser/util/popup.ts @@ -18,12 +18,13 @@ import { getUA } from '@firebase/util'; import { AuthErrorCode } from '../../core/errors'; -import { assert } from '../../core/util/assert'; +import { _assert } from '../../core/util/assert'; import { _isChromeIOS, _isFirefox, _isIOSStandalone } from '../../core/util/browser'; +import { AuthInternal } from '../../model/auth'; const BASE_POPUP_OPTIONS = { location: 'yes', @@ -53,7 +54,7 @@ export class AuthPopup { } export function _open( - appName: string, + auth: AuthInternal, url?: string, name?: string, width = DEFAULT_WIDTH, @@ -100,7 +101,7 @@ export function _open( // about:blank getting sanitized causing browsers like IE/Edge to display // brief error message before redirecting to handler. const newWin = window.open(url || '', target, optionsString); - assert(newWin, AuthErrorCode.POPUP_BLOCKED, { appName }); + _assert(newWin, auth, AuthErrorCode.POPUP_BLOCKED); // Flaky on IE edge, encapsulate with a try and catch. try { diff --git a/packages-exp/auth-exp/src/platform_browser/util/worker.ts b/packages-exp/auth-exp/src/platform_browser/util/worker.ts new file mode 100644 index 00000000000..ae82ecb6343 --- /dev/null +++ b/packages-exp/auth-exp/src/platform_browser/util/worker.ts @@ -0,0 +1,45 @@ +/** + * @license + * Copyright 2020 Google LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { _window } from '../auth_window'; + +export function _isWorker(): boolean { + return ( + typeof _window()['WorkerGlobalScope'] !== 'undefined' && + typeof _window()['importScripts'] === 'function' + ); +} + +export async function _getActiveServiceWorker(): Promise { + if (!navigator?.serviceWorker) { + return null; + } + try { + const registration = await navigator.serviceWorker.ready; + return registration.active; + } catch { + return null; + } +} + +export function _getServiceWorkerController(): ServiceWorker | null { + return navigator?.serviceWorker?.controller || null; +} + +export function _getWorkerGlobalScope(): ServiceWorker | null { + return _isWorker() ? ((self as unknown) as ServiceWorker) : null; +} diff --git a/packages-exp/auth-exp/src/platform_cordova/plugins.ts b/packages-exp/auth-exp/src/platform_cordova/plugins.ts new file mode 100644 index 00000000000..c143dd69c7d --- /dev/null +++ b/packages-exp/auth-exp/src/platform_cordova/plugins.ts @@ -0,0 +1,54 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface CordovaWindow extends Window { + cordova: { + plugins: { + browsertab: { + isAvailable(cb: (available: boolean) => void): void; + openUrl(url: string): void; + close(): void; + }; + }; + + InAppBrowser: { + open(url: string, target: string, options: string): InAppBrowserRef; + }; + }; + + universalLinks: { + subscribe( + n: null, + cb: (event: Record | null) => void + ): void; + }; + + BuildInfo: { + readonly packageName: string; + readonly displayName: string; + }; + + handleOpenURL(url: string): void; +} + +export interface InAppBrowserRef { + close?: () => void; +} + +export function _cordovaWindow(): CordovaWindow { + return (window as unknown) as CordovaWindow; +} diff --git a/packages-exp/auth-exp/src/platform_cordova/popup_redirect/events.test.ts b/packages-exp/auth-exp/src/platform_cordova/popup_redirect/events.test.ts new file mode 100644 index 00000000000..f259d6d98ec --- /dev/null +++ b/packages-exp/auth-exp/src/platform_cordova/popup_redirect/events.test.ts @@ -0,0 +1,232 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as sinonChai from 'sinon-chai'; +import * as sinon from 'sinon'; +import { FirebaseError, querystring } from '@firebase/util'; +import { expect, use } from 'chai'; +import { testAuth, TestAuth } from '../../../test/helpers/mock_auth'; +import { AuthEvent, AuthEventType } from '../../model/popup_redirect'; +import { + CordovaAuthEventManager, + _eventFromPartialAndUrl, + _generateNewEvent, + _getAndRemoveEvent, + _getDeepLinkFromCallback, + _savePartialEvent +} from './events'; +import { _createError } from '../../core/util/assert'; +import { AuthErrorCode } from '../../core/errors'; + +use(sinonChai); + +describe('platform_cordova/popup_redirect/events', () => { + let auth: TestAuth; + let storageStub: sinon.SinonStubbedInstance; + + beforeEach(async () => { + auth = await testAuth(); + storageStub = sinon.stub(localStorage); + }); + + afterEach(() => { + sinon.restore(); + }); + + describe('_generateNewEvent', () => { + it('sets the correct type and tenantId', () => { + auth.tenantId = 'tid---------------'; + const event = _generateNewEvent(auth, AuthEventType.LINK_VIA_REDIRECT); + expect(event.type).to.eq(AuthEventType.LINK_VIA_REDIRECT); + expect(event.tenantId).to.eq(auth.tenantId); + }); + + it('creates an event with a 20-char session id', () => { + const event = _generateNewEvent(auth, AuthEventType.SIGN_IN_VIA_REDIRECT); + expect(event.sessionId).to.be.a('string').with.length(20); + }); + + it('sets the error field to be a "no auth event" error', () => { + const { error } = _generateNewEvent( + auth, + AuthEventType.REAUTH_VIA_REDIRECT + ); + expect(error) + .to.be.instanceOf(FirebaseError) + .with.property('code', 'auth/no-auth-event'); + }); + }); + + describe('_savePartialEvent', () => { + it('sets the event', async () => { + const event = _generateNewEvent(auth, AuthEventType.REAUTH_VIA_REDIRECT); + await _savePartialEvent(auth, event); + expect(storageStub.setItem).to.have.been.calledWith( + 'firebase:authEvent:test-api-key:test-app', + JSON.stringify(event) + ); + }); + }); + + describe('_getAndRemoveEvent', () => { + it('returns null if no event is present', async () => { + storageStub.getItem.returns(null); + expect(await _getAndRemoveEvent(auth)).to.be.null; + }); + + it('returns the event and deletes the key if present', async () => { + const event = JSON.stringify( + _generateNewEvent(auth, AuthEventType.REAUTH_VIA_REDIRECT) + ); + storageStub.getItem.returns(event); + expect(await _getAndRemoveEvent(auth)).to.eql(JSON.parse(event)); + expect(storageStub.removeItem).to.have.been.calledWith( + 'firebase:authEvent:test-api-key:test-app' + ); + }); + }); + + describe('_eventFromPartialAndUrl', () => { + let partialEvent: AuthEvent; + beforeEach(() => { + partialEvent = _generateNewEvent( + auth, + AuthEventType.REAUTH_VIA_REDIRECT, + 'id' + ); + }); + + function generateCallbackUrl(params: Record): string { + const deepLink = `http://foo/__/auth/callback?${querystring(params)}`; + return `http://outer-app?link=${encodeURIComponent(deepLink)}`; + } + + it('returns the proper event if everything is correct w/ no error', () => { + const url = generateCallbackUrl({}); + expect(_eventFromPartialAndUrl(partialEvent, url)).to.eql({ + type: AuthEventType.REAUTH_VIA_REDIRECT, + eventId: 'id', + tenantId: null, + sessionId: partialEvent.sessionId, + urlResponse: 'http://foo/__/auth/callback?', + postBody: null + }); + }); + + it('returns null if the callback url has no link', () => { + expect(_eventFromPartialAndUrl(partialEvent, 'http://foo')).to.be.null; + }); + + it('generates an error if the callback has an error', () => { + const handlerError = _createError(AuthErrorCode.INTERNAL_ERROR); + const url = generateCallbackUrl({ + 'firebaseError': JSON.stringify(handlerError) + }); + const { error, ...rest } = _eventFromPartialAndUrl(partialEvent, url)!; + + expect(error) + .to.be.instanceOf(FirebaseError) + .with.property('code', 'auth/internal-error'); + expect(rest).to.eql({ + type: AuthEventType.REAUTH_VIA_REDIRECT, + eventId: 'id', + tenantId: null, + urlResponse: null, + sessionId: null, + postBody: null + }); + }); + }); + + describe('_getDeepLinkFromCallback', () => { + it('returns the iOS double deep link preferentially', () => { + expect( + _getDeepLinkFromCallback( + 'https://foo?link=http%3A%2F%2Ffoo%3Flink%3DdoubleDeep' + + '&deep_link_id=http%3A%2F%2Ffoo%3Flink%3DdoubleDeepIos' + ) + ).to.eq('doubleDeepIos'); + }); + + it('returns the iOS deep link preferentially', () => { + expect( + _getDeepLinkFromCallback( + 'https://foo?link=http%3A%2F%2Ffoo%3Flink%3DdoubleDeep' + + '&deep_link_id=http%3A%2F%2FfooIOS' + ) + ).to.eq('http://fooIOS'); + }); + + it('returns double deep link preferentially', () => { + expect( + _getDeepLinkFromCallback( + 'https://foo?link=http%3A%2F%2Ffoo%3Flink%3DdoubleDeep' + ) + ).to.eq('doubleDeep'); + }); + + it('returns the deep link preferentially', () => { + expect( + _getDeepLinkFromCallback( + 'https://foo?link=http%3A%2F%2Ffoo%3Funrelated%3Dyeah' + ) + ).to.eq('http://foo?unrelated=yeah'); + }); + + it('returns the passed-in url when all else fails', () => { + expect(_getDeepLinkFromCallback('https://foo?bar=baz')).to.eq( + 'https://foo?bar=baz' + ); + }); + }); + + describe('_CordovaAuthEventManager', () => { + let eventManager: CordovaAuthEventManager; + let event: AuthEvent; + + beforeEach(() => { + eventManager = new CordovaAuthEventManager(auth); + event = _generateNewEvent(auth, AuthEventType.REAUTH_VIA_REDIRECT); + }); + + it('triggers passive listeners on events', done => { + eventManager.addPassiveListener(actual => { + expect(actual).to.eq(event); + done(); + }); + + eventManager.onEvent(event); + }); + + it('removes passive listeners properly', () => { + const stub = sinon.stub(); + eventManager.addPassiveListener(stub); + eventManager.onEvent(event); + eventManager.removePassiveListener(stub); + eventManager.onEvent(event); + expect(stub).to.have.been.calledOnce; + }); + + it('initialization resolves after first event', async () => { + const promise = eventManager.initialized(); + eventManager.onEvent(event); + await promise; + + // If this test doesn't time out, it passed. + }); + }); +}); diff --git a/packages-exp/auth-exp/src/platform_cordova/popup_redirect/events.ts b/packages-exp/auth-exp/src/platform_cordova/popup_redirect/events.ts new file mode 100644 index 00000000000..b5cd09e65a6 --- /dev/null +++ b/packages-exp/auth-exp/src/platform_cordova/popup_redirect/events.ts @@ -0,0 +1,209 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { querystringDecode } from '@firebase/util'; +import { AuthEventManager } from '../../core/auth/auth_event_manager'; +import { AuthErrorCode } from '../../core/errors'; +import { PersistedBlob, PersistenceInternal } from '../../core/persistence'; +import { + KeyName, + _persistenceKeyName +} from '../../core/persistence/persistence_user_manager'; +import { _createError } from '../../core/util/assert'; +import { _getInstance } from '../../core/util/instantiator'; +import { AuthInternal } from '../../model/auth'; +import { AuthEvent, AuthEventType } from '../../model/popup_redirect'; +import { browserLocalPersistence } from '../../platform_browser/persistence/local_storage'; + +const SESSION_ID_LENGTH = 20; + +/** Custom AuthEventManager that adds passive listeners to events */ +export class CordovaAuthEventManager extends AuthEventManager { + private readonly passiveListeners = new Set<(e: AuthEvent) => void>(); + private resolveInialized!: () => void; + private initPromise = new Promise(resolve => { + this.resolveInialized = resolve; + }); + + addPassiveListener(cb: (e: AuthEvent) => void): void { + this.passiveListeners.add(cb); + } + + removePassiveListener(cb: (e: AuthEvent) => void): void { + this.passiveListeners.delete(cb); + } + + // In a Cordova environment, this manager can live through multiple redirect + // operations + resetRedirect(): void { + this.queuedRedirectEvent = null; + this.hasHandledPotentialRedirect = false; + } + + /** Override the onEvent method */ + onEvent(event: AuthEvent): boolean { + this.resolveInialized(); + this.passiveListeners.forEach(cb => cb(event)); + return super.onEvent(event); + } + + async initialized(): Promise { + await this.initPromise; + } +} + +/** + * Generates a (partial) {@link AuthEvent}. + */ +export function _generateNewEvent( + auth: AuthInternal, + type: AuthEventType, + eventId: string | null = null +): AuthEvent { + return { + type, + eventId, + urlResponse: null, + sessionId: generateSessionId(), + postBody: null, + tenantId: auth.tenantId, + error: _createError(auth, AuthErrorCode.NO_AUTH_EVENT) + }; +} + +export function _savePartialEvent( + auth: AuthInternal, + event: AuthEvent +): Promise { + return storage()._set( + persistenceKey(auth), + (event as object) as PersistedBlob + ); +} + +export async function _getAndRemoveEvent( + auth: AuthInternal +): Promise { + const event = (await storage()._get( + persistenceKey(auth) + )) as AuthEvent | null; + if (event) { + await storage()._remove(persistenceKey(auth)); + } + return event; +} + +export function _eventFromPartialAndUrl( + partialEvent: AuthEvent, + url: string +): AuthEvent | null { + // Parse the deep link within the dynamic link URL. + const callbackUrl = _getDeepLinkFromCallback(url); + // Confirm it is actually a callback URL. + // Currently the universal link will be of this format: + // https:///__/auth/callback + // This is a fake URL but is not intended to take the user anywhere + // and just redirect to the app. + if (callbackUrl.includes('/__/auth/callback')) { + // Check if there is an error in the URL. + // This mechanism is also used to pass errors back to the app: + // https:///__/auth/callback?firebaseError= + const params = searchParamsOrEmpty(callbackUrl); + // Get the error object corresponding to the stringified error if found. + const errorObject = params['firebaseError'] + ? parseJsonOrNull(decodeURIComponent(params['firebaseError'])) + : null; + const code = errorObject?.['code']?.split('auth/')?.[1]; + const error = code ? _createError(code) : null; + if (error) { + return { + type: partialEvent.type, + eventId: partialEvent.eventId, + tenantId: partialEvent.tenantId, + error, + urlResponse: null, + sessionId: null, + postBody: null + }; + } else { + return { + type: partialEvent.type, + eventId: partialEvent.eventId, + tenantId: partialEvent.tenantId, + sessionId: partialEvent.sessionId, + urlResponse: callbackUrl, + postBody: null + }; + } + } + + return null; +} + +function generateSessionId(): string { + const chars = []; + const allowedChars = + '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + for (let i = 0; i < SESSION_ID_LENGTH; i++) { + const idx = Math.floor(Math.random() * allowedChars.length); + chars.push(allowedChars.charAt(idx)); + } + return chars.join(''); +} + +function storage(): PersistenceInternal { + return _getInstance(browserLocalPersistence); +} + +function persistenceKey(auth: AuthInternal): string { + return _persistenceKeyName(KeyName.AUTH_EVENT, auth.config.apiKey, auth.name); +} + +function parseJsonOrNull(json: string): ReturnType | null { + try { + return JSON.parse(json); + } catch (e) { + return null; + } +} + +// Exported for testing +export function _getDeepLinkFromCallback(url: string): string { + const params = searchParamsOrEmpty(url); + const link = params['link'] ? decodeURIComponent(params['link']) : undefined; + // Double link case (automatic redirect) + const doubleDeepLink = searchParamsOrEmpty(link)['link']; + // iOS custom scheme links. + const iOSDeepLink = params['deep_link_id'] + ? decodeURIComponent(params['deep_link_id']) + : undefined; + const iOSDoubleDeepLink = searchParamsOrEmpty(iOSDeepLink)['link']; + return iOSDoubleDeepLink || iOSDeepLink || doubleDeepLink || link || url; +} + +/** + * Optimistically tries to get search params from a string, or else returns an + * empty search params object. + */ +function searchParamsOrEmpty(url: string | undefined): Record { + if (!url?.includes('?')) { + return {}; + } + + const [_, ...rest] = url.split('?'); + return querystringDecode(rest.join('?')) as Record; +} diff --git a/packages-exp/auth-exp/src/platform_cordova/popup_redirect/popup_redirect.test.ts b/packages-exp/auth-exp/src/platform_cordova/popup_redirect/popup_redirect.test.ts new file mode 100644 index 00000000000..9a867a07b9d --- /dev/null +++ b/packages-exp/auth-exp/src/platform_cordova/popup_redirect/popup_redirect.test.ts @@ -0,0 +1,303 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AuthProvider } from '../../model/public_types'; +import * as sinon from 'sinon'; +import * as chaiAsPromised from 'chai-as-promised'; +import * as sinonChai from 'sinon-chai'; +import { expect, use } from 'chai'; +import { testAuth, TestAuth } from '../../../test/helpers/mock_auth'; +import { SingletonInstantiator } from '../../core/util/instantiator'; +import { + AuthEvent, + AuthEventType, + EventManager, + PopupRedirectResolverInternal +} from '../../model/popup_redirect'; +import { cordovaPopupRedirectResolver } from './popup_redirect'; +import { GoogleAuthProvider } from '../../core/providers/google'; +import * as utils from './utils'; +import * as events from './events'; +import { FirebaseError } from '@firebase/util'; +import { + stubSingleTimeout, + TimerTripFn +} from '../../../test/helpers/timeout_stub'; +import { _cordovaWindow } from '../plugins'; + +use(chaiAsPromised); +use(sinonChai); + +const win = _cordovaWindow(); + +describe('platform_cordova/popup_redirect/popup_redirect', () => { + const PACKAGE_NAME = 'my.package'; + const NOT_PACKAGE_NAME = 'not.my.package'; + const NO_EVENT_TIMER_ID = 10001; + + let auth: TestAuth; + let resolver: PopupRedirectResolverInternal; + let provider: AuthProvider; + let utilsStubs: sinon.SinonStubbedInstance; + let eventsStubs: sinon.SinonStubbedInstance>; + let universalLinksCb: + | ((eventData: Record | null) => unknown) + | null; + let tripNoEventTimer: TimerTripFn; + + beforeEach(async () => { + auth = await testAuth(); + resolver = + new (cordovaPopupRedirectResolver as SingletonInstantiator)(); + provider = new GoogleAuthProvider(); + utilsStubs = sinon.stub(utils); + eventsStubs = { + _generateNewEvent: sinon.stub(events, '_generateNewEvent'), + _savePartialEvent: sinon.stub(events, '_savePartialEvent'), + _getAndRemoveEvent: sinon.stub(events, '_getAndRemoveEvent'), + _eventFromPartialAndUrl: sinon.stub(events, '_eventFromPartialAndUrl'), + _getDeepLinkFromCallback: sinon.stub(events, '_getDeepLinkFromCallback') + }; + + win.universalLinks = { + subscribe(_unused, cb) { + universalLinksCb = cb; + } + }; + win.BuildInfo = { + packageName: PACKAGE_NAME, + displayName: '' + }; + tripNoEventTimer = stubSingleTimeout(NO_EVENT_TIMER_ID); + sinon.stub(win, 'clearTimeout'); + }); + + afterEach(() => { + sinon.restore(); + universalLinksCb = null; + const anyWindow = win as unknown as Record; + delete anyWindow.universalLinks; + delete anyWindow.BuildInfo; + }); + + describe('_openRedirect', () => { + // The heavy-duty testing of the underlying configuration is in + // platform_cordova/popup_redirect/utils.test.ts. These tests will check + // primarily for the correct behavior using the values provided by the + // utils. + it('performs the redirect with the correct url after checking config', async () => { + const event = {} as AuthEvent; + utilsStubs._generateHandlerUrl.returns( + Promise.resolve('https://localhost/__/auth/handler') + ); + utilsStubs._performRedirect.returns(Promise.resolve({})); + utilsStubs._waitForAppResume.returns(Promise.resolve()); + utilsStubs._validateOrigin.returns(Promise.resolve()); + eventsStubs._generateNewEvent!.returns(event); + + const redirectPromise = resolver._openRedirect( + auth, + provider, + AuthEventType.REAUTH_VIA_REDIRECT + ); + // _openRedirect awaits the first event (eventManager initialized) + tripNoEventTimer(); + await redirectPromise; + + expect(utilsStubs._checkCordovaConfiguration).to.have.been.called; + expect(utilsStubs._generateHandlerUrl).to.have.been.calledWith( + auth, + event, + provider + ); + expect(utilsStubs._performRedirect).to.have.been.calledWith( + 'https://localhost/__/auth/handler' + ); + expect(utilsStubs._waitForAppResume).to.have.been.called; + }); + }); + + describe('_initialize', () => { + function event(manager: EventManager): Promise { + return new Promise(resolve => { + (manager as events.CordovaAuthEventManager).addPassiveListener(resolve); + }); + } + + context('when no event is present', () => { + it('clears local storage and dispatches no-event event', async () => { + const promise = event(await resolver._initialize(auth)); + tripNoEventTimer(); + const { error, ...rest } = await promise; + + expect(error) + .to.be.instanceOf(FirebaseError) + .with.property('code', 'auth/no-auth-event'); + expect(rest).to.eql({ + type: AuthEventType.UNKNOWN, + eventId: null, + sessionId: null, + urlResponse: null, + postBody: null, + tenantId: null + }); + expect(events._getAndRemoveEvent).to.have.been.called; + }); + }); + + context('when an event is present', () => { + it('clears the no event timeout', async () => { + await resolver._initialize(auth); + await universalLinksCb!({}); + expect(win.clearTimeout).to.have.been.calledWith(NO_EVENT_TIMER_ID); + }); + + it('signals no event if no url in event data', async () => { + const promise = event(await resolver._initialize(auth)); + await universalLinksCb!({}); + const { error, ...rest } = await promise; + + expect(error) + .to.be.instanceOf(FirebaseError) + .with.property('code', 'auth/no-auth-event'); + expect(rest).to.eql({ + type: AuthEventType.UNKNOWN, + eventId: null, + sessionId: null, + urlResponse: null, + postBody: null, + tenantId: null + }); + }); + + it('signals no event if partial parse turns up null', async () => { + const promise = event(await resolver._initialize(auth)); + eventsStubs._eventFromPartialAndUrl!.returns(null); + eventsStubs._getAndRemoveEvent!.returns( + Promise.resolve({ + type: AuthEventType.REAUTH_VIA_REDIRECT + } as AuthEvent) + ); + await universalLinksCb!({ url: 'foo-bar' }); + const { error, ...rest } = await promise; + + expect(error) + .to.be.instanceOf(FirebaseError) + .with.property('code', 'auth/no-auth-event'); + expect(rest).to.eql({ + type: AuthEventType.UNKNOWN, + eventId: null, + sessionId: null, + urlResponse: null, + postBody: null, + tenantId: null + }); + }); + + it('signals the final event if partial expansion success', async () => { + const finalEvent = { + type: AuthEventType.REAUTH_VIA_REDIRECT, + postBody: 'foo' + }; + eventsStubs._getAndRemoveEvent!.returns( + Promise.resolve({ + type: AuthEventType.REAUTH_VIA_REDIRECT + } as AuthEvent) + ); + + const promise = event(await resolver._initialize(auth)); + eventsStubs._eventFromPartialAndUrl!.returns(finalEvent as AuthEvent); + await universalLinksCb!({ url: 'foo-bar' }); + expect(await promise).to.eq(finalEvent); + expect(events._eventFromPartialAndUrl).to.have.been.calledWith( + { type: AuthEventType.REAUTH_VIA_REDIRECT }, + 'foo-bar' + ); + }); + }); + + context('when using global handleOpenURL callback', () => { + it('ignores inbound callbacks that are not for this app', async () => { + await resolver._initialize(auth); + win.handleOpenURL(`${NOT_PACKAGE_NAME}://foo`); + + // Clear timeout is called in the handler so we can check that + expect(win.clearTimeout).not.to.have.been.called; + }); + + it('passes through callback if package name matches', async () => { + await resolver._initialize(auth); + win.handleOpenURL(`${PACKAGE_NAME}://foo`); + expect(win.clearTimeout).to.have.been.calledWith(NO_EVENT_TIMER_ID); + }); + + it('signals the final event if partial expansion success', async () => { + const finalEvent = { + type: AuthEventType.REAUTH_VIA_REDIRECT, + postBody: 'foo' + }; + eventsStubs._getAndRemoveEvent!.returns( + Promise.resolve({ + type: AuthEventType.REAUTH_VIA_REDIRECT + } as AuthEvent) + ); + + const promise = event(await resolver._initialize(auth)); + eventsStubs._eventFromPartialAndUrl!.returns(finalEvent as AuthEvent); + win.handleOpenURL(`${PACKAGE_NAME}://foo`); + expect(await promise).to.eq(finalEvent); + expect(events._eventFromPartialAndUrl).to.have.been.calledWith( + { type: AuthEventType.REAUTH_VIA_REDIRECT }, + `${PACKAGE_NAME}://foo` + ); + }); + + it('calls the dev existing handleOpenURL function', async () => { + const oldHandleOpenURL = sinon.stub(); + win.handleOpenURL = oldHandleOpenURL; + + await resolver._initialize(auth); + win.handleOpenURL(`${PACKAGE_NAME}://foo`); + expect(oldHandleOpenURL).to.have.been.calledWith( + `${PACKAGE_NAME}://foo` + ); + }); + + it('calls the dev existing handleOpenURL function for other package', async () => { + const oldHandleOpenURL = sinon.stub(); + win.handleOpenURL = oldHandleOpenURL; + + await resolver._initialize(auth); + win.handleOpenURL(`${NOT_PACKAGE_NAME}://foo`); + expect(oldHandleOpenURL).to.have.been.calledWith( + `${NOT_PACKAGE_NAME}://foo` + ); + }); + }); + }); + + describe('_openPopup', () => { + it('throws an error', () => { + expect(() => + resolver._openPopup(auth, provider, AuthEventType.LINK_VIA_POPUP) + ).to.throw( + FirebaseError, + 'auth/operation-not-supported-in-this-environment' + ); + }); + }); +}); diff --git a/packages-exp/auth-exp/src/platform_cordova/popup_redirect/popup_redirect.ts b/packages-exp/auth-exp/src/platform_cordova/popup_redirect/popup_redirect.ts new file mode 100644 index 00000000000..7c49a651f1d --- /dev/null +++ b/packages-exp/auth-exp/src/platform_cordova/popup_redirect/popup_redirect.ts @@ -0,0 +1,201 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AuthProvider, PopupRedirectResolver } from '../../model/public_types'; +import { browserSessionPersistence } from '../../platform_browser/persistence/session_storage'; +import { AuthInternal } from '../../model/auth'; +import { + AuthEvent, + AuthEventType, + PopupRedirectResolverInternal +} from '../../model/popup_redirect'; +import { AuthPopup } from '../../platform_browser/util/popup'; +import { _createError, _fail } from '../../core/util/assert'; +import { AuthErrorCode } from '../../core/errors'; +import { + _checkCordovaConfiguration, + _generateHandlerUrl, + _performRedirect, + _validateOrigin, + _waitForAppResume +} from './utils'; +import { + CordovaAuthEventManager, + _eventFromPartialAndUrl, + _generateNewEvent, + _getAndRemoveEvent, + _savePartialEvent +} from './events'; +import { AuthEventManager } from '../../core/auth/auth_event_manager'; +import { _getRedirectResult } from '../../platform_browser/strategies/redirect'; +import { _clearRedirectOutcomes } from '../../core/strategies/redirect'; +import { _cordovaWindow } from '../plugins'; + +/** + * How long to wait for the initial auth event before concluding no + * redirect pending + */ +const INITIAL_EVENT_TIMEOUT_MS = 500; + +class CordovaPopupRedirectResolver implements PopupRedirectResolverInternal { + readonly _redirectPersistence = browserSessionPersistence; + readonly _shouldInitProactively = true; // This is lightweight for Cordova + private readonly eventManagers = new Map(); + private readonly originValidationPromises: Record> = {}; + + _completeRedirectFn = _getRedirectResult; + + async _initialize(auth: AuthInternal): Promise { + const key = auth._key(); + let manager = this.eventManagers.get(key); + if (!manager) { + manager = new CordovaAuthEventManager(auth); + this.eventManagers.set(key, manager); + this.attachCallbackListeners(auth, manager); + } + return manager; + } + + _openPopup(auth: AuthInternal): Promise { + _fail(auth, AuthErrorCode.OPERATION_NOT_SUPPORTED); + } + + async _openRedirect( + auth: AuthInternal, + provider: AuthProvider, + authType: AuthEventType, + eventId?: string + ): Promise { + _checkCordovaConfiguration(auth); + const manager = await this._initialize(auth); + await manager.initialized(); + + // Reset the persisted redirect states. This does not matter on Web where + // the redirect always blows away application state entirely. On Cordova, + // the app maintains control flow through the redirect. + manager.resetRedirect(); + _clearRedirectOutcomes(); + + await this._originValidation(auth); + + const event = _generateNewEvent(auth, authType, eventId); + await _savePartialEvent(auth, event); + const url = await _generateHandlerUrl(auth, event, provider); + const iabRef = await _performRedirect(url); + return _waitForAppResume(auth, manager, iabRef); + } + + _isIframeWebStorageSupported( + _auth: AuthInternal, + _cb: (support: boolean) => unknown + ): void { + throw new Error('Method not implemented.'); + } + + _originValidation(auth: AuthInternal): Promise { + const key = auth._key(); + if (!this.originValidationPromises[key]) { + this.originValidationPromises[key] = _validateOrigin(auth); + } + + return this.originValidationPromises[key]; + } + + private attachCallbackListeners( + auth: AuthInternal, + manager: AuthEventManager + ): void { + // Get the global plugins + const { universalLinks, handleOpenURL, BuildInfo } = _cordovaWindow(); + + const noEventTimeout = setTimeout(async () => { + // We didn't see that initial event. Clear any pending object and + // dispatch no event + await _getAndRemoveEvent(auth); + manager.onEvent(generateNoEvent()); + }, INITIAL_EVENT_TIMEOUT_MS); + + const universalLinksCb = async ( + eventData: Record | null + ): Promise => { + // We have an event so we can clear the no event timeout + clearTimeout(noEventTimeout); + + const partialEvent = await _getAndRemoveEvent(auth); + let finalEvent: AuthEvent | null = null; + if (partialEvent && eventData?.['url']) { + finalEvent = _eventFromPartialAndUrl(partialEvent, eventData['url']); + } + + // If finalEvent is never filled, trigger with no event + manager.onEvent(finalEvent || generateNoEvent()); + }; + + // Universal links subscriber doesn't exist for iOS, so we need to check + if ( + typeof universalLinks !== 'undefined' && + typeof universalLinks.subscribe === 'function' + ) { + universalLinks.subscribe(null, universalLinksCb); + } + + // iOS 7 or 8 custom URL schemes. + // This is also the current default behavior for iOS 9+. + // For this to work, cordova-plugin-customurlscheme needs to be installed. + // https://github.com/EddyVerbruggen/Custom-URL-scheme + // Do not overwrite the existing developer's URL handler. + const existingHandleOpenURL = handleOpenURL; + const packagePrefix = `${BuildInfo.packageName.toLowerCase()}://`; + _cordovaWindow().handleOpenURL = async url => { + if (url.toLowerCase().startsWith(packagePrefix)) { + // We want this intentionally to float + // eslint-disable-next-line @typescript-eslint/no-floating-promises + universalLinksCb({ url }); + } + // Call the developer's handler if it is present. + if (typeof existingHandleOpenURL === 'function') { + try { + existingHandleOpenURL(url); + } catch (e) { + // This is a developer error. Don't stop the flow of the SDK. + console.error(e); + } + } + }; + } +} + +/** + * An implementation of {@link PopupRedirectResolver} suitable for Cordova + * based applications. + * + * @public + */ +export const cordovaPopupRedirectResolver: PopupRedirectResolver = + CordovaPopupRedirectResolver; + +function generateNoEvent(): AuthEvent { + return { + type: AuthEventType.UNKNOWN, + eventId: null, + sessionId: null, + urlResponse: null, + postBody: null, + tenantId: null, + error: _createError(AuthErrorCode.NO_AUTH_EVENT) + }; +} diff --git a/packages-exp/auth-exp/src/platform_cordova/popup_redirect/utils.test.ts b/packages-exp/auth-exp/src/platform_cordova/popup_redirect/utils.test.ts new file mode 100644 index 00000000000..53430477960 --- /dev/null +++ b/packages-exp/auth-exp/src/platform_cordova/popup_redirect/utils.test.ts @@ -0,0 +1,423 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as chaiAsPromised from 'chai-as-promised'; +import * as sinonChai from 'sinon-chai'; +import * as sinon from 'sinon'; +import { expect, use } from 'chai'; +import { testAuth, TestAuth } from '../../../test/helpers/mock_auth'; +import * as fbUtils from '@firebase/util'; +import { + _checkCordovaConfiguration, + _generateHandlerUrl, + _performRedirect, + _validateOrigin, + _waitForAppResume +} from './utils'; +import { AuthEvent, AuthEventType } from '../../model/popup_redirect'; +import { GoogleAuthProvider } from '../../core/providers/google'; +import { AuthProvider } from '../../../internal'; +import { CordovaAuthEventManager, _generateNewEvent } from './events'; +import { + stubSingleTimeout, + TimerTripFn +} from '../../../test/helpers/timeout_stub'; +import { FirebaseError } from '@firebase/util'; +import { InAppBrowserRef, _cordovaWindow } from '../plugins'; +import * as projectConfig from '../../api/project_config/get_project_config'; + +const ANDROID_UA = 'UserAgent/5.0 (Linux; Android 0.0.0)'; +const IOS_UA = 'UserAgent/5.0 (iPhone; CPU iPhone 0.0.0)'; +const IOS_8_UA = 'UserAgent/5.0 (iPhone OS 8_2)'; +const DESKTOP_UA = 'UserAgent/5.0 (Linux; Ubuntu 0.0.0)'; + +use(chaiAsPromised); +use(sinonChai); + +const win = _cordovaWindow(); + +describe('platform_cordova/popup_redirect/utils', () => { + let auth: TestAuth; + + beforeEach(async () => { + auth = await testAuth(); + attachExpectedPlugins(); + }); + + afterEach(() => { + sinon.restore(); + // Clean up the window object from attachExpectedPlugins() + removeProp(win, 'cordova'); + removeProp(win, 'BuildInfo'); + removeProp(win, 'universalLinks'); + }); + + function setUA(ua: string): void { + sinon.stub(fbUtils, 'getUA').returns(ua); + } + + describe('_checkCordovaConfiguration', () => { + // TODO: Rest of the tests go here + it('does not reject if all plugins installed', () => { + expect(() => _checkCordovaConfiguration(auth)).not.to.throw; + }); + + it('rejects if universal links is missing', () => { + removeProp(win, 'universalLinks'); + expect(() => _checkCordovaConfiguration(auth)) + .to.throw(fbUtils.FirebaseError, 'auth/invalid-cordova-configuration') + .that.has.deep.property('customData', { + appName: 'test-app', + missingPlugin: 'cordova-universal-links-plugin-fix' + }); + }); + + it('rejects if build info is missing', () => { + removeProp(win.BuildInfo, 'packageName'); + expect(() => _checkCordovaConfiguration(auth)) + .to.throw(fbUtils.FirebaseError, 'auth/invalid-cordova-configuration') + .that.has.deep.property('customData', { + appName: 'test-app', + missingPlugin: 'cordova-plugin-buildInfo' + }); + }); + + it('rejects if browsertab openUrl is missing', () => { + removeProp(win.cordova.plugins.browsertab, 'openUrl'); + expect(() => _checkCordovaConfiguration(auth)) + .to.throw(fbUtils.FirebaseError, 'auth/invalid-cordova-configuration') + .that.has.deep.property('customData', { + appName: 'test-app', + missingPlugin: 'cordova-plugin-browsertab' + }); + }); + + it('rejects if InAppBrowser is missing', () => { + removeProp(win.cordova.InAppBrowser, 'open'); + expect(() => _checkCordovaConfiguration(auth)) + .to.throw(fbUtils.FirebaseError, 'auth/invalid-cordova-configuration') + .that.has.deep.property('customData', { + appName: 'test-app', + missingPlugin: 'cordova-plugin-inappbrowser' + }); + }); + }); + + describe('_generateHandlerUrl', () => { + let event: AuthEvent; + let provider: AuthProvider; + + beforeEach(() => { + event = _generateNewEvent(auth, AuthEventType.REAUTH_VIA_REDIRECT); + provider = new GoogleAuthProvider(); + }); + + function getParams(url: string): URLSearchParams { + return new URL(url).searchParams; + } + + it('hashes the sessionId and does not pass it through', async () => { + setUA(ANDROID_UA); + const hashedSessionId = getParams( + await _generateHandlerUrl(auth, event, provider) + ).get('sessionId'); + expect(hashedSessionId).not.to.eq(event.sessionId); + // SHA-256 hash as a hex string is 64 chars + expect(hashedSessionId).to.have.length(64); + }); + + it('sets the ibi and not apn for iOS devices', async () => { + setUA(IOS_UA); + const params = getParams( + await _generateHandlerUrl(auth, event, provider) + ); + expect(params.get('ibi')).to.eq('com.example.name.package'); + expect(params.has('apn')).to.be.false; + }); + + it('sets the apn and not ibi for Android devices', async () => { + setUA(ANDROID_UA); + const params = getParams( + await _generateHandlerUrl(auth, event, provider) + ); + expect(params.get('apn')).to.eq('com.example.name.package'); + expect(params.has('ibi')).to.be.false; + }); + + it('throws an error for any other user agent', async () => { + setUA(DESKTOP_UA); + await expect( + _generateHandlerUrl(auth, event, provider) + ).to.be.rejectedWith( + fbUtils.FirebaseError, + 'auth/operation-not-supported-in-this-environment' + ); + }); + + it('does not attach a display name if none is present', async () => { + setUA(ANDROID_UA); + delete (win.BuildInfo as { displayName?: string }).displayName; + const params = getParams( + await _generateHandlerUrl(auth, event, provider) + ); + expect(params.has('appDisplayName')).to.be.false; + }); + + it('attaches the relevant display name', async () => { + setUA(IOS_UA); + (win.BuildInfo as { displayName: string }).displayName = 'This is my app'; + const params = getParams( + await _generateHandlerUrl(auth, event, provider) + ); + expect(params.get('appDisplayName')).to.eq('This is my app'); + }); + }); + + describe('_validateOrigin', () => { + beforeEach(() => { + sinon.stub(win.BuildInfo, 'packageName').value('com.example.myapp'); + sinon + .stub(projectConfig, '_getProjectConfig') + .returns( + Promise.resolve({ /* does not matter here */ authorizedDomains: [] }) + ); + }); + + it('sets the correct fields for android', async () => { + setUA(ANDROID_UA); + await _validateOrigin(auth); + expect(projectConfig._getProjectConfig).to.have.been.calledWith(auth, { + androidPackageName: 'com.example.myapp' + }); + }); + + it('sets the correct fields for ios', async () => { + setUA(IOS_UA); + await _validateOrigin(auth); + expect(projectConfig._getProjectConfig).to.have.been.calledWith(auth, { + iosBundleId: 'com.example.myapp' + }); + }); + }); + + describe('_performRedirect', () => { + let isBrowsertabAvailable: boolean; + beforeEach(() => { + isBrowsertabAvailable = false; + sinon + .stub(win.cordova.plugins.browsertab, 'isAvailable') + .callsFake(cb => cb(isBrowsertabAvailable)); + sinon.stub(win.cordova.plugins.browsertab, 'openUrl'); + sinon.stub(win.cordova.InAppBrowser, 'open'); + }); + + it('uses browserTab if that is available', async () => { + isBrowsertabAvailable = true; + await _performRedirect('https://localhost/__/auth/handler'); + expect(win.cordova.plugins.browsertab.openUrl).to.have.been.calledWith( + 'https://localhost/__/auth/handler' + ); + expect(win.cordova.InAppBrowser.open).not.to.have.been.called; + }); + + it('falls back to InAppBrowser if need be', async () => { + isBrowsertabAvailable = false; + setUA(ANDROID_UA); + await _performRedirect('https://localhost/__/auth/handler'); + expect(win.cordova.plugins.browsertab.openUrl).not.to.have.been.called; + expect(win.cordova.InAppBrowser.open).to.have.been.calledWith( + 'https://localhost/__/auth/handler', + '_system', + 'location=yes' + ); + }); + + it('uses _blank for iOS 8', async () => { + isBrowsertabAvailable = false; + setUA(IOS_8_UA); + await _performRedirect('https://localhost/__/auth/handler'); + expect(win.cordova.plugins.browsertab.openUrl).not.to.have.been.called; + expect(win.cordova.InAppBrowser.open).to.have.been.calledWith( + 'https://localhost/__/auth/handler', + '_blank', + 'location=yes' + ); + }); + }); + + describe('_waitForAppResume', () => { + const CANCEL_TIMER_ID = 301; + let tripCancelTimer: TimerTripFn; + let eventManager: CordovaAuthEventManager; + + beforeEach(() => { + tripCancelTimer = stubSingleTimeout(CANCEL_TIMER_ID); + eventManager = new CordovaAuthEventManager(auth); + }); + + context('when no auth event is seen', () => { + it('rejects when cancel timer trips on resume', async () => { + const promise = _waitForAppResume(auth, eventManager, null); + document.dispatchEvent(new CustomEvent('resume')); + tripCancelTimer(); + await expect(promise).to.be.rejectedWith( + FirebaseError, + 'auth/redirect-cancelled-by-user' + ); + }); + + it('rejects when timer trips after visibility change', async () => { + setUA(ANDROID_UA); + const promise = _waitForAppResume(auth, eventManager, null); + sinon.stub(document, 'visibilityState').value('visible'); + document.dispatchEvent(new CustomEvent('visibilitychange')); + tripCancelTimer(); + await expect(promise).to.be.rejectedWith( + FirebaseError, + 'auth/redirect-cancelled-by-user' + ); + }); + + it('only sets reject timeout once', async () => { + const promise = _waitForAppResume(auth, eventManager, null); + document.dispatchEvent(new CustomEvent('resume')); + document.dispatchEvent(new CustomEvent('resume')); + document.dispatchEvent(new CustomEvent('resume')); + document.dispatchEvent(new CustomEvent('resume')); + document.dispatchEvent(new CustomEvent('resume')); + tripCancelTimer(); + await expect(promise).to.be.rejectedWith( + FirebaseError, + 'auth/redirect-cancelled-by-user' + ); + expect(win.setTimeout).to.have.been.calledOnce; + }); + + it('cleans up listeners and cancels timer', async () => { + sinon.stub(document, 'removeEventListener').callThrough(); + sinon.stub(eventManager, 'removePassiveListener'); + sinon.stub(win, 'clearTimeout'); + const promise = _waitForAppResume(auth, eventManager, null); + document.dispatchEvent(new CustomEvent('resume')); + tripCancelTimer(); + await expect(promise).to.be.rejectedWith( + FirebaseError, + 'auth/redirect-cancelled-by-user' + ); + + expect(document.removeEventListener).to.have.been.calledWith( + 'resume', + sinon.match.func + ); + expect(document.removeEventListener).to.have.been.calledWith( + 'visibilitychange', + sinon.match.func + ); + expect(eventManager.removePassiveListener).to.have.been.calledWith( + sinon.match.func + ); + expect(win.clearTimeout).to.have.been.calledWith(CANCEL_TIMER_ID); + }); + }); + + context('when auth event is seen', () => { + function sendEvent(): void { + eventManager.onEvent( + _generateNewEvent(auth, AuthEventType.LINK_VIA_REDIRECT) + ); + } + + let cordova: typeof win.cordova; + + beforeEach(() => { + cordova = win.cordova; + }); + + it('resolves the promise', async () => { + const promise = _waitForAppResume(auth, eventManager, null); + sendEvent(); + await expect(promise).to.be.fulfilled; + }); + + it('closes the browser tab', async () => { + sinon.stub(cordova.plugins.browsertab, 'close'); + const promise = _waitForAppResume(auth, eventManager, null); + sendEvent(); + await promise; + expect(cordova.plugins.browsertab.close).to.have.been.called; + }); + + it('calls close on inAppBrowserRef', async () => { + const iabRef: InAppBrowserRef = { close: sinon.stub() }; + const promise = _waitForAppResume(auth, eventManager, iabRef); + sendEvent(); + await promise; + expect(iabRef.close).to.have.been.called; + }); + + it('cleans up listeners and cancels timer', async () => { + sinon.stub(document, 'removeEventListener').callThrough(); + sinon.stub(eventManager, 'removePassiveListener'); + sinon.stub(win, 'clearTimeout'); + const promise = _waitForAppResume(auth, eventManager, null); + document.dispatchEvent(new CustomEvent('resume')); + sendEvent(); + await promise; + + expect(document.removeEventListener).to.have.been.calledWith( + 'resume', + sinon.match.func + ); + expect(document.removeEventListener).to.have.been.calledWith( + 'visibilitychange', + sinon.match.func + ); + expect(eventManager.removePassiveListener).to.have.been.calledWith( + sinon.match.func + ); + expect(win.clearTimeout).to.have.been.calledWith(CANCEL_TIMER_ID); + }); + }); + }); +}); + +function attachExpectedPlugins(): void { + // Eventually these will be replaced with full mocks + win.cordova = { + plugins: { + browsertab: { + isAvailable: () => {}, + openUrl: () => {}, + close: () => {} + } + }, + InAppBrowser: { + open: () => ({}) + } + }; + win.universalLinks = { + subscribe: () => {} + }; + win.BuildInfo = { + packageName: 'com.example.name.package', + displayName: 'display name' + }; +} + +function removeProp(obj: unknown, prop: string): void { + delete (obj as Record)[prop]; +} diff --git a/packages-exp/auth-exp/src/platform_cordova/popup_redirect/utils.ts b/packages-exp/auth-exp/src/platform_cordova/popup_redirect/utils.ts new file mode 100644 index 00000000000..ad3b55054e1 --- /dev/null +++ b/packages-exp/auth-exp/src/platform_cordova/popup_redirect/utils.ts @@ -0,0 +1,307 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AuthProvider } from '../../model/public_types'; +import { AuthErrorCode } from '../../core/errors'; +import { + debugAssert, + _assert, + _createError, + _fail +} from '../../core/util/assert'; +import { _isAndroid, _isIOS, _isIOS7Or8 } from '../../core/util/browser'; +import { _getRedirectUrl } from '../../core/util/handler'; +import { AuthInternal } from '../../model/auth'; +import { AuthEvent } from '../../model/popup_redirect'; +import { InAppBrowserRef, _cordovaWindow } from '../plugins'; +import { + GetProjectConfigRequest, + _getProjectConfig +} from '../../api/project_config/get_project_config'; + +/** + * How long to wait after the app comes back into focus before concluding that + * the user closed the sign in tab. + */ +const REDIRECT_TIMEOUT_MS = 2000; + +/** + * Generates the URL for the OAuth handler. + */ +export async function _generateHandlerUrl( + auth: AuthInternal, + event: AuthEvent, + provider: AuthProvider +): Promise { + // Get the cordova plugins + const { BuildInfo } = _cordovaWindow(); + debugAssert(event.sessionId, 'AuthEvent did not contain a session ID'); + const sessionDigest = await computeSha256(event.sessionId); + + const additionalParams: Record = {}; + if (_isIOS()) { + // iOS app identifier + additionalParams['ibi'] = BuildInfo.packageName; + } else if (_isAndroid()) { + // Android app identifier + additionalParams['apn'] = BuildInfo.packageName; + } else { + _fail(auth, AuthErrorCode.OPERATION_NOT_SUPPORTED); + } + + // Add the display name if available + if (BuildInfo.displayName) { + additionalParams['appDisplayName'] = BuildInfo.displayName; + } + + // Attached the hashed session ID + additionalParams['sessionId'] = sessionDigest; + return _getRedirectUrl( + auth, + provider, + event.type, + undefined, + event.eventId ?? undefined, + additionalParams + ); +} + +/** + * Validates that this app is valid for this project configuration + */ +export async function _validateOrigin(auth: AuthInternal): Promise { + const { BuildInfo } = _cordovaWindow(); + const request: GetProjectConfigRequest = {}; + if (_isIOS()) { + request.iosBundleId = BuildInfo.packageName; + } else if (_isAndroid()) { + request.androidPackageName = BuildInfo.packageName; + } else { + _fail(auth, AuthErrorCode.OPERATION_NOT_SUPPORTED); + } + + // Will fail automatically if package name is not authorized + await _getProjectConfig(auth, request); +} + +export function _performRedirect( + handlerUrl: string +): Promise { + // Get the cordova plugins + const { cordova } = _cordovaWindow(); + + return new Promise(resolve => { + cordova.plugins.browsertab.isAvailable(browserTabIsAvailable => { + let iabRef: InAppBrowserRef | null = null; + if (browserTabIsAvailable) { + cordova.plugins.browsertab.openUrl(handlerUrl); + } else { + // TODO: Return the inappbrowser ref that's returned from the open call + iabRef = cordova.InAppBrowser.open( + handlerUrl, + _isIOS7Or8() ? '_blank' : '_system', + 'location=yes' + ); + } + resolve(iabRef); + }); + }); +} + +// Thin interface wrapper to avoid circular dependency with ./events module +interface PassiveAuthEventListener { + addPassiveListener(cb: () => void): void; + removePassiveListener(cb: () => void): void; +} + +/** + * This function waits for app activity to be seen before resolving. It does + * this by attaching listeners to various dom events. Once the app is determined + * to be visible, this promise resolves. AFTER that resolution, the listeners + * are detached and any browser tabs left open will be closed. + */ +export async function _waitForAppResume( + auth: AuthInternal, + eventListener: PassiveAuthEventListener, + iabRef: InAppBrowserRef | null +): Promise { + // Get the cordova plugins + const { cordova } = _cordovaWindow(); + + let cleanup = (): void => {}; + try { + await new Promise((resolve, reject) => { + let onCloseTimer: number | null = null; + + // DEFINE ALL THE CALLBACKS ===== + function authEventSeen(): void { + // Auth event was detected. Resolve this promise and close the extra + // window if it's still open. + resolve(); + const closeBrowserTab = cordova.plugins.browsertab?.close; + if (typeof closeBrowserTab === 'function') { + closeBrowserTab(); + } + // Close inappbrowser emebedded webview in iOS7 and 8 case if still + // open. + if (typeof iabRef?.close === 'function') { + iabRef.close(); + } + } + + function resumed(): void { + if (onCloseTimer) { + // This code already ran; do not rerun. + return; + } + + onCloseTimer = window.setTimeout(() => { + // Wait two seeconds after resume then reject. + reject(_createError(auth, AuthErrorCode.REDIRECT_CANCELLED_BY_USER)); + }, REDIRECT_TIMEOUT_MS); + } + + function visibilityChanged(): void { + if (document?.visibilityState === 'visible') { + resumed(); + } + } + + // ATTACH ALL THE LISTENERS ===== + // Listen for the auth event + eventListener.addPassiveListener(authEventSeen); + + // Listen for resume and visibility events + document.addEventListener('resume', resumed, false); + if (_isAndroid()) { + document.addEventListener('visibilitychange', visibilityChanged, false); + } + + // SETUP THE CLEANUP FUNCTION ===== + cleanup = () => { + eventListener.removePassiveListener(authEventSeen); + document.removeEventListener('resume', resumed, false); + document.removeEventListener( + 'visibilitychange', + visibilityChanged, + false + ); + if (onCloseTimer) { + window.clearTimeout(onCloseTimer); + } + }; + }); + } finally { + cleanup(); + } +} + +/** + * Checks the configuration of the Cordova environment. This has no side effect + * if the configuration is correct; otherwise it throws an error with the + * missing plugin. + */ +export function _checkCordovaConfiguration(auth: AuthInternal): void { + const win = _cordovaWindow(); + // Check all dependencies installed. + // https://github.com/nordnet/cordova-universal-links-plugin + // Note that cordova-universal-links-plugin has been abandoned. + // A fork with latest fixes is available at: + // https://www.npmjs.com/package/cordova-universal-links-plugin-fix + _assert( + typeof win?.universalLinks?.subscribe === 'function', + auth, + AuthErrorCode.INVALID_CORDOVA_CONFIGURATION, + { + missingPlugin: 'cordova-universal-links-plugin-fix' + } + ); + + // https://www.npmjs.com/package/cordova-plugin-buildinfo + _assert( + typeof win?.BuildInfo?.packageName !== 'undefined', + auth, + AuthErrorCode.INVALID_CORDOVA_CONFIGURATION, + { + missingPlugin: 'cordova-plugin-buildInfo' + } + ); + + // https://github.com/google/cordova-plugin-browsertab + _assert( + typeof win?.cordova?.plugins?.browsertab?.openUrl === 'function', + auth, + AuthErrorCode.INVALID_CORDOVA_CONFIGURATION, + { + missingPlugin: 'cordova-plugin-browsertab' + } + ); + _assert( + typeof win?.cordova?.plugins?.browsertab?.isAvailable === 'function', + auth, + AuthErrorCode.INVALID_CORDOVA_CONFIGURATION, + { + missingPlugin: 'cordova-plugin-browsertab' + } + ); + + // https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-inappbrowser/ + _assert( + typeof win?.cordova?.InAppBrowser?.open === 'function', + auth, + AuthErrorCode.INVALID_CORDOVA_CONFIGURATION, + { + missingPlugin: 'cordova-plugin-inappbrowser' + } + ); +} + +/** + * Computes the SHA-256 of a session ID. The SubtleCrypto interface is only + * available in "secure" contexts, which covers Cordova (which is served on a file + * protocol). + */ +async function computeSha256(sessionId: string): Promise { + const bytes = stringToArrayBuffer(sessionId); + + // TODO: For IE11 crypto has a different name and this operation comes back + // as an object, not a promise. This is the old proposed standard that + // is used by IE11: + // https://www.w3.org/TR/2013/WD-WebCryptoAPI-20130108/#cryptooperation-interface + const buf = await crypto.subtle.digest('SHA-256', bytes); + const arr = Array.from(new Uint8Array(buf)); + return arr.map(num => num.toString(16).padStart(2, '0')).join(''); +} + +function stringToArrayBuffer(str: string): Uint8Array { + // This function is only meant to deal with an ASCII charset and makes + // certain simplifying assumptions. + debugAssert( + /[0-9a-zA-Z]+/.test(str), + 'Can only convert alpha-numeric strings' + ); + if (typeof TextEncoder !== 'undefined') { + return new TextEncoder().encode(str); + } + + const buff = new ArrayBuffer(str.length); + const view = new Uint8Array(buff); + for (let i = 0; i < str.length; i++) { + view[i] = str.charCodeAt(i); + } + return view; +} diff --git a/packages-exp/auth-exp/src/platform_cordova/strategies/redirect.ts b/packages-exp/auth-exp/src/platform_cordova/strategies/redirect.ts new file mode 100644 index 00000000000..e0c53fe59eb --- /dev/null +++ b/packages-exp/auth-exp/src/platform_cordova/strategies/redirect.ts @@ -0,0 +1,52 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Auth, + AuthProvider, + PopupRedirectResolver, + User +} from '../../model/public_types'; +import { + _linkWithRedirect, + _reauthenticateWithRedirect, + _signInWithRedirect +} from '../../platform_browser/strategies/redirect'; + +export function signInWithRedirect( + auth: Auth, + provider: AuthProvider, + resolver?: PopupRedirectResolver +): Promise { + return _signInWithRedirect(auth, provider, resolver) as Promise; +} + +export function reauthenticateWithRedirect( + user: User, + provider: AuthProvider, + resolver?: PopupRedirectResolver +): Promise { + return _reauthenticateWithRedirect(user, provider, resolver) as Promise; +} + +export function linkWithRedirect( + user: User, + provider: AuthProvider, + resolver?: PopupRedirectResolver +): Promise { + return _linkWithRedirect(user, provider, resolver) as Promise; +} diff --git a/packages-exp/auth-exp/src/platform_react_native/persistence/react_native.test.ts b/packages-exp/auth-exp/src/platform_react_native/persistence/react_native.test.ts index 36a9b62c530..63524fff70f 100644 --- a/packages-exp/auth-exp/src/platform_react_native/persistence/react_native.test.ts +++ b/packages-exp/auth-exp/src/platform_react_native/persistence/react_native.test.ts @@ -17,13 +17,13 @@ import { expect } from 'chai'; -import { ReactNativeAsyncStorage } from '@firebase/auth-types-exp'; +import { ReactNativeAsyncStorage } from '../../model/public_types'; import { testUser, testAuth } from '../../../test/helpers/mock_auth'; import { _getInstance } from '../../core/util/instantiator'; import { PersistedBlob, - Persistence, + PersistenceInternal, PersistenceType } from '../../core/persistence'; import { getReactNativePersistence } from './react_native'; @@ -53,7 +53,7 @@ class FakeAsyncStorage implements ReactNativeAsyncStorage { describe('core/persistence/react', () => { const fakeAsyncStorage = new FakeAsyncStorage(); - const persistence: Persistence = _getInstance( + const persistence: PersistenceInternal = _getInstance( getReactNativePersistence(fakeAsyncStorage) ); diff --git a/packages-exp/auth-exp/src/platform_react_native/persistence/react_native.ts b/packages-exp/auth-exp/src/platform_react_native/persistence/react_native.ts index 8d349a60a23..69be82f630b 100644 --- a/packages-exp/auth-exp/src/platform_react_native/persistence/react_native.ts +++ b/packages-exp/auth-exp/src/platform_react_native/persistence/react_native.ts @@ -15,16 +15,15 @@ * limitations under the License. */ -import * as externs from '@firebase/auth-types-exp'; +import { Persistence, ReactNativeAsyncStorage } from '../../model/public_types'; import { - Persistence, + PersistenceInternal, PersistenceType, PersistenceValue, STORAGE_AVAILABLE_KEY, StorageEventListener } from '../../core/persistence'; -import { debugFail } from '../../core/util/assert'; /** * Returns a persistence class that wraps AsyncStorage imported from @@ -41,9 +40,9 @@ import { debugFail } from '../../core/util/assert'; */ export function getReactNativePersistence( - storage: externs.ReactNativeAsyncStorage -): externs.Persistence { - return class implements Persistence { + storage: ReactNativeAsyncStorage +): Persistence { + return class implements PersistenceInternal { static type: 'LOCAL' = 'LOCAL'; readonly type: PersistenceType = PersistenceType.LOCAL; @@ -74,11 +73,13 @@ export function getReactNativePersistence( } _addListener(_key: string, _listener: StorageEventListener): void { - debugFail('not implemented'); + // Listeners are not supported for React Native storage. + return; } _removeListener(_key: string, _listener: StorageEventListener): void { - debugFail('not implemented'); + // Listeners are not supported for React Native storage. + return; } }; } diff --git a/packages-exp/auth-exp/test/helpers/fake_service_worker.ts b/packages-exp/auth-exp/test/helpers/fake_service_worker.ts new file mode 100644 index 00000000000..9515bfb50ba --- /dev/null +++ b/packages-exp/auth-exp/test/helpers/fake_service_worker.ts @@ -0,0 +1,59 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class FakeServiceWorker { + private readonly listeners: { + [type: string]: Set; + } = {}; + + postMessage(message: any, transfer: MessagePort[]): void { + if (!this.listeners['message']) { + return; + } + this.listeners['message'].forEach(listener => { + const event = new MessageEvent('message', { + data: message, + ports: transfer + }); + if (typeof listener === 'object') { + listener.handleEvent(event); + } else { + listener(event); + } + }); + } + addEventListener( + type: string, + listener: EventListenerOrEventListenerObject, + _options?: boolean | AddEventListenerOptions + ): void { + if (!this.listeners[type]) { + this.listeners[type] = new Set(); + } + this.listeners[type].add(listener); + } + removeEventListener( + type: string, + listener: EventListenerOrEventListenerObject, + _options?: boolean | EventListenerOptions + ): void { + this.listeners[type].delete(listener); + if (this.listeners[type].size === 0) { + delete this.listeners[type]; + } + } +} diff --git a/packages-exp/auth-exp/test/helpers/integration/emulator_rest_helpers.ts b/packages-exp/auth-exp/test/helpers/integration/emulator_rest_helpers.ts new file mode 100644 index 00000000000..5172eb3d6c4 --- /dev/null +++ b/packages-exp/auth-exp/test/helpers/integration/emulator_rest_helpers.ts @@ -0,0 +1,96 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as fetchImpl from 'node-fetch'; +import { getAppConfig, getEmulatorUrl } from './settings'; + +export interface VerificationSession { + code: string; + phoneNumber: string; + sessionInfo: string; +} + +interface VerificationCodesResponse { + verificationCodes: VerificationSession[]; +} + +export interface OobCodeSession { + email: string; + requestType: string; + oobCode: string; + oobLink: string; +} + +interface OobCodesResponse { + oobCodes: OobCodeSession[]; +} + +export async function getPhoneVerificationCodes(): Promise< + Record +> { + const url = buildEmulatorUrlForPath('verificationCodes'); + const response: VerificationCodesResponse = await (await doFetch(url)).json(); + + return response.verificationCodes.reduce((accum, session) => { + accum[session.sessionInfo] = session; + return accum; + }, {} as Record); +} + +export async function getOobCodes(): Promise { + const url = buildEmulatorUrlForPath('oobCodes'); + const response: OobCodesResponse = await (await doFetch(url)).json(); + return response.oobCodes; +} + +export async function resetEmulator(): Promise { + const url = buildEmulatorUrlForPath('accounts'); + await doFetch(url, { method: 'DELETE' }); +} + +export async function createAnonAccount(): Promise<{ + localId: string; + idToken: string; + refreshToken: string; +}> { + const url = `${getEmulatorUrl()}/identitytoolkit.googleapis.com/v1/accounts:signUp?key=fake-key`; + const response = await ( + await doFetch(url, { + method: 'POST', + body: '{}', + headers: { 'Content-Type': 'application/json' } + }) + ).json(); + return response; +} + +function buildEmulatorUrlForPath(endpoint: string): string { + const emulatorBaseUrl = getEmulatorUrl(); + const projectId = getAppConfig().projectId; + return `${emulatorBaseUrl}/emulator/v1/projects/${projectId}/${endpoint}`; +} + +function doFetch(url: string, request?: RequestInit): ReturnType { + if (typeof document !== 'undefined') { + return fetch(url, request); + } + + return (fetchImpl.default( + url, + request as fetchImpl.RequestInit + ) as unknown) as ReturnType; +} diff --git a/packages-exp/auth-exp/test/helpers/integration/helpers.ts b/packages-exp/auth-exp/test/helpers/integration/helpers.ts index dfc05cffe31..8be53a23e52 100644 --- a/packages-exp/auth-exp/test/helpers/integration/helpers.ts +++ b/packages-exp/auth-exp/test/helpers/integration/helpers.ts @@ -15,18 +15,14 @@ * limitations under the License. */ +import * as sinon from 'sinon'; import { deleteApp, initializeApp } from '@firebase/app-exp'; -import { Auth, User } from '@firebase/auth-types-exp'; +import { Auth, User } from '../../../src/model/public_types'; -import { getAuth } from '../../../index'; +import { getAuth, useAuthEmulator } from '../../../'; // Use browser OR node dist entrypoint depending on test env. import { _generateEventId } from '../../../src/core/util/event_id'; - -// eslint-disable-next-line @typescript-eslint/no-require-imports -const PROJECT_CONFIG = require('../../../../../config/project.json'); - -export const PROJECT_ID = PROJECT_CONFIG.projectId; -export const AUTH_DOMAIN = PROJECT_CONFIG.authDomain; -export const API_KEY = PROJECT_CONFIG.apiKey; +import { getAppConfig, getEmulatorUrl } from './settings'; +import { resetEmulator } from './emulator_rest_helpers'; interface IntegrationTestAuth extends Auth { cleanUp(): Promise; @@ -36,16 +32,22 @@ export function randomEmail(): string { return `${_generateEventId('test.email.')}@test.com`; } -export function getTestInstance(): Auth { - const app = initializeApp({ - apiKey: API_KEY, - projectId: PROJECT_ID, - authDomain: AUTH_DOMAIN - }); +export function getTestInstance(requireEmulator = false): Auth { + const app = initializeApp(getAppConfig()); const createdUsers: User[] = []; const auth = getAuth(app) as IntegrationTestAuth; auth.settings.appVerificationDisabledForTesting = true; + const emulatorUrl = getEmulatorUrl(); + + if (emulatorUrl) { + const stub = stubConsoleToSilenceEmulatorWarnings(); + useAuthEmulator(auth, emulatorUrl, { disableWarnings: true }); + stub.restore(); + } else if (requireEmulator) { + /* Emulator wasn't configured but test must use emulator */ + throw new Error('Test may only be run using the Auth Emulator!'); + } auth.onAuthStateChanged(user => { if (user) { @@ -54,12 +56,17 @@ export function getTestInstance(): Auth { }); auth.cleanUp = async () => { - // Clear out any new users that were created in the course of the test - for (const user of createdUsers) { - try { - await user.delete(); - } catch { - // Best effort. Maybe the test already deleted the user ¯\_(ツ)_/¯ + // If we're in an emulated environment, the emulator will clean up for us + if (emulatorUrl) { + await resetEmulator(); + } else { + // Clear out any new users that were created in the course of the test + for (const user of createdUsers) { + try { + await user.delete(); + } catch { + // Best effort. Maybe the test already deleted the user ¯\_(ツ)_/¯ + } } } @@ -73,3 +80,16 @@ export async function cleanUpTestInstance(auth: Auth): Promise { await auth.signOut(); await (auth as IntegrationTestAuth).cleanUp(); } + +function stubConsoleToSilenceEmulatorWarnings(): sinon.SinonStub { + const originalConsoleInfo = console.info.bind(console); + return sinon.stub(console, 'info').callsFake((...args: unknown[]) => { + if ( + !JSON.stringify(args[0]).includes( + 'WARNING: You are using the Auth Emulator' + ) + ) { + originalConsoleInfo(...args); + } + }); +} diff --git a/packages-exp/auth-exp/test/helpers/integration/settings.ts b/packages-exp/auth-exp/test/helpers/integration/settings.ts new file mode 100644 index 00000000000..0cbf665efc6 --- /dev/null +++ b/packages-exp/auth-exp/test/helpers/integration/settings.ts @@ -0,0 +1,64 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FirebaseOptions } from '@firebase/app-exp'; + +// __karma__ is an untyped global +// eslint-disable-next-line @typescript-eslint/no-explicit-any +declare const __karma__: any; + +// eslint-disable-next-line @typescript-eslint/no-require-imports +const PROJECT_CONFIG = require('../../../../../config/project.json'); + +const EMULATOR_HOST = process.env.FIREBASE_AUTH_EMULATOR_HOST; +const EMULATOR_PROJECT_ID = process.env.GCLOUD_PROJECT; + +export const USE_EMULATOR = !!EMULATOR_HOST; + +export const PROJECT_ID = USE_EMULATOR + ? EMULATOR_PROJECT_ID + : PROJECT_CONFIG.projectId; +export const AUTH_DOMAIN = USE_EMULATOR + ? 'emulator-auth-domain' + : PROJECT_CONFIG.authDomain; +export const API_KEY = USE_EMULATOR + ? 'emulator-api-key' + : PROJECT_CONFIG.apiKey; + +export function getAppConfig(): FirebaseOptions { + // Prefer the karma config, then fallback on node process.env stuff + return ( + getKarma()?.config?.authAppConfig || { + apiKey: API_KEY, + projectId: PROJECT_ID, + authDomain: AUTH_DOMAIN + } + ); +} + +export function getEmulatorUrl(): string | null { + // Check karma first, then fallback on node process + const host = + getKarma()?.config?.authEmulatorHost || + (USE_EMULATOR ? EMULATOR_HOST : null); + return host ? `http://${host}` : null; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function getKarma(): any { + return typeof __karma__ !== 'undefined' ? __karma__ : undefined; +} diff --git a/packages-exp/auth-exp/test/helpers/mock_auth.ts b/packages-exp/auth-exp/test/helpers/mock_auth.ts index 9ded87ad4c6..76c8aee1a49 100644 --- a/packages-exp/auth-exp/test/helpers/mock_auth.ts +++ b/packages-exp/auth-exp/test/helpers/mock_auth.ts @@ -15,16 +15,18 @@ * limitations under the License. */ -import { FirebaseApp } from '@firebase/app-types-exp'; -import { PopupRedirectResolver } from '@firebase/auth-types-exp'; +import { FirebaseApp } from '@firebase/app-exp'; +import { PopupRedirectResolver } from '../../src/model/public_types'; +import { debugErrorMap } from '../../src'; import { AuthImpl } from '../../src/core/auth/auth_impl'; import { PersistedBlob } from '../../src/core/persistence'; import { InMemoryPersistence } from '../../src/core/persistence/in_memory'; import { StsTokenManager } from '../../src/core/user/token_manager'; import { UserImpl } from '../../src/core/user/user_impl'; -import { Auth } from '../../src/model/auth'; -import { User } from '../../src/model/user'; +import { AuthInternal } from '../../src/model/auth'; +import { UserInternal } from '../../src/model/user'; +import { ClientPlatform } from '../../src/core/util/version'; export const TEST_HOST = 'localhost'; export const TEST_TOKEN_HOST = 'localhost/token'; @@ -66,8 +68,10 @@ export async function testAuth( apiHost: TEST_HOST, apiScheme: TEST_SCHEME, tokenApiHost: TEST_TOKEN_HOST, + clientPlatform: ClientPlatform.BROWSER, sdkClientVersion: 'testSDK/0.0.0' }) as TestAuth; + auth._updateErrorMap(debugErrorMap); await auth._initializeWithPersistence([persistence], popupRedirectResolver); auth.persistenceLayer = persistence; @@ -76,11 +80,11 @@ export async function testAuth( } export function testUser( - auth: Auth, + auth: AuthInternal, uid: string, email?: string, fakeTokens = false -): User { +): UserInternal { // Create a token manager that's valid off the bat to avoid refresh calls const stsTokenManager = new StsTokenManager(); if (fakeTokens) { @@ -93,7 +97,7 @@ export function testUser( return new UserImpl({ uid, - auth: auth as Auth, + auth: auth as AuthInternal, stsTokenManager, email }); diff --git a/packages-exp/auth-exp/test/helpers/mock_auth_credential.ts b/packages-exp/auth-exp/test/helpers/mock_auth_credential.ts index 9b6ff77419a..f165c628b8b 100644 --- a/packages-exp/auth-exp/test/helpers/mock_auth_credential.ts +++ b/packages-exp/auth-exp/test/helpers/mock_auth_credential.ts @@ -15,11 +15,11 @@ * limitations under the License. */ -import { ProviderId, SignInMethod } from '@firebase/auth-types-exp'; +import { ProviderId, SignInMethod } from '../../src/model/public_types'; import { PhoneOrOauthTokenResponse } from '../../src/api/authentication/mfa'; import { AuthCredential } from '../../src/core/credentials'; -import { Auth } from '../../src/model/auth'; +import { AuthInternal } from '../../src/model/auth'; import { IdTokenResponse } from '../../src/model/id_token'; export class MockAuthCredential implements AuthCredential { @@ -36,18 +36,22 @@ export class MockAuthCredential implements AuthCredential { throw new Error('Method not implemented.'); } - async _getIdTokenResponse(_auth: Auth): Promise { + async _getIdTokenResponse( + _auth: AuthInternal + ): Promise { throw new Error('Method not implemented.'); } async _linkToIdToken( - _auth: Auth, + _auth: AuthInternal, _idToken: string ): Promise { throw new Error('Method not implemented.'); } - async _getReauthenticationResolver(_auth: Auth): Promise { + async _getReauthenticationResolver( + _auth: AuthInternal + ): Promise { throw new Error('Method not implemented.'); } } diff --git a/packages-exp/auth-exp/test/helpers/mock_popup_redirect_resolver.ts b/packages-exp/auth-exp/test/helpers/mock_popup_redirect_resolver.ts index 0d0aeb36fbd..83372936667 100644 --- a/packages-exp/auth-exp/test/helpers/mock_popup_redirect_resolver.ts +++ b/packages-exp/auth-exp/test/helpers/mock_popup_redirect_resolver.ts @@ -15,11 +15,15 @@ * limitations under the License. */ -import { Persistence, PopupRedirectResolver } from '@firebase/auth-types-exp'; +import { + Persistence, + PopupRedirectResolver +} from '../../src/model/public_types'; import { AuthEventManager } from '../../src/core/auth/auth_event_manager'; import { AuthPopup } from '../../src/platform_browser/util/popup'; import { EventManager } from '../../src/model/popup_redirect'; +import { AuthInternal } from '../../src/model/auth'; /** * Generates a PopupRedirectResolver that can be used by the oauth methods. @@ -32,7 +36,9 @@ export function makeMockPopupRedirectResolver( ): PopupRedirectResolver { return class implements PopupRedirectResolver { async _initialize(): Promise { - return eventManager || new AuthEventManager('test-app'); + return ( + eventManager || new AuthEventManager(({} as unknown) as AuthInternal) + ); } async _openPopup(): Promise { @@ -49,5 +55,11 @@ export function makeMockPopupRedirectResolver( } _redirectPersistence?: Persistence; + + _shouldInitProactively = false; + + async _completeRedirectFn(): Promise {} + + async _originValidation(): Promise {} }; } diff --git a/packages-exp/auth-exp/test/helpers/redirect_persistence.ts b/packages-exp/auth-exp/test/helpers/redirect_persistence.ts new file mode 100644 index 00000000000..e6a06d66838 --- /dev/null +++ b/packages-exp/auth-exp/test/helpers/redirect_persistence.ts @@ -0,0 +1,35 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { PersistenceValue } from '../../src/core/persistence'; +import { InMemoryPersistence } from '../../src/core/persistence/in_memory'; + +/** Helper class for handling redirect persistence */ +export class RedirectPersistence extends InMemoryPersistence { + hasPendingRedirect = false; + redirectUser: object | null = null; + + async _get(key: string): Promise { + if (key.includes('pendingRedirect')) { + return this.hasPendingRedirect.toString() as T; + } else if (key.includes('redirectUser')) { + return this.redirectUser as T | null; + } + + throw new Error(`Unexpected redirect persistence key requested: ${key}`); + } +} diff --git a/packages-exp/auth-exp/test/helpers/timeout_stub.ts b/packages-exp/auth-exp/test/helpers/timeout_stub.ts index a3f1671b11b..d38c46cb28c 100644 --- a/packages-exp/auth-exp/test/helpers/timeout_stub.ts +++ b/packages-exp/auth-exp/test/helpers/timeout_stub.ts @@ -17,7 +17,7 @@ import * as sinon from 'sinon'; -interface TimerTripFn { +export interface TimerTripFn { (): void; } diff --git a/packages-exp/auth-exp/test/integration/flows/anonymous.test.ts b/packages-exp/auth-exp/test/integration/flows/anonymous.test.ts index 615cf3f488e..00aaea9d882 100644 --- a/packages-exp/auth-exp/test/integration/flows/anonymous.test.ts +++ b/packages-exp/auth-exp/test/integration/flows/anonymous.test.ts @@ -18,6 +18,7 @@ import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; +// eslint-disable-next-line import/no-extraneous-dependencies import { createUserWithEmailAndPassword, EmailAuthProvider, @@ -25,10 +26,10 @@ import { signInAnonymously, signInWithEmailAndPassword, updateEmail, - updatePassword - // eslint-disable-next-line import/no-extraneous-dependencies + updatePassword, + Auth, + OperationType } from '@firebase/auth-exp'; -import { Auth, OperationType } from '@firebase/auth-types-exp'; import { FirebaseError } from '@firebase/util'; import { diff --git a/packages-exp/auth-exp/test/integration/flows/custom.local.test.ts b/packages-exp/auth-exp/test/integration/flows/custom.local.test.ts new file mode 100644 index 00000000000..949e4f3589d --- /dev/null +++ b/packages-exp/auth-exp/test/integration/flows/custom.local.test.ts @@ -0,0 +1,228 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// eslint-disable-next-line import/no-extraneous-dependencies +import { + Auth, + createUserWithEmailAndPassword, + EmailAuthProvider, + getAdditionalUserInfo, + linkWithCredential, + OperationType, + reload, + signInAnonymously, + signInWithCustomToken, + signInWithEmailAndPassword, + updateEmail, + updatePassword, + updateProfile +} from '@firebase/auth-exp'; +import { FirebaseError } from '@firebase/util'; +import { expect, use } from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import { + cleanUpTestInstance, + getTestInstance, + randomEmail +} from '../../helpers/integration/helpers'; + +use(chaiAsPromised); + +describe('Integration test: custom auth', () => { + let auth: Auth; + let customToken: string; + let uid: string; + + beforeEach(() => { + auth = getTestInstance(/* requireEmulator */ true); + uid = randomEmail(); + customToken = JSON.stringify({ + uid, + claims: { + customClaim: 'some-claim' + } + }); + }); + + afterEach(async () => { + await cleanUpTestInstance(auth); + }); + + it('signs in with custom token', async () => { + const cred = await signInWithCustomToken(auth, customToken); + expect(auth.currentUser).to.eq(cred.user); + expect(cred.operationType).to.eq(OperationType.SIGN_IN); + + const { user } = cred; + expect(user.isAnonymous).to.be.false; + expect(user.uid).to.eq(uid); + expect((await user.getIdTokenResult(false)).claims.customClaim).to.eq( + 'some-claim' + ); + expect(user.providerId).to.eq('firebase'); + const additionalUserInfo = await getAdditionalUserInfo(cred)!; + expect(additionalUserInfo.providerId).to.be.null; + expect(additionalUserInfo.isNewUser).to.be.true; + }); + + it('uid will overwrite existing user, joining accounts', async () => { + const { user: anonUser } = await signInAnonymously(auth); + const customCred = await signInWithCustomToken( + auth, + JSON.stringify({ + uid: anonUser.uid + }) + ); + + expect(auth.currentUser).to.eq(customCred.user); + expect(customCred.user.uid).to.eq(anonUser.uid); + expect(customCred.user.isAnonymous).to.be.false; + }); + + it('allows the user to delete the account', async () => { + let { user } = await signInWithCustomToken(auth, customToken); + await updateProfile(user, { displayName: 'Display Name' }); + expect(user.displayName).to.eq('Display Name'); + + await user.delete(); + await expect(reload(user)).to.be.rejectedWith( + FirebaseError, + 'auth/user-token-expired' + ); + expect(auth.currentUser).to.be.null; + + ({ user } = await signInWithCustomToken(auth, customToken)); + // New user in the system: the display name should be missing + expect(user.displayName).to.be.null; + }); + + it('sign in can be called twice successively', async () => { + const { user: userA } = await signInWithCustomToken(auth, customToken); + const { user: userB } = await signInWithCustomToken(auth, customToken); + expect(userA.uid).to.eq(userB.uid); + }); + + it('allows user to update profile', async () => { + let { user } = await signInWithCustomToken(auth, customToken); + await updateProfile(user, { + displayName: 'Display Name', + photoURL: 'photo-url' + }); + expect(user.displayName).to.eq('Display Name'); + expect(user.photoURL).to.eq('photo-url'); + + await auth.signOut(); + + user = (await signInWithCustomToken(auth, customToken)).user; + expect(user.displayName).to.eq('Display Name'); + expect(user.photoURL).to.eq('photo-url'); + }); + + it('token can be refreshed', async () => { + const { user } = await signInWithCustomToken(auth, customToken); + const origToken = await user.getIdToken(); + await new Promise(resolve => setTimeout(resolve, 1000)); + expect(await user.getIdToken(true)).not.to.eq(origToken); + }); + + it('signing in will not override anonymous user', async () => { + const { user: anonUser } = await signInAnonymously(auth); + const { user: customUser } = await signInWithCustomToken(auth, customToken); + expect(auth.currentUser).to.eql(customUser); + expect(customUser.uid).not.to.eql(anonUser.uid); + }); + + context('email/password interaction', () => { + let email: string; + let customToken: string; + + beforeEach(() => { + email = randomEmail(); + customToken = JSON.stringify({ + uid: email + }); + }); + + it('custom / email-password accounts remain independent', async () => { + let customCred = await signInWithCustomToken(auth, customToken); + const emailCred = await createUserWithEmailAndPassword( + auth, + email, + 'password' + ); + expect(emailCred.user.uid).not.to.eql(customCred.user.uid); + + await auth.signOut(); + customCred = await signInWithCustomToken(auth, customToken); + const emailSignIn = await signInWithEmailAndPassword( + auth, + email, + 'password' + ); + expect(emailCred.user.uid).to.eql(emailSignIn.user.uid); + expect(emailSignIn.user.uid).not.to.eql(customCred.user.uid); + }); + + it('account can have email / password attached', async () => { + const { user: customUser } = await signInWithCustomToken( + auth, + customToken + ); + await updateEmail(customUser, email); + await updatePassword(customUser, 'password'); + + await auth.signOut(); + + const { user: emailPassUser } = await signInWithEmailAndPassword( + auth, + email, + 'password' + ); + expect(emailPassUser.uid).to.eq(customUser.uid); + }); + + it('account can be linked using email and password', async () => { + const { user: customUser } = await signInWithCustomToken( + auth, + customToken + ); + const cred = EmailAuthProvider.credential(email, 'password'); + await linkWithCredential(customUser, cred); + await auth.signOut(); + + const { user: emailPassUser } = await signInWithEmailAndPassword( + auth, + email, + 'password' + ); + expect(emailPassUser.uid).to.eq(customUser.uid); + }); + + it('account cannot be linked with existing email/password', async () => { + await createUserWithEmailAndPassword(auth, email, 'password'); + const { user: customUser } = await signInWithCustomToken( + auth, + customToken + ); + const cred = EmailAuthProvider.credential(email, 'password'); + await expect(linkWithCredential(customUser, cred)).to.be.rejectedWith( + FirebaseError, + 'auth/email-already-in-use' + ); + }); + }); +}); diff --git a/packages-exp/auth-exp/test/integration/flows/email.test.ts b/packages-exp/auth-exp/test/integration/flows/email.test.ts index 574caaae96b..88bee795d6b 100644 --- a/packages-exp/auth-exp/test/integration/flows/email.test.ts +++ b/packages-exp/auth-exp/test/integration/flows/email.test.ts @@ -18,16 +18,19 @@ import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; +// eslint-disable-next-line import/no-extraneous-dependencies import { createUserWithEmailAndPassword, EmailAuthProvider, reload, signInWithCredential, signInWithEmailAndPassword, - updateProfile - // eslint-disable-next-line import/no-extraneous-dependencies + updateProfile, + Auth, + OperationType, + UserCredential, + getAdditionalUserInfo } from '@firebase/auth-exp'; -import { Auth, OperationType, UserCredential } from '@firebase/auth-types-exp'; import { FirebaseError } from '@firebase/util'; import { @@ -62,6 +65,13 @@ describe('Integration test: email/password auth', () => { expect(user.uid).to.be.a('string'); expect(user.email).to.eq(email); expect(user.emailVerified).to.be.false; + expect(user.providerData.length).to.eq(1); + expect(user.providerData[0].providerId).to.eq('password'); + expect(user.providerData[0].email).to.eq(email); + + const additionalUserInfo = getAdditionalUserInfo(userCred)!; + expect(additionalUserInfo.isNewUser).to.be.true; + expect(additionalUserInfo.providerId).to.eq('password'); }); it('errors when createUser called twice', async () => { @@ -93,6 +103,9 @@ describe('Integration test: email/password auth', () => { expect(signInCred.operationType).to.eq(OperationType.SIGN_IN); expect(signInCred.user.uid).to.eq(signUpCred.user.uid); + const additionalUserInfo = getAdditionalUserInfo(signInCred)!; + expect(additionalUserInfo.isNewUser).to.be.false; + expect(additionalUserInfo.providerId).to.eq('password'); }); it('allows the user to sign in with signInWithCredential', async () => { @@ -102,6 +115,9 @@ describe('Integration test: email/password auth', () => { expect(signInCred.operationType).to.eq(OperationType.SIGN_IN); expect(signInCred.user.uid).to.eq(signUpCred.user.uid); + const additionalUserInfo = getAdditionalUserInfo(signInCred)!; + expect(additionalUserInfo.isNewUser).to.be.false; + expect(additionalUserInfo.providerId).to.eq('password'); }); it('allows the user to update profile', async () => { diff --git a/packages-exp/auth-exp/test/integration/flows/idp.local.test.ts b/packages-exp/auth-exp/test/integration/flows/idp.local.test.ts new file mode 100644 index 00000000000..920b2030e35 --- /dev/null +++ b/packages-exp/auth-exp/test/integration/flows/idp.local.test.ts @@ -0,0 +1,288 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// eslint-disable-next-line import/no-extraneous-dependencies +import { + Auth, + createUserWithEmailAndPassword, + FacebookAuthProvider, + getAdditionalUserInfo, + GithubAuthProvider, + GoogleAuthProvider, + linkWithCredential, + OperationType, + ProviderId, + signInWithCredential, + signInWithEmailAndPassword, + unlink, + updateEmail, + updatePassword, + updateProfile +} from '@firebase/auth-exp'; +import { FirebaseError } from '@firebase/util'; +import { expect, use } from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import { + cleanUpTestInstance, + getTestInstance, + randomEmail +} from '../../helpers/integration/helpers'; + +use(chaiAsPromised); + +// These tests handle OAuth sign in, but they're totally headless (they don't +// use the popup/redirect flows). For testing of the popup/redirect flows, look +// under the test/integration/webdriver directory. + +describe('Integration test: headless IdP', () => { + let auth: Auth; + let oauthIdToken: string; + let email: string; + + beforeEach(() => { + auth = getTestInstance(/* requireEmulator */ true); + email = randomEmail(); + oauthIdToken = JSON.stringify({ + email, + 'email_verified': true, + sub: `oauthidp--${email}--oauthidp` + }); + }); + + afterEach(async () => { + await cleanUpTestInstance(auth); + }); + + it('signs in with an OAuth token', async () => { + const cred = await signInWithCredential( + auth, + GoogleAuthProvider.credential(oauthIdToken) + ); + expect(auth.currentUser).to.eq(cred.user); + expect(cred.operationType).to.eq(OperationType.SIGN_IN); + + // Make sure the user is setup correctly + const { user } = cred; + expect(user.isAnonymous).to.be.false; + expect(user.emailVerified).to.be.true; + expect(user.providerData.length).to.eq(1); + expect(user.providerData[0].providerId).to.eq('google.com'); + expect(user.providerData[0].email).to.eq(email); + + // Make sure the additional user info is good + const additionalUserInfo = getAdditionalUserInfo(cred)!; + expect(additionalUserInfo.isNewUser).to.be.true; + expect(additionalUserInfo.providerId).to.eq('google.com'); + }); + + it('allows the user to update profile', async () => { + const credential = GithubAuthProvider.credential(oauthIdToken); + const { user } = await signInWithCredential(auth, credential); + + await updateProfile(user, { + displayName: 'David Copperfield', + photoURL: 'http://photo.test/david.png' + }); + + // Check everything first + expect(user.displayName).to.eq('David Copperfield'); + expect(user.photoURL).to.eq('http://photo.test/david.png'); + + await auth.signOut(); + + // Sign in again and double check; look at current user this time + await signInWithCredential(auth, credential); + expect(auth.currentUser!.displayName).to.eq('David Copperfield'); + expect(auth.currentUser!.photoURL).to.eq('http://photo.test/david.png'); + }); + + it('allows the user to change the email', async () => { + const credential = FacebookAuthProvider.credential(oauthIdToken); + const { user } = await signInWithCredential(auth, credential); + + expect(user.email).to.eq(email); + expect(user.emailVerified).to.be.true; + + const newEmail = randomEmail(); + await updateEmail(user, newEmail); + + // Check everything first + expect(user.email).to.eq(newEmail); + expect(user.emailVerified).to.be.false; + + await auth.signOut(); + + // Sign in again + await signInWithCredential(auth, credential); + expect(auth.currentUser!.email).to.eq(newEmail); + }); + + it('allows the user to set a password', async () => { + const credential = GoogleAuthProvider.credential(oauthIdToken); + const { user } = await signInWithCredential(auth, credential); + + expect(user.providerData.length).to.eq(1); + expect(user.providerData[0].providerId).to.eq('google.com'); + + // Set the password and check provider data + await updatePassword(user, 'password'); + expect(user.providerData.length).to.eq(2); + expect(user.providerData.map(p => p.providerId)).to.contain.members([ + 'google.com', + 'password' + ]); + + // Sign out and sign in again + await auth.signOut(); + await signInWithEmailAndPassword(auth, email, 'password'); + expect(auth.currentUser!.providerData.length).to.eq(2); + expect( + auth.currentUser!.providerData.map(p => p.providerId) + ).to.contain.members(['google.com', 'password']); + + // Update email, then sign out/sign in again + const newEmail = randomEmail(); + await updateEmail(auth.currentUser!, newEmail); + await auth.signOut(); + await signInWithEmailAndPassword(auth, newEmail, 'password'); + expect(auth.currentUser!.providerData.length).to.eq(2); + expect( + auth.currentUser!.providerData.map(p => p.providerId) + ).to.contain.members(['google.com', 'password']); + }); + + it('can link with multiple idps', async () => { + const googleEmail = randomEmail(); + const facebookEmail = randomEmail(); + + const googleCredential = GoogleAuthProvider.credential( + JSON.stringify({ + sub: googleEmail, + email: googleEmail, + 'email_verified': true + }) + ); + + const facebookCredential = FacebookAuthProvider.credential( + JSON.stringify({ + sub: facebookEmail, + email: facebookEmail + }) + ); + + // Link and then test everything + const { user } = await signInWithCredential(auth, facebookCredential); + await linkWithCredential(user, googleCredential); + expect(user.email).to.eq(facebookEmail); + expect(user.emailVerified).to.be.false; + expect(user.providerData.length).to.eq(2); + expect( + user.providerData.find(p => p.providerId === 'google.com')!.email + ).to.eq(googleEmail); + expect( + user.providerData.find(p => p.providerId === 'facebook.com')!.email + ).to.eq(facebookEmail); + + // Unlink Google and check everything again + await unlink(user, ProviderId.GOOGLE); + expect(user.email).to.eq(facebookEmail); + expect(user.emailVerified).to.be.false; + expect(user.providerData.length).to.eq(1); + expect(user.providerData[0].email).to.eq(facebookEmail); + expect(user.providerData[0].providerId).to.eq('facebook.com'); + }); + + it('IdP account takes over unverified email', async () => { + const credential = GoogleAuthProvider.credential(oauthIdToken); + const { user: emailUser } = await createUserWithEmailAndPassword( + auth, + email, + 'password' + ); + + // Check early state + expect(emailUser.emailVerified).to.be.false; + + // Sign in with the credential and expect auto-linking + const { user: googleUser } = await signInWithCredential(auth, credential); + expect(googleUser.uid).to.eq(emailUser.uid); + expect(googleUser.emailVerified).to.be.true; + expect(auth.currentUser).to.eq(googleUser); + console.log(googleUser.providerData); + expect(googleUser.providerData.length).to.eq(1); + expect(auth.currentUser!.providerData[0].providerId).to.eq('google.com'); + + // Signing in with password no longer works + await expect( + signInWithEmailAndPassword(auth, email, 'password') + ).to.be.rejectedWith(FirebaseError, 'auth/wrong-password'); + }); + + it('IdP accounts automatically link with verified emails', async () => { + const googleCredential = GoogleAuthProvider.credential( + JSON.stringify({ + sub: email, + email, + 'email_verified': true + }) + ); + + const githubCredential = GithubAuthProvider.credential( + JSON.stringify({ + sub: email, + email, + 'email_verified': true + }) + ); + + // First sign in with Google + const { user: initialUser } = await signInWithCredential( + auth, + googleCredential + ); + expect(initialUser.providerData.length).to.eq(1); + expect(initialUser.providerData[0].providerId).to.eq('google.com'); + + await auth.signOut(); + + // Now with GitHub + const { user: githubUser } = await signInWithCredential( + auth, + githubCredential + ); + expect(githubUser.uid).to.eq(initialUser.uid); + expect(githubUser.providerData.length).to.eq(2); + expect(githubUser.providerData.map(p => p.providerId)).to.have.members([ + 'google.com', + 'github.com' + ]); + + await auth.signOut(); + + // Sign in once again with the initial credential + const { user: googleUser } = await signInWithCredential( + auth, + googleCredential + ); + expect(googleUser.uid).to.eq(initialUser.uid); + expect(googleUser.providerData.length).to.eq(2); + expect(googleUser.providerData.map(p => p.providerId)).to.have.members([ + 'google.com', + 'github.com' + ]); + }); +}); diff --git a/packages-exp/auth-exp/test/integration/flows/oob.local.test.ts b/packages-exp/auth-exp/test/integration/flows/oob.local.test.ts new file mode 100644 index 00000000000..3d3347f9d84 --- /dev/null +++ b/packages-exp/auth-exp/test/integration/flows/oob.local.test.ts @@ -0,0 +1,358 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// eslint-disable-next-line import/no-extraneous-dependencies +import { + ActionCodeSettings, + applyActionCode, + Auth, + confirmPasswordReset, + createUserWithEmailAndPassword, + deleteUser, + EmailAuthProvider, + fetchSignInMethodsForEmail, + linkWithCredential, + OperationType, + reauthenticateWithCredential, + sendEmailVerification, + sendPasswordResetEmail, + sendSignInLinkToEmail, + signInAnonymously, + SignInMethod, + signInWithCredential, + signInWithCustomToken, + signInWithEmailAndPassword, + signInWithEmailLink, + updatePassword, + verifyBeforeUpdateEmail, + verifyPasswordResetCode +} from '@firebase/auth-exp'; +import { FirebaseError } from '@firebase/util'; +import { expect, use } from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import { + getOobCodes, + OobCodeSession +} from '../../helpers/integration/emulator_rest_helpers'; +import { + cleanUpTestInstance, + getTestInstance, + randomEmail +} from '../../helpers/integration/helpers'; + +use(chaiAsPromised); + +declare const xit: typeof it; + +const BASE_SETTINGS: ActionCodeSettings = { + url: 'http://localhost/action_code_return', + handleCodeInApp: true +}; + +describe('Integration test: oob codes', () => { + let auth: Auth; + let email: string; + + beforeEach(() => { + auth = getTestInstance(/* requireEmulator */ true); + email = randomEmail(); + }); + + afterEach(async () => { + await cleanUpTestInstance(auth); + }); + + async function code(toEmail: string): Promise { + const codes = await getOobCodes(); + return codes.reverse().find(({ email }) => email === toEmail)!; + } + + context('flows beginning with sendSignInLinkToEmail', () => { + let oobSession: OobCodeSession; + + beforeEach(async () => { + oobSession = await sendEmailLink(); + }); + + async function sendEmailLink(toEmail = email): Promise { + await sendSignInLinkToEmail(auth, toEmail, BASE_SETTINGS); + + // An email has been sent to the user. Normally you'd detect this state + // when the app redirects back. We will ask the emulator for the results + // and force the state instead. + return code(toEmail); + } + + it('allows user to sign in', async () => { + const { user, operationType } = await signInWithEmailLink( + auth, + email, + oobSession.oobLink + ); + + expect(operationType).to.eq(OperationType.SIGN_IN); + expect(user).to.eq(auth.currentUser); + expect(user.uid).to.be.a('string'); + expect(user.email).to.eq(email); + expect(user.emailVerified).to.be.true; + expect(user.isAnonymous).to.be.false; + }); + + it('sign in works with an email credential', async () => { + const cred = EmailAuthProvider.credentialWithLink( + email, + oobSession.oobLink + ); + const { user, operationType } = await signInWithCredential(auth, cred); + + expect(operationType).to.eq(OperationType.SIGN_IN); + expect(user).to.eq(auth.currentUser); + expect(user.uid).to.be.a('string'); + expect(user.email).to.eq(email); + expect(user.emailVerified).to.be.true; + expect(user.isAnonymous).to.be.false; + }); + + it('reauthenticate works with email credential', async () => { + let cred = EmailAuthProvider.credentialWithLink( + email, + oobSession.oobLink + ); + const { user: oldUser } = await signInWithCredential(auth, cred); + + const reauthSession = await sendEmailLink(); + cred = EmailAuthProvider.credentialWithLink(email, reauthSession.oobLink); + const { user: newUser, operationType } = + await reauthenticateWithCredential(oldUser, cred); + + expect(newUser.uid).to.eq(oldUser.uid); + expect(operationType).to.eq(OperationType.REAUTHENTICATE); + expect(auth.currentUser).to.eq(newUser); + }); + + it('reauthenticate throws with different email', async () => { + let cred = EmailAuthProvider.credentialWithLink( + email, + oobSession.oobLink + ); + const { user: oldUser } = await signInWithCredential(auth, cred); + + const newEmail = randomEmail(); + const reauthSession = await sendEmailLink(newEmail); + cred = EmailAuthProvider.credentialWithLink( + newEmail, + reauthSession.oobLink + ); + await expect( + reauthenticateWithCredential(oldUser, cred) + ).to.be.rejectedWith(FirebaseError, 'auth/user-mismatch'); + expect(auth.currentUser).to.eq(oldUser); + }); + + it('reauthenticate throws if user is deleted', async () => { + let cred = EmailAuthProvider.credentialWithLink( + email, + oobSession.oobLink + ); + const { user: oldUser } = await signInWithCredential(auth, cred); + + await deleteUser(oldUser); + const reauthSession = await sendEmailLink(email); + cred = EmailAuthProvider.credentialWithLink(email, reauthSession.oobLink); + await expect( + reauthenticateWithCredential(oldUser, cred) + ).to.be.rejectedWith(FirebaseError, 'auth/user-mismatch'); + expect(auth.currentUser).to.be.null; + }); + + it('other accounts can be linked', async () => { + const cred = EmailAuthProvider.credentialWithLink( + email, + oobSession.oobLink + ); + const { user: original } = await signInAnonymously(auth); + + expect(original.isAnonymous).to.be.true; + const { user: linked, operationType } = await linkWithCredential( + original, + cred + ); + + expect(operationType).to.eq(OperationType.LINK); + expect(linked.uid).to.eq(original.uid); + expect(linked.isAnonymous).to.be.false; + expect(auth.currentUser).to.eq(linked); + expect(linked.email).to.eq(email); + expect(linked.emailVerified).to.be.true; + }); + + it('can be linked to a custom token', async () => { + const { user: original } = await signInWithCustomToken( + auth, + JSON.stringify({ + uid: 'custom-uid' + }) + ); + + const cred = EmailAuthProvider.credentialWithLink( + email, + oobSession.oobLink + ); + const { user: linked } = await linkWithCredential(original, cred); + + expect(linked.uid).to.eq(original.uid); + expect(auth.currentUser).to.eq(linked); + expect(linked.email).to.eq(email); + expect(linked.emailVerified).to.be.true; + }); + + it('cannot link if original account is deleted', async () => { + const cred = EmailAuthProvider.credentialWithLink( + email, + oobSession.oobLink + ); + const { user } = await signInAnonymously(auth); + + expect(user.isAnonymous).to.be.true; + await deleteUser(user); + await expect(linkWithCredential(user, cred)).to.be.rejectedWith( + FirebaseError, + 'auth/user-token-expired' + ); + }); + + it('code can only be used once', async () => { + const link = oobSession.oobLink; + await signInWithEmailLink(auth, email, link); + await expect(signInWithEmailLink(auth, email, link)).to.be.rejectedWith( + FirebaseError, + 'auth/invalid-action-code' + ); + }); + + it('fetchSignInMethodsForEmail returns the correct values', async () => { + const { user } = await signInWithEmailLink( + auth, + email, + oobSession.oobLink + ); + expect(await fetchSignInMethodsForEmail(auth, email)).to.eql([ + SignInMethod.EMAIL_LINK + ]); + + await updatePassword(user, 'password'); + const updatedMethods = await fetchSignInMethodsForEmail(auth, email); + expect(updatedMethods).to.have.length(2); + expect(updatedMethods).to.include(SignInMethod.EMAIL_LINK); + expect(updatedMethods).to.include(SignInMethod.EMAIL_PASSWORD); + }); + + it('throws an error if the wrong code is provided', async () => { + const otherSession = await sendEmailLink(randomEmail()); + await expect( + signInWithEmailLink(auth, email, otherSession.oobLink) + ).to.be.rejectedWith(FirebaseError, 'auth/invalid-email'); + }); + }); + + it('can be used to verify email', async () => { + // Create an unverified user + const { user } = await createUserWithEmailAndPassword( + auth, + email, + 'password' + ); + expect(user.emailVerified).to.be.false; + expect(await fetchSignInMethodsForEmail(auth, email)).to.eql([ + SignInMethod.EMAIL_PASSWORD + ]); + await sendEmailVerification(user); + + // Apply the email verification code + await applyActionCode(auth, (await code(email)).oobCode); + await user.reload(); + expect(user.emailVerified).to.be.true; + }); + + it('can be used to initiate password reset', async () => { + const { user: original } = await createUserWithEmailAndPassword( + auth, + email, + 'password' + ); + await sendEmailVerification(original); // Can only reset verified user emails + await applyActionCode(auth, (await code(email)).oobCode); + + // Send and confirm the password reset + await sendPasswordResetEmail(auth, email); + const oobCode = (await code(email)).oobCode; + expect(await verifyPasswordResetCode(auth, oobCode)).to.eq(email); + await confirmPasswordReset(auth, oobCode, 'new-password'); + + // Make sure the new password works and the old one doesn't + const { user } = await signInWithEmailAndPassword( + auth, + email, + 'new-password' + ); + expect(user.uid).to.eq(original.uid); + expect(user.emailVerified).to.be.true; + expect(await fetchSignInMethodsForEmail(auth, email)).to.eql([ + SignInMethod.EMAIL_PASSWORD + ]); + + await expect( + signInWithEmailAndPassword(auth, email, 'password') + ).to.be.rejectedWith(FirebaseError, 'auth/wrong-password'); + }); + + // Test is ignored for now as the emulator does not currently support the + // verify-and-change-email operation. + xit('verifyBeforeUpdateEmail waits until flow completes', async () => { + const updatedEmail = randomEmail(); + + // Create an initial user with the basic email + await sendSignInLinkToEmail(auth, email, BASE_SETTINGS); + const { user } = await signInWithEmailLink( + auth, + email, + ( + await code(email) + ).oobLink + ); + await verifyBeforeUpdateEmail(user, updatedEmail, BASE_SETTINGS); + expect(user.email).to.eq(email); + + // Finish the update email flow + await applyActionCode(auth, (await code(updatedEmail)).oobCode); + await user.reload(); + expect(user.emailVerified).to.be.true; + expect(user.email).to.eq(updatedEmail); + expect(auth.currentUser).to.eq(user); + + // Old email doesn't work but new one does + await expect( + signInWithEmailAndPassword(auth, email, 'password') + ).to.be.rejectedWith(FirebaseError, 'auth/alskdjf'); + const { user: newSignIn } = await signInWithEmailAndPassword( + auth, + updatedEmail, + 'password' + ); + expect(newSignIn.uid).to.eq(user.uid); + }); +}); diff --git a/packages-exp/auth-exp/test/integration/flows/phone.test.ts b/packages-exp/auth-exp/test/integration/flows/phone.test.ts index 297b4c368fc..f5095fda81c 100644 --- a/packages-exp/auth-exp/test/integration/flows/phone.test.ts +++ b/packages-exp/auth-exp/test/integration/flows/phone.test.ts @@ -18,6 +18,7 @@ import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; +// eslint-disable-next-line import/no-extraneous-dependencies import { linkWithPhoneNumber, PhoneAuthProvider, @@ -26,21 +27,21 @@ import { signInAnonymously, signInWithPhoneNumber, unlink, - updatePhoneNumber - // eslint-disable-next-line import/no-extraneous-dependencies -} from '@firebase/auth-exp'; -import { + updatePhoneNumber, Auth, OperationType, ProviderId, - UserCredential -} from '@firebase/auth-types-exp'; + UserCredential, + signInWithCredential, + ConfirmationResult +} from '@firebase/auth-exp'; import { FirebaseError } from '@firebase/util'; import { cleanUpTestInstance, getTestInstance } from '../../helpers/integration/helpers'; +import { getPhoneVerificationCodes } from '../../helpers/integration/emulator_rest_helpers'; use(chaiAsPromised); @@ -82,9 +83,32 @@ describe('Integration test: phone auth', () => { document.body.removeChild(fakeRecaptchaContainer); }); + function resetVerifier(): void { + verifier.clear(); + verifier = new RecaptchaVerifier( + fakeRecaptchaContainer, + undefined as any, + auth + ); + } + + /** If in the emulator, search for the code in the API */ + async function code( + crOrId: ConfirmationResult | string, + fallback: string + ): Promise { + if (auth.emulatorConfig) { + const codes = await getPhoneVerificationCodes(); + const vid = typeof crOrId === 'string' ? crOrId : crOrId.verificationId; + return codes[vid].code; + } + + return fallback; + } + it('allows user to sign up', async () => { const cr = await signInWithPhoneNumber(auth, PHONE_A.phoneNumber, verifier); - const userCred = await cr.confirm(PHONE_A.code); + const userCred = await cr.confirm(await code(cr, PHONE_A.code)); expect(auth.currentUser).to.eq(userCred.user); expect(userCred.operationType).to.eq(OperationType.SIGN_IN); @@ -100,36 +124,60 @@ describe('Integration test: phone auth', () => { const { uid: anonId } = user; const cr = await linkWithPhoneNumber(user, PHONE_A.phoneNumber, verifier); - const linkResult = await cr.confirm(PHONE_A.code); + const linkResult = await cr.confirm(await code(cr, PHONE_A.code)); expect(linkResult.operationType).to.eq(OperationType.LINK); expect(linkResult.user.uid).to.eq(user.uid); expect(linkResult.user.phoneNumber).to.eq(PHONE_A.phoneNumber); await unlink(user, ProviderId.PHONE); expect(auth.currentUser!.uid).to.eq(anonId); - expect(auth.currentUser!.isAnonymous).to.be.true; + // Is anonymous stays false even after unlinking + expect(auth.currentUser!.isAnonymous).to.be.false; expect(auth.currentUser!.phoneNumber).to.be.null; }); + it('anonymous users can upgrade using phone number', async () => { + const { user } = await signInAnonymously(auth); + const { uid: anonId } = user; + + const provider = new PhoneAuthProvider(auth); + const verificationId = await provider.verifyPhoneNumber( + PHONE_B.phoneNumber, + verifier + ); + + await updatePhoneNumber( + user, + PhoneAuthProvider.credential( + verificationId, + await code(verificationId, PHONE_B.code) + ) + ); + expect(user.phoneNumber).to.eq(PHONE_B.phoneNumber); + + await auth.signOut(); + resetVerifier(); + + const cr = await signInWithPhoneNumber(auth, PHONE_B.phoneNumber, verifier); + const { user: secondSignIn } = await cr.confirm( + await code(cr, PHONE_B.code) + ); + expect(secondSignIn.uid).to.eq(anonId); + expect(secondSignIn.isAnonymous).to.be.false; + expect(secondSignIn.providerData[0].phoneNumber).to.eq(PHONE_B.phoneNumber); + expect(secondSignIn.providerData[0].providerId).to.eq('phone'); + }); + context('with already-created user', () => { let signUpCred: UserCredential; - function resetVerifier(): void { - verifier.clear(); - verifier = new RecaptchaVerifier( - fakeRecaptchaContainer, - undefined as any, - auth - ); - } - beforeEach(async () => { const cr = await signInWithPhoneNumber( auth, PHONE_A.phoneNumber, verifier ); - signUpCred = await cr.confirm(PHONE_A.code); + signUpCred = await cr.confirm(await code(cr, PHONE_A.code)); resetVerifier(); await auth.signOut(); }); @@ -140,14 +188,14 @@ describe('Integration test: phone auth', () => { PHONE_A.phoneNumber, verifier ); - const signInCred = await cr.confirm(PHONE_A.code); + const signInCred = await cr.confirm(await code(cr, PHONE_A.code)); expect(signInCred.user.uid).to.eq(signUpCred.user.uid); }); it('allows the user to update their phone number', async () => { let cr = await signInWithPhoneNumber(auth, PHONE_A.phoneNumber, verifier); - const { user } = await cr.confirm(PHONE_A.code); + const { user } = await cr.confirm(await code(cr, PHONE_A.code)); resetVerifier(); @@ -159,7 +207,10 @@ describe('Integration test: phone auth', () => { await updatePhoneNumber( user, - PhoneAuthProvider.credential(verificationId, PHONE_B.code) + PhoneAuthProvider.credential( + verificationId, + await code(verificationId, PHONE_B.code) + ) ); expect(user.phoneNumber).to.eq(PHONE_B.phoneNumber); @@ -167,30 +218,37 @@ describe('Integration test: phone auth', () => { resetVerifier(); cr = await signInWithPhoneNumber(auth, PHONE_B.phoneNumber, verifier); - const { user: secondSignIn } = await cr.confirm(PHONE_B.code); + const { user: secondSignIn } = await cr.confirm( + await code(cr, PHONE_B.code) + ); expect(secondSignIn.uid).to.eq(user.uid); }); it('allows the user to reauthenticate with phone number', async () => { let cr = await signInWithPhoneNumber(auth, PHONE_A.phoneNumber, verifier); - const { user } = await cr.confirm(PHONE_A.code); + const { user } = await cr.confirm(await code(cr, PHONE_A.code)); const oldToken = await user.getIdToken(); resetVerifier(); + // Wait a bit to ensure the sign in time is different in the token + await new Promise((resolve): void => { + setTimeout(resolve, 1500); + }); + cr = await reauthenticateWithPhoneNumber( user, PHONE_A.phoneNumber, verifier ); - await cr.confirm(PHONE_A.code); + await cr.confirm(await code(cr, PHONE_A.code)); expect(await user.getIdToken()).not.to.eq(oldToken); }); it('prevents reauthentication with wrong phone number', async () => { let cr = await signInWithPhoneNumber(auth, PHONE_A.phoneNumber, verifier); - const { user } = await cr.confirm(PHONE_A.code); + const { user } = await cr.confirm(await code(cr, PHONE_A.code)); resetVerifier(); @@ -199,7 +257,7 @@ describe('Integration test: phone auth', () => { PHONE_B.phoneNumber, verifier ); - await expect(cr.confirm(PHONE_B.code)).to.be.rejectedWith( + await expect(cr.confirm(await code(cr, PHONE_B.code))).to.be.rejectedWith( FirebaseError, 'auth/user-mismatch' ); @@ -208,8 +266,44 @@ describe('Integration test: phone auth', () => { // reauthenticateWithPhoneNumber does not trigger a state change resetVerifier(); cr = await signInWithPhoneNumber(auth, PHONE_B.phoneNumber, verifier); - const { user: otherUser } = await cr.confirm(PHONE_B.code); + const { user: otherUser } = await cr.confirm( + await code(cr, PHONE_B.code) + ); await otherUser.delete(); }); + + it('handles account exists with credential errors', async () => { + // PHONE_A is already a user. Try to link it with an email account + const { user } = await signInAnonymously(auth); + expect(user.uid).not.to.eq(signUpCred.user.uid); + + const provider = new PhoneAuthProvider(auth); + const verificationId = await provider.verifyPhoneNumber( + PHONE_A.phoneNumber, + verifier + ); + let error: FirebaseError | null = null; + + try { + await updatePhoneNumber( + user, + PhoneAuthProvider.credential( + verificationId, + await code(verificationId, PHONE_A.code) + ) + ); + } catch (e) { + error = e; + } + + expect(error!.customData!.phoneNumber).to.eq(PHONE_A.phoneNumber); + expect(error!.code).to.eq( + 'auth/account-exists-with-different-credential' + ); + const credential = PhoneAuthProvider.credentialFromError(error!); + expect(credential).not.be.null; + const errorUserCred = await signInWithCredential(auth, credential!); + expect(errorUserCred.user.uid).to.eq(signUpCred.user.uid); + }); }); }); diff --git a/packages-exp/auth-exp/test/integration/webdriver/anonymous.test.ts b/packages-exp/auth-exp/test/integration/webdriver/anonymous.test.ts new file mode 100644 index 00000000000..8d30329dd43 --- /dev/null +++ b/packages-exp/auth-exp/test/integration/webdriver/anonymous.test.ts @@ -0,0 +1,70 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// eslint-disable-next-line import/no-extraneous-dependencies +import { OperationType, UserCredential } from '@firebase/auth-exp'; +import { expect } from 'chai'; +import { AnonFunction } from './util/functions'; +import { browserDescribe } from './util/test_runner'; + +/** + * Simple smoke test to demonstrate webdriver testing and serve as a template + * for future tests; anonymous is largely covered by the headless tests in + * test/integration/flows/anonymous.test.ts + */ +browserDescribe('WebDriver anonymous auth test', driver => { + it('basic sign in is possible', async () => { + const cred: UserCredential = await driver.call( + AnonFunction.SIGN_IN_ANONYMOUSLY + ); + expect(cred).not.to.be.null; + expect(cred.user.isAnonymous).to.be.true; + expect(cred.operationType).to.eq(OperationType.SIGN_IN); + expect(await driver.getUserSnapshot()).to.eql(cred.user); + }); + + it('same user persists after refresh and sign in', async () => { + const { user: before }: UserCredential = await driver.call( + AnonFunction.SIGN_IN_ANONYMOUSLY + ); + await driver.refresh(); + + // First, is the user signed in from persistence? + expect(await driver.getUserSnapshot()).to.eql(before); + + // Then, sign in again and check + const { user: after }: UserCredential = await driver.call( + AnonFunction.SIGN_IN_ANONYMOUSLY + ); + expect(after.uid).to.eq(before.uid); + }); + + it('user persists after refresh and sign in (no init wait)', async () => { + const { user: before }: UserCredential = await driver.call( + AnonFunction.SIGN_IN_ANONYMOUSLY + ); + await driver.webDriver.navigate().refresh(); + await driver.injectConfigAndInitAuth(); + + // At this point we aren't waiting for auth to "settle" + // Sign in before the first onAuthStateChanged has occurred + const { user: after }: UserCredential = await driver.call( + AnonFunction.SIGN_IN_ANONYMOUSLY + ); + expect(after.uid).to.eq(before.uid); + }); +}); diff --git a/packages-exp/auth-exp/test/integration/webdriver/compat/firebaseui.test.ts b/packages-exp/auth-exp/test/integration/webdriver/compat/firebaseui.test.ts new file mode 100644 index 00000000000..4daa6f0bb5b --- /dev/null +++ b/packages-exp/auth-exp/test/integration/webdriver/compat/firebaseui.test.ts @@ -0,0 +1,176 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { CoreFunction, UiFunction } from '../util/functions'; +import { until } from 'selenium-webdriver'; +import { User } from '@firebase/auth-types'; +import { expect } from 'chai'; +import { browserDescribe } from '../util/test_runner'; +import { UiPage } from '../util/ui_page'; +import { IdPPage } from '../util/idp_page'; +import { getPhoneVerificationCodes } from '../../../helpers/integration/emulator_rest_helpers'; + +// These tests only run using the compat layer. Due to npm dependency issues, +// this code needs to stay with the modular tests + +browserDescribe('WebDriver integration with FirebaseUI', driver => { + beforeEach(async () => { + await driver.call(UiFunction.LOAD); + }); + + async function startUi(signInFlow = 'redirect'): Promise { + await driver.call(UiFunction.START, signInFlow); + return new UiPage(driver.webDriver); + } + + async function waitForLoggedInPage(): Promise { + await driver.webDriver.wait(until.titleIs('User logged in')); + await driver.reinitOnRedirect(); + } + + it('allows anonymous sign in', async () => { + const page = await startUi(); + await page.clickGuestSignIn(); + await waitForLoggedInPage(); + const snap = (await driver.getUserSnapshot()) as User; + expect(snap.isAnonymous).to.be.true; + expect(snap.uid).to.be.a('string'); + }); + + it('allows google redirect sign in', async () => { + const page = await startUi(); + await page.clickGoogleSignIn(); + const widget = new IdPPage(driver.webDriver); + + // We're now on the widget page; wait for load + await widget.pageLoad(); + await widget.clickAddAccount(); + await widget.fillEmail('bob@bob.test'); + await widget.fillDisplayName('Bob Test'); + await widget.fillScreenName('bob.test'); + await widget.fillProfilePhoto('http://bob.test/bob.png'); + await widget.clickSignIn(); + + // Now we're back. Firebase UI should handle the redirect result handoff + await driver.reinitOnRedirect(); + await driver.call(UiFunction.LOAD); + await startUi(); + await waitForLoggedInPage(); + const snap = (await driver.getUserSnapshot()) as User; + expect(snap.isAnonymous).to.be.false; + expect(snap.displayName).to.eq('Bob Test'); + expect(snap.email).to.eq('bob@bob.test'); + expect(snap.photoURL).to.eq('http://bob.test/bob.png'); + expect(snap.uid).to.be.a('string'); + expect(snap.providerData[0]!.providerId).to.eq('google.com'); + }); + + it('allows google popup sign in', async () => { + const page = await startUi('popup'); + await page.clickGoogleSignIn(); + const widget = new IdPPage(driver.webDriver); + await driver.selectPopupWindow(); + + // We're now on the widget page; wait for load + await widget.pageLoad(); + await widget.clickAddAccount(); + await widget.fillEmail('bob@bob.test'); + await widget.fillDisplayName('Bob Test'); + await widget.fillScreenName('bob.test'); + await widget.fillProfilePhoto('http://bob.test/bob.png'); + await widget.clickSignIn(); + + // Now we're back. Firebase UI should handle the redirect result handoff + await driver.selectMainWindow(); + await waitForLoggedInPage(); + const snap = (await driver.getUserSnapshot()) as User; + expect(snap.isAnonymous).to.be.false; + expect(snap.displayName).to.eq('Bob Test'); + expect(snap.email).to.eq('bob@bob.test'); + expect(snap.photoURL).to.eq('http://bob.test/bob.png'); + expect(snap.uid).to.be.a('string'); + expect(snap.providerData[0]!.providerId).to.eq('google.com'); + }); + + it('allows phone sign in', async () => { + const page = await startUi(); + + // It's not possible to tell the latest session and since the UI + // is controlling the flow we can't get the session info. Instead + // we'll rely on a random number and hope it doesn't ever clash + // with other tests + + const phoneNumber = + '415555' + + Math.floor(Math.random() * 1000) + .toString() + .padStart(4, '0'); + await page.clickPhoneSignIn(); + await page.enterPhoneNumber(phoneNumber); + await driver.pause(500); + await page.clickSubmit(); + + // Wait for the code input to show (happens after the code is sent) + await page.waitForCodeInputToBePresent(); + + // Get the number from the emulator REST endpoint + const code = Object.values( + await getPhoneVerificationCodes() + ).find(session => session.phoneNumber.includes(phoneNumber))!.code; + await page.enterPhoneCode(code); + await page.clickSubmit(); + + await waitForLoggedInPage(); + const snap = (await driver.getUserSnapshot()) as User; + expect(snap.isAnonymous).to.be.false; + expect(snap.phoneNumber).to.eq(`+1${phoneNumber}`); + expect(snap.uid).to.be.a('string'); + }); + + it('allows email sign up/sign in', async () => { + const page = await startUi(); + await page.clickEmailSignIn(); + + await page.enterEmail('foo@foo.test'); + await page.clickSubmit(); + await page.enterEmailDisplayName('Foo Test'); + await page.enterPassword('password'); + await page.clickSubmit(); + + await waitForLoggedInPage(); + const snap = (await driver.getUserSnapshot()) as User; + expect(snap.isAnonymous).to.be.false; + expect(snap.displayName).to.eq('Foo Test'); + expect(snap.email).to.eq('foo@foo.test'); + + // Sign up was successful; now try signing in. + await driver.goToTestPage(); + await driver.injectConfigAndInitAuth(); + await driver.waitForAuthInit(); + await driver.call(CoreFunction.SIGN_OUT); + await driver.call(UiFunction.LOAD); + await startUi(); + await page.clickEmailSignIn(); + await page.enterEmail('foo@foo.test'); + await page.clickSubmit(); + await page.enterPassword('password'); + await page.clickSubmit(); + + await waitForLoggedInPage(); + expect((await driver.getUserSnapshot()).uid).to.eq(snap.uid); + }); +}); diff --git a/packages-exp/auth-exp/test/integration/webdriver/persistence.test.ts b/packages-exp/auth-exp/test/integration/webdriver/persistence.test.ts new file mode 100644 index 00000000000..8014333aa2f --- /dev/null +++ b/packages-exp/auth-exp/test/integration/webdriver/persistence.test.ts @@ -0,0 +1,497 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// eslint-disable-next-line import/no-extraneous-dependencies +import { UserCredential } from '@firebase/auth-exp'; +import { expect } from 'chai'; +import { createAnonAccount } from '../../helpers/integration/emulator_rest_helpers'; +import { API_KEY } from '../../helpers/integration/settings'; +import { START_FUNCTION } from './util/auth_driver'; +import { + AnonFunction, + CoreFunction, + PersistenceFunction +} from './util/functions'; +import { JsLoadCondition } from './util/js_load_condition'; +import { browserDescribe } from './util/test_runner'; + +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +async function testPersistedUser() { + const account = await createAnonAccount(); + return { + uid: account.localId, + emailVerified: false, + isAnonymous: true, + providerData: [], + stsTokenManager: { + refreshToken: account.refreshToken, + accessToken: account.idToken, + expirationTime: Date.now() + 3600 * 1000 + }, + createdAt: Date.now().toString(), + lastLoginAt: Date.now().toString() + }; +} + +browserDescribe('WebDriver persistence test', (driver, browser) => { + const fullPersistenceKey = `firebase:authUser:${API_KEY}:[DEFAULT]`; + context('default persistence hierarchy (indexedDB > localStorage)', () => { + it('stores user in indexedDB by default', async () => { + const cred: UserCredential = await driver.call( + AnonFunction.SIGN_IN_ANONYMOUSLY + ); + const uid = cred.user.uid; + + expect(await driver.getUserSnapshot()).to.eql(cred.user); + expect(await driver.call(PersistenceFunction.LOCAL_STORAGE_SNAP)).to.eql( + {} + ); + expect( + await driver.call(PersistenceFunction.SESSION_STORAGE_SNAP) + ).to.eql({}); + + const snap = await driver.call(PersistenceFunction.INDEXED_DB_SNAP); + expect(snap).to.have.property(fullPersistenceKey).that.contains({ uid }); + + // Persistence should survive a refresh: + await driver.webDriver.navigate().refresh(); + await driver.injectConfigAndInitAuth(); + await driver.waitForAuthInit(); + expect(await driver.getUserSnapshot()).to.contain({ uid }); + }); + + it('should work fine if indexedDB is available while localStorage is not', async () => { + await driver.webDriver.navigate().refresh(); + // Simulate browsers that do not support localStorage. + await driver.webDriver.executeScript('delete window.localStorage;'); + await driver.injectConfigAndInitAuth(); + await driver.waitForAuthInit(); + + const cred: UserCredential = await driver.call( + AnonFunction.SIGN_IN_ANONYMOUSLY + ); + const uid = cred.user.uid; + + expect(await driver.getUserSnapshot()).to.eql(cred.user); + expect(await driver.call(PersistenceFunction.LOCAL_STORAGE_SNAP)).to.eql( + {} + ); + expect( + await driver.call(PersistenceFunction.SESSION_STORAGE_SNAP) + ).to.eql({}); + + const snap = await driver.call(PersistenceFunction.INDEXED_DB_SNAP); + expect(snap).to.have.property(fullPersistenceKey).that.contains({ uid }); + + // Persistence should survive a refresh: + await driver.webDriver.navigate().refresh(); + await driver.injectConfigAndInitAuth(); + await driver.waitForAuthInit(); + expect(await driver.getUserSnapshot()).to.contain({ uid }); + }); + + it('stores user in localStorage if indexedDB is not available', async () => { + await driver.webDriver.navigate().refresh(); + // Simulate browsers that do not support indexedDB. + await driver.webDriver.executeScript('delete window.indexedDB;'); + await driver.injectConfigAndInitAuth(); + await driver.waitForAuthInit(); + + const cred: UserCredential = await driver.call( + AnonFunction.SIGN_IN_ANONYMOUSLY + ); + const uid = cred.user.uid; + + expect(await driver.getUserSnapshot()).to.eql(cred.user); + expect( + await driver.call(PersistenceFunction.SESSION_STORAGE_SNAP) + ).to.eql({}); + + const snap = await driver.call(PersistenceFunction.LOCAL_STORAGE_SNAP); + expect(snap).to.have.property(fullPersistenceKey).that.contains({ uid }); + + // Persistence should survive a refresh: + await driver.webDriver.navigate().refresh(); + await driver.injectConfigAndInitAuth(); + await driver.waitForAuthInit(); + expect(await driver.getUserSnapshot()).to.contain({ uid }); + }); + + it('fall back to in-memory if neither indexedDB or localStorage is present', async () => { + await driver.webDriver.navigate().refresh(); + // Simulate browsers that do not support indexedDB or localStorage. + await driver.webDriver.executeScript( + 'delete window.indexedDB; delete window.localStorage;' + ); + await driver.injectConfigAndInitAuth(); + await driver.waitForAuthInit(); + const cred: UserCredential = await driver.call( + AnonFunction.SIGN_IN_ANONYMOUSLY + ); + + expect(await driver.getUserSnapshot()).to.eql(cred.user); + expect( + await driver.call(PersistenceFunction.SESSION_STORAGE_SNAP) + ).to.eql({}); + expect(await driver.call(PersistenceFunction.LOCAL_STORAGE_SNAP)).to.eql( + {} + ); + expect(await driver.call(PersistenceFunction.INDEXED_DB_SNAP)).to.eql({}); + + // User will be gone (a.k.a. logged out) after refresh. + await driver.webDriver.navigate().refresh(); + await driver.injectConfigAndInitAuth(); + await driver.waitForAuthInit(); + expect(await driver.getUserSnapshot()).to.equal(null); + }); + + it('migrate stored user from localStorage if indexedDB is available', async () => { + const persistedUser = await testPersistedUser(); + await driver.webDriver.navigate().refresh(); + await driver.call(PersistenceFunction.LOCAL_STORAGE_SET, { + [fullPersistenceKey]: persistedUser + }); + await driver.injectConfigAndInitAuth(); + await driver.waitForAuthInit(); + + // User from localStorage should be picked up. + const user = await driver.getUserSnapshot(); + expect(user.uid).eql(persistedUser.uid); + + // User should be migrated to indexedDB, and the key in localStorage should be deleted. + const snap = await driver.call(PersistenceFunction.INDEXED_DB_SNAP); + expect(snap) + .to.have.property(fullPersistenceKey) + .that.contains({ uid: persistedUser.uid }); + expect(await driver.call(PersistenceFunction.LOCAL_STORAGE_SNAP)).to.eql( + {} + ); + }); + + it('migrate stored user to localStorage if indexedDB is readonly', async () => { + // Sign in first, which gets persisted in indexedDB. + const cred: UserCredential = await driver.call( + AnonFunction.SIGN_IN_ANONYMOUSLY + ); + const uid = cred.user.uid; + + await driver.webDriver.navigate().refresh(); + await driver.call(PersistenceFunction.MAKE_INDEXED_DB_READONLY); + await driver.injectConfigAndInitAuth(); + await driver.waitForAuthInit(); + + // User from indexedDB should be picked up. + const user = await driver.getUserSnapshot(); + expect(user.uid).eql(uid); + + // User should be migrated to localStorage, and the key in indexedDB should be deleted. + const snap = await driver.call(PersistenceFunction.LOCAL_STORAGE_SNAP); + expect(snap).to.have.property(fullPersistenceKey).that.contains({ uid }); + expect(await driver.call(PersistenceFunction.INDEXED_DB_SNAP)).to.eql({}); + }); + + it('use in-memory and clear all persistences if indexedDB and localStorage are both broken', async () => { + const persistedUser = await testPersistedUser(); + await driver.webDriver.navigate().refresh(); + await driver.call(PersistenceFunction.LOCAL_STORAGE_SET, { + [fullPersistenceKey]: persistedUser + }); + // Simulate browsers that do not support indexedDB. + await driver.webDriver.executeScript('delete window.indexedDB;'); + // Simulate browsers denying writes to localStorage (e.g. Safari private browsing). + await driver.webDriver.executeScript( + 'Storage.prototype.setItem = () => { throw new Error("setItem disabled for testing"); };' + ); + await driver.injectConfigAndInitAuth(); + await driver.waitForAuthInit(); + + // User from localStorage should be picked up. + const user = await driver.getUserSnapshot(); + expect(user.uid).eql(persistedUser.uid); + + // Both storage should be cleared. + expect(await driver.call(PersistenceFunction.LOCAL_STORAGE_SNAP)).to.eql( + {} + ); + expect(await driver.call(PersistenceFunction.INDEXED_DB_SNAP)).to.eql({}); + + // User will be gone (a.k.a. logged out) after refresh. + await driver.webDriver.navigate().refresh(); + await driver.injectConfigAndInitAuth(); + await driver.waitForAuthInit(); + expect(await driver.getUserSnapshot()).to.equal(null); + }); + }); + + context('setPersistence(...)', () => { + it('clears storage when switching to in-memory', async () => { + await driver.call(AnonFunction.SIGN_IN_ANONYMOUSLY); + const user = await driver.getUserSnapshot(); + + await driver.call(PersistenceFunction.SET_PERSISTENCE_MEMORY); + + const snapshotAfter = await driver.getUserSnapshot(); + expect(snapshotAfter.uid).to.eql(user.uid); + expect(await driver.call(PersistenceFunction.LOCAL_STORAGE_SNAP)).to.eql( + {} + ); + expect(await driver.call(PersistenceFunction.INDEXED_DB_SNAP)).to.eql({}); + + // User will be gone (a.k.a. logged out) after refresh. + await driver.webDriver.navigate().refresh(); + await driver.injectConfigAndInitAuth(); + await driver.waitForAuthInit(); + expect(await driver.getUserSnapshot()).to.equal(null); + }); + + it('migrates user when switching to session', async () => { + await driver.call(AnonFunction.SIGN_IN_ANONYMOUSLY); + const user = await driver.getUserSnapshot(); + + await driver.call(PersistenceFunction.SET_PERSISTENCE_SESSION); + + const snapshotAfter = await driver.getUserSnapshot(); + expect(snapshotAfter.uid).to.eql(user.uid); + expect(await driver.call(PersistenceFunction.INDEXED_DB_SNAP)).to.eql({}); + const snap = await driver.call(PersistenceFunction.SESSION_STORAGE_SNAP); + expect(snap) + .to.have.property(fullPersistenceKey) + .that.contains({ uid: user.uid }); + + // User will be gone (a.k.a. logged out) after refresh. + await driver.webDriver.navigate().refresh(); + await driver.injectConfigAndInitAuth(); + await driver.waitForAuthInit(); + expect(await driver.getUserSnapshot()).to.equal(null); + }); + + it('migrates user when switching from indexedDB to localStorage', async () => { + // This test only works in the modular SDK: the compat package does not + // make the distinction between indexedDB and local storage (both are just + // 'local'). + if (driver.isCompatLayer()) { + console.warn('Skipping indexedDB to local migration in compat test'); + return; + } + + await driver.call(AnonFunction.SIGN_IN_ANONYMOUSLY); + const user = await driver.getUserSnapshot(); + + await driver.call(PersistenceFunction.SET_PERSISTENCE_LOCAL_STORAGE); + + expect((await driver.getUserSnapshot()).uid).to.eql(user.uid); + expect(await driver.call(PersistenceFunction.INDEXED_DB_SNAP)).to.eql({}); + const snap = await driver.call(PersistenceFunction.LOCAL_STORAGE_SNAP); + expect(snap) + .to.have.property(fullPersistenceKey) + .that.contains({ uid: user.uid }); + + await driver.webDriver.navigate().refresh(); + await driver.injectConfigAndInitAuth(); + await driver.waitForAuthInit(); + // User should be picked up from localStorage after refresh. + expect((await driver.getUserSnapshot()).uid).to.eql(user.uid); + }); + + it('migrates user when switching from in-memory to indexedDB', async () => { + await driver.call(PersistenceFunction.SET_PERSISTENCE_MEMORY); + await driver.call(AnonFunction.SIGN_IN_ANONYMOUSLY); + const user = await driver.getUserSnapshot(); + + await driver.call(PersistenceFunction.SET_PERSISTENCE_INDEXED_DB); + + expect((await driver.getUserSnapshot()).uid).to.eql(user.uid); + const snap = await driver.call(PersistenceFunction.INDEXED_DB_SNAP); + expect(snap) + .to.have.property(fullPersistenceKey) + .that.contains({ uid: user.uid }); + + await driver.webDriver.navigate().refresh(); + await driver.injectConfigAndInitAuth(); + await driver.waitForAuthInit(); + // User should be picked up from indexedDB after refresh. + expect((await driver.getUserSnapshot()).uid).to.eql(user.uid); + }); + }); + + context('persistence compatibility with legacy SDK', () => { + it('stays logged in when switching to legacy SDK and then back', async () => { + const cred: UserCredential = await driver.call( + AnonFunction.SIGN_IN_ANONYMOUSLY + ); + const uid = cred.user.uid; + + await driver.webDriver.navigate().refresh(); + await driver.injectConfigAndInitLegacySDK(); + await driver.waitForLegacyAuthInit(); + const user = await driver.call(CoreFunction.LEGACY_USER_SNAPSHOT); + expect(user).to.include({ uid }); + + await driver.webDriver.navigate().refresh(); + await driver.injectConfigAndInitAuth(); + await driver.waitForAuthInit(); + // User should be picked up from indexedDB after refresh. + expect((await driver.getUserSnapshot()).uid).to.eql(uid); + }); + + it('stays logged in when switching from legacy SDK and then back', async () => { + await driver.webDriver.navigate().refresh(); + await driver.injectConfigAndInitLegacySDK(); + await driver.waitForLegacyAuthInit(); + + const result = await driver.call<{ user: { uid: string } }>( + 'legacyAuth.signInAnonymously' + ); + const uid = result.user.uid; + const persisted1 = await driver.call(PersistenceFunction.INDEXED_DB_SNAP); + + await driver.webDriver.navigate().refresh(); + await driver.injectConfigAndInitAuth(); + await driver.waitForAuthInit(); + // User should be picked up from indexedDB after refresh. + expect((await driver.getUserSnapshot()).uid).to.eql(uid); + const persisted2 = await driver.call(PersistenceFunction.INDEXED_DB_SNAP); + + await driver.webDriver.navigate().refresh(); + await driver.injectConfigAndInitLegacySDK(); + await driver.waitForLegacyAuthInit(); + const user = await driver.call(CoreFunction.LEGACY_USER_SNAPSHOT); + if (!user) { + expect( + persisted2, + 'user is not recognized by legacy SDK, possibly due to fields being different' + ).to.eql(persisted1); + } else { + expect(user).to.include({ uid }); // and again in legacy SDK + } + }); + + it('stays logged in when switching from legacy SDK and then back (no indexedDB support)', async () => { + // Skip this test if running in Firefox. The Legacy SDK incorrectly + // implements the db delete + reopen workaround for Firefox. + if (browser === 'firefox') { + return; + } + + await driver.webDriver.navigate().refresh(); + // Simulate browsers that do not support indexedDB. + await driver.webDriver.executeScript('delete window.indexedDB'); + await driver.injectConfigAndInitLegacySDK(); + await driver.waitForLegacyAuthInit(); + + const result = await driver.call<{ user: { uid: string } }>( + 'legacyAuth.signInAnonymously' + ); + const uid = result.user.uid; + const persisted1 = await driver.call( + PersistenceFunction.LOCAL_STORAGE_SNAP + ); + + await driver.webDriver.navigate().refresh(); + await driver.webDriver.executeScript('delete window.indexedDB'); + await driver.injectConfigAndInitAuth(); + await driver.waitForAuthInit(); + // User should be picked up from localStorage after refresh. + expect((await driver.getUserSnapshot()).uid).to.eql(uid); + const persisted2 = await driver.call( + PersistenceFunction.LOCAL_STORAGE_SNAP + ); + + await driver.webDriver.navigate().refresh(); + await driver.injectConfigAndInitLegacySDK(); + await driver.waitForLegacyAuthInit(); + const user = await driver.call(CoreFunction.LEGACY_USER_SNAPSHOT); + if (!user) { + expect( + persisted2, + 'user is not recognized by legacy SDK, possibly due to fields being different' + ).to.eql(persisted1); + } else { + expect(user).to.include({ uid }); // and again in legacy SDK + } + }); + }); + + context('persistence sync across windows and tabs', () => { + it('sync current user across windows with indexedDB', async () => { + const cred: UserCredential = await driver.call( + AnonFunction.SIGN_IN_ANONYMOUSLY + ); + const uid = cred.user.uid; + await driver.webDriver.executeScript('window.open(".");'); + await driver.selectPopupWindow(); + await driver.webDriver.wait(new JsLoadCondition(START_FUNCTION)); + await driver.injectConfigAndInitAuth(); + await driver.waitForAuthInit(); + const userInPopup = await driver.getUserSnapshot(); + expect(userInPopup).not.to.be.null; + expect(userInPopup.uid).to.equal(uid); + + await driver.call(CoreFunction.SIGN_OUT); + expect(await driver.getUserSnapshot()).to.be.null; + await driver.selectMainWindow({ noWait: true }); + await driver.pause(500); + expect(await driver.getUserSnapshot()).to.be.null; + + const cred2: UserCredential = await driver.call( + AnonFunction.SIGN_IN_ANONYMOUSLY + ); + const uid2 = cred2.user.uid; + + await driver.selectPopupWindow(); + await driver.pause(500); + expect(await driver.getUserSnapshot()).to.contain({ uid: uid2 }); + }); + + it('sync current user across windows with localStorage', async () => { + await driver.webDriver.navigate().refresh(); + // Simulate browsers that do not support indexedDB. + await driver.webDriver.executeScript('delete window.indexedDB'); + await driver.injectConfigAndInitAuth(); + await driver.waitForAuthInit(); + const cred: UserCredential = await driver.call( + AnonFunction.SIGN_IN_ANONYMOUSLY + ); + const uid = cred.user.uid; + await driver.webDriver.executeScript('window.open(".");'); + await driver.selectPopupWindow(); + await driver.webDriver.wait(new JsLoadCondition(START_FUNCTION)); + // Simulate browsers that do not support indexedDB. + await driver.webDriver.executeScript('delete window.indexedDB'); + await driver.injectConfigAndInitAuth(); + await driver.waitForAuthInit(); + const userInPopup = await driver.getUserSnapshot(); + expect(userInPopup).not.to.be.null; + expect(userInPopup.uid).to.equal(uid); + + await driver.call(CoreFunction.SIGN_OUT); + expect(await driver.getUserSnapshot()).to.be.null; + await driver.selectMainWindow({ noWait: true }); + await driver.pause(500); + expect(await driver.getUserSnapshot()).to.be.null; + + const cred2: UserCredential = await driver.call( + AnonFunction.SIGN_IN_ANONYMOUSLY + ); + const uid2 = cred2.user.uid; + + await driver.selectPopupWindow(); + await driver.pause(500); + expect(await driver.getUserSnapshot()).to.contain({ uid: uid2 }); + }); + }); +}); diff --git a/packages-exp/auth-exp/test/integration/webdriver/popup.test.ts b/packages-exp/auth-exp/test/integration/webdriver/popup.test.ts new file mode 100644 index 00000000000..1b3146d7c5a --- /dev/null +++ b/packages-exp/auth-exp/test/integration/webdriver/popup.test.ts @@ -0,0 +1,372 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// eslint-disable-next-line import/no-extraneous-dependencies +import { + OperationType, + UserCredential, + User, + OAuthCredential +} from '@firebase/auth-exp'; +import { expect, use } from 'chai'; +import { IdPPage } from './util/idp_page'; +import * as chaiAsPromised from 'chai-as-promised'; +import { browserDescribe } from './util/test_runner'; +import { + AnonFunction, + CoreFunction, + EmailFunction, + PopupFunction +} from './util/functions'; + +use(chaiAsPromised); + +browserDescribe('Popup IdP tests', driver => { + it('allows users to sign in', async () => { + await driver.callNoWait(PopupFunction.IDP_POPUP); + await driver.selectPopupWindow(); + const widget = new IdPPage(driver.webDriver); + + // We're now on the widget page; wait for load + await widget.pageLoad(); + await widget.clickAddAccount(); + await widget.fillEmail('bob@bob.test'); + await widget.fillDisplayName('Bob Test'); + await widget.fillScreenName('bob.test'); + await widget.fillProfilePhoto('http://bob.test/bob.png'); + await widget.clickSignIn(); + + await driver.selectMainWindow(); + const result: UserCredential = await driver.call( + PopupFunction.POPUP_RESULT + ); + const currentUser = await driver.getUserSnapshot(); + expect(currentUser.email).to.eq('bob@bob.test'); + expect(currentUser.displayName).to.eq('Bob Test'); + expect(currentUser.photoURL).to.eq('http://bob.test/bob.png'); + + expect(result.operationType).to.eq(OperationType.SIGN_IN); + expect(result.user).to.eql(currentUser); + }); + + it('can link with another account account', async () => { + // First, sign in anonymously + const { user: anonUser }: UserCredential = await driver.call( + AnonFunction.SIGN_IN_ANONYMOUSLY + ); + + // Then, link with popup + await driver.callNoWait(PopupFunction.IDP_LINK_POPUP); + await driver.selectPopupWindow(); + const widget = new IdPPage(driver.webDriver); + await widget.pageLoad(); + await widget.clickAddAccount(); + await widget.fillEmail('bob@bob.test'); + await widget.clickSignIn(); + + await driver.selectMainWindow(); + // Back on main page; check for the current user matching the anonymous + // account as well as the new IdP account + const user: User = await driver.getUserSnapshot(); + expect(user.uid).to.eq(anonUser.uid); + expect(user.email).to.eq('bob@bob.test'); + }); + + it('can be converted to a credential', async () => { + // Start with popup + await driver.callNoWait(PopupFunction.IDP_POPUP); + await driver.selectPopupWindow(); + const widget = new IdPPage(driver.webDriver); + await widget.pageLoad(); + await widget.clickAddAccount(); + await widget.fillEmail('bob@bob.test'); + await widget.clickSignIn(); + + // Generate a credential, then store it on the window before logging out + await driver.selectMainWindow(); + const first = await driver.getUserSnapshot(); + const cred: OAuthCredential = await driver.call( + PopupFunction.GENERATE_CREDENTIAL_FROM_RESULT + ); + expect(cred.accessToken).to.be.a('string'); + expect(cred.idToken).to.be.a('string'); + expect(cred.signInMethod).to.eq('google.com'); + + // We've now generated that credential. Sign out and sign back in using it + await driver.call(CoreFunction.SIGN_OUT); + const { user: second }: UserCredential = await driver.call( + PopupFunction.SIGN_IN_WITH_POPUP_CREDENTIAL + ); + expect(second.uid).to.eq(first.uid); + expect(second.providerData).to.eql(first.providerData); + }); + + it('handles account exists different credential errors', async () => { + // Start with popup and a verified account + await driver.callNoWait(PopupFunction.IDP_POPUP); + await driver.selectPopupWindow(); + const widget = new IdPPage(driver.webDriver); + await widget.pageLoad(); + await widget.clickAddAccount(); + await widget.fillEmail('bob@bob.test'); + await widget.clickSignIn(); + + await driver.selectMainWindow(); + const original = await driver.getUserSnapshot(); + expect(original.emailVerified).to.be.true; + + // Try to sign in with an unverified Facebook account + // TODO: Convert this to the widget once unverified accounts work + // Come back and verify error / prepare for link + await expect( + driver.call(PopupFunction.TRY_TO_SIGN_IN_UNVERIFIED, 'bob@bob.test') + ).to.be.rejected.and.eventually.have.property( + 'code', + 'auth/account-exists-with-different-credential' + ); + + // Now do the link + await driver.call(PopupFunction.LINK_WITH_ERROR_CREDENTIAL); + + // Check the user for both providers + const user = await driver.getUserSnapshot(); + expect(user.uid).to.eq(original.uid); + expect(user.providerData.map(d => d.providerId)).to.have.members([ + 'google.com', + 'facebook.com' + ]); + }); + + it('does not auto-upgrade anon accounts', async () => { + const { user: anonUser }: UserCredential = await driver.call( + AnonFunction.SIGN_IN_ANONYMOUSLY + ); + await driver.callNoWait(PopupFunction.IDP_POPUP); + await driver.selectPopupWindow(); + const widget = new IdPPage(driver.webDriver); + await widget.pageLoad(); + await widget.clickAddAccount(); + await widget.fillEmail('bob@bob.test'); + await widget.clickSignIn(); + + // On redirect, check that the signed in user is different + await driver.selectMainWindow(); + const curUser = await driver.getUserSnapshot(); + expect(curUser.uid).not.to.eq(anonUser.uid); + }); + + it('linking with anonymous user upgrades account', async () => { + const { user: anonUser }: UserCredential = await driver.call( + AnonFunction.SIGN_IN_ANONYMOUSLY + ); + await driver.callNoWait(PopupFunction.IDP_LINK_POPUP); + await driver.selectPopupWindow(); + const widget = new IdPPage(driver.webDriver); + await widget.pageLoad(); + await widget.clickAddAccount(); + await widget.fillEmail('bob@bob.test'); + await widget.clickSignIn(); + + // On redirect, check that the signed in user is upgraded + await driver.selectMainWindow(); + const curUser = await driver.getUserSnapshot(); + expect(curUser.uid).to.eq(anonUser.uid); + expect(curUser.isAnonymous).to.be.false; + }); + + it('is possible to link with different email', async () => { + const { user: emailUser }: UserCredential = await driver.call( + EmailFunction.CREATE_USER, + 'user@test.test' + ); + + // Link using pre-poulated user + await driver.callNoWait(PopupFunction.IDP_LINK_POPUP); + await driver.selectPopupWindow(); + const widget = new IdPPage(driver.webDriver); + await widget.pageLoad(); + await widget.clickAddAccount(); + await widget.fillEmail('other-user@test.test'); + await widget.clickSignIn(); + + // Check the linked account + await driver.selectMainWindow(); + const curUser = await driver.getUserSnapshot(); + expect(curUser.uid).to.eq(emailUser.uid); + expect(curUser.emailVerified).to.be.false; + expect(curUser.providerData.length).to.eq(2); + }); + + it('is possible to link with the same email', async () => { + const { user: emailUser }: UserCredential = await driver.call( + EmailFunction.CREATE_USER, + 'same@test.test' + ); + + // Link using pre-poulated user + await driver.callNoWait(PopupFunction.IDP_LINK_POPUP); + await driver.selectPopupWindow(); + const widget = new IdPPage(driver.webDriver); + await widget.pageLoad(); + await widget.clickAddAccount(); + await widget.fillEmail('same@test.test'); + await widget.clickSignIn(); + + // Check the linked account + await driver.selectMainWindow(); + const curUser = await driver.getUserSnapshot(); + expect(curUser.uid).to.eq(emailUser.uid); + expect(curUser.emailVerified).to.be.true; + expect(curUser.providerData.length).to.eq(2); + }); + + context('with existing user', () => { + let user1: User; + let user2: User; + + beforeEach(async () => { + // Create a couple existing users + let cred: UserCredential = await driver.call( + PopupFunction.CREATE_FAKE_GOOGLE_USER, + 'bob@bob.test' + ); + user1 = cred.user; + cred = await driver.call( + PopupFunction.CREATE_FAKE_GOOGLE_USER, + 'sally@sally.test' + ); + user2 = cred.user; + await driver.call(CoreFunction.SIGN_OUT); + }); + + it('a user can sign in again', async () => { + // Sign in using pre-poulated user + await driver.callNoWait(PopupFunction.IDP_POPUP); + await driver.selectPopupWindow(); + + // This time, select an existing account + const widget = new IdPPage(driver.webDriver); + await widget.pageLoad(); + await widget.selectExistingAccountByEmail(user1.email!); + + // Double check the new sign in matches the old + await driver.selectMainWindow(); + const user = await driver.getUserSnapshot(); + expect(user.uid).to.eq(user1.uid); + expect(user.email).to.eq(user1.email); + }); + + it('reauthenticate works for the correct user', async () => { + // Sign in using pre-poulated user + await driver.callNoWait(PopupFunction.IDP_POPUP); + await driver.selectPopupWindow(); + + const widget = new IdPPage(driver.webDriver); + await widget.pageLoad(); + await widget.selectExistingAccountByEmail(user1.email!); + + // Double check the new sign in matches the old + await driver.selectMainWindow(); + let user = await driver.getUserSnapshot(); + expect(user.uid).to.eq(user1.uid); + expect(user.email).to.eq(user1.email); + + // Reauthenticate specifically + await driver.callNoWait(PopupFunction.IDP_REAUTH_POPUP); + await driver.selectPopupWindow(); + await widget.pageLoad(); + await widget.selectExistingAccountByEmail(user1.email!); + + await driver.selectMainWindow(); + user = await driver.getUserSnapshot(); + expect(user.uid).to.eq(user1.uid); + expect(user.email).to.eq(user1.email); + }); + + it('reauthenticate throws for wrong user', async () => { + // Sign in using pre-poulated user + await driver.callNoWait(PopupFunction.IDP_POPUP); + await driver.selectPopupWindow(); + + const widget = new IdPPage(driver.webDriver); + await widget.pageLoad(); + await widget.selectExistingAccountByEmail(user1.email!); + + // Immediately reauth but with the wrong user + await driver.selectMainWindow(); + await driver.callNoWait(PopupFunction.IDP_REAUTH_POPUP); + await driver.selectPopupWindow(); + await widget.pageLoad(); + await widget.selectExistingAccountByEmail(user2.email!); + + await driver.selectMainWindow(); + await expect( + driver.call(PopupFunction.POPUP_RESULT) + ).to.be.rejected.and.eventually.have.property( + 'code', + 'auth/user-mismatch' + ); + }); + + it('handles aborted sign ins', async () => { + await driver.callNoWait(PopupFunction.IDP_POPUP); + await driver.selectPopupWindow(); + const widget = new IdPPage(driver.webDriver); + + // Don't actually sign in; go back to the previous page + await widget.pageLoad(); + await driver.closePopup(); + await expect( + driver.call(PopupFunction.POPUP_RESULT) + ).to.be.rejected.and.eventually.have.property( + 'code', + 'auth/popup-closed-by-user' + ); + expect(await driver.getUserSnapshot()).to.be.null; + + // Now do sign in + await driver.callNoWait(PopupFunction.IDP_POPUP); + await driver.selectPopupWindow(); + // Use user1 + await widget.pageLoad(); + await widget.selectExistingAccountByEmail(user1.email!); + + // Ensure the user was signed in... + await driver.selectMainWindow(); + let user = await driver.getUserSnapshot(); + expect(user.uid).to.eq(user1.uid); + expect(user.email).to.eq(user1.email); + + // Now open another sign in, but return + await driver.callNoWait(PopupFunction.IDP_REAUTH_POPUP); + await driver.selectPopupWindow(); + await widget.pageLoad(); + await driver.closePopup(); + await expect( + driver.call(PopupFunction.POPUP_RESULT) + ).to.be.rejected.and.eventually.have.property( + 'code', + 'auth/popup-closed-by-user' + ); + + // Make sure state remained + user = await driver.getUserSnapshot(); + expect(user.uid).to.eq(user1.uid); + expect(user.email).to.eq(user1.email); + }).timeout(12_000); // Test takes a while due to the closed-by-user errors + }); +}); diff --git a/packages-exp/auth-exp/test/integration/webdriver/redirect.test.ts b/packages-exp/auth-exp/test/integration/webdriver/redirect.test.ts new file mode 100644 index 00000000000..26dfea0269f --- /dev/null +++ b/packages-exp/auth-exp/test/integration/webdriver/redirect.test.ts @@ -0,0 +1,352 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// eslint-disable-next-line import/no-extraneous-dependencies +import { + OperationType, + UserCredential, + User, + OAuthCredential +} from '@firebase/auth-exp'; +import { expect, use } from 'chai'; +import { IdPPage } from './util/idp_page'; +import * as chaiAsPromised from 'chai-as-promised'; +import { browserDescribe } from './util/test_runner'; +import { + AnonFunction, + CoreFunction, + EmailFunction, + RedirectFunction +} from './util/functions'; + +use(chaiAsPromised); + +browserDescribe('WebDriver redirect IdP test', driver => { + beforeEach(async () => { + await driver.pause(200); // Race condition on auth init + }); + + it('allows users to sign in', async () => { + await driver.callNoWait(RedirectFunction.IDP_REDIRECT); + const widget = new IdPPage(driver.webDriver); + + // We're now on the widget page; wait for load + await widget.pageLoad(); + await widget.clickAddAccount(); + await widget.fillEmail('bob@bob.test'); + await widget.fillDisplayName('Bob Test'); + await widget.fillScreenName('bob.test'); + await widget.fillProfilePhoto('http://bob.test/bob.png'); + await widget.clickSignIn(); + + await driver.reinitOnRedirect(); + const currentUser = await driver.getUserSnapshot(); + expect(currentUser.email).to.eq('bob@bob.test'); + expect(currentUser.displayName).to.eq('Bob Test'); + expect(currentUser.photoURL).to.eq('http://bob.test/bob.png'); + + const redirectResult: UserCredential = await driver.call( + RedirectFunction.REDIRECT_RESULT + ); + expect(redirectResult.operationType).to.eq(OperationType.SIGN_IN); + expect(redirectResult.user).to.eql(currentUser); + }); + + it('can link with another account account', async () => { + // First, sign in anonymously + const { user: anonUser }: UserCredential = await driver.call( + AnonFunction.SIGN_IN_ANONYMOUSLY + ); + + // Then, link with redirect + await driver.callNoWait(RedirectFunction.IDP_LINK_REDIRECT); + const widget = new IdPPage(driver.webDriver); + await widget.pageLoad(); + await widget.clickAddAccount(); + await widget.fillEmail('bob@bob.test'); + await widget.clickSignIn(); + + await driver.reinitOnRedirect(); + // Back on page; check for the current user matching the anonymous account + // as well as the new IdP account + const user: User = await driver.getUserSnapshot(); + expect(user.uid).to.eq(anonUser.uid); + expect(user.email).to.eq('bob@bob.test'); + }); + + it('can be converted to a credential', async () => { + // Start with redirect + await driver.callNoWait(RedirectFunction.IDP_REDIRECT); + const widget = new IdPPage(driver.webDriver); + await widget.pageLoad(); + await widget.clickAddAccount(); + await widget.fillEmail('bob@bob.test'); + await widget.clickSignIn(); + + // Generate a credential, then store it on the window before logging out + await driver.reinitOnRedirect(); + const first = await driver.getUserSnapshot(); + const cred: OAuthCredential = await driver.call( + RedirectFunction.GENERATE_CREDENTIAL_FROM_RESULT + ); + expect(cred.accessToken).to.be.a('string'); + expect(cred.idToken).to.be.a('string'); + expect(cred.signInMethod).to.eq('google.com'); + + // We've now generated that credential. Sign out and sign back in using it + await driver.call(CoreFunction.SIGN_OUT); + const { user: second }: UserCredential = await driver.call( + RedirectFunction.SIGN_IN_WITH_REDIRECT_CREDENTIAL + ); + expect(second.uid).to.eq(first.uid); + expect(second.providerData).to.eql(first.providerData); + }); + + it('handles account exists different credential errors', async () => { + // Start with redirect and a verified account + await driver.callNoWait(RedirectFunction.IDP_REDIRECT); + const widget = new IdPPage(driver.webDriver); + await widget.pageLoad(); + await widget.clickAddAccount(); + await widget.fillEmail('bob@bob.test'); + await widget.clickSignIn(); + await driver.reinitOnRedirect(); + + const original = await driver.getUserSnapshot(); + expect(original.emailVerified).to.be.true; + + // Try to sign in with an unverified Facebook account + // TODO: Convert this to the widget once unverified accounts work + // Come back and verify error / prepare for link + await expect( + driver.call(RedirectFunction.TRY_TO_SIGN_IN_UNVERIFIED, 'bob@bob.test') + ).to.be.rejected.and.eventually.have.property( + 'code', + 'auth/account-exists-with-different-credential' + ); + + // Now do the link + await driver.call(RedirectFunction.LINK_WITH_ERROR_CREDENTIAL); + + // Check the user for both providers + const user = await driver.getUserSnapshot(); + expect(user.uid).to.eq(original.uid); + expect(user.providerData.map(d => d.providerId)).to.have.members([ + 'google.com', + 'facebook.com' + ]); + }); + + it('does not auto-upgrade anon accounts', async () => { + const { user: anonUser }: UserCredential = await driver.call( + AnonFunction.SIGN_IN_ANONYMOUSLY + ); + await driver.callNoWait(RedirectFunction.IDP_REDIRECT); + const widget = new IdPPage(driver.webDriver); + await widget.pageLoad(); + await widget.clickAddAccount(); + await widget.fillEmail('bob@bob.test'); + await widget.clickSignIn(); + + // On redirect, check that the signed in user is different + await driver.reinitOnRedirect(); + const curUser = await driver.getUserSnapshot(); + expect(curUser.uid).not.to.eq(anonUser.uid); + }); + + it('linking with anonymous user upgrades account', async () => { + const { user: anonUser }: UserCredential = await driver.call( + AnonFunction.SIGN_IN_ANONYMOUSLY + ); + await driver.callNoWait(RedirectFunction.IDP_LINK_REDIRECT); + const widget = new IdPPage(driver.webDriver); + await widget.pageLoad(); + await widget.clickAddAccount(); + await widget.fillEmail('bob@bob.test'); + await widget.clickSignIn(); + + // On redirect, check that the signed in user is upgraded + await driver.reinitOnRedirect(); + const curUser = await driver.getUserSnapshot(); + expect(curUser.uid).to.eq(anonUser.uid); + expect(curUser.isAnonymous).to.be.false; + }); + + it('is possible to link with different email', async () => { + const { user: emailUser }: UserCredential = await driver.call( + EmailFunction.CREATE_USER, + 'user@test.test' + ); + + // Link using pre-poulated user + await driver.callNoWait(RedirectFunction.IDP_LINK_REDIRECT); + + const widget = new IdPPage(driver.webDriver); + await widget.pageLoad(); + await widget.clickAddAccount(); + await widget.fillEmail('other-user@test.test'); + await widget.clickSignIn(); + + // Check the linked account + await driver.reinitOnRedirect(); + const curUser = await driver.getUserSnapshot(); + expect(curUser.uid).to.eq(emailUser.uid); + expect(curUser.emailVerified).to.be.false; + expect(curUser.providerData.length).to.eq(2); + }); + + it('is possible to link with the same email', async () => { + const { user: emailUser }: UserCredential = await driver.call( + EmailFunction.CREATE_USER, + 'same@test.test' + ); + + // Link using pre-poulated user + await driver.callNoWait(RedirectFunction.IDP_LINK_REDIRECT); + + const widget = new IdPPage(driver.webDriver); + await widget.pageLoad(); + await widget.clickAddAccount(); + await widget.fillEmail('same@test.test'); + await widget.clickSignIn(); + + // Check the linked account + await driver.reinitOnRedirect(); + const curUser = await driver.getUserSnapshot(); + expect(curUser.uid).to.eq(emailUser.uid); + expect(curUser.emailVerified).to.be.true; + expect(curUser.providerData.length).to.eq(2); + }); + + context('with existing user', () => { + let user1: User; + let user2: User; + + beforeEach(async () => { + // Create a couple existing users + let cred: UserCredential = await driver.call( + RedirectFunction.CREATE_FAKE_GOOGLE_USER, + 'bob@bob.test' + ); + user1 = cred.user; + cred = await driver.call( + RedirectFunction.CREATE_FAKE_GOOGLE_USER, + 'sally@sally.test' + ); + user2 = cred.user; + await driver.call(CoreFunction.SIGN_OUT); + }); + + it('a user can sign in again', async () => { + // Sign in using pre-poulated user + await driver.callNoWait(RedirectFunction.IDP_REDIRECT); + + // This time, select an existing account + const widget = new IdPPage(driver.webDriver); + await widget.pageLoad(); + await widget.selectExistingAccountByEmail(user1.email!); + + // Double check the new sign in matches the old + await driver.reinitOnRedirect(); + const user = await driver.getUserSnapshot(); + expect(user.uid).to.eq(user1.uid); + expect(user.email).to.eq(user1.email); + }); + + it('reauthenticate works for the correct user', async () => { + // Sign in using pre-poulated user + await driver.callNoWait(RedirectFunction.IDP_REDIRECT); + + const widget = new IdPPage(driver.webDriver); + await widget.pageLoad(); + await widget.selectExistingAccountByEmail(user1.email!); + + // Double check the new sign in matches the old + await driver.reinitOnRedirect(); + let user = await driver.getUserSnapshot(); + expect(user.uid).to.eq(user1.uid); + expect(user.email).to.eq(user1.email); + + // Reauthenticate specifically + await driver.callNoWait(RedirectFunction.IDP_REAUTH_REDIRECT); + await widget.pageLoad(); + await widget.selectExistingAccountByEmail(user1.email!); + + await driver.reinitOnRedirect(); + user = await driver.getUserSnapshot(); + expect(user.uid).to.eq(user1.uid); + expect(user.email).to.eq(user1.email); + }); + + it('reauthenticate throws for wrong user', async () => { + // Sign in using pre-poulated user + await driver.callNoWait(RedirectFunction.IDP_REDIRECT); + + const widget = new IdPPage(driver.webDriver); + await widget.pageLoad(); + await widget.selectExistingAccountByEmail(user1.email!); + + // Immediately reauth but with the wrong user + await driver.reinitOnRedirect(); + await driver.callNoWait(RedirectFunction.IDP_REAUTH_REDIRECT); + await widget.pageLoad(); + await widget.selectExistingAccountByEmail(user2.email!); + + await driver.reinitOnRedirect(); + await expect( + driver.call(RedirectFunction.REDIRECT_RESULT) + ).to.be.rejected.and.eventually.have.property( + 'code', + 'auth/user-mismatch' + ); + }); + + it('handles aborted sign ins', async () => { + await driver.callNoWait(RedirectFunction.IDP_REDIRECT); + const widget = new IdPPage(driver.webDriver); + + // Don't actually sign in; go back to the previous page + await widget.pageLoad(); + await driver.goToTestPage(); + await driver.reinitOnRedirect(); + expect(await driver.getUserSnapshot()).to.be.null; + + // Now do sign in + await driver.callNoWait(RedirectFunction.IDP_REDIRECT); + // Use user1 + await widget.pageLoad(); + await widget.selectExistingAccountByEmail(user1.email!); + + // Ensure the user was signed in... + await driver.reinitOnRedirect(); + let user = await driver.getUserSnapshot(); + expect(user.uid).to.eq(user1.uid); + expect(user.email).to.eq(user1.email); + + // Now open another sign in, but return + await driver.callNoWait(RedirectFunction.IDP_REAUTH_REDIRECT); + await widget.pageLoad(); + await driver.goToTestPage(); + await driver.reinitOnRedirect(); + + // Make sure state remained + user = await driver.getUserSnapshot(); + expect(user.uid).to.eq(user1.uid); + expect(user.email).to.eq(user1.email); + }); + }); +}); diff --git a/packages-exp/auth-exp/test/integration/webdriver/static/anonymous.js b/packages-exp/auth-exp/test/integration/webdriver/static/anonymous.js new file mode 100644 index 00000000000..dbc853b6394 --- /dev/null +++ b/packages-exp/auth-exp/test/integration/webdriver/static/anonymous.js @@ -0,0 +1,23 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { signInAnonymously } from '@firebase/auth-exp'; + +export async function anonymousSignIn() { + const userCred = await signInAnonymously(auth); + return userCred; +} diff --git a/packages-exp/auth-exp/test/integration/webdriver/static/core.js b/packages-exp/auth-exp/test/integration/webdriver/static/core.js new file mode 100644 index 00000000000..c4a748ba1f5 --- /dev/null +++ b/packages-exp/auth-exp/test/integration/webdriver/static/core.js @@ -0,0 +1,49 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { clearPersistence } from './persistence'; + +export function reset() { + return clearPersistence(); +} + +export function authInit() { + return new Promise(resolve => { + auth.onAuthStateChanged(() => resolve()); + }); +} + +export function legacyAuthInit() { + return new Promise(resolve => { + legacyAuth.onAuthStateChanged(() => resolve()); + }); +} + +export async function userSnap() { + return auth.currentUser; +} + +export async function legacyUserSnap() { + return legacyAuth.currentUser; +} + +export async function authSnap() { + return auth; +} + +export function signOut() { + return auth.signOut(); +} diff --git a/packages-exp/auth-exp/test/integration/webdriver/static/email.js b/packages-exp/auth-exp/test/integration/webdriver/static/email.js new file mode 100644 index 00000000000..b13bcb2924d --- /dev/null +++ b/packages-exp/auth-exp/test/integration/webdriver/static/email.js @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createUserWithEmailAndPassword } from '@firebase/auth-exp'; + +const TEST_PASSWORD = 'password'; + +export function createUser(email) { + return createUserWithEmailAndPassword(auth, email, TEST_PASSWORD); +} diff --git a/packages-exp/auth-exp/test/integration/webdriver/static/index.html b/packages-exp/auth-exp/test/integration/webdriver/static/index.html new file mode 100644 index 00000000000..c38e4830e36 --- /dev/null +++ b/packages-exp/auth-exp/test/integration/webdriver/static/index.html @@ -0,0 +1,6 @@ + + + diff --git a/packages-exp/auth-exp/test/integration/webdriver/static/index.js b/packages-exp/auth-exp/test/integration/webdriver/static/index.js new file mode 100644 index 00000000000..ec69288acc2 --- /dev/null +++ b/packages-exp/auth-exp/test/integration/webdriver/static/index.js @@ -0,0 +1,69 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as redirect from './redirect'; +import * as anonymous from './anonymous'; +import * as core from './core'; +import * as popup from './popup'; +import * as email from './email'; +import * as persistence from './persistence'; +import { initializeApp } from '@firebase/app-exp'; +import { getAuth, useAuthEmulator } from '@firebase/auth-exp'; + +window.core = core; +window.anonymous = anonymous; +window.redirect = redirect; +window.popup = popup; +window.email = email; +window.persistence = persistence; + +window.auth = null; +window.legacyAuth = null; + +// The config and emulator URL are injected by the test. The test framework +// calls this function after that injection. +window.startAuth = async () => { + const app = initializeApp(firebaseConfig); + const auth = getAuth(app); + useAuthEmulator(auth, emulatorUrl); + window.auth = auth; +}; + +window.startLegacySDK = async persistence => { + return new Promise((resolve, reject) => { + const appScript = document.createElement('script'); + // TODO: Find some way to make the tests work without Internet. + appScript.src = 'https://www.gstatic.com/firebasejs/8.3.0/firebase-app.js'; + appScript.onerror = reject; + appScript.onload = () => { + const authScript = document.createElement('script'); + authScript.src = + 'https://www.gstatic.com/firebasejs/8.3.0/firebase-auth.js'; + authScript.onerror = reject; + authScript.onload = () => { + firebase.initializeApp(firebaseConfig); + const legacyAuth = firebase.auth(); + legacyAuth.useEmulator(emulatorUrl); + legacyAuth.setPersistence(persistence.toLowerCase()); + window.legacyAuth = legacyAuth; + resolve(); + }; + document.head.appendChild(authScript); + }; + document.head.appendChild(appScript); + }); +}; diff --git a/packages-exp/auth-exp/test/integration/webdriver/static/persistence.js b/packages-exp/auth-exp/test/integration/webdriver/static/persistence.js new file mode 100644 index 00000000000..d2bb4fb89b5 --- /dev/null +++ b/packages-exp/auth-exp/test/integration/webdriver/static/persistence.js @@ -0,0 +1,149 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + browserLocalPersistence, + browserSessionPersistence, + indexedDBLocalPersistence, + inMemoryPersistence +} from '@firebase/auth-exp'; + +const INDEXED_DB_NAME = 'firebaseLocalStorageDb'; + +// Save these variables for test utils use below, since some tests may delete them. +const indexedDB = window.indexedDB; +const localStorage = window.localStorage; +const sessionStorage = window.sessionStorage; + +export async function clearPersistence() { + sessionStorage.clear(); + localStorage.clear(); + // HACK: Deleting databases in Firefox sometimes take a few seconds. Let's just return early. + return withTimeout( + 1000, + dbPromise(indexedDB.deleteDatabase(INDEXED_DB_NAME)) + ).catch(e => { + console.error(e); + return; + }); +} + +export async function localStorageSnap() { + return dumpStorage(localStorage); +} +export async function localStorageSet(dict) { + setInStorage(localStorage, dict); +} +export async function sessionStorageSnap() { + return dumpStorage(sessionStorage); +} +export async function sessionStorageSet(dict) { + setInStorage(sessionStorage, dict); +} + +const DB_OBJECTSTORE_NAME = 'firebaseLocalStorage'; + +export async function indexedDBSnap() { + const db = await dbPromise(indexedDB.open(INDEXED_DB_NAME)); + let entries; + try { + const store = db + .transaction([DB_OBJECTSTORE_NAME], 'readonly') + .objectStore(DB_OBJECTSTORE_NAME); + entries = await dbPromise(store.getAll()); + } catch { + // May throw if DB_OBJECTSTORE_NAME is never created -- this is normal. + return {}; + } + const result = {}; + for (const { fbase_key: key, value } of entries) { + result[key] = value; + } + return result; +} + +export async function setPersistenceMemory() { + return auth.setPersistence(inMemoryPersistence); +} + +export async function setPersistenceSession() { + return auth.setPersistence(browserSessionPersistence); +} + +export async function setPersistenceLocalStorage() { + return auth.setPersistence(browserLocalPersistence); +} + +export async function setPersistenceIndexedDB() { + return auth.setPersistence(indexedDBLocalPersistence); +} + +// Mock functions for testing edge cases +export async function makeIndexedDBReadonly() { + IDBObjectStore.prototype.add = IDBObjectStore.prototype.put = () => { + return { + error: 'add/put is disabled for test purposes', + readyState: 'done', + addEventListener(event, listener) { + if (event === 'error') { + void Promise.resolve({}).then(listener); + } + } + }; + }; +} + +function dumpStorage(storage) { + const result = {}; + for (let i = 0; i < storage.length; i++) { + const key = storage.key(i); + result[key] = JSON.parse(storage.getItem(key)); + } + return result; +} + +function setInStorage(storage, dict) { + for (const [key, value] of Object.entries(dict)) { + if (value === undefined) { + storage.removeItem(key); + } else { + storage.setItem(key, JSON.stringify(value)); + } + } +} + +function dbPromise(dbRequest) { + return new Promise((resolve, reject) => { + dbRequest.addEventListener('success', () => { + resolve(dbRequest.result); + }); + dbRequest.addEventListener('error', () => { + reject(dbRequest.error); + }); + dbRequest.addEventListener('blocked', () => { + reject('blocked'); + }); + }); +} + +function withTimeout(ms, promise) { + return Promise.race([ + new Promise((_, reject) => + setTimeout(() => reject(new Error('operation timed out')), ms) + ), + promise + ]); +} diff --git a/packages-exp/auth-exp/test/integration/webdriver/static/popup.js b/packages-exp/auth-exp/test/integration/webdriver/static/popup.js new file mode 100644 index 00000000000..8b06048976a --- /dev/null +++ b/packages-exp/auth-exp/test/integration/webdriver/static/popup.js @@ -0,0 +1,99 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + FacebookAuthProvider, + GoogleAuthProvider, + linkWithCredential, + linkWithPopup, + OAuthProvider, + reauthenticateWithPopup, + signInWithCredential, + signInWithPopup +} from '@firebase/auth-exp'; + +// These functions are a little funky: WebDriver relies on callbacks to +// pass data back to the main Node process. Because of that setup, we can't +// return the popup tasks as pending promises as they won't resolve until +// the WebDriver is allowed to do other stuff. Instead, we'll store the +// promises in variables and provide a way to retrieve them later, unblocking +// the WebDriver process. +let popupPromise = null; +let popupCred = null; +let errorCred = null; + +export function idpPopup(optProvider) { + const provider = optProvider + ? new OAuthProvider(optProvider) + : new GoogleAuthProvider(); + popupPromise = signInWithPopup(auth, provider); +} + +export function idpReauthPopup() { + popupPromise = reauthenticateWithPopup( + auth.currentUser, + new GoogleAuthProvider() + ); +} + +export function idpLinkPopup() { + popupPromise = linkWithPopup(auth.currentUser, new GoogleAuthProvider()); +} + +export function popupResult() { + return popupPromise; +} + +export async function generateCredentialFromResult() { + const result = await popupPromise; + popupCred = GoogleAuthProvider.credentialFromResult(result); + return popupCred; +} + +export async function signInWithPopupCredential() { + return signInWithCredential(auth, popupCred); +} + +export async function linkWithErrorCredential() { + await linkWithCredential(auth.currentUser, errorCred); +} + +// These below are not technically popup functions but they're helpers for +// the popup tests. + +export function createFakeGoogleUser(email) { + return signInWithCredential( + auth, + GoogleAuthProvider.credential( + `{"sub": "__${email}__", "email": "${email}", "email_verified": true}` + ) + ); +} + +export async function tryToSignInUnverified(email) { + try { + await signInWithCredential( + auth, + FacebookAuthProvider.credential( + `{"sub": "$$${email}$$", "email": "${email}", "email_verified": false}` + ) + ); + } catch (e) { + errorCred = FacebookAuthProvider.credentialFromError(e); + throw e; + } +} diff --git a/packages-exp/auth-exp/test/integration/webdriver/static/redirect.js b/packages-exp/auth-exp/test/integration/webdriver/static/redirect.js new file mode 100644 index 00000000000..5ef2353f0b4 --- /dev/null +++ b/packages-exp/auth-exp/test/integration/webdriver/static/redirect.js @@ -0,0 +1,90 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + FacebookAuthProvider, + getRedirectResult, + GoogleAuthProvider, + linkWithCredential, + linkWithRedirect, + OAuthProvider, + reauthenticateWithRedirect, + signInWithCredential, + signInWithRedirect +} from '@firebase/auth-exp'; + +let redirectCred = null; +let errorCred = null; + +export function idpRedirect(optProvider) { + const provider = optProvider + ? new OAuthProvider(optProvider) + : new GoogleAuthProvider(); + signInWithRedirect(auth, provider); +} + +export function idpReauthRedirect() { + reauthenticateWithRedirect(auth.currentUser, new GoogleAuthProvider()); +} + +export function idpLinkRedirect() { + linkWithRedirect(auth.currentUser, new GoogleAuthProvider()); +} + +export function redirectResult() { + return getRedirectResult(auth); +} + +export async function generateCredentialFromRedirectResultAndStore() { + const result = await getRedirectResult(auth); + redirectCred = GoogleAuthProvider.credentialFromResult(result); + return redirectCred; +} + +export async function signInWithRedirectCredential() { + return signInWithCredential(auth, redirectCred); +} + +export async function linkWithErrorCredential() { + await linkWithCredential(auth.currentUser, errorCred); +} + +// These below are not technically redirect functions but they're helpers for +// the redirect tests. + +export function createFakeGoogleUser(email) { + return signInWithCredential( + auth, + GoogleAuthProvider.credential( + `{"sub": "__${email}__", "email": "${email}", "email_verified": true}` + ) + ); +} + +export async function tryToSignInUnverified(email) { + try { + await signInWithCredential( + auth, + FacebookAuthProvider.credential( + `{"sub": "$$${email}$$", "email": "${email}", "email_verified": false}` + ) + ); + } catch (e) { + errorCred = FacebookAuthProvider.credentialFromError(e); + throw e; + } +} diff --git a/packages-exp/auth-exp/test/integration/webdriver/static/rollup.config.js b/packages-exp/auth-exp/test/integration/webdriver/static/rollup.config.js new file mode 100644 index 00000000000..d48a1208ff1 --- /dev/null +++ b/packages-exp/auth-exp/test/integration/webdriver/static/rollup.config.js @@ -0,0 +1,28 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { nodeResolve } from '@rollup/plugin-node-resolve'; + +// This is run from the auth-exp package.json +export default { + input: ['test/integration/webdriver/static/index.js'], + output: { + file: 'test/integration/webdriver/static/dist/bundle.js', + format: 'esm' + }, + plugins: [nodeResolve()] +}; diff --git a/packages-exp/auth-exp/test/integration/webdriver/util/auth_driver.ts b/packages-exp/auth-exp/test/integration/webdriver/util/auth_driver.ts new file mode 100644 index 00000000000..639514dc4ad --- /dev/null +++ b/packages-exp/auth-exp/test/integration/webdriver/util/auth_driver.ts @@ -0,0 +1,236 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// eslint-disable-next-line import/no-extraneous-dependencies +import { Auth, User, Persistence } from '@firebase/auth-exp'; +import { Builder, Condition, WebDriver } from 'selenium-webdriver'; +import { resetEmulator } from '../../../helpers/integration/emulator_rest_helpers'; +import { + getEmulatorUrl, + PROJECT_ID, + USE_EMULATOR +} from '../../../helpers/integration/settings'; +import { CoreFunction } from './functions'; +import { JsLoadCondition } from './js_load_condition'; +import { authTestServer } from './test_server'; + +export const START_FUNCTION = 'startAuth'; +const START_LEGACY_SDK_FUNCTION = 'startLegacySDK'; +const PASSED_ARGS = '...Array.prototype.slice.call(arguments, 0, -1)'; + +type DriverCallResult = + | { type: 'success'; value: string /* JSON stringified */ } + | { + type: 'error'; + fields: string /* JSON stringified */; + message: string; + stack: string; + }; + +/** Helper wraper around the WebDriver object */ +export class AuthDriver { + webDriver!: WebDriver; + + async start(browser: string): Promise { + await authTestServer.start(); + this.webDriver = await new Builder().forBrowser(browser).build(); + await this.goToTestPage(); + } + + async stop(): Promise { + authTestServer.stop(); + if (process.env.WEBDRIVER_BROWSER_LOGS) { + await this.webDriver + .manage() + .logs() + .get('browser') + .then( + logs => { + for (const { level, message } of logs) { + console.log(level.name, message); + } + }, + () => + console.log( + 'Failed to dump browser logs (this is normal for Firefox).' + ) + ); + } + await this.webDriver.quit(); + } + + async call(fn: string, ...args: unknown[]): Promise { + // When running on firefox we can't just return result immediately. For + // some reason, the binding ends up causing a cycle dependency issue during + // serialization which blows up the whole thing. It's okay though; this is + // an integration test: we don't care about the internal (hidden) values of + // these objects. + const result = await this.webDriver.executeAsyncScript( + ` + var callback = arguments[arguments.length - 1]; + ${fn}(${PASSED_ARGS}).then(result => { + callback({type: 'success', value: JSON.stringify(result)}); + }).catch(e => { + callback({type: 'error', message: e.message, stack: e.stack, fields: JSON.stringify(e)}); + }); + `, + ...args + ); + + if (result.type === 'success') { + return JSON.parse(result.value) as T; + } else { + const e = new Error(result.message); + const stack = e.stack; + Object.assign(e, JSON.parse(result.fields)); + + const trimmedDriverStack = result.stack.split('at eval (')[0]; + e.stack = `${trimmedDriverStack}\nfrom WebDriver call ${fn}(...)\n${stack}`; + throw e; + } + } + + async callNoWait(fn: string, ...args: unknown[]): Promise { + return this.webDriver.executeScript(`${fn}(...arguments)`, ...args); + } + + async getAuthSnapshot(): Promise { + return this.call(CoreFunction.AUTH_SNAPSHOT); + } + + async getUserSnapshot(): Promise { + return this.call(CoreFunction.USER_SNAPSHOT); + } + + async reset(): Promise { + await resetEmulator(); + await this.goToTestPage(); + return this.call(CoreFunction.RESET); + } + + async goToTestPage(): Promise { + await this.webDriver.get(authTestServer.address!); + } + + async waitForAuthInit(): Promise { + return this.call(CoreFunction.AWAIT_AUTH_INIT); + } + + async waitForLegacyAuthInit(): Promise { + return this.call(CoreFunction.AWAIT_LEGACY_AUTH_INIT); + } + + async reinitOnRedirect(): Promise { + // In this unique case we don't know when the page is back; check for the + // presence of the core module + await this.webDriver.wait(new JsLoadCondition(START_FUNCTION)); + await this.injectConfigAndInitAuth(); + await this.waitForAuthInit(); + } + + pause(ms: number): Promise { + return new Promise(resolve => { + setTimeout(() => resolve(), ms); + }); + } + + async refresh(): Promise { + await this.webDriver.navigate().refresh(); + await this.injectConfigAndInitAuth(); + await this.waitForAuthInit(); + } + + private async injectConfig(): Promise { + if (!USE_EMULATOR) { + throw new Error( + 'Local testing against emulator requested, but ' + + 'GCLOUD_PROJECT and FIREBASE_AUTH_EMULATOR_HOST env variables ' + + 'are missing' + ); + } + + await this.webDriver.executeScript(` + window.firebaseConfig = { + apiKey: 'emulator-api-key', + projectId: '${PROJECT_ID}', + authDomain: 'localhost/emulator', + }; + window.emulatorUrl = '${getEmulatorUrl()}'; + `); + } + + async injectConfigAndInitAuth(): Promise { + await this.injectConfig(); + await this.call(START_FUNCTION); + } + + async injectConfigAndInitLegacySDK( + persistence: Persistence['type'] = 'LOCAL' + ): Promise { + await this.injectConfig(); + await this.call(START_LEGACY_SDK_FUNCTION, persistence); + } + + async selectPopupWindow(): Promise { + const currentWindowHandle = await this.webDriver.getWindowHandle(); + const condition = new Condition( + 'Waiting for popup to open', + async driver => { + return (await driver.getAllWindowHandles()).length > 1; + } + ); + await this.webDriver.wait(condition); + const handles = await this.webDriver.getAllWindowHandles(); + return this.webDriver + .switchTo() + .window(handles.find(h => h !== currentWindowHandle)!); + } + + async selectMainWindow(options: { noWait?: boolean } = {}): Promise { + if (!options.noWait) { + const condition = new Condition( + 'Waiting for popup to close', + async driver => { + return (await driver.getAllWindowHandles()).length === 1; + } + ); + await this.webDriver.wait(condition); + } + const handles = await this.webDriver.getAllWindowHandles(); + return this.webDriver.switchTo().window(handles[0]); + } + + async closePopup(): Promise { + // This assumes the current driver is already the popup + await this.webDriver.close(); + return this.selectMainWindow(); + } + + async closeExtraWindows(): Promise { + const handles = await this.webDriver.getAllWindowHandles(); + await this.webDriver.switchTo().window(handles[handles.length - 1]); + while (handles.length > 1) { + await this.webDriver.close(); + handles.pop(); + await this.webDriver.switchTo().window(handles[handles.length - 1]); + } + } + + isCompatLayer(): boolean { + return process.env.COMPAT_LAYER === 'true'; + } +} diff --git a/packages-exp/auth-exp/test/integration/webdriver/util/functions.ts b/packages-exp/auth-exp/test/integration/webdriver/util/functions.ts new file mode 100644 index 00000000000..6350f59e9f9 --- /dev/null +++ b/packages-exp/auth-exp/test/integration/webdriver/util/functions.ts @@ -0,0 +1,84 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** Available in the browser. See static/anonymous.js */ +export enum AnonFunction { + SIGN_IN_ANONYMOUSLY = 'anonymous.anonymousSignIn' +} + +/** Available redirect functions. See static/redirect.js */ +export enum RedirectFunction { + IDP_REDIRECT = 'redirect.idpRedirect', + IDP_REAUTH_REDIRECT = 'redirect.idpReauthRedirect', + IDP_LINK_REDIRECT = 'redirect.idpLinkRedirect', + REDIRECT_RESULT = 'redirect.redirectResult', + GENERATE_CREDENTIAL_FROM_RESULT = 'redirect.generateCredentialFromRedirectResultAndStore', + SIGN_IN_WITH_REDIRECT_CREDENTIAL = 'redirect.signInWithRedirectCredential', + LINK_WITH_ERROR_CREDENTIAL = 'redirect.linkWithErrorCredential', + CREATE_FAKE_GOOGLE_USER = 'redirect.createFakeGoogleUser', + TRY_TO_SIGN_IN_UNVERIFIED = 'redirect.tryToSignInUnverified' +} + +/** Available popup functions. See static/popup.js */ +export enum PopupFunction { + IDP_POPUP = 'popup.idpPopup', + IDP_REAUTH_POPUP = 'popup.idpReauthPopup', + IDP_LINK_POPUP = 'popup.idpLinkPopup', + POPUP_RESULT = 'popup.popupResult', + GENERATE_CREDENTIAL_FROM_RESULT = 'popup.generateCredentialFromResult', + SIGN_IN_WITH_POPUP_CREDENTIAL = 'popup.signInWithPopupCredential', + LINK_WITH_ERROR_CREDENTIAL = 'popup.linkWithErrorCredential', + CREATE_FAKE_GOOGLE_USER = 'popup.createFakeGoogleUser', + TRY_TO_SIGN_IN_UNVERIFIED = 'popup.tryToSignInUnverified' +} + +/** Available email functions. See static/email.js */ +export enum EmailFunction { + CREATE_USER = 'email.createUser' +} + +/** Available core functions within the browser. See static/core.js */ +export enum CoreFunction { + RESET = 'core.reset', + AWAIT_AUTH_INIT = 'core.authInit', + USER_SNAPSHOT = 'core.userSnap', + AUTH_SNAPSHOT = 'core.authSnap', + SIGN_OUT = 'core.signOut', + AWAIT_LEGACY_AUTH_INIT = 'core.legacyAuthInit', + LEGACY_USER_SNAPSHOT = 'core.legacyUserSnap' +} + +/** Available persistence functions within the browser. See static/persistence.js */ +export enum PersistenceFunction { + CLEAR_PERSISTENCE = 'persistence.clearPersistence', + LOCAL_STORAGE_SNAP = 'persistence.localStorageSnap', + LOCAL_STORAGE_SET = 'persistence.localStorageSet', + SESSION_STORAGE_SNAP = 'persistence.sessionStorageSnap', + SESSION_STORAGE_SET = 'persistence.sessionStorageSet', + INDEXED_DB_SNAP = 'persistence.indexedDBSnap', + MAKE_INDEXED_DB_READONLY = 'persistence.makeIndexedDBReadonly', + SET_PERSISTENCE_MEMORY = 'persistence.setPersistenceMemory', + SET_PERSISTENCE_SESSION = 'persistence.setPersistenceSession', + SET_PERSISTENCE_INDEXED_DB = 'persistence.setPersistenceIndexedDB', + SET_PERSISTENCE_LOCAL_STORAGE = 'persistence.setPersistenceLocalStorage' +} + +/** Available firebase UI functions (only for compat tests) */ +export enum UiFunction { + LOAD = 'ui.loadUiCode', + START = 'ui.startUi' +} diff --git a/packages-exp/auth-exp/test/integration/webdriver/util/idp_page.ts b/packages-exp/auth-exp/test/integration/webdriver/util/idp_page.ts new file mode 100644 index 00000000000..d52aad1f088 --- /dev/null +++ b/packages-exp/auth-exp/test/integration/webdriver/util/idp_page.ts @@ -0,0 +1,84 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { By, until, WebDriver } from 'selenium-webdriver'; +import { JsLoadCondition } from './js_load_condition'; + +const ADD_ACCOUNT_BUTTON = By.css('.js-new-account'); +const SIGN_IN_BUTTON = By.id('sign-in'); +const EMAIL_INPUT = By.id('email-input'); +const DISPLAY_NAME_INPUT = By.id('display-name-input'); +const SCREEN_NAME_INPUT = By.id('screen-name-input'); +const PROFILE_PHOTO_INPUT = By.id('profile-photo-input'); +const ACCOUNT_LIST_ITEMS = By.css('#accounts-list li'); + +export class IdPPage { + static PAGE_TITLE = 'Auth Emulator IDP Login Widget'; + + constructor(private readonly driver: WebDriver) {} + + async pageLoad(): Promise { + await this.driver.wait(until.titleContains('Auth Emulator')); + await this.driver.wait(new JsLoadCondition('toggleForm')); + } + + async clickAddAccount(): Promise { + await this.driver.wait(until.elementLocated(ADD_ACCOUNT_BUTTON)); + await this.driver.findElement(ADD_ACCOUNT_BUTTON).click(); + } + + async clickSignIn(): Promise { + await this.driver.wait(until.elementLocated(SIGN_IN_BUTTON)); + const button = await this.driver.findElement(SIGN_IN_BUTTON); + await this.driver.wait(until.elementIsEnabled(button)); + await button.click(); + } + + async selectExistingAccountByEmail(email: string): Promise { + await this.driver.wait(until.elementLocated(ACCOUNT_LIST_ITEMS)); + const accounts = await this.driver.findElements(ACCOUNT_LIST_ITEMS); + for (const account of accounts) { + if ((await account.getText()).includes(email)) { + await account.click(); + return; + } + } + } + + fillEmail(email: string): Promise { + return this.fillInput(EMAIL_INPUT, email); + } + + fillDisplayName(displayName: string): Promise { + return this.fillInput(DISPLAY_NAME_INPUT, displayName); + } + + fillScreenName(screenName: string): Promise { + return this.fillInput(SCREEN_NAME_INPUT, screenName); + } + + fillProfilePhoto(prophilePhoto: string): Promise { + return this.fillInput(PROFILE_PHOTO_INPUT, prophilePhoto); + } + + private async fillInput(input: By, text: string): Promise { + await this.driver.wait(until.elementLocated(input)); + const el = await this.driver.findElement(input); + await el.click(); + await el.sendKeys(text); + } +} diff --git a/packages-exp/auth-exp/test/integration/webdriver/util/js_load_condition.ts b/packages-exp/auth-exp/test/integration/webdriver/util/js_load_condition.ts new file mode 100644 index 00000000000..0eb6417a057 --- /dev/null +++ b/packages-exp/auth-exp/test/integration/webdriver/util/js_load_condition.ts @@ -0,0 +1,33 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Condition } from 'selenium-webdriver'; + +/** + * A condition that looks for the presence of a specified function. This is + * used with WebDriver .wait() as a proxy for determining when the JS has + * finished loading in a page. + */ +export class JsLoadCondition extends Condition { + constructor(globalValue: string) { + super(`Waiting for global value ${globalValue}`, driver => { + return driver.executeScript( + `return typeof ${globalValue} !== 'undefined';` + ); + }); + } +} diff --git a/packages-exp/auth-exp/test/integration/webdriver/util/test_runner.ts b/packages-exp/auth-exp/test/integration/webdriver/util/test_runner.ts new file mode 100644 index 00000000000..10e711ed876 --- /dev/null +++ b/packages-exp/auth-exp/test/integration/webdriver/util/test_runner.ts @@ -0,0 +1,84 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AuthDriver } from './auth_driver'; + +/* + * The most expensive operation in these tests is setting up / tearing down the + * driver. In order to avoid that cost, all of the tests are collected and + * bundled into single suites for each browser. To do this, we create a new + * describe function that is used to generate the new suites. + * + * This test is started with the --delay flag, which allows us to control when + * test execution starts. Collection of the tests is synchronous, but we need + * a way to ensure that run() is called after they're all added. To accomplish + * this, we put the final construction of the suites (and the subsequent run() + * call) after a delay of 1ms. + */ + +interface TempSuite { + generator: (driver: AuthDriver, browser: string) => void; + title: string; +} + +/** The browsers that these tests will run in */ +const BROWSERS = ['chrome', 'firefox']; + +/** One single AuthDriver instance to control everything */ +const DRIVER = new AuthDriver(); +const SUITES: TempSuite[] = []; + +/** Main entry point for all WebDriver tests */ +export function browserDescribe( + title: string, + generator: (driver: AuthDriver, browser: string) => void +): void { + SUITES.push({ + title, + generator + }); +} + +// Construct the final suites after a delay of 1ms, then kick off tests +setTimeout(() => { + for (const browser of BROWSERS) { + describe(`Testing in browser "${browser}"`, () => { + before(async function () { + this.timeout(20000); // Starting browsers can be slow. + await DRIVER.start(browser); + }); + + after(async () => { + await DRIVER.stop(); + }); + + // It's assumed that the tests will start with a clean slate (i.e. + // no storage). + beforeEach(async () => { + await DRIVER.closeExtraWindows(); + await DRIVER.reset(); + await DRIVER.injectConfigAndInitAuth(); + }); + + for (const { title, generator } of SUITES) { + describe(title, () => generator(DRIVER, browser)); + } + }); + } + + run(); +}, 1); diff --git a/packages-exp/auth-exp/test/integration/webdriver/util/test_server.ts b/packages-exp/auth-exp/test/integration/webdriver/util/test_server.ts new file mode 100644 index 00000000000..7d7e7ce057f --- /dev/null +++ b/packages-exp/auth-exp/test/integration/webdriver/util/test_server.ts @@ -0,0 +1,63 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as path from 'path'; +import * as express from 'express'; +import { Server } from 'http'; + +const PORT_NUMBER = '4100'; + +const INTEGRATION_TEST_ASSETS = express.static( + path.join( + // process.env.PWD == packages-exp/auth-exp + process.env.PWD!, + 'test/integration/webdriver/static' + ) +); + +/** Simple express server for serving up the static files for testing */ +class AuthTestServer { + private app = express(); + private server: Server | null = null; + + constructor() { + this.app.use([INTEGRATION_TEST_ASSETS]); + } + + get address(): string { + return `http://localhost:${PORT_NUMBER}`; + } + + async start(): Promise { + if (this.server) { + return; + } + + return new Promise(resolve => { + this.server = this.app.listen(PORT_NUMBER, resolve); + }); + } + + stop(): void { + if (this.server) { + this.server.close(); + this.server = null; + } + } +} + +export const authTestServer = new AuthTestServer(); diff --git a/packages-exp/auth-exp/test/integration/webdriver/util/ui_page.ts b/packages-exp/auth-exp/test/integration/webdriver/util/ui_page.ts new file mode 100644 index 00000000000..5c5489c3830 --- /dev/null +++ b/packages-exp/auth-exp/test/integration/webdriver/util/ui_page.ts @@ -0,0 +1,89 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { By, until, WebDriver } from 'selenium-webdriver'; + +const ANONYMOUS_IDP_BUTTON = By.css('button.firebaseui-idp-anonymous'); +const GOOGLE_IDP_BUTTON = By.css('button.firebaseui-idp-google'); +const PHONE_IDP_BUTTON = By.css('button.firebaseui-idp-phone'); +const EMAIL_IDP_BUTTON = By.css('button.firebaseui-idp-password'); +const PHONE_INPUT = By.css('input[name="phoneNumber"]'); +const SUBMIT_BUTTON = By.css('button.firebaseui-id-submit'); +const PHONE_CONFIRMATION_INPUT = By.css('input[name="phoneConfirmationCode"]'); +const EMAIL_INPUT = By.css('input[name="email"]'); +const EMAIL_NAME_INPUT = By.css('input[name="name"]'); +const EMAIL_PASSWORD_INPUT = By.css('input[type="password"]'); + +export class UiPage { + constructor(private readonly driver: WebDriver) {} + + async clickGuestSignIn(): Promise { + await this.driver.wait(until.elementLocated(ANONYMOUS_IDP_BUTTON)); + return this.driver.findElement(ANONYMOUS_IDP_BUTTON).click(); + } + + async clickGoogleSignIn(): Promise { + await this.driver.wait(until.elementLocated(GOOGLE_IDP_BUTTON)); + return this.driver.findElement(GOOGLE_IDP_BUTTON).click(); + } + + async clickPhoneSignIn(): Promise { + await this.driver.wait(until.elementLocated(PHONE_IDP_BUTTON)); + return this.driver.findElement(PHONE_IDP_BUTTON).click(); + } + + async clickEmailSignIn(): Promise { + await this.driver.wait(until.elementLocated(EMAIL_IDP_BUTTON)); + return this.driver.findElement(EMAIL_IDP_BUTTON).click(); + } + + async clickSubmit(): Promise { + await this.driver.wait(until.elementLocated(SUBMIT_BUTTON)); + return this.driver.findElement(SUBMIT_BUTTON).click(); + } + + async enterPhoneNumber(phoneNumber: string): Promise { + return this.fillInput(PHONE_INPUT, phoneNumber); + } + + async waitForCodeInputToBePresent(): Promise { + await this.driver.wait(until.elementLocated(PHONE_CONFIRMATION_INPUT)); + } + + async enterPhoneCode(code: string): Promise { + return this.fillInput(PHONE_CONFIRMATION_INPUT, code); + } + + async enterEmail(email: string): Promise { + return this.fillInput(EMAIL_INPUT, email); + } + + async enterEmailDisplayName(name: string): Promise { + return this.fillInput(EMAIL_NAME_INPUT, name); + } + + async enterPassword(name: string): Promise { + return this.fillInput(EMAIL_PASSWORD_INPUT, name); + } + + private async fillInput(input: By, text: string): Promise { + await this.driver.wait(until.elementLocated(input)); + const el = await this.driver.findElement(input); + await el.click(); + await el.sendKeys(text); + } +} diff --git a/packages-exp/auth-types-exp/README.md b/packages-exp/auth-types-exp/README.md deleted file mode 100644 index e63106206bf..00000000000 --- a/packages-exp/auth-types-exp/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# @firebase/auth-types-exp - -**This package is not intended for direct usage, and should only be used via the officially supported [firebase](https://www.npmjs.com/package/firebase) package.** diff --git a/packages-exp/auth-types-exp/api-extractor.json b/packages-exp/auth-types-exp/api-extractor.json deleted file mode 100644 index 42f37a88c4b..00000000000 --- a/packages-exp/auth-types-exp/api-extractor.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "../../config/api-extractor.json", - // Point it to your entry point d.ts file. - "mainEntryPointFilePath": "/index.d.ts" -} \ No newline at end of file diff --git a/packages-exp/auth-types-exp/index.d.ts b/packages-exp/auth-types-exp/index.d.ts deleted file mode 100644 index c24d133b7c8..00000000000 --- a/packages-exp/auth-types-exp/index.d.ts +++ /dev/null @@ -1,525 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - CompleteFn, - ErrorFn, - FirebaseError, - NextFn, - Observer, - Unsubscribe -} from '@firebase/util'; - -/** - * Supported providers - */ -export const enum ProviderId { - ANONYMOUS = 'anonymous', - CUSTOM = 'custom', - FACEBOOK = 'facebook.com', - FIREBASE = 'firebase', - GITHUB = 'github.com', - GOOGLE = 'google.com', - PASSWORD = 'password', - PHONE = 'phone', - TWITTER = 'twitter.com' -} - -/** - * Supported sign in methods - */ -export const enum SignInMethod { - ANONYMOUS = 'anonymous', - EMAIL_LINK = 'emailLink', - EMAIL_PASSWORD = 'password', - FACEBOOK = 'facebook.com', - GITHUB = 'github.com', - GOOGLE = 'google.com', - PHONE = 'phone', - TWITTER = 'twitter.com' -} - -/** - * Supported operation types - */ -export const enum OperationType { - LINK = 'link', - REAUTHENTICATE = 'reauthenticate', - SIGN_IN = 'signIn' -} - -/** - * Auth config object - */ -export interface Config { - apiKey: string; - apiHost: string; - apiScheme: string; - tokenApiHost: string; - sdkClientVersion: string; - authDomain?: string; -} - -/** - * Parsed Id Token - * - * TODO(avolkovi): consolidate with parsed_token in implementation - */ -export interface ParsedToken { - 'exp'?: string; - 'sub'?: string; - 'auth_time'?: string; - 'iat'?: string; - 'firebase'?: { - 'sign_in_provider'?: string; - 'sign_in_second_factor'?: string; - }; - [key: string]: string | object | undefined; -} - -/** - * TODO(avolkovi): should we consolidate with Subscribe since we're changing the API anyway? - */ -export type NextOrObserver = NextFn | Observer; - -/** - * https://firebase.google.com/docs/reference/js/firebase.auth.AuthError - */ -export interface AuthError extends FirebaseError { - readonly appName: string; - - readonly email?: string; - readonly phoneNumber?: string; - readonly tenantid?: string; -} - -/** - * https://firebase.google.com/docs/reference/js/firebase.auth.AuthSettings - */ -export interface AuthSettings { - appVerificationDisabledForTesting: boolean; -} - -/** - * https://firebase.google.com/docs/reference/js/firebase.auth.Auth - */ -export interface Auth { - readonly name: string; - readonly config: Config; - setPersistence(persistence: Persistence): void; - languageCode: string | null; - tenantId: string | null; - readonly settings: AuthSettings; - onAuthStateChanged( - nextOrObserver: NextOrObserver, - error?: ErrorFn, - completed?: CompleteFn - ): Unsubscribe; - onIdTokenChanged( - nextOrObserver: NextOrObserver, - error?: ErrorFn, - completed?: CompleteFn - ): Unsubscribe; - readonly currentUser: User | null; - updateCurrentUser(user: User | null): Promise; - useDeviceLanguage(): void; - useEmulator(url: string): void; - signOut(): Promise; -} - -/** - * https://firebase.google.com/docs/reference/js/firebase.auth.Auth#persistence - */ -export interface Persistence { - readonly type: 'SESSION' | 'LOCAL' | 'NONE'; -} - -/** - * Parsed IdToken for use in public API - * - * https://firebase.google.com/docs/reference/js/firebase.auth.IDTokenResult - */ -export interface IdTokenResult { - authTime: string; - expirationTime: string; - issuedAtTime: string; - signInProvider: string | null; - signInSecondFactor: string | null; - token: string; - claims: ParsedToken; -} - -/** - * https://firebase.google.com/docs/reference/js/firebase.auth.ActionCodeInfo - */ -export interface ActionCodeInfo { - data: { - email?: string | null; - multiFactorInfo?: MultiFactorInfo | null; - previousEmail?: string | null; - }; - operation: Operation; -} - -/** - * https://firebase.google.com/docs/reference/js/firebase.auth.ActionCodeInfo#operation_2 - */ -export const enum Operation { - EMAIL_SIGNIN = 'EMAIL_SIGNIN', - PASSWORD_RESET = 'PASSWORD_RESET', - RECOVER_EMAIL = 'RECOVER_EMAIL', - REVERT_SECOND_FACTOR_ADDITION = 'REVERT_SECOND_FACTOR_ADDITION', - VERIFY_AND_CHANGE_EMAIL = 'VERIFY_AND_CHANGE_EMAIL', - VERIFY_EMAIL = 'VERIFY_EMAIL' -} - -/** - * https://firebase.google.com/docs/reference/js/firebase.auth#actioncodesettings - */ -export interface ActionCodeSettings { - android?: { - installApp?: boolean; - minimumVersion?: string; - packageName: string; - }; - handleCodeInApp?: boolean; - iOS?: { - bundleId: string; - }; - url: string; - dynamicLinkDomain?: string; -} - -/** - * A utility class to parse email action URLs such as password reset, email verification, - * email link sign in, etc. - * - * @public - */ -export abstract class ActionCodeURL { - /** - * The API key of the email action link. - */ - readonly apiKey: string; - /** - * The action code of the email action link. - */ - readonly code: string; - /** - * The continue URL of the email action link. Null if not provided. - */ - readonly continueUrl: string | null; - /** - * The language code of the email action link. Null if not provided. - */ - readonly languageCode: string | null; - /** - * The action performed by the email action link. It returns from one of the types from {@link ActionCodeInfo} - */ - readonly operation: Operation; - /** - * The tenant ID of the email action link. Null if the email action is from the parent project. - */ - readonly tenantId: string | null; - - /** - * Parses the email action link string and returns an ActionCodeURL object if the link is valid, otherwise returns null. - * - * @param link - The email action link string. - * @returns The ActionCodeURL object, or null if the link is invalid. - * - * @public - */ - static parseLink(link: string): ActionCodeURL | null; -} - -/** - * https://firebase.google.com/docs/reference/js/firebase.auth.ApplicationVerifier - */ -export interface ApplicationVerifier { - readonly type: string; - verify(): Promise; -} - -/** - * https://firebase.google.com/docs/reference/js/firebase.auth.RecaptchaVerifier - */ -export abstract class RecaptchaVerifier implements ApplicationVerifier { - constructor( - container: any | string, - parameters?: Object | null, - auth?: Auth | null - ); - clear(): void; - render(): Promise; - readonly type: string; - verify(): Promise; -} - -/** - * https://firebase.google.com/docs/reference/js/firebase.auth.AuthCredential - */ -export abstract class AuthCredential { - static fromJSON(json: object | string): AuthCredential | null; - - readonly providerId: string; - readonly signInMethod: string; - toJSON(): object; -} - -/** - * https://firebase.google.com/docs/reference/js/firebase.auth.OAuthCredential - */ -export abstract class OAuthCredential extends AuthCredential { - static fromJSON(json: object | string): OAuthCredential | null; - - readonly accessToken?: string; - readonly idToken?: string; - readonly secret?: string; -} -/** - * https://firebase.google.com/docs/reference/js/firebase.auth.phoneauthcredential - */ -export abstract class PhoneAuthCredential extends AuthCredential { - static fromJSON(json: object | string): PhoneAuthCredential | null; -} - -/** - * A provider for generating credentials - * - * https://firebase.google.com/docs/reference/js/firebase.auth.AuthProvider - */ -export interface AuthProvider { - readonly providerId: string; -} - -/** - * A provider for generating email & password and email link credentials - * - * https://firebase.google.com/docs/reference/js/firebase.auth.EmailAuthProvider - */ -export abstract class EmailAuthProvider implements AuthProvider { - private constructor(); - static readonly PROVIDER_ID: ProviderId; - static readonly EMAIL_PASSWORD_SIGN_IN_METHOD: SignInMethod; - static readonly EMAIL_LINK_SIGN_IN_METHOD: SignInMethod; - static credential(email: string, password: string): AuthCredential; - static credentialWithLink( - auth: Auth, - email: string, - emailLink: string - ): AuthCredential; - readonly providerId: ProviderId; -} - -/** - * A provider for generating phone credentials - * - * https://firebase.google.com/docs/reference/js/firebase.auth.PhoneAuthProvider - */ -export class PhoneAuthProvider implements AuthProvider { - static readonly PROVIDER_ID: ProviderId; - static readonly PHONE_SIGN_IN_METHOD: SignInMethod; - static credential( - verificationId: string, - verificationCode: string - ): AuthCredential; - - constructor(auth?: Auth | null); - - readonly providerId: ProviderId; - - verifyPhoneNumber( - phoneInfoOptions: PhoneInfoOptions | string, - applicationVerifier: ApplicationVerifier - ): Promise; -} - -/** - * https://firebase.google.com/docs/reference/js/firebase.auth.ConfirmationResult - */ -export interface ConfirmationResult { - readonly verificationId: string; - confirm(verificationCode: string): Promise; -} - -/** - * https://firebase.google.com/docs/reference/js/firebase.auth.multifactorassertion - */ -export interface MultiFactorAssertion { - readonly factorId: string; -} - -/** - * https://firebase.google.com/docs/reference/js/firebase.auth.multifactorerror - */ -export interface MultiFactorError extends AuthError { - readonly credential: AuthCredential; - readonly operationType: OperationType; -} - -/** - * https://firebase.google.com/docs/reference/js/firebase.auth.multifactorinfo - */ -export interface MultiFactorInfo { - readonly uid: string; - readonly displayName?: string | null; - readonly enrollmentTime: string; - readonly factorId: ProviderId; -} - -/** - * https://firebase.google.com/docs/reference/js/firebase.auth.multifactorresolver - */ -export abstract class MultiFactorResolver { - hints: MultiFactorInfo[]; - session: MultiFactorSession; - resolveSignIn(assertion: MultiFactorAssertion): Promise; -} - -/** - * https://firebase.google.com/docs/reference/js/firebase.auth.multifactorsession - */ -export interface MultiFactorSession {} - -/** - * https://firebase.google.com/docs/reference/js/firebase.user.multifactoruser - */ -export interface MultiFactorUser { - readonly enrolledFactors: MultiFactorInfo[]; - getSession(): Promise; - enroll( - assertion: MultiFactorAssertion, - displayName?: string | null - ): Promise; - unenroll(option: MultiFactorInfo | string): Promise; -} - -/** - * https://firebase.google.com/docs/reference/js/firebase.auth.phonemultifactorassertion - */ -export interface PhoneMultiFactorAssertion extends MultiFactorAssertion {} - -/** - * https://firebase.google.com/docs/reference/js/firebase.auth.phonemultifactorgenerator - */ -export abstract class PhoneMultiFactorGenerator { - static FACTOR_ID: ProviderId; - static assertion( - phoneAuthCredential: PhoneAuthCredential - ): PhoneMultiFactorAssertion; -} - -/** - * https://firebase.google.com/docs/reference/js/firebase.auth#phoneinfooptions - */ -export type PhoneInfoOptions = - | PhoneSingleFactorInfoOptions - | PhoneMultiFactorEnrollInfoOptions - | PhoneMultiFactorSignInInfoOptions; - -export interface PhoneSingleFactorInfoOptions { - phoneNumber: string; -} - -export interface PhoneMultiFactorEnrollInfoOptions { - phoneNumber: string; - session: MultiFactorSession; -} - -export interface PhoneMultiFactorSignInInfoOptions { - multiFactorHint?: MultiFactorInfo; - multiFactorUid?: string; - session: MultiFactorSession; -} - -export interface ReactNativeAsyncStorage { - setItem(key: string, value: string): Promise; - getItem(key: string): Promise; - removeItem(key: string): Promise; -} - -/** - * https://firebase.google.com/docs/reference/js/firebase.User - */ -export interface User extends UserInfo { - readonly emailVerified: boolean; - readonly isAnonymous: boolean; - readonly metadata: UserMetadata; - readonly providerData: UserInfo[]; - readonly refreshToken: string; - readonly tenantId: string | null; - - delete(): Promise; - getIdToken(forceRefresh?: boolean): Promise; - getIdTokenResult(forceRefresh?: boolean): Promise; - reload(): Promise; - toJSON(): object; -} - -/** - * https://firebase.google.com/docs/reference/js/firebase.auth#usercredential - */ -export interface UserCredential { - user: User; - providerId: ProviderId | null; - operationType: OperationType; -} - -/** - * https://firebase.google.com/docs/reference/js/firebase.UserInfo - */ -export interface UserInfo { - readonly displayName: string | null; - readonly email: string | null; - readonly phoneNumber: string | null; - readonly photoURL: string | null; - readonly providerId: string; - readonly uid: string; -} - -/** - * https://firebase.google.com/docs/reference/js/firebase.auth.UserMetadata - */ -export interface UserMetadata { - readonly creationTime?: string; - readonly lastSignInTime?: string; -} - -/** - * Additional user information. - */ -export interface AdditionalUserInfo { - readonly isNewUser: boolean; - readonly profile: UserProfile | null; - readonly providerId: ProviderId | null; - readonly username?: string | null; -} - -/** - * User profile used in `AdditionalUserInfo` - */ -export type UserProfile = Record; - -/** No documentation for this yet */ -export interface PopupRedirectResolver {} - -declare module '@firebase/component' { - interface NameServiceMapping { - 'auth-exp': Auth; - } -} diff --git a/packages-exp/auth-types-exp/package.json b/packages-exp/auth-types-exp/package.json deleted file mode 100644 index e0a5b339fe7..00000000000 --- a/packages-exp/auth-types-exp/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "@firebase/auth-types-exp", - "private": true, - "version": "0.0.800", - "description": "@firebase/auth-exp Types", - "author": "Firebase (https://firebase.google.com/)", - "license": "Apache-2.0", - "scripts": { - "test": "tsc", - "api-report": "api-extractor run --local --verbose", - "predoc": "node ../../scripts/exp/remove-exp.js temp", - "doc": "api-documenter markdown --input temp --output docs", - "build:doc": "yarn api-report && yarn doc" - }, - "files": [ - "index.d.ts" - ], - "repository": { - "directory": "packages-exp/auth-types-exp", - "type": "git", - "url": "https://github.com/firebase/firebase-js-sdk.git" - }, - "bugs": { - "url": "https://github.com/firebase/firebase-js-sdk/issues" - }, - "devDependencies": { - "typescript": "4.0.2" - } -} diff --git a/packages-exp/firebase-exp/analytics/index.ts b/packages-exp/firebase-exp/analytics/index.ts new file mode 100644 index 00000000000..586d64db7af --- /dev/null +++ b/packages-exp/firebase-exp/analytics/index.ts @@ -0,0 +1,18 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from '@firebase/analytics-exp'; diff --git a/packages-exp/firebase-exp/analytics/package.json b/packages-exp/firebase-exp/analytics/package.json new file mode 100644 index 00000000000..07cba47df64 --- /dev/null +++ b/packages-exp/firebase-exp/analytics/package.json @@ -0,0 +1,7 @@ +{ + "name": "firebase-exp/analytics", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.esm.js", + "typings": "dist/analytics/index.d.ts" +} diff --git a/packages-exp/firebase-exp/app-check/index.ts b/packages-exp/firebase-exp/app-check/index.ts new file mode 100644 index 00000000000..85c036a330d --- /dev/null +++ b/packages-exp/firebase-exp/app-check/index.ts @@ -0,0 +1,18 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from '@firebase/app-check-exp'; diff --git a/packages-exp/firebase-exp/app-check/package.json b/packages-exp/firebase-exp/app-check/package.json new file mode 100644 index 00000000000..4b547cb9347 --- /dev/null +++ b/packages-exp/firebase-exp/app-check/package.json @@ -0,0 +1,7 @@ +{ + "name": "firebase-exp/app-check", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.esm.js", + "typings": "dist/app-check/index.d.ts" +} \ No newline at end of file diff --git a/packages-exp/firebase-exp/auth/index.ts b/packages-exp/firebase-exp/auth/index.ts new file mode 100644 index 00000000000..2505d8e8794 --- /dev/null +++ b/packages-exp/firebase-exp/auth/index.ts @@ -0,0 +1,17 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from '@firebase/auth-exp'; diff --git a/packages-exp/firebase-exp/auth/package.json b/packages-exp/firebase-exp/auth/package.json new file mode 100644 index 00000000000..a3874cfe19b --- /dev/null +++ b/packages-exp/firebase-exp/auth/package.json @@ -0,0 +1,7 @@ +{ + "name": "firebase-exp/auth", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.esm.js", + "typings": "dist/auth/index.d.ts" +} diff --git a/packages-exp/firebase-exp/compat/analytics/index.ts b/packages-exp/firebase-exp/compat/analytics/index.ts new file mode 100644 index 00000000000..81c6ed08c46 --- /dev/null +++ b/packages-exp/firebase-exp/compat/analytics/index.ts @@ -0,0 +1,18 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import '@firebase/analytics-compat'; diff --git a/packages-exp/firebase-exp/compat/analytics/package.json b/packages-exp/firebase-exp/compat/analytics/package.json new file mode 100644 index 00000000000..073bb7020d9 --- /dev/null +++ b/packages-exp/firebase-exp/compat/analytics/package.json @@ -0,0 +1,8 @@ +{ + "name": "firebase-exp/compat/analytics", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.esm.js", + "typings": "dist/compat/analytics/index.d.ts" + } + \ No newline at end of file diff --git a/packages-exp/firebase-exp/compat/app-check/index.ts b/packages-exp/firebase-exp/compat/app-check/index.ts new file mode 100644 index 00000000000..0ae5549d036 --- /dev/null +++ b/packages-exp/firebase-exp/compat/app-check/index.ts @@ -0,0 +1,18 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import '@firebase/app-check-compat'; diff --git a/packages-exp/firebase-exp/compat/app-check/package.json b/packages-exp/firebase-exp/compat/app-check/package.json new file mode 100644 index 00000000000..7bbceafa3e8 --- /dev/null +++ b/packages-exp/firebase-exp/compat/app-check/package.json @@ -0,0 +1,7 @@ +{ + "name": "firebase-exp/compat/app-check", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.esm.js", + "typings": "dist/compat/app-check/index.d.ts" +} diff --git a/packages-exp/firebase-exp/compat/app/package.json b/packages-exp/firebase-exp/compat/app/package.json index b4e10f5a47c..968d833c679 100644 --- a/packages-exp/firebase-exp/compat/app/package.json +++ b/packages-exp/firebase-exp/compat/app/package.json @@ -3,6 +3,6 @@ "main": "dist/index.cjs.js", "browser": "dist/index.esm.js", "module": "dist/index.esm.js", - "typings": "dist/compat/app/index.d.ts" + "typings": "../index.d.ts" } \ No newline at end of file diff --git a/packages-exp/firebase-exp/compat/auth/index.ts b/packages-exp/firebase-exp/compat/auth/index.ts new file mode 100644 index 00000000000..64b2abaccbe --- /dev/null +++ b/packages-exp/firebase-exp/compat/auth/index.ts @@ -0,0 +1,18 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import '@firebase/auth-compat'; diff --git a/packages-exp/firebase-exp/compat/auth/package.json b/packages-exp/firebase-exp/compat/auth/package.json new file mode 100644 index 00000000000..6fc48fbfced --- /dev/null +++ b/packages-exp/firebase-exp/compat/auth/package.json @@ -0,0 +1,8 @@ +{ + "name": "firebase-exp/compat/auth", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.esm.js", + "typings": "dist/compat/auth/index.d.ts" + } + \ No newline at end of file diff --git a/packages-exp/firebase-exp/compat/database/index.ts b/packages-exp/firebase-exp/compat/database/index.ts new file mode 100644 index 00000000000..f0dec0dca12 --- /dev/null +++ b/packages-exp/firebase-exp/compat/database/index.ts @@ -0,0 +1,18 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import '@firebase/database-compat'; diff --git a/packages-exp/firebase-exp/compat/database/package.json b/packages-exp/firebase-exp/compat/database/package.json new file mode 100644 index 00000000000..6d1facb28fe --- /dev/null +++ b/packages-exp/firebase-exp/compat/database/package.json @@ -0,0 +1,8 @@ +{ + "name": "firebase-exp/compat/database", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.esm.js", + "typings": "dist/compat/database/index.d.ts" + } + \ No newline at end of file diff --git a/packages-exp/firebase-exp/compat/firestore/index.ts b/packages-exp/firebase-exp/compat/firestore/index.ts new file mode 100644 index 00000000000..3608ac3d049 --- /dev/null +++ b/packages-exp/firebase-exp/compat/firestore/index.ts @@ -0,0 +1,18 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import '@firebase/firestore-compat'; diff --git a/packages-exp/firebase-exp/compat/firestore/package.json b/packages-exp/firebase-exp/compat/firestore/package.json new file mode 100644 index 00000000000..f8cbbf416d9 --- /dev/null +++ b/packages-exp/firebase-exp/compat/firestore/package.json @@ -0,0 +1,8 @@ +{ + "name": "firebase-exp/compat/firestore", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.esm.js", + "typings": "dist/compat/firestore/index.d.ts" + } + \ No newline at end of file diff --git a/packages-exp/firebase-exp/compat/functions/index.ts b/packages-exp/firebase-exp/compat/functions/index.ts new file mode 100644 index 00000000000..cc006ab7747 --- /dev/null +++ b/packages-exp/firebase-exp/compat/functions/index.ts @@ -0,0 +1,18 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import '@firebase/functions-compat'; diff --git a/packages-exp/firebase-exp/compat/functions/package.json b/packages-exp/firebase-exp/compat/functions/package.json new file mode 100644 index 00000000000..d876df9fd29 --- /dev/null +++ b/packages-exp/firebase-exp/compat/functions/package.json @@ -0,0 +1,8 @@ +{ + "name": "firebase-exp/compat/functions", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.esm.js", + "typings": "dist/compat/functions/index.d.ts" + } + \ No newline at end of file diff --git a/packages-exp/firebase-exp/compat/index.cdn.ts b/packages-exp/firebase-exp/compat/index.cdn.ts index 387883d1afd..71073b002b8 100644 --- a/packages-exp/firebase-exp/compat/index.cdn.ts +++ b/packages-exp/firebase-exp/compat/index.cdn.ts @@ -30,15 +30,16 @@ import '@firebase/polyfill'; import firebase from './app'; import { name, version } from '../package.json'; -// import './auth'; -// import './database'; -// import './firestore'; -// import './functions'; -// import './messaging'; -// import './storage'; -// import './performance'; -// import './analytics'; -// import './remote-config'; +import './analytics'; +import './app-check'; +import './auth'; +import './database'; +import './firestore'; +import './functions'; +import './messaging'; +import './storage'; +import './performance'; +import './remote-config'; firebase.registerVersion(name, version, 'compat-cdn'); diff --git a/packages-exp/firebase-exp/compat/index.d.ts b/packages-exp/firebase-exp/compat/index.d.ts new file mode 100644 index 00000000000..1312845e3b8 --- /dev/null +++ b/packages-exp/firebase-exp/compat/index.d.ts @@ -0,0 +1,10093 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * firebase is a global namespace from which all Firebase + * services are accessed. + */ +declare namespace firebase { + /** + * @hidden + */ + type NextFn = (value: T) => void; + /** + * @hidden + */ + type ErrorFn = (error: E) => void; + /** + * @hidden + */ + type CompleteFn = () => void; + + /** + * `FirebaseError` is a subclass of the standard JavaScript `Error` object. In + * addition to a message string and stack trace, it contains a string code. + */ + interface FirebaseError { + /** + * Error codes are strings using the following format: `"service/string-code"`. + * Some examples include `"app/no-app"` and `"auth/user-not-found"`. + * + * While the message for a given error can change, the code will remain the same + * between backward-compatible versions of the Firebase SDK. + */ + code: string; + /** + * An explanatory message for the error that just occurred. + * + * This message is designed to be helpful to you, the developer. Because + * it generally does not convey meaningful information to end users, + * this message should not be displayed in your application. + */ + message: string; + /** + * The name of the class of errors, which is `"FirebaseError"`. + */ + name: 'FirebaseError'; + /** + * A string value containing the execution backtrace when the error originally + * occurred. This may not always be available. + * + * When it is available, this information can be sent to + * {@link https://firebase.google.com/support/ Firebase Support} to help + * explain the cause of an error. + */ + stack?: string; + } + + /** + * @hidden + */ + interface Observer { + next: NextFn; + error: ErrorFn; + complete: CompleteFn; + } + + /** + * The JS SDK supports 5 log levels and also allows a user the ability to + * silence the logs altogether. + * + * The order is as follows: + * silent < debug < verbose < info < warn < error + */ + type LogLevel = 'debug' | 'verbose' | 'info' | 'warn' | 'error' | 'silent'; + + /** + * The current SDK version. + */ + var SDK_VERSION: string; + + /** + * Registers a library's name and version for platform logging purposes. + * @param library Name of 1p or 3p library (e.g. firestore, angularfire) + * @param version Current version of that library. + * @param variant Bundle variant, e.g., node, rn, etc. + */ + function registerVersion( + library: string, + version: string, + variant?: string + ): void; + + /** + * Sets log level for all Firebase packages. + * + * All of the log types above the current log level are captured (i.e. if + * you set the log level to `info`, errors are logged, but `debug` and + * `verbose` logs are not). + */ + function setLogLevel(logLevel: LogLevel): void; + + /** + * Sets log handler for all Firebase packages. + * @param logCallback An optional custom log handler that executes user code whenever + * the Firebase SDK makes a logging call. + */ + function onLog( + logCallback: (callbackParams: { + /** + * Level of event logged. + */ + level: LogLevel; + /** + * Any text from logged arguments joined into one string. + */ + message: string; + /** + * The raw arguments passed to the log call. + */ + args: any[]; + /** + * A string indicating the name of the package that made the log call, + * such as `@firebase/firestore`. + */ + type: string; + }) => void, + options?: { + /** + * Threshhold log level. Only logs at or above this level trigger the `logCallback` + * passed to `onLog`. + */ + level: LogLevel; + } + ): void; + + /** + * @hidden + */ + type Unsubscribe = () => void; + + /** + * A user account. + */ + interface User extends firebase.UserInfo { + /** + * Deletes and signs out the user. + * + * Important: this is a security-sensitive operation that requires the + * user to have recently signed in. If this requirement isn't met, ask the user + * to authenticate again and then call + * {@link firebase.User.reauthenticateWithCredential}. + * + *

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

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

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes (thrown synchronously)

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

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

Common Error Codes

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

Common Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

Error Codes

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

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

value event

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

child_added event

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

child_removed event

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

child_changed event

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

child_moved event

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

Note: this must be called before this instance has been used to do any operations. + * + * @param host the emulator host (ex: localhost). + * @param port the emulator port (ex: 9000). + */ + useEmulator(host: string, port: number): void; + + /** + * Attempts to enable persistent storage, if possible. + * + * Must be called before any other methods (other than settings() and + * clearPersistence()). + * + * If this fails, enablePersistence() will reject the promise it returns. + * Note that even after this failure, the firestore instance will remain + * usable, however offline persistence will be disabled. + * + * There are several reasons why this can fail, which can be identified by + * the `code` on the error. + * + * * failed-precondition: The app is already open in another browser tab. + * * unimplemented: The browser is incompatible with the offline + * persistence implementation. + * + * @param settings Optional settings object to configure persistence. + * @return A promise that represents successfully enabling persistent + * storage. + */ + enablePersistence(settings?: PersistenceSettings): Promise; + + /** + * Gets a `CollectionReference` instance that refers to the collection at + * the specified path. + * + * @param collectionPath A slash-separated path to a collection. + * @return The `CollectionReference` instance. + */ + collection(collectionPath: string): CollectionReference; + + /** + * Gets a `DocumentReference` instance that refers to the document at the + * specified path. + * + * @param documentPath A slash-separated path to a document. + * @return The `DocumentReference` instance. + */ + doc(documentPath: string): DocumentReference; + + /** + * Creates and returns a new Query that includes all documents in the + * database that are contained in a collection or subcollection with the + * given collectionId. + * + * @param collectionId Identifies the collections to query over. Every + * collection or subcollection with this ID as the last segment of its path + * will be included. Cannot contain a slash. + * @return The created Query. + */ + collectionGroup(collectionId: string): Query; + + /** + * Executes the given `updateFunction` and then attempts to commit the changes + * applied within the transaction. If any document read within the transaction + * has changed, Cloud Firestore retries the `updateFunction`. If it fails to + * commit after 5 attempts, the transaction fails. + * + * The maximum number of writes allowed in a single transaction is 500, but + * note that each usage of `FieldValue.serverTimestamp()`, + * `FieldValue.arrayUnion()`, `FieldValue.arrayRemove()`, or + * `FieldValue.increment()` inside a transaction counts as an additional write. + * + * @param updateFunction + * The function to execute within the transaction context. + * + * @return + * If the transaction completed successfully or was explicitly aborted + * (the `updateFunction` returned a failed promise), + * the promise returned by the updateFunction is returned here. Else, if the + * transaction failed, a rejected promise with the corresponding failure + * error will be returned. + */ + runTransaction( + updateFunction: (transaction: Transaction) => Promise + ): Promise; + + /** + * Creates a write batch, used for performing multiple writes as a single + * atomic operation. The maximum number of writes allowed in a single WriteBatch + * is 500, but note that each usage of `FieldValue.serverTimestamp()`, + * `FieldValue.arrayUnion()`, `FieldValue.arrayRemove()`, or + * `FieldValue.increment()` inside a WriteBatch counts as an additional write. + * + * @return + * A `WriteBatch` that can be used to atomically execute multiple writes. + */ + batch(): WriteBatch; + + /** + * The {@link firebase.app.App app} associated with this `Firestore` service + * instance. + */ + app: firebase.app.App; + + /** + * Clears the persistent storage. This includes pending writes and cached + * documents. + * + * Must be called while the firestore instance is not started (after the app + * is shutdown or when the app is first initialized). On startup, this + * method must be called before other methods (other than settings()). If + * the firestore instance is still running, the promise will be rejected + * with the error code of `failed-precondition`. + * + * Note: clearPersistence() is primarily intended to help write reliable + * tests that use Cloud Firestore. It uses an efficient mechanism for + * dropping existing data but does not attempt to securely overwrite or + * otherwise make cached data unrecoverable. For applications that are + * sensitive to the disclosure of cached data in between user sessions, we + * strongly recommend not enabling persistence at all. + * + * @return A promise that is resolved when the persistent storage is + * cleared. Otherwise, the promise is rejected with an error. + */ + clearPersistence(): Promise; + + /** + * Re-enables use of the network for this Firestore instance after a prior + * call to {@link firebase.firestore.Firestore.disableNetwork + * `disableNetwork()`}. + * + * @return A promise that is resolved once the network has been + * enabled. + */ + enableNetwork(): Promise; + + /** + * Disables network usage for this instance. It can be re-enabled via + * {@link firebase.firestore.Firestore.enableNetwork `enableNetwork()`}. While + * the network is disabled, any snapshot listeners or get() calls will return + * results from cache, and any write operations will be queued until the network + * is restored. + * + * @return A promise that is resolved once the network has been + * disabled. + */ + disableNetwork(): Promise; + + /** + * Waits until all currently pending writes for the active user have been acknowledged by the + * backend. + * + * The returned Promise resolves immediately if there are no outstanding writes. Otherwise, the + * Promise waits for all previously issued writes (including those written in a previous app + * session), but it does not wait for writes that were added after the method is called. If you + * want to wait for additional writes, call `waitForPendingWrites()` again. + * + * Any outstanding `waitForPendingWrites()` Promises are rejected during user changes. + * + * @return A Promise which resolves when all currently pending writes have been + * acknowledged by the backend. + */ + waitForPendingWrites(): Promise; + + /** + * Attaches a listener for a snapshots-in-sync event. The snapshots-in-sync + * event indicates that all listeners affected by a given change have fired, + * even if a single server-generated change affects multiple listeners. + * + * NOTE: The snapshots-in-sync event only indicates that listeners are in sync + * with each other, but does not relate to whether those snapshots are in sync + * with the server. Use SnapshotMetadata in the individual listeners to + * determine if a snapshot is from the cache or the server. + * + * @param observer A single object containing `next` and `error` callbacks. + * @return An unsubscribe function that can be called to cancel the snapshot + * listener. + */ + onSnapshotsInSync(observer: { + next?: (value: void) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + }): () => void; + + /** + * Attaches a listener for a snapshots-in-sync event. The snapshots-in-sync + * event indicates that all listeners affected by a given change have fired, + * even if a single server-generated change affects multiple listeners. + * + * NOTE: The snapshots-in-sync event only indicates that listeners are in sync + * with each other, but does not relate to whether those snapshots are in sync + * with the server. Use SnapshotMetadata in the individual listeners to + * determine if a snapshot is from the cache or the server. + * + * @param onSync A callback to be called every time all snapshot listeners are + * in sync with each other. + * @return An unsubscribe function that can be called to cancel the snapshot + * listener. + */ + onSnapshotsInSync(onSync: () => void): () => void; + + /** + * Terminates this Firestore instance. + * + * After calling `terminate()` only the `clearPersistence()` method may be used. Any other method + * will throw a `FirestoreError`. + * + * To restart after termination, create a new instance of FirebaseFirestore with + * `firebase.firestore()`. + * + * Termination does not cancel any pending writes, and any promises that are awaiting a response + * from the server will not be resolved. If you have persistence enabled, the next time you + * start this instance, it will resume sending these writes to the server. + * + * Note: Under normal circumstances, calling `terminate()` is not required. This + * method is useful only when you want to force this instance to release all of its resources or + * in combination with `clearPersistence()` to ensure that all local state is destroyed + * between test runs. + * + * @return A promise that is resolved when the instance has been successfully terminated. + */ + terminate(): Promise; + + /** + * Loads a Firestore bundle into the local cache. + * + * @param bundleData + * An object representing the bundle to be loaded. Valid objects are `ArrayBuffer`, + * `ReadableStream` or `string`. + * + * @return + * A `LoadBundleTask` object, which notifies callers with progress updates, and completion + * or error events. It can be used as a `Promise`. + */ + loadBundle( + bundleData: ArrayBuffer | ReadableStream | string + ): LoadBundleTask; + + /** + * Reads a Firestore `Query` from local cache, identified by the given name. + * + * The named queries are packaged into bundles on the server side (along + * with resulting documents), and loaded to local cache using `loadBundle`. Once in local + * cache, use this method to extract a `Query` by name. + */ + namedQuery(name: string): Promise | null>; + + /** + * @hidden + */ + INTERNAL: { delete: () => Promise }; + } + + /** + * Represents the task of loading a Firestore bundle. It provides progress of bundle + * loading, as well as task completion and error events. + * + * The API is compatible with `Promise`. + */ + export interface LoadBundleTask extends PromiseLike { + /** + * Registers functions to listen to bundle loading progress events. + * @param next + * Called when there is a progress update from bundle loading. Typically `next` calls occur + * each time a Firestore document is loaded from the bundle. + * @param error + * Called when an error occurs during bundle loading. The task aborts after reporting the + * error, and there should be no more updates after this. + * @param complete + * Called when the loading task is complete. + */ + onProgress( + next?: (progress: LoadBundleTaskProgress) => any, + error?: (error: Error) => any, + complete?: () => void + ): void; + + /** + * Implements the `Promise.then` interface. + * + * @param onFulfilled + * Called on the completion of the loading task with a final `LoadBundleTaskProgress` update. + * The update will always have its `taskState` set to `"Success"`. + * @param onRejected + * Called when an error occurs during bundle loading. + */ + then( + onFulfilled?: (a: LoadBundleTaskProgress) => T | PromiseLike, + onRejected?: (a: Error) => R | PromiseLike + ): Promise; + + /** + * Implements the `Promise.catch` interface. + * + * @param onRejected + * Called when an error occurs during bundle loading. + */ + catch( + onRejected: (a: Error) => R | PromiseLike + ): Promise; + } + + /** + * Represents a progress update or a final state from loading bundles. + */ + export interface LoadBundleTaskProgress { + /** How many documents have been loaded. */ + documentsLoaded: number; + /** How many documents are in the bundle being loaded. */ + totalDocuments: number; + /** How many bytes have been loaded. */ + bytesLoaded: number; + /** How many bytes are in the bundle being loaded. */ + totalBytes: number; + /** Current task state. */ + taskState: TaskState; + } + + /** + * Represents the state of bundle loading tasks. + * + * Both 'Error' and 'Success' are sinking state: task will abort or complete and there will + * be no more updates after they are reported. + */ + export type TaskState = 'Error' | 'Running' | 'Success'; + + /** + * An immutable object representing a geo point in Firestore. The geo point + * is represented as latitude/longitude pair. + * + * Latitude values are in the range of [-90, 90]. + * Longitude values are in the range of [-180, 180]. + */ + export class GeoPoint { + /** + * Creates a new immutable GeoPoint object with the provided latitude and + * longitude values. + * @param latitude The latitude as number between -90 and 90. + * @param longitude The longitude as number between -180 and 180. + */ + constructor(latitude: number, longitude: number); + + /** + * The latitude of this GeoPoint instance. + */ + readonly latitude: number; + /** + * The longitude of this GeoPoint instance. + */ + readonly longitude: number; + + /** + * Returns true if this `GeoPoint` is equal to the provided one. + * + * @param other The `GeoPoint` to compare against. + * @return true if this `GeoPoint` is equal to the provided one. + */ + isEqual(other: GeoPoint): boolean; + } + + /** + * A Timestamp represents a point in time independent of any time zone or + * calendar, represented as seconds and fractions of seconds at nanosecond + * resolution in UTC Epoch time. + * + * It is encoded using the Proleptic Gregorian + * Calendar which extends the Gregorian calendar backwards to year one. It is + * encoded assuming all minutes are 60 seconds long, i.e. leap seconds are + * "smeared" so that no leap second table is needed for interpretation. Range is + * from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. + * + * @see https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto + */ + export class Timestamp { + /** + * Creates a new timestamp. + * + * @param seconds The number of seconds of UTC time since Unix epoch + * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to + * 9999-12-31T23:59:59Z inclusive. + * @param nanoseconds The non-negative fractions of a second at nanosecond + * resolution. Negative second values with fractions must still have + * non-negative nanoseconds values that count forward in time. Must be + * from 0 to 999,999,999 inclusive. + */ + constructor(seconds: number, nanoseconds: number); + + /** + * Creates a new timestamp with the current date, with millisecond precision. + * + * @return a new timestamp representing the current date. + */ + static now(): Timestamp; + + /** + * Creates a new timestamp from the given date. + * + * @param date The date to initialize the `Timestamp` from. + * @return A new `Timestamp` representing the same point in time as the given + * date. + */ + static fromDate(date: Date): Timestamp; + + /** + * Creates a new timestamp from the given number of milliseconds. + * + * @param milliseconds Number of milliseconds since Unix epoch + * 1970-01-01T00:00:00Z. + * @return A new `Timestamp` representing the same point in time as the given + * number of milliseconds. + */ + static fromMillis(milliseconds: number): Timestamp; + + readonly seconds: number; + readonly nanoseconds: number; + + /** + * Convert a Timestamp to a JavaScript `Date` object. This conversion causes + * a loss of precision since `Date` objects only support millisecond precision. + * + * @return JavaScript `Date` object representing the same point in time as + * this `Timestamp`, with millisecond precision. + */ + toDate(): Date; + + /** + * Convert a timestamp to a numeric timestamp (in milliseconds since epoch). + * This operation causes a loss of precision. + * + * @return The point in time corresponding to this timestamp, represented as + * the number of milliseconds since Unix epoch 1970-01-01T00:00:00Z. + */ + toMillis(): number; + + /** + * Returns true if this `Timestamp` is equal to the provided one. + * + * @param other The `Timestamp` to compare against. + * @return true if this `Timestamp` is equal to the provided one. + */ + isEqual(other: Timestamp): boolean; + + /** + * Converts this object to a primitive string, which allows Timestamp objects to be compared + * using the `>`, `<=`, `>=` and `>` operators. + */ + valueOf(): string; + } + + /** + * An immutable object representing an array of bytes. + */ + export class Blob { + private constructor(); + + /** + * Creates a new Blob from the given Base64 string, converting it to + * bytes. + * + * @param base64 + * The Base64 string used to create the Blob object. + */ + static fromBase64String(base64: string): Blob; + + /** + * Creates a new Blob from the given Uint8Array. + * + * @param array + * The Uint8Array used to create the Blob object. + */ + static fromUint8Array(array: Uint8Array): Blob; + + /** + * Returns the bytes of a Blob as a Base64-encoded string. + * + * @return + * The Base64-encoded string created from the Blob object. + */ + public toBase64(): string; + + /** + * Returns the bytes of a Blob in a new Uint8Array. + * + * @return + * The Uint8Array created from the Blob object. + */ + public toUint8Array(): Uint8Array; + + /** + * Returns true if this `Blob` is equal to the provided one. + * + * @param other The `Blob` to compare against. + * @return true if this `Blob` is equal to the provided one. + */ + isEqual(other: Blob): boolean; + } + + /** + * A reference to a transaction. + * The `Transaction` object passed to a transaction's updateFunction provides + * the methods to read and write data within the transaction context. See + * `Firestore.runTransaction()`. + */ + export class Transaction { + private constructor(); + + /** + * Reads the document referenced by the provided `DocumentReference.` + * + * @param documentRef A reference to the document to be read. + * @return A DocumentSnapshot for the read data. + */ + get(documentRef: DocumentReference): Promise>; + + /** + * Writes to the document referred to by the provided `DocumentReference`. + * If the document does not exist yet, it will be created. If you pass + * `SetOptions`, the provided data can be merged into the existing document. + * + * @param documentRef A reference to the document to be set. + * @param data An object of the fields and values for the document. + * @param options An object to configure the set behavior. + * @return This `Transaction` instance. Used for chaining method calls. + */ + set( + documentRef: DocumentReference, + data: Partial, + options: SetOptions + ): Transaction; + + /** + * Writes to the document referred to by the provided `DocumentReference`. + * If the document does not exist yet, it will be created. If you pass + * `SetOptions`, the provided data can be merged into the existing document. + * + * @param documentRef A reference to the document to be set. + * @param data An object of the fields and values for the document. + * @return This `Transaction` instance. Used for chaining method calls. + */ + set(documentRef: DocumentReference, data: T): Transaction; + + /** + * Updates fields in the document referred to by the provided + * `DocumentReference`. The update will fail if applied to a document that + * does not exist. + * + * @param documentRef A reference to the document to be updated. + * @param data An object containing the fields and values with which to + * update the document. Fields can contain dots to reference nested fields + * within the document. + * @return This `Transaction` instance. Used for chaining method calls. + */ + update(documentRef: DocumentReference, data: UpdateData): Transaction; + + /** + * Updates fields in the document referred to by the provided + * `DocumentReference`. The update will fail if applied to a document that + * does not exist. + * + * Nested fields can be updated by providing dot-separated field path + * strings or by providing FieldPath objects. + * + * @param documentRef A reference to the document to be updated. + * @param field The first field to update. + * @param value The first value. + * @param moreFieldsAndValues Additional key/value pairs. + * @return A Promise resolved once the data has been successfully written + * to the backend (Note that it won't resolve while you're offline). + */ + update( + documentRef: DocumentReference, + field: string | FieldPath, + value: any, + ...moreFieldsAndValues: any[] + ): Transaction; + + /** + * Deletes the document referred to by the provided `DocumentReference`. + * + * @param documentRef A reference to the document to be deleted. + * @return This `Transaction` instance. Used for chaining method calls. + */ + delete(documentRef: DocumentReference): Transaction; + } + + /** + * A write batch, used to perform multiple writes as a single atomic unit. + * + * A `WriteBatch` object can be acquired by calling `Firestore.batch()`. It + * provides methods for adding writes to the write batch. None of the + * writes will be committed (or visible locally) until `WriteBatch.commit()` + * is called. + * + * Unlike transactions, write batches are persisted offline and therefore are + * preferable when you don't need to condition your writes on read data. + */ + export class WriteBatch { + private constructor(); + + /** + * Writes to the document referred to by the provided `DocumentReference`. + * If the document does not exist yet, it will be created. If you pass + * `SetOptions`, the provided data can be merged into the existing document. + * + * @param documentRef A reference to the document to be set. + * @param data An object of the fields and values for the document. + * @param options An object to configure the set behavior. + * @return This `WriteBatch` instance. Used for chaining method calls. + */ + set( + documentRef: DocumentReference, + data: Partial, + options: SetOptions + ): WriteBatch; + + /** + * Writes to the document referred to by the provided `DocumentReference`. + * If the document does not exist yet, it will be created. If you pass + * `SetOptions`, the provided data can be merged into the existing document. + * + * @param documentRef A reference to the document to be set. + * @param data An object of the fields and values for the document. + * @return This `WriteBatch` instance. Used for chaining method calls. + */ + set(documentRef: DocumentReference, data: T): WriteBatch; + + /** + * Updates fields in the document referred to by the provided + * `DocumentReference`. The update will fail if applied to a document that + * does not exist. + * + * @param documentRef A reference to the document to be updated. + * @param data An object containing the fields and values with which to + * update the document. Fields can contain dots to reference nested fields + * within the document. + * @return This `WriteBatch` instance. Used for chaining method calls. + */ + update(documentRef: DocumentReference, data: UpdateData): WriteBatch; + + /** + * Updates fields in the document referred to by this `DocumentReference`. + * The update will fail if applied to a document that does not exist. + * + * Nested fields can be update by providing dot-separated field path strings + * or by providing FieldPath objects. + * + * @param documentRef A reference to the document to be updated. + * @param field The first field to update. + * @param value The first value. + * @param moreFieldsAndValues Additional key value pairs. + * @return A Promise resolved once the data has been successfully written + * to the backend (Note that it won't resolve while you're offline). + */ + update( + documentRef: DocumentReference, + field: string | FieldPath, + value: any, + ...moreFieldsAndValues: any[] + ): WriteBatch; + + /** + * Deletes the document referred to by the provided `DocumentReference`. + * + * @param documentRef A reference to the document to be deleted. + * @return This `WriteBatch` instance. Used for chaining method calls. + */ + delete(documentRef: DocumentReference): WriteBatch; + + /** + * Commits all of the writes in this write batch as a single atomic unit. + * + * @return A Promise resolved once all of the writes in the batch have been + * successfully written to the backend as an atomic unit. Note that it won't + * resolve while you're offline. + */ + commit(): Promise; + } + + /** + * An options object that can be passed to `DocumentReference.onSnapshot()`, + * `Query.onSnapshot()` and `QuerySnapshot.docChanges()` to control which + * types of changes to include in the result set. + */ + export interface SnapshotListenOptions { + /** + * Include a change even if only the metadata of the query or of a document + * changed. Default is false. + */ + readonly includeMetadataChanges?: boolean; + } + + /** + * An options object that configures the behavior of `set()` calls in + * {@link firebase.firestore.DocumentReference.set DocumentReference}, {@link + * firebase.firestore.WriteBatch.set WriteBatch} and {@link + * firebase.firestore.Transaction.set Transaction}. These calls can be + * configured to perform granular merges instead of overwriting the target + * documents in their entirety by providing a `SetOptions` with `merge: true`. + */ + export interface SetOptions { + /** + * Changes the behavior of a set() call to only replace the values specified + * in its data argument. Fields omitted from the set() call remain + * untouched. + */ + readonly merge?: boolean; + + /** + * Changes the behavior of set() calls to only replace the specified field + * paths. Any field path that is not specified is ignored and remains + * untouched. + */ + readonly mergeFields?: (string | FieldPath)[]; + } + + /** + * An options object that configures the behavior of `get()` calls on + * `DocumentReference` and `Query`. By providing a `GetOptions` object, these + * methods can be configured to fetch results only from the server, only from + * the local cache or attempt to fetch results from the server and fall back to + * the cache (which is the default). + */ + export interface GetOptions { + /** + * Describes whether we should get from server or cache. + * + * Setting to `default` (or not setting at all), causes Firestore to try to + * retrieve an up-to-date (server-retrieved) snapshot, but fall back to + * returning cached data if the server can't be reached. + * + * Setting to `server` causes Firestore to avoid the cache, generating an + * error if the server cannot be reached. Note that the cache will still be + * updated if the server request succeeds. Also note that latency-compensation + * still takes effect, so any pending write operations will be visible in the + * returned data (merged into the server-provided data). + * + * Setting to `cache` causes Firestore to immediately return a value from the + * cache, ignoring the server completely (implying that the returned value + * may be stale with respect to the value on the server.) If there is no data + * in the cache to satisfy the `get()` call, `DocumentReference.get()` will + * return an error and `QuerySnapshot.get()` will return an empty + * `QuerySnapshot` with no documents. + */ + readonly source?: 'default' | 'server' | 'cache'; + } + + /** + * A `DocumentReference` refers to a document location in a Firestore database + * and can be used to write, read, or listen to the location. The document at + * the referenced location may or may not exist. A `DocumentReference` can + * also be used to create a `CollectionReference` to a subcollection. + */ + export class DocumentReference { + private constructor(); + + /** + * The document's identifier within its collection. + */ + readonly id: string; + + /** + * The {@link firebase.firestore.Firestore} the document is in. + * This is useful for performing transactions, for example. + */ + readonly firestore: Firestore; + + /** + * The Collection this `DocumentReference` belongs to. + */ + readonly parent: CollectionReference; + + /** + * A string representing the path of the referenced document (relative + * to the root of the database). + */ + readonly path: string; + + /** + * Gets a `CollectionReference` instance that refers to the collection at + * the specified path. + * + * @param collectionPath A slash-separated path to a collection. + * @return The `CollectionReference` instance. + */ + collection(collectionPath: string): CollectionReference; + + /** + * Returns true if this `DocumentReference` is equal to the provided one. + * + * @param other The `DocumentReference` to compare against. + * @return true if this `DocumentReference` is equal to the provided one. + */ + isEqual(other: DocumentReference): boolean; + + /** + * Writes to the document referred to by this `DocumentReference`. If the + * document does not yet exist, it will be created. If you pass + * `SetOptions`, the provided data can be merged into an existing document. + * + * @param data A map of the fields and values for the document. + * @param options An object to configure the set behavior. + * @return A Promise resolved once the data has been successfully written + * to the backend (Note that it won't resolve while you're offline). + */ + set(data: Partial, options: SetOptions): Promise; + + /** + * Writes to the document referred to by this `DocumentReference`. If the + * document does not yet exist, it will be created. If you pass + * `SetOptions`, the provided data can be merged into an existing document. + * + * @param data A map of the fields and values for the document. + * @return A Promise resolved once the data has been successfully written + * to the backend (Note that it won't resolve while you're offline). + */ + set(data: T): Promise; + + /** + * Updates fields in the document referred to by this `DocumentReference`. + * The update will fail if applied to a document that does not exist. + * + * @param data An object containing the fields and values with which to + * update the document. Fields can contain dots to reference nested fields + * within the document. + * @return A Promise resolved once the data has been successfully written + * to the backend (Note that it won't resolve while you're offline). + */ + update(data: UpdateData): Promise; + + /** + * Updates fields in the document referred to by this `DocumentReference`. + * The update will fail if applied to a document that does not exist. + * + * Nested fields can be updated by providing dot-separated field path + * strings or by providing FieldPath objects. + * + * @param field The first field to update. + * @param value The first value. + * @param moreFieldsAndValues Additional key value pairs. + * @return A Promise resolved once the data has been successfully written + * to the backend (Note that it won't resolve while you're offline). + */ + update( + field: string | FieldPath, + value: any, + ...moreFieldsAndValues: any[] + ): Promise; + + /** + * Deletes the document referred to by this `DocumentReference`. + * + * @return A Promise resolved once the document has been successfully + * deleted from the backend (Note that it won't resolve while you're + * offline). + */ + delete(): Promise; + + /** + * Reads the document referred to by this `DocumentReference`. + * + * Note: By default, get() attempts to provide up-to-date data when possible + * by waiting for data from the server, but it may return cached data or fail + * if you are offline and the server cannot be reached. This behavior can be + * altered via the `GetOptions` parameter. + * + * @param options An object to configure the get behavior. + * @return A Promise resolved with a DocumentSnapshot containing the + * current document contents. + */ + get(options?: GetOptions): Promise>; + + /** + * Attaches a listener for DocumentSnapshot events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param observer A single object containing `next` and `error` callbacks. + * @return An unsubscribe function that can be called to cancel + * the snapshot listener. + */ + onSnapshot(observer: { + next?: (snapshot: DocumentSnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + }): () => void; + /** + * Attaches a listener for DocumentSnapshot events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param options Options controlling the listen behavior. + * @param observer A single object containing `next` and `error` callbacks. + * @return An unsubscribe function that can be called to cancel + * the snapshot listener. + */ + onSnapshot( + options: SnapshotListenOptions, + observer: { + next?: (snapshot: DocumentSnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + } + ): () => void; + /** + * Attaches a listener for DocumentSnapshot events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param onNext A callback to be called every time a new `DocumentSnapshot` + * is available. + * @param onError A callback to be called if the listen fails or is + * cancelled. No further callbacks will occur. + * @return An unsubscribe function that can be called to cancel + * the snapshot listener. + */ + onSnapshot( + onNext: (snapshot: DocumentSnapshot) => void, + onError?: (error: FirestoreError) => void, + onCompletion?: () => void + ): () => void; + /** + * Attaches a listener for DocumentSnapshot events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param options Options controlling the listen behavior. + * @param onNext A callback to be called every time a new `DocumentSnapshot` + * is available. + * @param onError A callback to be called if the listen fails or is + * cancelled. No further callbacks will occur. + * @return An unsubscribe function that can be called to cancel + * the snapshot listener. + */ + onSnapshot( + options: SnapshotListenOptions, + onNext: (snapshot: DocumentSnapshot) => void, + onError?: (error: FirestoreError) => void, + onCompletion?: () => void + ): () => void; + + /** + * Applies a custom data converter to this DocumentReference, allowing you + * to use your own custom model objects with Firestore. When you call + * set(), get(), etc. on the returned DocumentReference instance, the + * provided converter will convert between Firestore data and your custom + * type U. + * + * Passing in `null` as the converter parameter removes the current + * converter. + * + * @param converter Converts objects to and from Firestore. Passing in + * `null` removes the current converter. + * @return A DocumentReference that uses the provided converter. + */ + withConverter(converter: null): DocumentReference; + /** + * Applies a custom data converter to this DocumentReference, allowing you + * to use your own custom model objects with Firestore. When you call + * set(), get(), etc. on the returned DocumentReference instance, the + * provided converter will convert between Firestore data and your custom + * type U. + * + * Passing in `null` as the converter parameter removes the current + * converter. + * + * @param converter Converts objects to and from Firestore. Passing in + * `null` removes the current converter. + * @return A DocumentReference that uses the provided converter. + */ + withConverter( + converter: FirestoreDataConverter + ): DocumentReference; + } + + /** + * Options that configure how data is retrieved from a `DocumentSnapshot` + * (e.g. the desired behavior for server timestamps that have not yet been set + * to their final value). + */ + export interface SnapshotOptions { + /** + * If set, controls the return value for server timestamps that have not yet + * been set to their final value. + * + * By specifying 'estimate', pending server timestamps return an estimate + * based on the local clock. This estimate will differ from the final value + * and cause these values to change once the server result becomes available. + * + * By specifying 'previous', pending timestamps will be ignored and return + * their previous value instead. + * + * If omitted or set to 'none', `null` will be returned by default until the + * server value becomes available. + */ + readonly serverTimestamps?: 'estimate' | 'previous' | 'none'; + } + + /** + * Metadata about a snapshot, describing the state of the snapshot. + */ + export interface SnapshotMetadata { + /** + * True if the snapshot contains the result of local writes (e.g. set() or + * update() calls) that have not yet been committed to the backend. + * If your listener has opted into metadata updates (via + * `SnapshotListenOptions`) you will receive another + * snapshot with `hasPendingWrites` equal to false once the writes have been + * committed to the backend. + */ + readonly hasPendingWrites: boolean; + + /** + * True if the snapshot was created from cached data rather than guaranteed + * up-to-date server data. If your listener has opted into metadata updates + * (via `SnapshotListenOptions`) + * you will receive another snapshot with `fromCache` set to false once + * the client has received up-to-date data from the backend. + */ + readonly fromCache: boolean; + + /** + * Returns true if this `SnapshotMetadata` is equal to the provided one. + * + * @param other The `SnapshotMetadata` to compare against. + * @return true if this `SnapshotMetadata` is equal to the provided one. + */ + isEqual(other: SnapshotMetadata): boolean; + } + + /** + * A `DocumentSnapshot` contains data read from a document in your Firestore + * database. The data can be extracted with `.data()` or `.get()` to + * get a specific field. + * + * For a `DocumentSnapshot` that points to a non-existing document, any data + * access will return 'undefined'. You can use the `exists` property to + * explicitly verify a document's existence. + */ + export class DocumentSnapshot { + protected constructor(); + + /** + * Property of the `DocumentSnapshot` that signals whether or not the data + * exists. True if the document exists. + */ + readonly exists: boolean; + /** + * The `DocumentReference` for the document included in the `DocumentSnapshot`. + */ + readonly ref: DocumentReference; + /** + * Property of the `DocumentSnapshot` that provides the document's ID. + */ + readonly id: string; + /** + * Metadata about the `DocumentSnapshot`, including information about its + * source and local modifications. + */ + readonly metadata: SnapshotMetadata; + + /** + * Retrieves all fields in the document as an Object. Returns 'undefined' if + * the document doesn't exist. + * + * By default, `FieldValue.serverTimestamp()` values that have not yet been + * set to their final value will be returned as `null`. You can override + * this by passing an options object. + * + * @param options An options object to configure how data is retrieved from + * the snapshot (e.g. the desired behavior for server timestamps that have + * not yet been set to their final value). + * @return An Object containing all fields in the document or 'undefined' if + * the document doesn't exist. + */ + data(options?: SnapshotOptions): T | undefined; + + /** + * Retrieves the field specified by `fieldPath`. Returns `undefined` if the + * document or field doesn't exist. + * + * By default, a `FieldValue.serverTimestamp()` that has not yet been set to + * its final value will be returned as `null`. You can override this by + * passing an options object. + * + * @param fieldPath The path (e.g. 'foo' or 'foo.bar') to a specific field. + * @param options An options object to configure how the field is retrieved + * from the snapshot (e.g. the desired behavior for server timestamps that have + * not yet been set to their final value). + * @return The data at the specified field location or undefined if no such + * field exists in the document. + */ + get(fieldPath: string | FieldPath, options?: SnapshotOptions): any; + + /** + * Returns true if this `DocumentSnapshot` is equal to the provided one. + * + * @param other The `DocumentSnapshot` to compare against. + * @return true if this `DocumentSnapshot` is equal to the provided one. + */ + isEqual(other: DocumentSnapshot): boolean; + } + + /** + * A `QueryDocumentSnapshot` contains data read from a document in your + * Firestore database as part of a query. The document is guaranteed to exist + * and its data can be extracted with `.data()` or `.get()` to get a + * specific field. + * + * A `QueryDocumentSnapshot` offers the same API surface as a + * `DocumentSnapshot`. Since query results contain only existing documents, the + * `exists` property will always be true and `data()` will never return + * 'undefined'. + */ + export class QueryDocumentSnapshot< + T = DocumentData + > extends DocumentSnapshot { + private constructor(); + + /** + * Retrieves all fields in the document as an Object. + * + * By default, `FieldValue.serverTimestamp()` values that have not yet been + * set to their final value will be returned as `null`. You can override + * this by passing an options object. + * + * @override + * @param options An options object to configure how data is retrieved from + * the snapshot (e.g. the desired behavior for server timestamps that have + * not yet been set to their final value). + * @return An Object containing all fields in the document. + */ + data(options?: SnapshotOptions): T; + } + + /** + * The direction of a `Query.orderBy()` clause is specified as 'desc' or 'asc' + * (descending or ascending). + */ + export type OrderByDirection = 'desc' | 'asc'; + + /** + * Filter conditions in a `Query.where()` clause are specified using the + * strings '<', '<=', '==', '!=', '>=', '>', 'array-contains', 'in', + * 'array-contains-any', and 'not-in'. + */ + export type WhereFilterOp = + | '<' + | '<=' + | '==' + | '!=' + | '>=' + | '>' + | 'array-contains' + | 'in' + | 'array-contains-any' + | 'not-in'; + + /** + * A `Query` refers to a Query which you can read or listen to. You can also + * construct refined `Query` objects by adding filters and ordering. + */ + export class Query { + protected constructor(); + + /** + * The `Firestore` for the Firestore database (useful for performing + * transactions, etc.). + */ + readonly firestore: Firestore; + + /** + * Creates and returns a new Query with the additional filter that documents + * must contain the specified field and the value should satisfy the + * relation constraint provided. + * + * @param fieldPath The path to compare + * @param opStr The operation string (e.g "<", "<=", "==", ">", ">="). + * @param value The value for comparison + * @return The created Query. + */ + where( + fieldPath: string | FieldPath, + opStr: WhereFilterOp, + value: any + ): Query; + + /** + * Creates and returns a new Query that's additionally sorted by the + * specified field, optionally in descending order instead of ascending. + * + * @param fieldPath The field to sort by. + * @param directionStr Optional direction to sort by (`asc` or `desc`). If + * not specified, order will be ascending. + * @return The created Query. + */ + orderBy( + fieldPath: string | FieldPath, + directionStr?: OrderByDirection + ): Query; + + /** + * Creates and returns a new Query that only returns the first matching + * documents. + * + * @param limit The maximum number of items to return. + * @return The created Query. + */ + limit(limit: number): Query; + + /** + * Creates and returns a new Query that only returns the last matching + * documents. + * + * You must specify at least one `orderBy` clause for `limitToLast` queries, + * otherwise an exception will be thrown during execution. + * + * @param limit The maximum number of items to return. + * @return The created Query. + */ + limitToLast(limit: number): Query; + + /** + * Creates and returns a new Query that starts at the provided document + * (inclusive). The starting position is relative to the order of the query. + * The document must contain all of the fields provided in the `orderBy` of + * this query. + * + * @param snapshot The snapshot of the document to start at. + * @return The created Query. + */ + startAt(snapshot: DocumentSnapshot): Query; + + /** + * Creates and returns a new Query that starts at the provided fields + * relative to the order of the query. The order of the field values + * must match the order of the order by clauses of the query. + * + * @param fieldValues The field values to start this query at, in order + * of the query's order by. + * @return The created Query. + */ + startAt(...fieldValues: any[]): Query; + + /** + * Creates and returns a new Query that starts after the provided document + * (exclusive). The starting position is relative to the order of the query. + * The document must contain all of the fields provided in the orderBy of + * this query. + * + * @param snapshot The snapshot of the document to start after. + * @return The created Query. + */ + startAfter(snapshot: DocumentSnapshot): Query; + + /** + * Creates and returns a new Query that starts after the provided fields + * relative to the order of the query. The order of the field values + * must match the order of the order by clauses of the query. + * + * @param fieldValues The field values to start this query after, in order + * of the query's order by. + * @return The created Query. + */ + startAfter(...fieldValues: any[]): Query; + + /** + * Creates and returns a new Query that ends before the provided document + * (exclusive). The end position is relative to the order of the query. The + * document must contain all of the fields provided in the orderBy of this + * query. + * + * @param snapshot The snapshot of the document to end before. + * @return The created Query. + */ + endBefore(snapshot: DocumentSnapshot): Query; + + /** + * Creates and returns a new Query that ends before the provided fields + * relative to the order of the query. The order of the field values + * must match the order of the order by clauses of the query. + * + * @param fieldValues The field values to end this query before, in order + * of the query's order by. + * @return The created Query. + */ + endBefore(...fieldValues: any[]): Query; + + /** + * Creates and returns a new Query that ends at the provided document + * (inclusive). The end position is relative to the order of the query. The + * document must contain all of the fields provided in the orderBy of this + * query. + * + * @param snapshot The snapshot of the document to end at. + * @return The created Query. + */ + endAt(snapshot: DocumentSnapshot): Query; + + /** + * Creates and returns a new Query that ends at the provided fields + * relative to the order of the query. The order of the field values + * must match the order of the order by clauses of the query. + * + * @param fieldValues The field values to end this query at, in order + * of the query's order by. + * @return The created Query. + */ + endAt(...fieldValues: any[]): Query; + + /** + * Returns true if this `Query` is equal to the provided one. + * + * @param other The `Query` to compare against. + * @return true if this `Query` is equal to the provided one. + */ + isEqual(other: Query): boolean; + + /** + * Executes the query and returns the results as a `QuerySnapshot`. + * + * Note: By default, get() attempts to provide up-to-date data when possible + * by waiting for data from the server, but it may return cached data or fail + * if you are offline and the server cannot be reached. This behavior can be + * altered via the `GetOptions` parameter. + * + * @param options An object to configure the get behavior. + * @return A Promise that will be resolved with the results of the Query. + */ + get(options?: GetOptions): Promise>; + + /** + * Attaches a listener for QuerySnapshot events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. The listener can be cancelled by + * calling the function that is returned when `onSnapshot` is called. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param observer A single object containing `next` and `error` callbacks. + * @return An unsubscribe function that can be called to cancel + * the snapshot listener. + */ + onSnapshot(observer: { + next?: (snapshot: QuerySnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + }): () => void; + /** + * Attaches a listener for QuerySnapshot events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. The listener can be cancelled by + * calling the function that is returned when `onSnapshot` is called. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param options Options controlling the listen behavior. + * @param observer A single object containing `next` and `error` callbacks. + * @return An unsubscribe function that can be called to cancel + * the snapshot listener. + */ + onSnapshot( + options: SnapshotListenOptions, + observer: { + next?: (snapshot: QuerySnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + } + ): () => void; + /** + * Attaches a listener for QuerySnapshot events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. The listener can be cancelled by + * calling the function that is returned when `onSnapshot` is called. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param onNext A callback to be called every time a new `QuerySnapshot` + * is available. + * @param onError A callback to be called if the listen fails or is + * cancelled. No further callbacks will occur. + * @return An unsubscribe function that can be called to cancel + * the snapshot listener. + */ + onSnapshot( + onNext: (snapshot: QuerySnapshot) => void, + onError?: (error: FirestoreError) => void, + onCompletion?: () => void + ): () => void; + /** + * Attaches a listener for QuerySnapshot events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. The listener can be cancelled by + * calling the function that is returned when `onSnapshot` is called. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param options Options controlling the listen behavior. + * @param onNext A callback to be called every time a new `QuerySnapshot` + * is available. + * @param onError A callback to be called if the listen fails or is + * cancelled. No further callbacks will occur. + * @return An unsubscribe function that can be called to cancel + * the snapshot listener. + */ + onSnapshot( + options: SnapshotListenOptions, + onNext: (snapshot: QuerySnapshot) => void, + onError?: (error: FirestoreError) => void, + onCompletion?: () => void + ): () => void; + + /** + * Applies a custom data converter to this Query, allowing you to use your + * own custom model objects with Firestore. When you call get() on the + * returned Query, the provided converter will convert between Firestore + * data and your custom type U. + * + * Passing in `null` as the converter parameter removes the current + * converter. + * + * @param converter Converts objects to and from Firestore. Passing in + * `null` removes the current converter. + * @return A Query that uses the provided converter. + */ + withConverter(converter: null): Query; + /** + * Applies a custom data converter to this Query, allowing you to use your + * own custom model objects with Firestore. When you call get() on the + * returned Query, the provided converter will convert between Firestore + * data and your custom type U. + * + * Passing in `null` as the converter parameter removes the current + * converter. + * + * @param converter Converts objects to and from Firestore. Passing in + * `null` removes the current converter. + * @return A Query that uses the provided converter. + */ + withConverter(converter: FirestoreDataConverter): Query; + } + + /** + * A `QuerySnapshot` contains zero or more `DocumentSnapshot` objects + * representing the results of a query. The documents can be accessed as an + * array via the `docs` property or enumerated using the `forEach` method. The + * number of documents can be determined via the `empty` and `size` + * properties. + */ + export class QuerySnapshot { + private constructor(); + + /** + * The query on which you called `get` or `onSnapshot` in order to get this + * `QuerySnapshot`. + */ + readonly query: Query; + /** + * Metadata about this snapshot, concerning its source and if it has local + * modifications. + */ + readonly metadata: SnapshotMetadata; + + /** An array of all the documents in the `QuerySnapshot`. */ + readonly docs: Array>; + + /** The number of documents in the `QuerySnapshot`. */ + readonly size: number; + + /** True if there are no documents in the `QuerySnapshot`. */ + readonly empty: boolean; + + /** + * Returns an array of the documents changes since the last snapshot. If this + * is the first snapshot, all documents will be in the list as added changes. + * + * @param options `SnapshotListenOptions` that control whether metadata-only + * changes (i.e. only `DocumentSnapshot.metadata` changed) should trigger + * snapshot events. + */ + docChanges(options?: SnapshotListenOptions): Array>; + + /** + * Enumerates all of the documents in the `QuerySnapshot`. + * + * @param callback A callback to be called with a `QueryDocumentSnapshot` for + * each document in the snapshot. + * @param thisArg The `this` binding for the callback. + */ + forEach( + callback: (result: QueryDocumentSnapshot) => void, + thisArg?: any + ): void; + + /** + * Returns true if this `QuerySnapshot` is equal to the provided one. + * + * @param other The `QuerySnapshot` to compare against. + * @return true if this `QuerySnapshot` is equal to the provided one. + */ + isEqual(other: QuerySnapshot): boolean; + } + + /** + * The type of a `DocumentChange` may be 'added', 'removed', or 'modified'. + */ + export type DocumentChangeType = 'added' | 'removed' | 'modified'; + + /** + * A `DocumentChange` represents a change to the documents matching a query. + * It contains the document affected and the type of change that occurred. + */ + export interface DocumentChange { + /** The type of change ('added', 'modified', or 'removed'). */ + readonly type: DocumentChangeType; + + /** The document affected by this change. */ + readonly doc: QueryDocumentSnapshot; + + /** + * The index of the changed document in the result set immediately prior to + * this `DocumentChange` (i.e. supposing that all prior `DocumentChange` objects + * have been applied). Is -1 for 'added' events. + */ + readonly oldIndex: number; + + /** + * The index of the changed document in the result set immediately after + * this `DocumentChange` (i.e. supposing that all prior `DocumentChange` + * objects and the current `DocumentChange` object have been applied). + * Is -1 for 'removed' events. + */ + readonly newIndex: number; + } + + /** + * A `CollectionReference` object can be used for adding documents, getting + * document references, and querying for documents (using the methods + * inherited from `Query`). + */ + export class CollectionReference extends Query { + private constructor(); + + /** The collection's identifier. */ + readonly id: string; + + /** + * A reference to the containing `DocumentReference` if this is a subcollection. + * If this isn't a subcollection, the reference is null. + */ + readonly parent: DocumentReference | null; + + /** + * A string representing the path of the referenced collection (relative + * to the root of the database). + */ + readonly path: string; + + /** + * Get a `DocumentReference` for the document within the collection at the + * specified path. If no path is specified, an automatically-generated + * unique ID will be used for the returned DocumentReference. + * + * @param documentPath A slash-separated path to a document. + * @return The `DocumentReference` instance. + */ + doc(documentPath?: string): DocumentReference; + + /** + * Add a new document to this collection with the specified data, assigning + * it a document ID automatically. + * + * @param data An Object containing the data for the new document. + * @return A Promise resolved with a `DocumentReference` pointing to the + * newly created document after it has been written to the backend. + */ + add(data: T): Promise>; + + /** + * Returns true if this `CollectionReference` is equal to the provided one. + * + * @param other The `CollectionReference` to compare against. + * @return true if this `CollectionReference` is equal to the provided one. + */ + isEqual(other: CollectionReference): boolean; + + /** + * Applies a custom data converter to this CollectionReference, allowing you + * to use your own custom model objects with Firestore. When you call add() + * on the returned CollectionReference instance, the provided converter will + * convert between Firestore data and your custom type U. + * + * Passing in `null` as the converter parameter removes the current + * converter. + * + * @param converter Converts objects to and from Firestore. Passing in + * `null` removes the current converter. + * @return A CollectionReference that uses the provided converter. + */ + withConverter(converter: null): CollectionReference; + /** + * Applies a custom data converter to this CollectionReference, allowing you + * to use your own custom model objects with Firestore. When you call add() + * on the returned CollectionReference instance, the provided converter will + * convert between Firestore data and your custom type U. + * + * Passing in `null` as the converter parameter removes the current + * converter. + * + * @param converter Converts objects to and from Firestore. Passing in + * `null` removes the current converter. + * @return A CollectionReference that uses the provided converter. + */ + withConverter( + converter: FirestoreDataConverter + ): CollectionReference; + } + + /** + * Sentinel values that can be used when writing document fields with `set()` + * or `update()`. + */ + export class FieldValue { + private constructor(); + + /** + * Returns a sentinel used with `set()` or `update()` to include a + * server-generated timestamp in the written data. + */ + static serverTimestamp(): FieldValue; + + /** + * Returns a sentinel for use with `update()` to mark a field for deletion. + */ + static delete(): FieldValue; + + /** + * Returns a special value that can be used with `set()` or `update()` that tells + * the server to union the given elements with any array value that already + * exists on the server. Each specified element that doesn't already exist in + * the array will be added to the end. If the field being modified is not + * already an array it will be overwritten with an array containing exactly + * the specified elements. + * + * @param elements The elements to union into the array. + * @return The FieldValue sentinel for use in a call to `set()` or `update()`. + */ + static arrayUnion(...elements: any[]): FieldValue; + + /** + * Returns a special value that can be used with `set()` or `update()` that tells + * the server to remove the given elements from any array value that already + * exists on the server. All instances of each element specified will be + * removed from the array. If the field being modified is not already an + * array it will be overwritten with an empty array. + * + * @param elements The elements to remove from the array. + * @return The FieldValue sentinel for use in a call to `set()` or `update()`. + */ + static arrayRemove(...elements: any[]): FieldValue; + + /** + * Returns a special value that can be used with `set()` or `update()` that tells + * the server to increment the field's current value by the given value. + * + * If either the operand or the current field value uses floating point precision, + * all arithmetic follows IEEE 754 semantics. If both values are integers, + * values outside of JavaScript's safe number range (`Number.MIN_SAFE_INTEGER` to + * `Number.MAX_SAFE_INTEGER`) are also subject to precision loss. Furthermore, + * once processed by the Firestore backend, all integer operations are capped + * between -2^63 and 2^63-1. + * + * If the current field value is not of type `number`, or if the field does not + * yet exist, the transformation sets the field to the given value. + * + * @param n The value to increment by. + * @return The FieldValue sentinel for use in a call to `set()` or `update()`. + */ + static increment(n: number): FieldValue; + + /** + * Returns true if this `FieldValue` is equal to the provided one. + * + * @param other The `FieldValue` to compare against. + * @return true if this `FieldValue` is equal to the provided one. + */ + isEqual(other: FieldValue): boolean; + } + + /** + * A FieldPath refers to a field in a document. The path may consist of a + * single field name (referring to a top-level field in the document), or a + * list of field names (referring to a nested field in the document). + * + * Create a FieldPath by providing field names. If more than one field + * name is provided, the path will point to a nested field in a document. + * + */ + export class FieldPath { + /** + * Creates a FieldPath from the provided field names. If more than one field + * name is provided, the path will point to a nested field in a document. + * + * @param fieldNames A list of field names. + */ + constructor(...fieldNames: string[]); + + /** + * Returns a special sentinel `FieldPath` to refer to the ID of a document. + * It can be used in queries to sort or filter by the document ID. + */ + static documentId(): FieldPath; + + /** + * Returns true if this `FieldPath` is equal to the provided one. + * + * @param other The `FieldPath` to compare against. + * @return true if this `FieldPath` is equal to the provided one. + */ + isEqual(other: FieldPath): boolean; + } + + /** + * The set of Firestore status codes. The codes are the same at the ones + * exposed by gRPC here: + * https://github.com/grpc/grpc/blob/master/doc/statuscodes.md + * + * Possible values: + * - 'cancelled': The operation was cancelled (typically by the caller). + * - 'unknown': Unknown error or an error from a different error domain. + * - 'invalid-argument': Client specified an invalid argument. Note that this + * differs from 'failed-precondition'. 'invalid-argument' indicates + * arguments that are problematic regardless of the state of the system + * (e.g. an invalid field name). + * - 'deadline-exceeded': Deadline expired before operation could complete. + * For operations that change the state of the system, this error may be + * returned even if the operation has completed successfully. For example, + * a successful response from a server could have been delayed long enough + * for the deadline to expire. + * - 'not-found': Some requested document was not found. + * - 'already-exists': Some document that we attempted to create already + * exists. + * - 'permission-denied': The caller does not have permission to execute the + * specified operation. + * - 'resource-exhausted': Some resource has been exhausted, perhaps a + * per-user quota, or perhaps the entire file system is out of space. + * - 'failed-precondition': Operation was rejected because the system is not + * in a state required for the operation's execution. + * - 'aborted': The operation was aborted, typically due to a concurrency + * issue like transaction aborts, etc. + * - 'out-of-range': Operation was attempted past the valid range. + * - 'unimplemented': Operation is not implemented or not supported/enabled. + * - 'internal': Internal errors. Means some invariants expected by + * underlying system has been broken. If you see one of these errors, + * something is very broken. + * - 'unavailable': The service is currently unavailable. This is most likely + * a transient condition and may be corrected by retrying with a backoff. + * - 'data-loss': Unrecoverable data loss or corruption. + * - 'unauthenticated': The request does not have valid authentication + * credentials for the operation. + */ + export type FirestoreErrorCode = + | 'cancelled' + | 'unknown' + | 'invalid-argument' + | 'deadline-exceeded' + | 'not-found' + | 'already-exists' + | 'permission-denied' + | 'resource-exhausted' + | 'failed-precondition' + | 'aborted' + | 'out-of-range' + | 'unimplemented' + | 'internal' + | 'unavailable' + | 'data-loss' + | 'unauthenticated'; + + /** An error returned by a Firestore operation. */ + // TODO(b/63008957): FirestoreError should extend firebase.FirebaseError + export interface FirestoreError { + code: FirestoreErrorCode; + message: string; + name: string; + stack?: string; + } +} + +export default firebase; +export as namespace firebase; diff --git a/packages-exp/firebase-exp/compat/index.node.ts b/packages-exp/firebase-exp/compat/index.node.ts index ad532d78a4a..a7442d90f28 100644 --- a/packages-exp/firebase-exp/compat/index.node.ts +++ b/packages-exp/firebase-exp/compat/index.node.ts @@ -18,10 +18,10 @@ import firebase from './app'; import { name, version } from '../package.json'; -// import './auth'; -// import './database'; -// import './firestore'; -// import './functions'; +import './auth'; +import './database'; +import './firestore'; +import './functions'; firebase.registerVersion(name, version, 'compat-node'); diff --git a/packages-exp/firebase-exp/compat/index.perf.ts b/packages-exp/firebase-exp/compat/index.perf.ts index e2efa3bfdd0..267f9b550ee 100644 --- a/packages-exp/firebase-exp/compat/index.perf.ts +++ b/packages-exp/firebase-exp/compat/index.perf.ts @@ -16,7 +16,7 @@ */ import firebase from './app'; -// import './performance'; +import './performance'; import { name, version } from '../package.json'; firebase.registerVersion(name, version, 'compat-lite'); diff --git a/packages-exp/firebase-exp/compat/index.rn.ts b/packages-exp/firebase-exp/compat/index.rn.ts index 0bfc80dd3ce..a06db7a631e 100644 --- a/packages-exp/firebase-exp/compat/index.rn.ts +++ b/packages-exp/firebase-exp/compat/index.rn.ts @@ -18,12 +18,12 @@ import firebase from './app'; import { name, version } from '../package.json'; -// import './auth'; -// import './database'; +import './auth'; +import './database'; // // TODO(b/158625454): Storage doesn't actually work by default in RN (it uses // // `atob`). We should provide a RN build that works out of the box. // import './storage'; -// import './firestore'; +import './firestore'; firebase.registerVersion(name, version, 'compat-rn'); diff --git a/packages-exp/firebase-exp/compat/index.ts b/packages-exp/firebase-exp/compat/index.ts index 89e4a793ebd..aa3fc53001c 100644 --- a/packages-exp/firebase-exp/compat/index.ts +++ b/packages-exp/firebase-exp/compat/index.ts @@ -32,22 +32,23 @@ import firebase from 'firebase/app'; import 'firebase/'; Typescript: -import * as firebase from 'firebase/app'; +import firebase from 'firebase/app'; import 'firebase/'; `); import firebase from './app'; import { name, version } from '../package.json'; -// import './auth'; -// import './database'; -// import './firestore'; -// import './functions'; -// import './messaging'; -// import './storage'; -// import './performance'; -// import './analytics'; -// import './remote-config'; +import './analytics'; +import './app-check'; +import './auth'; +import './database'; +import './firestore'; +import './functions'; +import './messaging'; +import './storage'; +import './performance'; +import './remote-config'; firebase.registerVersion(name, version, 'compat'); diff --git a/packages-exp/firebase-exp/compat/messaging/index.ts b/packages-exp/firebase-exp/compat/messaging/index.ts new file mode 100644 index 00000000000..6cb59a59696 --- /dev/null +++ b/packages-exp/firebase-exp/compat/messaging/index.ts @@ -0,0 +1,18 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import '@firebase/messaging-compat'; diff --git a/packages-exp/firebase-exp/compat/messaging/package.json b/packages-exp/firebase-exp/compat/messaging/package.json new file mode 100644 index 00000000000..02b7b3bfd15 --- /dev/null +++ b/packages-exp/firebase-exp/compat/messaging/package.json @@ -0,0 +1,8 @@ +{ + "name": "firebase-exp/compat/messaging", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.esm.js", + "typings": "dist/compat/messaging/index.d.ts" + } + \ No newline at end of file diff --git a/packages-exp/firebase-exp/compat/package.json b/packages-exp/firebase-exp/compat/package.json index ba13f52a70f..0da0d1bd5dd 100644 --- a/packages-exp/firebase-exp/compat/package.json +++ b/packages-exp/firebase-exp/compat/package.json @@ -6,6 +6,16 @@ "react-native": "dist/index.rn.cjs.js", "typings": "index.d.ts", "components": [ - "app" + "app", + "analytics", + "app-check", + "auth", + "functions", + "messaging", + "performance", + "remote-config", + "firestore", + "storage", + "database" ] } \ No newline at end of file diff --git a/packages-exp/firebase-exp/compat/performance/index.ts b/packages-exp/firebase-exp/compat/performance/index.ts new file mode 100644 index 00000000000..a454c595e1b --- /dev/null +++ b/packages-exp/firebase-exp/compat/performance/index.ts @@ -0,0 +1,18 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import '@firebase/performance-compat'; diff --git a/packages-exp/firebase-exp/compat/performance/package.json b/packages-exp/firebase-exp/compat/performance/package.json new file mode 100644 index 00000000000..490128adbe0 --- /dev/null +++ b/packages-exp/firebase-exp/compat/performance/package.json @@ -0,0 +1,8 @@ +{ + "name": "firebase-exp/compat/performance", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.esm.js", + "typings": "dist/compat/performance/index.d.ts" + } + \ No newline at end of file diff --git a/packages-exp/firebase-exp/compat/remote-config/index.ts b/packages-exp/firebase-exp/compat/remote-config/index.ts new file mode 100644 index 00000000000..6e160e8dd70 --- /dev/null +++ b/packages-exp/firebase-exp/compat/remote-config/index.ts @@ -0,0 +1,18 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import '@firebase/remote-config-compat'; diff --git a/packages-exp/firebase-exp/compat/remote-config/package.json b/packages-exp/firebase-exp/compat/remote-config/package.json new file mode 100644 index 00000000000..9d1d297385a --- /dev/null +++ b/packages-exp/firebase-exp/compat/remote-config/package.json @@ -0,0 +1,8 @@ +{ + "name": "firebase-exp/compat/remote-config", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.esm.js", + "typings": "dist/compat/remote-config/index.d.ts" + } + \ No newline at end of file diff --git a/packages-exp/firebase-exp/compat/rollup.config.js b/packages-exp/firebase-exp/compat/rollup.config.js index 8c01a151955..cd126ac9344 100644 --- a/packages-exp/firebase-exp/compat/rollup.config.js +++ b/packages-exp/firebase-exp/compat/rollup.config.js @@ -241,7 +241,7 @@ const completeBuilds = [ plugins: [ sourcemaps(), resolveModule({ - mainFields: ['lite-esm2017', 'esm2017', 'module', 'main'] + mainFields: ['lite', 'module', 'main'] }), rollupTypescriptPlugin({ typescript, diff --git a/packages-exp/firebase-exp/compat/rollup.config.release.js b/packages-exp/firebase-exp/compat/rollup.config.release.js index c9d5204647a..4fa04b5ecfd 100644 --- a/packages-exp/firebase-exp/compat/rollup.config.release.js +++ b/packages-exp/firebase-exp/compat/rollup.config.release.js @@ -16,19 +16,73 @@ */ import { resolve } from 'path'; +import resolveModule from '@rollup/plugin-node-resolve'; +import commonjs from '@rollup/plugin-commonjs'; import sourcemaps from 'rollup-plugin-sourcemaps'; import rollupTypescriptPlugin from 'rollup-plugin-typescript2'; import typescript from 'typescript'; +import { terser } from 'rollup-plugin-terser'; import json from '@rollup/plugin-json'; import pkg from '../package.json'; import compatPkg from './package.json'; import appPkg from './app/package.json'; +import alias from '@rollup/plugin-alias'; const external = Object.keys(pkg.dependencies || {}); -const plugins = [sourcemaps(), json()]; +/** + * Global UMD Build + */ +const GLOBAL_NAME = 'firebase'; + +function createUmdOutputConfig(output) { + return { + file: output, + format: 'umd', + sourcemap: true, + extend: true, + name: GLOBAL_NAME, + globals: { + '@firebase/app-compat': GLOBAL_NAME, + '@firebase/app': `${GLOBAL_NAME}.INTERNAL.modularAPIs` + }, + + /** + * use iife to avoid below error in the old Safari browser + * SyntaxError: Functions cannot be declared in a nested block in strict mode + * https://github.com/firebase/firebase-js-sdk/issues/1228 + * + */ + intro: ` + try { + (function() {`, + outro: ` + }).apply(this, arguments); + } catch(err) { + console.error(err); + throw new Error( + 'Cannot instantiate ${output} - ' + + 'be sure to load firebase-app.js first.' + ); + }` + }; +} + +const plugins = [ + sourcemaps(), + resolveModule({ + // hack to find firestore-compat and storage-compat + moduleDirectories: ['node_modules', resolve(__dirname, '../..')] + }), + json(), + commonjs() +]; const typescriptPlugin = rollupTypescriptPlugin({ + typescript +}); + +const typescriptPluginUMD = rollupTypescriptPlugin({ typescript, tsconfigOverride: { compilerOptions: { @@ -42,7 +96,7 @@ const typescriptPlugin = rollupTypescriptPlugin({ */ const appBuilds = [ /** - * App Browser Builds + * App NPM Builds */ { input: `${__dirname}/app/index.ts`, @@ -60,6 +114,31 @@ const appBuilds = [ ], plugins: [...plugins, typescriptPlugin], external + }, + /** + * App UMD Builds + */ + { + input: `${__dirname}/app/index.cdn.ts`, + output: { + file: 'firebase-app-compat.js', + sourcemap: true, + format: 'umd', + name: GLOBAL_NAME + }, + plugins: [ + ...plugins, + typescriptPluginUMD, + alias({ + entries: [ + { + find: '@firebase/app', + replacement: '@firebase/app-exp' + } + ] + }), + terser() + ] } ]; @@ -85,9 +164,240 @@ const componentBuilds = compatPkg.components ], plugins: [...plugins, typescriptPlugin], external + }, + { + input: `${__dirname}/${component}/index.ts`, + output: createUmdOutputConfig(`firebase-${component}-compat.js`), + plugins: [ + ...plugins, + typescriptPluginUMD, + alias({ + entries: [ + { + find: `@firebase/${component}`, + replacement: `@firebase/${component}-exp` + }, + { + find: '@firebase/installations', + replacement: '@firebase/installations-exp' + }, + { + // hack to locate firestore-compat + find: '@firebase/firestore-compat', + replacement: 'firestore-compat' + }, + { + // hack to locate storage-compat + find: '@firebase/storage-compat', + replacement: 'storage-compat' + }, + { + // hack to locate database-compat + find: '@firebase/database-compat', + replacement: 'database-compat' + } + ] + }), + terser() + ], + external: ['@firebase/app-compat', '@firebase/app'] } ]; }) .reduce((a, b) => a.concat(b), []); -export default [...appBuilds, ...componentBuilds]; +const aliasForCompleteCDNBuild = compatPkg.components + .filter(component => { + return ( + component !== 'firestore' && + component !== 'storage' && + component !== 'database' + ); + }) + .map(component => ({ + find: `@firebase/${component}`, + replacement: `@firebase/${component}-exp` + })) + .concat([ + { + find: '@firebase/installations', + replacement: '@firebase/installations-exp' + }, + { + // hack to locate firestore-compat + find: '@firebase/firestore-compat', + replacement: 'firestore-compat' + }, + { + // hack to locate storage-compat + find: '@firebase/storage-compat', + replacement: 'storage-compat' + }, + { + // hack to locate database-compat + find: '@firebase/database-compat', + replacement: 'database-compat' + } + ]); + +/** + * Complete Package Builds + */ +const completeBuilds = [ + /** + * App Browser Builds + */ + { + input: `${__dirname}/index.ts`, + output: [ + { + file: resolve(__dirname, compatPkg.module), + format: 'es', + sourcemap: true + } + ], + plugins: [...plugins, typescriptPluginUMD], + external + }, + { + input: `${__dirname}/index.cdn.ts`, + output: { + file: 'firebase-compat.js', + format: 'umd', + // disable sourcemap, otherwise build will fail with Error: Multiple conflicting contents for sourcemap source + // TODO: I think it's related to the alias() we are using, so let's try to reenable it for GA. + sourcemap: false, + name: GLOBAL_NAME + }, + plugins: [ + ...plugins, + typescriptPluginUMD, + terser(), + alias({ + entries: aliasForCompleteCDNBuild + }) + ] + }, + /** + * App Node.js Builds + */ + { + input: `${__dirname}/index.node.ts`, + output: { + file: resolve(__dirname, compatPkg.main), + format: 'cjs', + sourcemap: true + }, + plugins: [...plugins, typescriptPluginUMD], + external + }, + /** + * App React Native Builds + */ + { + input: `${__dirname}/index.rn.ts`, + output: { + file: resolve(__dirname, compatPkg['react-native']), + format: 'cjs', + sourcemap: true + }, + plugins: [...plugins, typescriptPluginUMD], + external + }, + /** + * Performance script Build + */ + { + input: `${__dirname}/index.perf.ts`, + output: { + file: 'firebase-performance-standalone-compat.js', + format: 'umd', + sourcemap: true, + name: GLOBAL_NAME + }, + plugins: [ + sourcemaps(), + resolveModule({ + mainFields: ['lite', 'module', 'main'] + }), + typescriptPluginUMD, + json(), + commonjs(), + terser({ + format: { + comments: false + } + }), + alias({ + entries: [ + { + find: '@firebase/app', + replacement: '@firebase/app-exp' + }, + { + find: `@firebase/performance`, + replacement: `@firebase/performance-exp` + }, + { + find: '@firebase/installations', + replacement: '@firebase/installations-exp' + } + ] + }) + ] + }, + /** + * Performance script Build in ES2017 + */ + { + input: `${__dirname}/index.perf.ts`, + output: { + file: 'firebase-performance-standalone-compat.es2017.js', + format: 'umd', + sourcemap: true, + name: GLOBAL_NAME + }, + plugins: [ + sourcemaps(), + resolveModule({ + mainFields: ['lite', 'module', 'main'] + }), + rollupTypescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017', + declaration: false + } + } + }), + json({ + preferConst: true + }), + commonjs(), + terser({ + format: { + comments: false + } + }), + alias({ + entries: [ + { + find: '@firebase/app', + replacement: '@firebase/app-exp' + }, + { + find: `@firebase/performance`, + replacement: `@firebase/performance-exp` + }, + { + find: '@firebase/installations', + replacement: '@firebase/installations-exp' + } + ] + }) + ] + } +]; + +export default [...appBuilds, ...componentBuilds, ...completeBuilds]; diff --git a/packages-exp/firebase-exp/compat/storage/index.ts b/packages-exp/firebase-exp/compat/storage/index.ts new file mode 100644 index 00000000000..c28db60c5d6 --- /dev/null +++ b/packages-exp/firebase-exp/compat/storage/index.ts @@ -0,0 +1,18 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import '@firebase/storage-compat'; diff --git a/packages-exp/firebase-exp/compat/storage/package.json b/packages-exp/firebase-exp/compat/storage/package.json new file mode 100644 index 00000000000..e0a33b427c7 --- /dev/null +++ b/packages-exp/firebase-exp/compat/storage/package.json @@ -0,0 +1,8 @@ +{ + "name": "firebase-exp/compat/storage", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.esm.js", + "typings": "dist/compat/storage/index.d.ts" + } + \ No newline at end of file diff --git a/packages-exp/firebase-exp/database/index.ts b/packages-exp/firebase-exp/database/index.ts new file mode 100644 index 00000000000..912b6daaab2 --- /dev/null +++ b/packages-exp/firebase-exp/database/index.ts @@ -0,0 +1,18 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from '@firebase/database'; diff --git a/packages-exp/firebase-exp/database/package.json b/packages-exp/firebase-exp/database/package.json new file mode 100644 index 00000000000..d4f715a53bb --- /dev/null +++ b/packages-exp/firebase-exp/database/package.json @@ -0,0 +1,8 @@ +{ + "name": "firebase-exp/database", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.esm.js", + "typings": "dist/database/index.d.ts" + } + \ No newline at end of file diff --git a/packages-exp/firebase-exp/messaging/index.ts b/packages-exp/firebase-exp/messaging/index.ts new file mode 100644 index 00000000000..4a8d59d79ac --- /dev/null +++ b/packages-exp/firebase-exp/messaging/index.ts @@ -0,0 +1,18 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from '@firebase/messaging-exp'; diff --git a/packages-exp/firebase-exp/messaging/package.json b/packages-exp/firebase-exp/messaging/package.json new file mode 100644 index 00000000000..c2e46721890 --- /dev/null +++ b/packages-exp/firebase-exp/messaging/package.json @@ -0,0 +1,7 @@ +{ + "name": "firebase-exp/messaging", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.esm.js", + "typings": "dist/messaging/index.d.ts" +} diff --git a/packages-exp/firebase-exp/messaging/sw/index.ts b/packages-exp/firebase-exp/messaging/sw/index.ts new file mode 100644 index 00000000000..b5e3a0dfe44 --- /dev/null +++ b/packages-exp/firebase-exp/messaging/sw/index.ts @@ -0,0 +1,18 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { onBackgroundMessage, getMessaging } from '@firebase/messaging-exp/sw'; diff --git a/packages-exp/firebase-exp/messaging/sw/package.json b/packages-exp/firebase-exp/messaging/sw/package.json new file mode 100644 index 00000000000..2c2ebcc3afc --- /dev/null +++ b/packages-exp/firebase-exp/messaging/sw/package.json @@ -0,0 +1,8 @@ +{ + "name": "firebase-exp/messaging/sw", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.esm.js", + "typings": "dist/messaging/sw/index.d.ts" + } + \ No newline at end of file diff --git a/packages-exp/firebase-exp/package.json b/packages-exp/firebase-exp/package.json index 4497de8be78..cc41dfae329 100644 --- a/packages-exp/firebase-exp/package.json +++ b/packages-exp/firebase-exp/package.json @@ -1,6 +1,6 @@ { "name": "firebase-exp", - "version": "0.800.11", + "version": "9.0.0-beta.6", "private": true, "description": "Firebase JavaScript library for web and Node.js", "author": "Firebase (https://firebase.google.com/)", @@ -20,48 +20,169 @@ "**/dist/", "**/package.json", "/firebase*.js", - "/firebase*.map" + "/firebase*.map", + "compat/index.d.ts" ], + "exports": { + "./analytics": { + "node": "./analytics/dist/index.cjs.js", + "default": "./analytics/dist/index.esm.js" + }, + "./app": { + "node": "./app/dist/index.cjs.js", + "default": "./app/dist/index.esm.js" + }, + "./app-check": { + "node": "./app-check/dist/index.cjs.js", + "default": "./app-check/dist/index.esm.js" + }, + "./auth": { + "node": "./auth/dist/index.cjs.js", + "default": "./auth/dist/index.esm.js" + }, + "./database": { + "node": "./database/dist/index.cjs.js", + "default": "./database/dist/index.esm.js" + }, + "./firestore": { + "node": "./firestore/dist/index.cjs.js", + "default": "./firestore/dist/index.esm.js" + }, + "./firestore/lite": { + "node": "./firestore/lite/dist/index.cjs.js", + "default": "./firestore/lite/dist/index.esm.js" + }, + "./functions": { + "node": "./functions/dist/index.cjs.js", + "default": "./functions/dist/index.esm.js" + }, + "./messaging": { + "node": "./messaging/dist/index.cjs.js", + "default": "./messaging/dist/index.esm.js" + }, + "./messaging/sw": { + "node": "./messaging/sw/dist/index.cjs.js", + "default": "./messaging/sw/dist/index.esm.js" + }, + "./performance": { + "node": "./performance/dist/index.cjs.js", + "default": "./performance/dist/index.esm.js" + }, + "./remote-config": { + "node": "./remote-config/dist/index.cjs.js", + "default": "./remote-config/dist/index.esm.js" + }, + "./storage": { + "node": "./storage/dist/index.cjs.js", + "default": "./storage/dist/index.esm.js" + }, + "./compat/analytics": { + "node": "./compat/analytics/dist/index.cjs.js", + "default": "./compat/analytics/dist/index.esm.js" + }, + "./compat/app": { + "node": "./compat/app/dist/index.cjs.js", + "default": "./compat/app/dist/index.esm.js" + }, + "./compat/app-check": { + "node": "./compat/app-check/dist/index.cjs.js", + "default": "./compat/app-check/dist/index.esm.js" + }, + "./compat/auth": { + "node": "./compat/auth/dist/index.cjs.js", + "default": "./compat/auth/dist/index.esm.js" + }, + "./compat/database": { + "node": "./compat/database/dist/index.cjs.js", + "default": "./compat/database/dist/index.esm.js" + }, + "./compat/firestore": { + "node": "./compat/firestore/dist/index.cjs.js", + "default": "./compat/firestore/dist/index.esm.js" + }, + "./compat/functions": { + "node": "./compat/functions/dist/index.cjs.js", + "default": "./compat/functions/dist/index.esm.js" + }, + "./compat/messaging": { + "node": "./compat/messaging/dist/index.cjs.js", + "default": "./compat/messaging/dist/index.esm.js" + }, + "./compat/performance": { + "node": "./compat/performance/dist/index.cjs.js", + "default": "./compat/performance/dist/index.esm.js" + }, + "./compat/remote-config": { + "node": "./compat/remote-config/dist/index.cjs.js", + "default": "./compat/remote-config/dist/index.esm.js" + }, + "./compat/storage": { + "node": "./compat/storage/dist/index.cjs.js", + "default": "./compat/storage/dist/index.esm.js" + } + }, "repository": { "type": "git", "url": "https://github.com/firebase/firebase-js-sdk.git" }, "scripts": { - "build": "rollup -c && gulp firebase-js && yarn build:compat", - "build:release": "rollup -c rollup.config.release.js && gulp firebase-js && yarn build:compat:release", + "build": "rollup -c && yarn build:compat", + "build:release": "rollup -c rollup.config.release.js && yarn build:compat:release", "build:compat": "rollup -c compat/rollup.config.js", "build:compat:release": "rollup -c compat/rollup.config.release.js", "dev": "rollup -c -w", - "prepare": "yarn build:release", "test": "echo 'No test suite for firebase wrapper'", "test:ci": "echo 'No test suite for firebase wrapper'" }, "dependencies": { - "@firebase/app-exp": "0.0.800", - "@firebase/app-compat": "0.0.800", - "@firebase/functions-exp": "0.0.800", - "@firebase/firestore": "1.18.0", - "@firebase/performance-exp": "0.0.800" + "@firebase/analytics-exp": "0.0.900", + "@firebase/analytics-compat": "0.0.900", + "@firebase/app-exp": "0.0.900", + "@firebase/app-compat": "0.0.900", + "@firebase/app-check-exp": "0.0.900", + "@firebase/app-check-compat": "0.0.900", + "@firebase/auth-exp": "0.0.900", + "@firebase/auth-compat": "0.0.900", + "@firebase/database": "0.10.7", + "@firebase/functions-exp": "0.0.900", + "@firebase/functions-compat": "0.0.900", + "@firebase/firestore": "2.3.8", + "@firebase/storage": "0.5.6", + "@firebase/performance-exp": "0.0.900", + "@firebase/performance-compat": "0.0.900", + "@firebase/remote-config-exp": "0.0.900", + "@firebase/remote-config-compat": "0.0.900", + "@firebase/messaging-exp": "0.0.900", + "@firebase/messaging-compat": "0.0.900" }, "devDependencies": { - "rollup": "2.29.0", - "@rollup/plugin-commonjs": "15.1.0", - "rollup-plugin-license": "2.2.0", - "@rollup/plugin-node-resolve": "9.0.0", + "rollup": "2.52.2", + "@rollup/plugin-commonjs": "17.1.0", + "rollup-plugin-license": "2.5.0", + "@rollup/plugin-node-resolve": "11.2.0", "rollup-plugin-sourcemaps": "0.6.3", "rollup-plugin-terser": "7.0.2", - "rollup-plugin-typescript2": "0.27.3", + "rollup-plugin-typescript2": "0.30.0", "rollup-plugin-uglify": "6.0.4", + "@rollup/plugin-alias": "3.1.2", "gulp": "4.0.2", - "gulp-sourcemaps": "2.6.5", + "gulp-sourcemaps": "3.0.0", "gulp-concat": "2.6.1", - "typescript": "4.0.2" + "typescript": "4.2.2" }, "components": [ + "analytics", "app", + "app-check", + "auth", "functions", "firestore", "firestore/lite", - "performance" + "storage", + "performance", + "remote-config", + "messaging", + "messaging/sw", + "database" ] } diff --git a/packages-exp/firebase-exp/remote-config/index.ts b/packages-exp/firebase-exp/remote-config/index.ts new file mode 100644 index 00000000000..73b26c811e9 --- /dev/null +++ b/packages-exp/firebase-exp/remote-config/index.ts @@ -0,0 +1,17 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from '@firebase/remote-config-exp'; diff --git a/packages-exp/firebase-exp/remote-config/package.json b/packages-exp/firebase-exp/remote-config/package.json new file mode 100644 index 00000000000..86878ce0089 --- /dev/null +++ b/packages-exp/firebase-exp/remote-config/package.json @@ -0,0 +1,7 @@ +{ + "name": "firebase-exp/remote-config", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.esm.js", + "typings": "dist/remote-config/index.d.ts" +} diff --git a/packages-exp/firebase-exp/rollup.config.js b/packages-exp/firebase-exp/rollup.config.js index d6b7aa10ee9..b742052c059 100644 --- a/packages-exp/firebase-exp/rollup.config.js +++ b/packages-exp/firebase-exp/rollup.config.js @@ -15,63 +15,25 @@ * limitations under the License. */ +import appPkg from './app/package.json'; +import commonjs from '@rollup/plugin-commonjs'; +import json from '@rollup/plugin-json'; +import pkg from './package.json'; import { resolve } from 'path'; import resolveModule from '@rollup/plugin-node-resolve'; -import commonjs from '@rollup/plugin-commonjs'; -import sourcemaps from 'rollup-plugin-sourcemaps'; import rollupTypescriptPlugin from 'rollup-plugin-typescript2'; +import sourcemaps from 'rollup-plugin-sourcemaps'; import typescript from 'typescript'; -import { uglify } from 'rollup-plugin-uglify'; -import json from '@rollup/plugin-json'; -import pkg from './package.json'; -import appPkg from './app/package.json'; +import alias from '@rollup/plugin-alias'; const external = Object.keys(pkg.dependencies || {}); - -/** - * Global UMD Build - */ -const GLOBAL_NAME = 'firebase'; - -function createUmdOutputConfig(output, componentName) { - return { - file: output, - format: 'umd', - sourcemap: true, - extend: true, - name: `${GLOBAL_NAME}.${componentName}`, - globals: { - '@firebase/app-exp': `${GLOBAL_NAME}.app` - }, - - /** - * use iife to avoid below error in the old Safari browser - * SyntaxError: Functions cannot be declared in a nested block in strict mode - * https://github.com/firebase/firebase-js-sdk/issues/1228 - * - */ - intro: ` - try { - (function() {`, - outro: ` - }).apply(this, arguments); - } catch(err) { - console.error(err); - throw new Error( - 'Cannot instantiate ${output} - ' + - 'be sure to load firebase-app.js first.' - ); - }` - }; -} - const plugins = [sourcemaps(), resolveModule(), json(), commonjs()]; const typescriptPlugin = rollupTypescriptPlugin({ typescript }); -const typescriptPluginUMD = rollupTypescriptPlugin({ +const typescriptPluginCDN = rollupTypescriptPlugin({ typescript, tsconfigOverride: { compilerOptions: { @@ -95,19 +57,6 @@ const appBuilds = [ ], plugins: [...plugins, typescriptPlugin], external - }, - /** - * App UMD Builds - */ - { - input: 'app/index.cdn.ts', - output: { - file: 'firebase-app.js', - sourcemap: true, - format: 'umd', - name: `${GLOBAL_NAME}.app` - }, - plugins: [...plugins, typescriptPluginUMD, uglify()] } ]; @@ -116,9 +65,6 @@ const componentBuilds = pkg.components .filter(component => component !== 'app') .map(component => { const pkg = require(`./${component}/package.json`); - // It is needed for handling sub modules, for example firestore/lite which should produce firebase-firestore-lite.js - // Otherwise, we will create a directory with '/' in the name. - const componentName = component.replace('/', '-'); return [ { input: `${component}/index.ts`, @@ -136,18 +82,51 @@ const componentBuilds = pkg.components ], plugins: [...plugins, typescriptPlugin], external - }, - { - input: `${component}/index.ts`, - output: createUmdOutputConfig( - `firebase-${componentName}.js`, - componentName - ), - plugins: [...plugins, typescriptPluginUMD, uglify()], - external: ['@firebase/app-exp'] } ]; }) .reduce((a, b) => a.concat(b), []); -export default [...appBuilds, ...componentBuilds]; +/** + * CDN script builds + */ +const FIREBASE_APP_URL = `https://www.gstatic.com/firebasejs/${pkg.version}/firebase-app.js`; +const cdnBuilds = [ + { + input: 'app/index.cdn.ts', + output: { + file: 'firebase-app.js', + sourcemap: true, + format: 'es' + }, + plugins: [...plugins, typescriptPluginCDN] + }, + ...pkg.components + .filter(component => component !== 'app') + .map(component => { + // It is needed for handling sub modules, for example firestore/lite which should produce firebase-firestore-lite.js + // Otherwise, we will create a directory with '/' in the name. + const componentName = component.replace('/', '-'); + + return { + input: `${component}/index.ts`, + output: { + file: `firebase-${componentName}.js`, + sourcemap: true, + format: 'es' + }, + plugins: [ + ...plugins, + typescriptPluginCDN, + alias({ + entries: { + '@firebase/app': FIREBASE_APP_URL + } + }) + ], + external: [FIREBASE_APP_URL] + }; + }) +]; + +export default [...appBuilds, ...componentBuilds, ...cdnBuilds]; diff --git a/packages-exp/firebase-exp/rollup.config.release.js b/packages-exp/firebase-exp/rollup.config.release.js index 2af5a3183de..7d6e655ba69 100644 --- a/packages-exp/firebase-exp/rollup.config.release.js +++ b/packages-exp/firebase-exp/rollup.config.release.js @@ -15,61 +15,23 @@ * limitations under the License. */ +import appPkg from './app/package.json'; +import commonjs from '@rollup/plugin-commonjs'; +import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; +import json from '@rollup/plugin-json'; +import pkg from './package.json'; import { resolve } from 'path'; import resolveModule from '@rollup/plugin-node-resolve'; -import commonjs from '@rollup/plugin-commonjs'; -import sourcemaps from 'rollup-plugin-sourcemaps'; import rollupTypescriptPlugin from 'rollup-plugin-typescript2'; -import alias from '@rollup/plugin-alias'; +import sourcemaps from 'rollup-plugin-sourcemaps'; import typescript from 'typescript'; -import { uglify } from 'rollup-plugin-uglify'; -import json from '@rollup/plugin-json'; -import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; -import pkg from './package.json'; -import appPkg from './app/package.json'; +import alias from '@rollup/plugin-alias'; // remove -exp from dependencies name const deps = Object.keys(pkg.dependencies || {}).map(name => name.replace('-exp', '') ); -/** - * Global UMD Build - */ -const GLOBAL_NAME = 'firebase'; - -function createUmdOutputConfig(output, componentName) { - return { - file: output, - format: 'umd', - sourcemap: true, - extend: true, - name: `${GLOBAL_NAME}.${componentName}`, - globals: { - '@firebase/app': `${GLOBAL_NAME}.app` - }, - - /** - * use iife to avoid below error in the old Safari browser - * SyntaxError: Functions cannot be declared in a nested block in strict mode - * https://github.com/firebase/firebase-js-sdk/issues/1228 - * - */ - intro: ` - try { - (function() {`, - outro: ` - }).apply(this, arguments); - } catch(err) { - console.error(err); - throw new Error( - 'Cannot instantiate ${output} - ' + - 'be sure to load firebase-app.js first.' - ); - }` - }; -} - const plugins = [sourcemaps(), resolveModule(), json(), commonjs()]; const typescriptPlugin = rollupTypescriptPlugin({ @@ -77,7 +39,7 @@ const typescriptPlugin = rollupTypescriptPlugin({ transformers: [importPathTransformer] }); -const typescriptPluginUMD = rollupTypescriptPlugin({ +const typescriptPluginCDN = rollupTypescriptPlugin({ typescript, tsconfigOverride: { compilerOptions: { @@ -101,19 +63,6 @@ const appBuilds = [ ], plugins: [...plugins, typescriptPlugin], external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - }, - /** - * App UMD Builds - */ - { - input: 'app/index.ts', - output: { - file: 'firebase-app.js', - sourcemap: true, - format: 'umd', - name: `${GLOBAL_NAME}.app` - }, - plugins: [...plugins, typescriptPluginUMD, uglify()] } ]; @@ -122,9 +71,7 @@ const componentBuilds = pkg.components .filter(component => component !== 'app') .map(component => { const pkg = require(`./${component}/package.json`); - // It is needed for handling sub modules, for example firestore/lite which should produce firebase-firestore-lite.js - // Otherwise, we will create a directory with '/' in the name. - const componentName = component.replace('/', '-'); + return [ { input: `${component}/index.ts`, @@ -142,32 +89,52 @@ const componentBuilds = pkg.components ], plugins: [...plugins, typescriptPlugin], external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - }, - { + } + ]; + }) + .reduce((a, b) => a.concat(b), []); + +/** + * CDN script builds + */ +const FIREBASE_APP_URL = `https://www.gstatic.com/firebasejs/${pkg.version}/firebase-app.js`; +const cdnBuilds = [ + { + input: 'app/index.cdn.ts', + output: { + file: 'firebase-app.js', + sourcemap: true, + format: 'es' + }, + plugins: [...plugins, typescriptPluginCDN] + }, + ...pkg.components + .filter(component => component !== 'app') + .map(component => { + const pkg = require(`./${component}/package.json`); + // It is needed for handling sub modules, for example firestore/lite which should produce firebase-firestore-lite.js + // Otherwise, we will create a directory with '/' in the name. + const componentName = component.replace('/', '-'); + + return { input: `${component}/index.ts`, - output: createUmdOutputConfig( - `firebase-${componentName}.js`, - componentName - ), + output: { + file: `firebase-${componentName}.js`, + sourcemap: true, + format: 'es' + }, plugins: [ ...plugins, - typescriptPluginUMD, - /** - * Hack to bundle @firebase/installations-exp - */ + typescriptPluginCDN, alias({ - entries: [ - { - find: '@firebase/installations', - replacement: '@firebase/installations-exp' - } - ] + entries: { + '@firebase/app': FIREBASE_APP_URL, + '@firebase/installations': '@firebase/installations-exp' + } }) ], - external: ['@firebase/app'] - } - ]; - }) - .reduce((a, b) => a.concat(b), []); - -export default [...appBuilds, ...componentBuilds]; + external: [FIREBASE_APP_URL] + }; + }) +]; +export default [...appBuilds, ...componentBuilds, ...cdnBuilds]; diff --git a/packages-exp/firebase-exp/storage/index.ts b/packages-exp/firebase-exp/storage/index.ts new file mode 100644 index 00000000000..196d6e4ecd7 --- /dev/null +++ b/packages-exp/firebase-exp/storage/index.ts @@ -0,0 +1,18 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from '@firebase/storage'; diff --git a/packages-exp/firebase-exp/storage/package.json b/packages-exp/firebase-exp/storage/package.json new file mode 100644 index 00000000000..888a893701e --- /dev/null +++ b/packages-exp/firebase-exp/storage/package.json @@ -0,0 +1,7 @@ +{ + "name": "firebase-exp/storage", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm.js", + "module": "dist/index.esm.js", + "typings": "dist/storage/index.d.ts" +} diff --git a/packages-exp/functions-compat/.eslintrc.js b/packages-exp/functions-compat/.eslintrc.js new file mode 100644 index 00000000000..11fa60d3e6a --- /dev/null +++ b/packages-exp/functions-compat/.eslintrc.js @@ -0,0 +1,37 @@ +/* eslint-disable @typescript-eslint/no-require-imports */ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const path = require('path'); + +module.exports = { + extends: '../../config/.eslintrc.js', + parserOptions: { + project: 'tsconfig.json', + // to make vscode-eslint work with monorepo + // https://github.com/typescript-eslint/typescript-eslint/issues/251#issuecomment-463943250 + tsconfigRootDir: __dirname + }, + rules: { + 'import/no-extraneous-dependencies': [ + 'error', + { + 'packageDir': [path.resolve(__dirname, '../../'), __dirname], + devDependencies: true + } + ] + } +}; diff --git a/packages-exp/functions-compat/karma.conf.js b/packages-exp/functions-compat/karma.conf.js new file mode 100644 index 00000000000..c0737457c55 --- /dev/null +++ b/packages-exp/functions-compat/karma.conf.js @@ -0,0 +1,35 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const karmaBase = require('../../config/karma.base'); + +const files = ['test/**/*', 'src/**/*.test.ts']; + +module.exports = function (config) { + const karmaConfig = Object.assign({}, karmaBase, { + // files to load into karma + files: files, + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + preprocessors: { '**/*.ts': ['webpack', 'sourcemap'] }, + frameworks: ['mocha'] + }); + + config.set(karmaConfig); +}; + +module.exports.files = files; diff --git a/packages-exp/functions-compat/package.json b/packages-exp/functions-compat/package.json new file mode 100644 index 00000000000..09e0cea5613 --- /dev/null +++ b/packages-exp/functions-compat/package.json @@ -0,0 +1,64 @@ +{ + "name": "@firebase/functions-compat", + "version": "0.0.900", + "description": "", + "private": true, + "author": "Firebase (https://firebase.google.com/)", + "main": "dist/index.node.cjs.js", + "browser": "dist/index.esm2017.js", + "module": "dist/index.esm2017.js", + "files": [ + "dist" + ], + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-compat": "0.x" + }, + "devDependencies": { + "@firebase/app-compat": "0.0.900", + "rollup": "2.52.2", + "@rollup/plugin-json": "4.1.0", + "rollup-plugin-typescript2": "0.30.0", + "typescript": "4.2.2" + }, + "repository": { + "directory": "packages-exp/functions-compat", + "type": "git", + "url": "https://github.com/firebase/firebase-js-sdk.git" + }, + "bugs": { + "url": "https://github.com/firebase/firebase-js-sdk/issues" + }, + "scripts": { + "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "build": "rollup -c", + "build:deps": "lerna run --scope @firebase/functions-compat --include-dependencies build", + "build:release": "rollup -c rollup.config.release.js && yarn add-compat-overloads", + "dev": "rollup -c -w", + "test": "run-p lint test:all", + "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:all", + "test:all": "run-p test:browser test:node", + "test:browser": "karma start --single-run", + "test:browser:debug": "karma start --browsers=Chrome --auto-watch", + "test:node": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha 'src/{,!(browser)/**/}*.test.ts' --file src/index.node.ts --config ../../config/mocharc.node.js", + "test:emulator": "env FIREBASE_FUNCTIONS_HOST=http://localhost FIREBASE_FUNCTIONS_PORT=5005 run-p test:node", + "add-compat-overloads": "ts-node-script ../../scripts/exp/create-overloads.ts -i ../functions-exp/dist/functions-exp-public.d.ts -o dist/src/index.d.ts -a -r Functions:types.FirebaseFunctions -r FirebaseApp:FirebaseAppCompat --moduleToEnhance @firebase/functions" + }, + "typings": "dist/src/index.d.ts", + "dependencies": { + "@firebase/component": "0.5.4", + "@firebase/functions-exp": "0.0.900", + "@firebase/functions-types": "0.4.0", + "@firebase/messaging-types": "0.5.0", + "@firebase/util": "1.1.0", + "tslib": "^2.1.0" + }, + "nyc": { + "extension": [ + ".ts" + ], + "reportDir": "./coverage/node" + }, + "esm5": "dist/index.esm5.js" +} \ No newline at end of file diff --git a/packages-exp/functions-compat/rollup.config.base.js b/packages-exp/functions-compat/rollup.config.base.js new file mode 100644 index 00000000000..a8ac2951f69 --- /dev/null +++ b/packages-exp/functions-compat/rollup.config.base.js @@ -0,0 +1,101 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import json from '@rollup/plugin-json'; +import typescriptPlugin from 'rollup-plugin-typescript2'; +import typescript from 'typescript'; +import pkg from './package.json'; + +const deps = [ + ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), + '@firebase/functions' +]; + +/** + * ES5 Builds + */ +export function getEs5Builds(additionalTypescriptPlugins = {}) { + const es5BuildPlugins = [ + typescriptPlugin({ + typescript, + abortOnError: false, + ...additionalTypescriptPlugins + }), + json() + ]; + return [ + /** + * Browser Builds + */ + { + input: 'src/index.ts', + output: [{ file: pkg.esm5, format: 'es', sourcemap: true }], + plugins: es5BuildPlugins, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + }, + /** + * Node.js Build + */ + { + input: 'src/index.node.ts', + output: [{ file: pkg.main, format: 'cjs', sourcemap: true }], + plugins: es5BuildPlugins, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + } + ]; +} + +/** + * ES2017 Builds + */ +export function getEs2017Builds(additionalTypescriptPlugins = {}) { + const es2017BuildPlugins = [ + typescriptPlugin({ + typescript, + abortOnError: false, + tsconfigOverride: { + compilerOptions: { + target: 'es2017' + } + }, + ...additionalTypescriptPlugins + }), + json({ preferConst: true }) + ]; + return [ + { + /** + * Browser Build + */ + input: 'src/index.ts', + output: { + file: pkg.browser, + format: 'es', + sourcemap: true + }, + plugins: es2017BuildPlugins, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + } + ]; +} + +export function getAllBuilds(additionalTypescriptPlugins = {}) { + return [ + ...getEs5Builds(additionalTypescriptPlugins), + ...getEs2017Builds(additionalTypescriptPlugins) + ]; +} diff --git a/packages-exp/functions-compat/rollup.config.js b/packages-exp/functions-compat/rollup.config.js new file mode 100644 index 00000000000..7746175a9a6 --- /dev/null +++ b/packages-exp/functions-compat/rollup.config.js @@ -0,0 +1,21 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getAllBuilds } from './rollup.config.base'; + +// eslint-disable-next-line import/no-default-export +export default getAllBuilds({}); diff --git a/packages-exp/functions-compat/rollup.config.release.js b/packages-exp/functions-compat/rollup.config.release.js new file mode 100644 index 00000000000..d364683678a --- /dev/null +++ b/packages-exp/functions-compat/rollup.config.release.js @@ -0,0 +1,25 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; +import { getAllBuilds } from './rollup.config.base'; + +// eslint-disable-next-line import/no-default-export +export default getAllBuilds({ + clean: true, + transformers: [importPathTransformer] +}); diff --git a/packages-exp/functions-compat/src/callable.test.ts b/packages-exp/functions-compat/src/callable.test.ts new file mode 100644 index 00000000000..248cae71c3e --- /dev/null +++ b/packages-exp/functions-compat/src/callable.test.ts @@ -0,0 +1,145 @@ +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { expect } from 'chai'; +import { FunctionsErrorCode } from '@firebase/functions-exp'; +import { createTestService } from '../test/utils'; +import { firebase, FirebaseApp } from '@firebase/app-compat'; + +// eslint-disable-next-line @typescript-eslint/no-require-imports +export const TEST_PROJECT = require('../../../config/project.json'); + +// Chai doesn't handle Error comparisons in a useful way. +// https://github.com/chaijs/chai/issues/608 +async function expectError( + promise: Promise, + code: FunctionsErrorCode, + message: string, + details?: any +): Promise { + let failed = false; + try { + await promise; + } catch (e) { + failed = true; + // Errors coming from callable functions usually have the functions-exp + // code in the message since it's thrown inside functions-exp. + expect(e.code).to.match(new RegExp(`functions.*/${code}`)); + expect(e.message).to.equal(message); + expect(e.details).to.deep.equal(details); + } + if (!failed) { + expect(false, 'Promise should have failed.').to.be.true; + } +} + +describe('Firebase Functions > Call', () => { + let app: FirebaseApp; + const region = 'us-central1'; + + before(() => { + const useEmulator = !!process.env.HOST; + const projectId = useEmulator + ? 'functions-integration-test' + : TEST_PROJECT.projectId; + const messagingSenderId = 'messaging-sender-id'; + + app = firebase.initializeApp({ projectId, messagingSenderId }); + }); + + after(async () => { + await app.delete(); + }); + + it('simple data', async () => { + const functions = createTestService(app, region); + // TODO(klimt): Should we add an API to create a "long" in JS? + const data = { + bool: true, + int: 2, + str: 'four', + array: [5, 6], + null: null + }; + + const func = functions.httpsCallable('dataTest'); + const result = await func(data); + + expect(result.data).to.deep.equal({ + message: 'stub response', + code: 42, + long: 420 + }); + }); + + it('scalars', async () => { + const functions = createTestService(app, region); + const func = functions.httpsCallable('scalarTest'); + const result = await func(17); + expect(result.data).to.equal(76); + }); + + it('null', async () => { + const functions = createTestService(app, region); + const func = functions.httpsCallable('nullTest'); + let result = await func(null); + expect(result.data).to.be.null; + + // Test with void arguments version. + result = await func(); + expect(result.data).to.be.null; + }); + + it('missing result', async () => { + const functions = createTestService(app, region); + const func = functions.httpsCallable('missingResultTest'); + await expectError(func(), 'internal', 'Response is missing data field.'); + }); + + it('unhandled error', async () => { + const functions = createTestService(app, region); + const func = functions.httpsCallable('unhandledErrorTest'); + await expectError(func(), 'internal', 'internal'); + }); + + it('unknown error', async () => { + const functions = createTestService(app, region); + const func = functions.httpsCallable('unknownErrorTest'); + await expectError(func(), 'internal', 'internal'); + }); + + it('explicit error', async () => { + const functions = createTestService(app, region); + const func = functions.httpsCallable('explicitErrorTest'); + await expectError(func(), 'out-of-range', 'explicit nope', { + start: 10, + end: 20, + long: 30 + }); + }); + + it('http error', async () => { + const functions = createTestService(app, region); + const func = functions.httpsCallable('httpErrorTest'); + await expectError(func(), 'invalid-argument', 'invalid-argument'); + }); + + it('timeout', async () => { + const functions = createTestService(app, region); + const func = functions.httpsCallable('timeoutTest', { timeout: 10 }); + await expectError(func(), 'deadline-exceeded', 'deadline-exceeded'); + }); +}); diff --git a/packages-exp/functions-compat/src/index.node.ts b/packages-exp/functions-compat/src/index.node.ts new file mode 100644 index 00000000000..f560f4aa251 --- /dev/null +++ b/packages-exp/functions-compat/src/index.node.ts @@ -0,0 +1,23 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { firebase } from '@firebase/app-compat'; +import { name, version } from '../package.json'; +import { registerFunctions } from './register'; + +registerFunctions(); +firebase.registerVersion(name, version, 'node'); diff --git a/packages-exp/functions-compat/src/index.ts b/packages-exp/functions-compat/src/index.ts new file mode 100644 index 00000000000..ac85ad18dce --- /dev/null +++ b/packages-exp/functions-compat/src/index.ts @@ -0,0 +1,36 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import firebase from '@firebase/app-compat'; +import { name, version } from '../package.json'; +import { registerFunctions } from './register'; +import * as types from '@firebase/functions-types'; + +registerFunctions(); +firebase.registerVersion(name, version); + +declare module '@firebase/app-compat' { + interface FirebaseNamespace { + functions: { + (app?: FirebaseApp): types.FirebaseFunctions; + Functions: typeof types.FirebaseFunctions; + }; + } + interface FirebaseApp { + functions(regionOrCustomDomain?: string): types.FirebaseFunctions; + } +} diff --git a/packages-exp/functions-compat/src/register.ts b/packages-exp/functions-compat/src/register.ts new file mode 100644 index 00000000000..70b3ab63a3c --- /dev/null +++ b/packages-exp/functions-compat/src/register.ts @@ -0,0 +1,66 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import firebase, { + _FirebaseNamespace, + FirebaseApp +} from '@firebase/app-compat'; +import { FunctionsService } from './service'; +import { + Component, + ComponentType, + InstanceFactory, + ComponentContainer, + InstanceFactoryOptions +} from '@firebase/component'; +import { Functions as FunctionsServiceExp } from '@firebase/functions-exp'; + +const DEFAULT_REGION = 'us-central1'; + +declare module '@firebase/component' { + interface NameServiceMapping { + 'app-compat': FirebaseApp; + 'functions-compat': FunctionsService; + 'functions-exp': FunctionsServiceExp; + } +} + +const factory: InstanceFactory<'functions-compat'> = ( + container: ComponentContainer, + { instanceIdentifier: regionOrCustomDomain }: InstanceFactoryOptions +) => { + // Dependencies + const app = container.getProvider('app-compat').getImmediate(); + const functionsServiceExp = container + .getProvider('functions-exp') + .getImmediate({ + identifier: regionOrCustomDomain ?? DEFAULT_REGION + }); + + return new FunctionsService(app, functionsServiceExp); +}; + +export function registerFunctions(): void { + const namespaceExports = { + Functions: FunctionsService + }; + (firebase as _FirebaseNamespace).INTERNAL.registerComponent( + new Component('functions-compat', factory, ComponentType.PUBLIC) + .setServiceProps(namespaceExports) + .setMultipleInstances(true) + ); +} diff --git a/packages-exp/functions-compat/src/service.test.ts b/packages-exp/functions-compat/src/service.test.ts new file mode 100644 index 00000000000..4c81596cacf --- /dev/null +++ b/packages-exp/functions-compat/src/service.test.ts @@ -0,0 +1,94 @@ +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { expect, use } from 'chai'; +import { createTestService } from '../test/utils'; +import { FunctionsService } from './service'; +import { firebase, FirebaseApp } from '@firebase/app-compat'; +import * as functionsExp from '@firebase/functions-exp'; +import { stub, match, SinonStub } from 'sinon'; +import * as sinonChai from 'sinon-chai'; + +use(sinonChai); + +describe('Firebase Functions > Service', () => { + let app: FirebaseApp; + let service: FunctionsService; + let functionsEmulatorStub: SinonStub = stub(); + let httpsCallableStub: SinonStub = stub(); + + before(() => { + functionsEmulatorStub = stub(functionsExp, 'useFunctionsEmulator'); + httpsCallableStub = stub(functionsExp, 'httpsCallable'); + }); + + beforeEach(() => { + app = firebase.initializeApp({ + projectId: 'my-project', + messagingSenderId: 'messaging-sender-id' + }); + }); + + afterEach(async () => { + await app.delete(); + }); + + after(() => { + functionsEmulatorStub.restore(); + httpsCallableStub.restore(); + }); + + it('useFunctionsEmulator (deprecated) calls modular useEmulator', () => { + service = createTestService(app); + service.useFunctionsEmulator('http://localhost:5005'); + expect(functionsEmulatorStub).to.be.calledWith( + match.any, + 'localhost', + 5005 + ); + functionsEmulatorStub.resetHistory(); + }); + + it('useEmulator calls modular useEmulator', () => { + service = createTestService(app); + service.useEmulator('otherlocalhost', 5006); + expect(functionsEmulatorStub).to.be.calledWith( + match.any, + 'otherlocalhost', + 5006 + ); + functionsEmulatorStub.resetHistory(); + }); + + it('httpsCallable calls modular httpsCallable', () => { + service = createTestService(app); + service.httpsCallable('blah', { timeout: 2000 }); + expect(httpsCallableStub).to.be.calledWith(match.any, 'blah', { + timeout: 2000 + }); + httpsCallableStub.resetHistory(); + }); + + it('correctly sets region', () => { + service = createTestService(app, 'my-region'); + expect(service._region).to.equal('my-region'); + }); + + it('correctly sets custom domain', () => { + service = createTestService(app, 'https://mydomain.com'); + expect(service._customDomain).to.equal('https://mydomain.com'); + }); +}); diff --git a/packages-exp/functions-compat/src/service.ts b/packages-exp/functions-compat/src/service.ts new file mode 100644 index 00000000000..d72902dc62e --- /dev/null +++ b/packages-exp/functions-compat/src/service.ts @@ -0,0 +1,75 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FirebaseFunctions, HttpsCallable } from '@firebase/functions-types'; +import { + httpsCallable as httpsCallableExp, + useFunctionsEmulator as useFunctionsEmulatorExp, + HttpsCallableOptions, + Functions as FunctionsServiceExp +} from '@firebase/functions-exp'; +import { FirebaseApp, _FirebaseService } from '@firebase/app-compat'; +import { FirebaseError } from '@firebase/util'; + +export class FunctionsService implements FirebaseFunctions, _FirebaseService { + /** + * For testing. + * @internal + */ + _region: string; + /** + * For testing. + * @internal + */ + _customDomain: string | null; + + constructor( + public app: FirebaseApp, + readonly _delegate: FunctionsServiceExp + ) { + this._region = this._delegate.region; + this._customDomain = this._delegate.customDomain; + } + httpsCallable(name: string, options?: HttpsCallableOptions): HttpsCallable { + return httpsCallableExp(this._delegate, name, options); + } + /** + * Deprecated in pre-modularized repo, does not exist in modularized + * functions package, need to convert to "host" and "port" args that + * `useFunctionsEmulatorExp` takes. + * @deprecated + */ + useFunctionsEmulator(origin: string): void { + const match = origin.match('[a-zA-Z]+://([a-zA-Z0-9.-]+)(?::([0-9]+))?'); + if (match == null) { + throw new FirebaseError( + 'functions', + 'No origin provided to useFunctionsEmulator()' + ); + } + if (match[2] == null) { + throw new FirebaseError( + 'functions', + 'Port missing in origin provided to useFunctionsEmulator()' + ); + } + return useFunctionsEmulatorExp(this._delegate, match[1], Number(match[2])); + } + useEmulator(host: string, port: number): void { + return useFunctionsEmulatorExp(this._delegate, host, port); + } +} diff --git a/packages-exp/functions-compat/test/utils.ts b/packages-exp/functions-compat/test/utils.ts new file mode 100644 index 00000000000..8d4bac907c8 --- /dev/null +++ b/packages-exp/functions-compat/test/utils.ts @@ -0,0 +1,38 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FirebaseApp } from '@firebase/app-compat'; +import { FunctionsService } from '../src/service'; +import { getFunctions } from '@firebase/functions-exp'; + +export function createTestService( + app: FirebaseApp, + regionOrCustomDomain?: string +): FunctionsService { + const functions = new FunctionsService( + app, + getFunctions(app, regionOrCustomDomain) + ); + const useEmulator = !!process.env.FIREBASE_FUNCTIONS_EMULATOR_HOST; + if (useEmulator) { + functions.useEmulator( + process.env.FIREBASE_FUNCTIONS_EMULATOR_HOST!, + Number(process.env.FIREBASE_FUNCTIONS_EMULATOR_PORT!) + ); + } + return functions; +} diff --git a/packages-exp/functions-compat/tsconfig.json b/packages-exp/functions-compat/tsconfig.json new file mode 100644 index 00000000000..a06ed9a374c --- /dev/null +++ b/packages-exp/functions-compat/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../config/tsconfig.base.json", + "compilerOptions": { + "outDir": "dist" + }, + "exclude": [ + "dist/**/*" + ] +} \ No newline at end of file diff --git a/packages-exp/functions-exp/package.json b/packages-exp/functions-exp/package.json index b75cbd82c21..f521197ae56 100644 --- a/packages-exp/functions-exp/package.json +++ b/packages-exp/functions-exp/package.json @@ -1,14 +1,15 @@ { "name": "@firebase/functions-exp", - "version": "0.0.800", + "version": "0.0.900", "description": "", "private": true, "author": "Firebase (https://firebase.google.com/)", "main": "dist/index.node.cjs.js", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "esm2017": "dist/index.esm2017.js", - "files": ["dist"], + "browser": "dist/index.esm2017.js", + "module": "dist/index.esm2017.js", + "files": [ + "dist" + ], "scripts": { "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", @@ -23,7 +24,6 @@ "test:browser:debug": "karma start --browsers=Chrome --auto-watch", "test:node": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha 'src/{,!(browser)/**/}*.test.ts' --file src/index.node.ts --config ../../config/mocharc.node.js", "test:emulator": "env FIREBASE_FUNCTIONS_EMULATOR_ORIGIN=http://localhost:5005 run-p test:node", - "prepare": "yarn build:release", "api-report": "api-extractor run --local --verbose", "predoc": "node ../../scripts/exp/remove-exp.js temp", "doc": "api-documenter markdown --input temp --output docs", @@ -31,14 +31,14 @@ }, "license": "Apache-2.0", "peerDependencies": { - "@firebase/app-exp": "0.x", - "@firebase/app-types-exp": "0.x" + "@firebase/app-exp": "0.x" }, "devDependencies": { - "@firebase/app-exp": "0.0.800", - "rollup": "2.29.0", - "rollup-plugin-typescript2": "0.27.3", - "typescript": "4.0.2" + "@firebase/app-exp": "0.0.900", + "rollup": "2.52.2", + "@rollup/plugin-json": "4.1.0", + "rollup-plugin-typescript2": "0.30.0", + "typescript": "4.2.2" }, "repository": { "directory": "packages/functions", @@ -50,17 +50,19 @@ }, "typings": "dist/functions-exp-public.d.ts", "dependencies": { - "@firebase/component": "0.1.19", - "@firebase/functions-types-exp": "0.0.800", + "@firebase/component": "0.5.4", "@firebase/messaging-types": "0.5.0", - "@firebase/util": "0.3.2", + "@firebase/auth-interop-types": "0.1.6", + "@firebase/app-check-interop-types": "0.1.0", + "@firebase/util": "1.1.0", "node-fetch": "2.6.1", - "tslib": "^1.11.1" + "tslib": "^2.1.0" }, "nyc": { "extension": [ ".ts" ], "reportDir": "./coverage/node" - } -} + }, + "esm5": "dist/index.esm.js" +} \ No newline at end of file diff --git a/packages-exp/functions-exp/rollup.shared.js b/packages-exp/functions-exp/rollup.shared.js index 222b923d075..15d16f45300 100644 --- a/packages-exp/functions-exp/rollup.shared.js +++ b/packages-exp/functions-exp/rollup.shared.js @@ -16,9 +16,10 @@ */ import pkg from './package.json'; -const deps = Object.keys( - Object.assign({}, pkg.peerDependencies, pkg.dependencies) -); +const deps = [ + ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), + '@firebase/app' +]; export const es5BuildsNoPlugin = [ /** @@ -26,7 +27,7 @@ export const es5BuildsNoPlugin = [ */ { input: 'src/index.ts', - output: [{ file: pkg.module, format: 'es', sourcemap: true }], + output: [{ file: pkg.esm5, format: 'es', sourcemap: true }], external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) }, /** @@ -49,7 +50,7 @@ export const es2017BuildsNoPlugin = [ */ input: 'src/index.ts', output: { - file: pkg.esm2017, + file: pkg.browser, format: 'es', sourcemap: true }, diff --git a/packages-exp/functions-exp/src/api.ts b/packages-exp/functions-exp/src/api.ts index 3d7ff92f740..70b98696960 100644 --- a/packages-exp/functions-exp/src/api.ts +++ b/packages-exp/functions-exp/src/api.ts @@ -15,22 +15,20 @@ * limitations under the License. */ -import { _getProvider } from '@firebase/app-exp'; -import { FirebaseApp } from '@firebase/app-types-exp'; +import { _getProvider, FirebaseApp, getApp } from '@firebase/app-exp'; import { FUNCTIONS_TYPE } from './constants'; import { Provider } from '@firebase/component'; -import { - Functions, - HttpsCallableOptions, - HttpsCallable -} from '@firebase/functions-types-exp'; +import { Functions, HttpsCallableOptions, HttpsCallable } from './public-types'; import { FunctionsService, DEFAULT_REGION, useFunctionsEmulator as _useFunctionsEmulator, httpsCallable as _httpsCallable } from './service'; +import { getModularInstance } from '@firebase/util'; + +export * from './public-types'; /** * Returns a Functions instance for the given app. @@ -41,12 +39,12 @@ import { * @public */ export function getFunctions( - app: FirebaseApp, + app: FirebaseApp = getApp(), regionOrCustomDomain: string = DEFAULT_REGION ): Functions { // Dependencies - const functionsProvider: Provider<'functions'> = _getProvider( - app, + const functionsProvider: Provider<'functions-exp'> = _getProvider( + getModularInstance(app), FUNCTIONS_TYPE ); const functionsInstance = functionsProvider.getImmediate({ @@ -56,18 +54,24 @@ export function getFunctions( } /** - * Changes this instance to point to a Cloud Functions emulator running - * locally. See https://firebase.google.com/docs/functions/local-emulator + * Modify this instance to communicate with the Cloud Functions emulator. + * + * Note: this must be called before this instance has been used to do any operations. * - * @param origin - The origin of the local emulator, such as - * "http://localhost:5005". + * @param host - The emulator host (ex: localhost) + * @param port - The emulator port (ex: 5001) * @public */ export function useFunctionsEmulator( functionsInstance: Functions, - origin: string + host: string, + port: number ): void { - _useFunctionsEmulator(functionsInstance as FunctionsService, origin); + _useFunctionsEmulator( + getModularInstance(functionsInstance as FunctionsService), + host, + port + ); } /** @@ -75,10 +79,14 @@ export function useFunctionsEmulator( * @param name - The name of the trigger. * @public */ -export function httpsCallable( +export function httpsCallable( functionsInstance: Functions, name: string, options?: HttpsCallableOptions -): HttpsCallable { - return _httpsCallable(functionsInstance as FunctionsService, name, options); +): HttpsCallable { + return _httpsCallable( + getModularInstance(functionsInstance as FunctionsService), + name, + options + ); } diff --git a/packages-exp/functions-exp/src/callable.test.ts b/packages-exp/functions-exp/src/callable.test.ts index f2d2ca72be3..c1128198470 100644 --- a/packages-exp/functions-exp/src/callable.test.ts +++ b/packages-exp/functions-exp/src/callable.test.ts @@ -16,8 +16,8 @@ */ import { expect } from 'chai'; import * as sinon from 'sinon'; -import { FirebaseApp } from '@firebase/app-types-exp'; -import { FunctionsErrorCode } from '@firebase/functions-types-exp'; +import { FirebaseApp } from '@firebase/app-exp'; +import { FunctionsErrorCode } from './public-types'; import { Provider, ComponentContainer, @@ -86,7 +86,10 @@ describe('Firebase Functions > Call', () => { null: null }; - const func = httpsCallable(functions, 'dataTest'); + const func = httpsCallable< + Record, + { message: string; code: number; long: number } + >(functions, 'dataTest'); const result = await func(data); expect(result.data).to.deep.equal({ @@ -98,7 +101,7 @@ describe('Firebase Functions > Call', () => { it('scalars', async () => { const functions = createTestService(app, region); - const func = httpsCallable(functions, 'scalarTest'); + const func = httpsCallable(functions, 'scalarTest'); const result = await func(17); expect(result.data).to.equal(76); }); diff --git a/packages-exp/functions-exp/src/config.ts b/packages-exp/functions-exp/src/config.ts index 9279835b2c7..12a5be4104b 100644 --- a/packages-exp/functions-exp/src/config.ts +++ b/packages-exp/functions-exp/src/config.ts @@ -24,24 +24,27 @@ import { InstanceFactory } from '@firebase/component'; import { FUNCTIONS_TYPE } from './constants'; +import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types'; -export const DEFAULT_REGION = 'us-central1'; - +const APP_CHECK_INTERNAL_NAME: AppCheckInternalComponentName = + 'app-check-internal'; export function registerFunctions(fetchImpl: typeof fetch): void { - const factory: InstanceFactory<'functions'> = ( + const factory: InstanceFactory<'functions-exp'> = ( container: ComponentContainer, - regionOrCustomDomain?: string + { instanceIdentifier: regionOrCustomDomain } ) => { // Dependencies const app = container.getProvider('app-exp').getImmediate(); const authProvider = container.getProvider('auth-internal'); const messagingProvider = container.getProvider('messaging'); + const appCheckProvider = container.getProvider(APP_CHECK_INTERNAL_NAME); // eslint-disable-next-line @typescript-eslint/no-explicit-any return new FunctionsService( app, authProvider, messagingProvider, + appCheckProvider, regionOrCustomDomain, fetchImpl ); diff --git a/packages-exp/functions-exp/src/constants.ts b/packages-exp/functions-exp/src/constants.ts index bbb08917fe9..18e029bfd03 100644 --- a/packages-exp/functions-exp/src/constants.ts +++ b/packages-exp/functions-exp/src/constants.ts @@ -18,4 +18,4 @@ /** * Type constant for Firebase Functions. */ -export const FUNCTIONS_TYPE = 'functions'; +export const FUNCTIONS_TYPE = 'functions-exp'; diff --git a/packages-exp/functions-exp/src/context.ts b/packages-exp/functions-exp/src/context.ts index 7ccd5f8823a..2e481239acb 100644 --- a/packages-exp/functions-exp/src/context.ts +++ b/packages-exp/functions-exp/src/context.ts @@ -1,3 +1,7 @@ +import { + FirebaseAuthInternal, + FirebaseAuthInternalName +} from '@firebase/auth-interop-types'; /** * @license * Copyright 2017 Google LLC @@ -18,11 +22,12 @@ import { FirebaseMessaging, FirebaseMessagingName } from '@firebase/messaging-types'; -import { - FirebaseAuthInternal, - FirebaseAuthInternalName -} from '@firebase/auth-interop-types'; + import { Provider } from '@firebase/component'; +import { + AppCheckInternalComponentName, + FirebaseAppCheckInternal +} from '@firebase/app-check-interop-types'; /** * The metadata that should be supplied with function calls. @@ -31,6 +36,7 @@ import { Provider } from '@firebase/component'; export interface Context { authToken?: string; messagingToken?: string; + appCheckToken: string | null; } /** @@ -40,9 +46,11 @@ export interface Context { export class ContextProvider { private auth: FirebaseAuthInternal | null = null; private messaging: FirebaseMessaging | null = null; + private appCheck: FirebaseAppCheckInternal | null = null; constructor( authProvider: Provider, - messagingProvider: Provider + messagingProvider: Provider, + appCheckProvider: Provider ) { this.auth = authProvider.getImmediate({ optional: true }); this.messaging = messagingProvider.getImmediate({ @@ -66,6 +74,15 @@ export class ContextProvider { } ); } + + if (!this.appCheck) { + appCheckProvider.get().then( + appCheck => (this.appCheck = appCheck), + () => { + /* get() never rejects */ + } + ); + } } async getAuthToken(): Promise { @@ -92,7 +109,7 @@ export class ContextProvider { } try { - return this.messaging.getToken(); + return await this.messaging.getToken(); } catch (e) { // We don't warn on this, because it usually means messaging isn't set up. // console.warn('Failed to retrieve instance id token.', e); @@ -102,9 +119,22 @@ export class ContextProvider { } } + async getAppCheckToken(): Promise { + if (this.appCheck) { + const result = await this.appCheck.getToken(); + // If getToken() fails, it will still return a dummy token that also has + // an error field containing the error message. We will send any token + // provided here and show an error if/when it is rejected by the functions + // endpoint. + return result.token; + } + return null; + } + async getContext(): Promise { const authToken = await this.getAuthToken(); const messagingToken = await this.getMessagingToken(); - return { authToken, messagingToken }; + const appCheckToken = await this.getAppCheckToken(); + return { authToken, messagingToken, appCheckToken }; } } diff --git a/packages-exp/functions-exp/src/error.ts b/packages-exp/functions-exp/src/error.ts index 16d454e025c..b30abbdfe6a 100644 --- a/packages-exp/functions-exp/src/error.ts +++ b/packages-exp/functions-exp/src/error.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { FunctionsErrorCode } from '@firebase/functions-types-exp'; +import { FunctionsErrorCode } from './public-types'; import { decode } from './serializer'; import { HttpResponseBody } from './service'; import { FirebaseError } from '@firebase/util'; diff --git a/packages-exp/functions-exp/src/index.ts b/packages-exp/functions-exp/src/index.ts index ad8b299b634..903974e84a2 100644 --- a/packages-exp/functions-exp/src/index.ts +++ b/packages-exp/functions-exp/src/index.ts @@ -1,3 +1,9 @@ +/** + * Cloud Functions for Firebase + * + * @packageDocumentation + */ + /** * @license * Copyright 2017 Google LLC diff --git a/packages-exp/functions-exp/src/public-types.ts b/packages-exp/functions-exp/src/public-types.ts new file mode 100644 index 00000000000..e64f9dbe759 --- /dev/null +++ b/packages-exp/functions-exp/src/public-types.ts @@ -0,0 +1,157 @@ +/** + * @license + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { FirebaseApp } from '@firebase/app-exp'; +import { FirebaseError } from '@firebase/util'; + +/** + * An HttpsCallableResult wraps a single result from a function call. + * @public + */ +export interface HttpsCallableResult { + /** + * Data returned from callable function. + */ + readonly data: ResponseData; +} + +/** + * An HttpsCallable is a reference to a "callable" http trigger in + * Google Cloud Functions. + * @param data - Data to be passed to callable function. + * @public + */ +export type HttpsCallable = ( + data?: RequestData | null +) => Promise>; + +/** + * HttpsCallableOptions specify metadata about how calls should be executed. + * @public + */ +export interface HttpsCallableOptions { + /** + * Time in milliseconds after which to cancel if there is no response. + * Default is 70000. + */ + timeout?: number; +} + +/** + * `Functions` represents a Functions instance, and is a required argument for + * all Functions operations. + * @public + */ +export interface Functions { + /** + * The FirebaseApp this Functions instance is associated with. + */ + app: FirebaseApp; + + /** + * The region the callable Cloud Functions are located in. + * Default is `us-central-1`. + */ + region: string; + + /** + * A custom domain hosting the callable Cloud Functions. + * ex: https://mydomain.com + */ + customDomain: string | null; +} + +/** + * The set of Firebase Functions status codes. The codes are the same at the + * ones exposed by gRPC here: + * https://github.com/grpc/grpc/blob/master/doc/statuscodes.md + * + * Possible values: + * - 'cancelled': The operation was cancelled (typically by the caller). + * - 'unknown': Unknown error or an error from a different error domain. + * - 'invalid-argument': Client specified an invalid argument. Note that this + * differs from 'failed-precondition'. 'invalid-argument' indicates + * arguments that are problematic regardless of the state of the system + * (e.g. an invalid field name). + * - 'deadline-exceeded': Deadline expired before operation could complete. + * For operations that change the state of the system, this error may be + * returned even if the operation has completed successfully. For example, + * a successful response from a server could have been delayed long enough + * for the deadline to expire. + * - 'not-found': Some requested document was not found. + * - 'already-exists': Some document that we attempted to create already + * exists. + * - 'permission-denied': The caller does not have permission to execute the + * specified operation. + * - 'resource-exhausted': Some resource has been exhausted, perhaps a + * per-user quota, or perhaps the entire file system is out of space. + * - 'failed-precondition': Operation was rejected because the system is not + * in a state required for the operation's execution. + * - 'aborted': The operation was aborted, typically due to a concurrency + * issue like transaction aborts, etc. + * - 'out-of-range': Operation was attempted past the valid range. + * - 'unimplemented': Operation is not implemented or not supported/enabled. + * - 'internal': Internal errors. Means some invariants expected by + * underlying system has been broken. If you see one of these errors, + * something is very broken. + * - 'unavailable': The service is currently unavailable. This is most likely + * a transient condition and may be corrected by retrying with a backoff. + * - 'data-loss': Unrecoverable data loss or corruption. + * - 'unauthenticated': The request does not have valid authentication + * credentials for the operation. + * @public + */ +export type FunctionsErrorCode = + | 'ok' + | 'cancelled' + | 'unknown' + | 'invalid-argument' + | 'deadline-exceeded' + | 'not-found' + | 'already-exists' + | 'permission-denied' + | 'resource-exhausted' + | 'failed-precondition' + | 'aborted' + | 'out-of-range' + | 'unimplemented' + | 'internal' + | 'unavailable' + | 'data-loss' + | 'unauthenticated'; + +/** + * An error returned by the Firebase Functions client SDK. + * @public + */ +export interface FunctionsError extends FirebaseError { + /** + * A standard error code that will be returned to the client. This also + * determines the HTTP status code of the response, as defined in code.proto. + */ + readonly code: FunctionsErrorCode; + + /** + * Extra data to be converted to JSON and included in the error response. + */ + readonly details?: unknown; +} + +declare module '@firebase/component' { + interface NameServiceMapping { + 'functions-exp': Functions; + } +} diff --git a/packages-exp/functions-exp/src/serializer.test.ts b/packages-exp/functions-exp/src/serializer.test.ts index 0a675cd01f3..8c1709d37c2 100644 --- a/packages-exp/functions-exp/src/serializer.test.ts +++ b/packages-exp/functions-exp/src/serializer.test.ts @@ -82,6 +82,18 @@ describe('Serializer', () => { expect(decode('hello')).to.equal('hello'); }); + it('encodes date to ISO string', () => { + expect(encode(new Date(1620666095891))).to.equal( + '2021-05-10T17:01:35.891Z' + ); + }); + + it('decodes date string without modifying it', () => { + expect(decode('2021-05-10T17:01:35.891Z')).to.equal( + '2021-05-10T17:01:35.891Z' + ); + }); + // TODO(klimt): Make this test more interesting once we have a complex type // that can be created in JavaScript. it('encodes array', () => { @@ -111,12 +123,14 @@ describe('Serializer', () => { encode({ foo: 1, bar: 'hello', - baz: [1, 2, 3] + baz: [1, 2, 3], + date: new Date(1620666095891) }) ).to.deep.equal({ foo: 1, bar: 'hello', - baz: [1, 2, 3] + baz: [1, 2, 3], + date: '2021-05-10T17:01:35.891Z' }); }); @@ -132,12 +146,14 @@ describe('Serializer', () => { value: '1099511627776', '@type': 'type.googleapis.com/google.protobuf.Int64Value' } - ] + ], + date: '2021-05-10T17:01:35.891Z' }) ).to.deep.equal({ foo: 1, bar: 'hello', - baz: [1, 2, 1099511627776] + baz: [1, 2, 1099511627776], + date: '2021-05-10T17:01:35.891Z' }); }); diff --git a/packages-exp/functions-exp/src/serializer.ts b/packages-exp/functions-exp/src/serializer.ts index bbe2208b025..e6247ec34d1 100644 --- a/packages-exp/functions-exp/src/serializer.ts +++ b/packages-exp/functions-exp/src/serializer.ts @@ -56,6 +56,9 @@ export function encode(data: unknown): unknown { if (Object.prototype.toString.call(data) === '[object String]') { return data; } + if (data instanceof Date) { + return data.toISOString(); + } if (Array.isArray(data)) { return data.map(x => encode(x)); } diff --git a/packages-exp/functions-exp/src/service.test.ts b/packages-exp/functions-exp/src/service.test.ts index 9f518d29284..764e323ed03 100644 --- a/packages-exp/functions-exp/src/service.test.ts +++ b/packages-exp/functions-exp/src/service.test.ts @@ -41,7 +41,7 @@ describe('Firebase Functions > Service', () => { it('can use emulator', () => { service = createTestService(app); - useFunctionsEmulator(service, 'http://localhost:5005'); + useFunctionsEmulator(service, 'localhost', 5005); assert.equal( service._url('foo'), 'http://localhost:5005/my-project/us-central1/foo' @@ -58,7 +58,7 @@ describe('Firebase Functions > Service', () => { it('correctly sets region with emulator', () => { service = createTestService(app, 'my-region'); - useFunctionsEmulator(service, 'http://localhost:5005'); + useFunctionsEmulator(service, 'localhost', 5005); assert.equal( service._url('foo'), 'http://localhost:5005/my-project/my-region/foo' @@ -72,7 +72,7 @@ describe('Firebase Functions > Service', () => { it('prefers emulator to custom domain', () => { const service = createTestService(app, 'https://mydomain.com'); - useFunctionsEmulator(service, 'http://localhost:5005'); + useFunctionsEmulator(service, 'localhost', 5005); assert.equal( service._url('foo'), 'http://localhost:5005/my-project/us-central1/foo' diff --git a/packages-exp/functions-exp/src/service.ts b/packages-exp/functions-exp/src/service.ts index 30c557761a5..3ec217aaabc 100644 --- a/packages-exp/functions-exp/src/service.ts +++ b/packages-exp/functions-exp/src/service.ts @@ -15,18 +15,19 @@ * limitations under the License. */ -import { FirebaseApp, _FirebaseService } from '@firebase/app-types-exp'; +import { FirebaseApp, _FirebaseService } from '@firebase/app-exp'; import { HttpsCallable, HttpsCallableResult, HttpsCallableOptions -} from '@firebase/functions-types-exp'; +} from './public-types'; import { _errorForResponse, FunctionsError } from './error'; import { ContextProvider } from './context'; import { encode, decode } from './serializer'; import { Provider } from '@firebase/component'; import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; import { FirebaseMessagingName } from '@firebase/messaging-types'; +import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types'; export const DEFAULT_REGION = 'us-central1'; @@ -86,10 +87,15 @@ export class FunctionsService implements _FirebaseService { readonly app: FirebaseApp, authProvider: Provider, messagingProvider: Provider, + appCheckProvider: Provider, regionOrCustomDomain: string = DEFAULT_REGION, readonly fetchImpl: typeof fetch ) { - this.contextProvider = new ContextProvider(authProvider, messagingProvider); + this.contextProvider = new ContextProvider( + authProvider, + messagingProvider, + appCheckProvider + ); // Cancels all ongoing requests when resolved. this.cancelAllRequests = new Promise(resolve => { this.deleteService = () => { @@ -133,18 +139,20 @@ export class FunctionsService implements _FirebaseService { } /** - * Changes this instance to point to a Cloud Functions emulator running - * locally. See https://firebase.google.com/docs/functions/local-emulator + * Modify this instance to communicate with the Cloud Functions emulator. + * + * Note: this must be called before this instance has been used to do any operations. * - * @param origin - The origin of the local emulator, such as - * "http://localhost:5005". + * @param host The emulator host (ex: localhost) + * @param port The emulator port (ex: 5001) * @public */ export function useFunctionsEmulator( functionsInstance: FunctionsService, - origin: string + host: string, + port: number ): void { - functionsInstance.emulatorOrigin = origin; + functionsInstance.emulatorOrigin = `http://${host}:${port}`; } /** @@ -152,14 +160,14 @@ export function useFunctionsEmulator( * @param name - The name of the trigger. * @public */ -export function httpsCallable( +export function httpsCallable( functionsInstance: FunctionsService, name: string, options?: HttpsCallableOptions -): HttpsCallable { - return data => { +): HttpsCallable { + return (data => { return call(functionsInstance, name, data, options || {}); - }; + }) as HttpsCallable; } /** @@ -232,6 +240,9 @@ async function call( if (context.messagingToken) { headers['Firebase-Instance-ID-Token'] = context.messagingToken; } + if (context.appCheckToken !== null) { + headers['X-Firebase-AppCheck'] = context.appCheckToken; + } // Default timeout to 70s, but let the options override it. const timeout = options.timeout || 70000; diff --git a/packages-exp/functions-exp/test/utils.ts b/packages-exp/functions-exp/test/utils.ts index 659016c01df..8a258bf23ca 100644 --- a/packages-exp/functions-exp/test/utils.ts +++ b/packages-exp/functions-exp/test/utils.ts @@ -15,10 +15,11 @@ * limitations under the License. */ -import { FirebaseOptions, FirebaseApp } from '@firebase/app-types-exp'; +import { FirebaseOptions, FirebaseApp } from '@firebase/app-exp'; import { Provider, ComponentContainer } from '@firebase/component'; import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; import { FirebaseMessagingName } from '@firebase/messaging-types'; +import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types'; import { FunctionsService } from '../src/service'; import { useFunctionsEmulator } from '../src/api'; import nodeFetch from 'node-fetch'; @@ -51,6 +52,10 @@ export function createTestService( messagingProvider = new Provider( 'messaging', new ComponentContainer('test') + ), + appCheckProvider = new Provider( + 'app-check-internal', + new ComponentContainer('test') ) ): FunctionsService { const fetchImpl: typeof fetch = @@ -59,14 +64,17 @@ export function createTestService( app, authProvider, messagingProvider, + appCheckProvider, region, fetchImpl ); const useEmulator = !!process.env.FIREBASE_FUNCTIONS_EMULATOR_ORIGIN; if (useEmulator) { + const url = new URL(process.env.FIREBASE_FUNCTIONS_EMULATOR_ORIGIN!); useFunctionsEmulator( functions, - process.env.FIREBASE_FUNCTIONS_EMULATOR_ORIGIN! + url.hostname, + Number.parseInt(url.port, 10) ); } return functions; diff --git a/packages-exp/functions-types-exp/README.md b/packages-exp/functions-types-exp/README.md deleted file mode 100644 index 59e884b28ea..00000000000 --- a/packages-exp/functions-types-exp/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# @firebase/functions-types - -**This package is not intended for direct usage, and should only be used via the officially supported [firebase](https://www.npmjs.com/package/firebase) package.** diff --git a/packages-exp/functions-types-exp/api-extractor.json b/packages-exp/functions-types-exp/api-extractor.json deleted file mode 100644 index 42f37a88c4b..00000000000 --- a/packages-exp/functions-types-exp/api-extractor.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "../../config/api-extractor.json", - // Point it to your entry point d.ts file. - "mainEntryPointFilePath": "/index.d.ts" -} \ No newline at end of file diff --git a/packages-exp/functions-types-exp/index.d.ts b/packages-exp/functions-types-exp/index.d.ts deleted file mode 100644 index 9b529870825..00000000000 --- a/packages-exp/functions-types-exp/index.d.ts +++ /dev/null @@ -1,140 +0,0 @@ -/** - * @license - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { FirebaseApp } from '@firebase/app-types-exp'; -import { FirebaseError } from '@firebase/util'; - -/** - * An HttpsCallableResult wraps a single result from a function call. - */ -export interface HttpsCallableResult { - readonly data: any; -} - -/** - * An HttpsCallable is a reference to a "callable" http trigger in - * Google Cloud Functions. - */ -export interface HttpsCallable { - (data?: {} | null): Promise; -} - -/** - * HttpsCallableOptions specify metadata about how calls should be executed. - */ -export interface HttpsCallableOptions { - timeout?: number; // in millis -} - -/** - * `Functions` represents a Functions instance, and is a required argument for - * all Functions operations. - */ -export interface Functions { - /** - * The FirebaseApp this Functions instance is associated with. - */ - app: FirebaseApp; - - /** - * The region the callable Cloud Functions are located in. - * Default is `us-central-1`. - */ - region: string; - - /** - * A custom domain hosting the callable Cloud Functions. - * ex: https://mydomain.com - */ - customDomain: string | null; -} - -/** - * The set of Firebase Functions status codes. The codes are the same at the - * ones exposed by gRPC here: - * https://github.com/grpc/grpc/blob/master/doc/statuscodes.md - * - * Possible values: - * - 'cancelled': The operation was cancelled (typically by the caller). - * - 'unknown': Unknown error or an error from a different error domain. - * - 'invalid-argument': Client specified an invalid argument. Note that this - * differs from 'failed-precondition'. 'invalid-argument' indicates - * arguments that are problematic regardless of the state of the system - * (e.g. an invalid field name). - * - 'deadline-exceeded': Deadline expired before operation could complete. - * For operations that change the state of the system, this error may be - * returned even if the operation has completed successfully. For example, - * a successful response from a server could have been delayed long enough - * for the deadline to expire. - * - 'not-found': Some requested document was not found. - * - 'already-exists': Some document that we attempted to create already - * exists. - * - 'permission-denied': The caller does not have permission to execute the - * specified operation. - * - 'resource-exhausted': Some resource has been exhausted, perhaps a - * per-user quota, or perhaps the entire file system is out of space. - * - 'failed-precondition': Operation was rejected because the system is not - * in a state required for the operation's execution. - * - 'aborted': The operation was aborted, typically due to a concurrency - * issue like transaction aborts, etc. - * - 'out-of-range': Operation was attempted past the valid range. - * - 'unimplemented': Operation is not implemented or not supported/enabled. - * - 'internal': Internal errors. Means some invariants expected by - * underlying system has been broken. If you see one of these errors, - * something is very broken. - * - 'unavailable': The service is currently unavailable. This is most likely - * a transient condition and may be corrected by retrying with a backoff. - * - 'data-loss': Unrecoverable data loss or corruption. - * - 'unauthenticated': The request does not have valid authentication - * credentials for the operation. - */ -export type FunctionsErrorCode = - | 'ok' - | 'cancelled' - | 'unknown' - | 'invalid-argument' - | 'deadline-exceeded' - | 'not-found' - | 'already-exists' - | 'permission-denied' - | 'resource-exhausted' - | 'failed-precondition' - | 'aborted' - | 'out-of-range' - | 'unimplemented' - | 'internal' - | 'unavailable' - | 'data-loss' - | 'unauthenticated'; - -export interface FunctionsError extends FirebaseError { - /** - * A standard error code that will be returned to the client. This also - * determines the HTTP status code of the response, as defined in code.proto. - */ - readonly code: FunctionsErrorCode; - - /** - * Extra data to be converted to JSON and included in the error response. - */ - readonly details?: any; -} - -declare module '@firebase/component' { - interface NameServiceMapping { - 'functions': Functions; - } -} diff --git a/packages-exp/functions-types-exp/package.json b/packages-exp/functions-types-exp/package.json deleted file mode 100644 index 714acf335ea..00000000000 --- a/packages-exp/functions-types-exp/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "@firebase/functions-types-exp", - "version": "0.0.800", - "description": "@firebase/functions Types", - "private": true, - "author": "Firebase (https://firebase.google.com/)", - "license": "Apache-2.0", - "scripts": { - "test": "tsc", - "test:ci": "node ../../scripts/run_tests_in_ci.js", - "prepare": "node ../../scripts/exp/remove-exp.js ./index.d.ts", - "api-report": "api-extractor run --local --verbose", - "predoc": "node ../../scripts/exp/remove-exp.js temp", - "doc": "api-documenter markdown --input temp --output docs", - "build:doc": "yarn api-report && yarn doc" - }, - "files": [ - "index.d.ts" - ], - "repository": { - "directory": "packages/functions-types", - "type": "git", - "url": "https://github.com/firebase/firebase-js-sdk.git" - }, - "bugs": { - "url": "https://github.com/firebase/firebase-js-sdk/issues" - }, - "devDependencies": { - "typescript": "4.0.2" - } -} diff --git a/packages-exp/functions-types-exp/tsconfig.json b/packages-exp/functions-types-exp/tsconfig.json deleted file mode 100644 index 9a785433d90..00000000000 --- a/packages-exp/functions-types-exp/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../config/tsconfig.base.json", - "compilerOptions": { - "noEmit": true - }, - "exclude": [ - "dist/**/*" - ] -} diff --git a/packages-exp/installations-compat/.eslintrc.js b/packages-exp/installations-compat/.eslintrc.js new file mode 100644 index 00000000000..ca80aa0f69a --- /dev/null +++ b/packages-exp/installations-compat/.eslintrc.js @@ -0,0 +1,26 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +module.exports = { + extends: '../../config/.eslintrc.js', + parserOptions: { + project: 'tsconfig.json', + // to make vscode-eslint work with monorepo + // https://github.com/typescript-eslint/typescript-eslint/issues/251#issuecomment-463943250 + tsconfigRootDir: __dirname + } +}; diff --git a/packages-exp/installations-compat/karma.conf.js b/packages-exp/installations-compat/karma.conf.js new file mode 100644 index 00000000000..1699a0681ec --- /dev/null +++ b/packages-exp/installations-compat/karma.conf.js @@ -0,0 +1,35 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const karmaBase = require('../../config/karma.base'); + +const files = [`src/**/*.test.ts`]; + +module.exports = function (config) { + const karmaConfig = { + ...karmaBase, + // files to load into karma + files, + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['mocha'] + }; + + config.set(karmaConfig); +}; + +module.exports.files = files; diff --git a/packages-exp/installations-compat/package.json b/packages-exp/installations-compat/package.json new file mode 100644 index 00000000000..bc45755ae15 --- /dev/null +++ b/packages-exp/installations-compat/package.json @@ -0,0 +1,60 @@ +{ + "name": "@firebase/installations-compat", + "version": "0.0.900", + "private": true, + "author": "Firebase (https://firebase.google.com/)", + "main": "dist/index.cjs.js", + "module": "dist/index.esm2017.js", + "browser": "dist/index.esm2017.js", + "typings": "dist/installations-compat.d.ts", + "license": "Apache-2.0", + "files": [ + "dist" + ], + "scripts": { + "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "build": "rollup -c", + "build:deps": "lerna run --scope @firebase/installations-compat --include-dependencies build", + "build:release": "rollup -c rollup.config.release.js", + "dev": "rollup -c -w", + "test": "yarn type-check && yarn test:karma && yarn lint", + "test:ci": "node ../../scripts/run_tests_in_ci.js", + "test:karma": "karma start --single-run", + "test:debug": "karma start --browsers=Chrome --auto-watch", + "type-check": "tsc -p . --noEmit", + "serve": "yarn serve:build && yarn serve:host", + "serve:build": "rollup -c test-app/rollup.config.js", + "serve:host": "http-server -c-1 test-app" + }, + "repository": { + "directory": "packages-exp/installations-compat", + "type": "git", + "url": "https://github.com/firebase/firebase-js-sdk.git" + }, + "bugs": { + "url": "https://github.com/firebase/firebase-js-sdk/issues" + }, + "devDependencies": { + "@firebase/app-compat": "0.0.900", + "rollup": "2.52.2", + "@rollup/plugin-commonjs": "17.1.0", + "@rollup/plugin-json": "4.1.0", + "@rollup/plugin-node-resolve": "11.2.0", + "rollup-plugin-typescript2": "0.30.0", + "rollup-plugin-uglify": "6.0.4", + "typescript": "4.2.2" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + }, + "dependencies": { + "@firebase/installations-exp": "0.0.900", + "@firebase/installations-types": "0.3.4", + "@firebase/util": "1.1.0", + "@firebase/component": "0.5.4", + "idb": "3.0.2", + "tslib": "^2.1.0" + }, + "esm5": "dist/index.esm.js" +} \ No newline at end of file diff --git a/packages-exp/installations-compat/rollup.config.js b/packages-exp/installations-compat/rollup.config.js new file mode 100644 index 00000000000..d66e464f546 --- /dev/null +++ b/packages-exp/installations-compat/rollup.config.js @@ -0,0 +1,53 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import json from '@rollup/plugin-json'; +import typescriptPlugin from 'rollup-plugin-typescript2'; +import typescript from 'typescript'; +import { es2017BuildsNoPlugin, es5BuildsNoPlugin } from './rollup.shared'; + +/** + * ES5 Builds + */ +const es5BuildPlugins = [typescriptPlugin({ typescript }), json()]; + +const es5Builds = es5BuildsNoPlugin.map(build => ({ + ...build, + plugins: es5BuildPlugins +})); + +/** + * ES2017 Builds + */ +const es2017BuildPlugins = [ + typescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017' + } + } + }), + json({ preferConst: true }) +]; + +const es2017Builds = es2017BuildsNoPlugin.map(build => ({ + ...build, + plugins: es2017BuildPlugins +})); + +export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/installations-compat/rollup.config.release.js b/packages-exp/installations-compat/rollup.config.release.js new file mode 100644 index 00000000000..5050932f4a4 --- /dev/null +++ b/packages-exp/installations-compat/rollup.config.release.js @@ -0,0 +1,71 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import json from '@rollup/plugin-json'; +import typescriptPlugin from 'rollup-plugin-typescript2'; +import typescript from 'typescript'; +import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; +import { es2017BuildsNoPlugin, es5BuildsNoPlugin } from './rollup.shared'; + +/** + * ES5 Builds + */ +const es5BuildPlugins = [ + typescriptPlugin({ + typescript, + clean: true, + abortOnError: false, + transformers: [importPathTransformer] + }), + json() +]; + +const es5Builds = es5BuildsNoPlugin.map(build => ({ + ...build, + plugins: es5BuildPlugins, + treeshake: { + moduleSideEffects: false + } +})); + +/** + * ES2017 Builds + */ +const es2017BuildPlugins = [ + typescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017' + } + }, + abortOnError: false, + clean: true, + transformers: [importPathTransformer] + }), + json({ preferConst: true }) +]; + +const es2017Builds = es2017BuildsNoPlugin.map(build => ({ + ...build, + plugins: es2017BuildPlugins, + treeshake: { + moduleSideEffects: false + } +})); + +export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/installations-compat/rollup.shared.js b/packages-exp/installations-compat/rollup.shared.js new file mode 100644 index 00000000000..9b9b1f6cf38 --- /dev/null +++ b/packages-exp/installations-compat/rollup.shared.js @@ -0,0 +1,48 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import pkg from './package.json'; + +const deps = Object.keys({ ...pkg.peerDependencies, ...pkg.dependencies }); + +/** + * ES5 Builds + */ +export const es5BuildsNoPlugin = [ + { + input: 'src/index.ts', + output: [ + { file: pkg.main, format: 'cjs', sourcemap: true }, + { file: pkg.esm5, format: 'es', sourcemap: true } + ], + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + } +]; + +/** + * ES2017 Builds + */ +export const es2017BuildsNoPlugin = [ + { + input: 'src/index.ts', + output: { + file: pkg.browser, + format: 'es', + sourcemap: true + }, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + } +]; diff --git a/packages-exp/installations-compat/src/index.ts b/packages-exp/installations-compat/src/index.ts new file mode 100644 index 00000000000..56475aba6c2 --- /dev/null +++ b/packages-exp/installations-compat/src/index.ts @@ -0,0 +1,60 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import firebase, { _FirebaseNamespace } from '@firebase/app-compat'; +import { name, version } from '../package.json'; +import { Component, ComponentType } from '@firebase/component'; +import { FirebaseInstallations as FirebaseInstallationsCompat } from '@firebase/installations-types'; +import { InstallationsCompat } from './installationsCompat'; + +declare module '@firebase/component' { + interface NameServiceMapping { + 'installations-compat': FirebaseInstallationsCompat; + } +} + +function registerInstallations(instance: _FirebaseNamespace): void { + instance.INTERNAL.registerComponent( + new Component( + 'installations-compat', + container => { + const app = container.getProvider('app-compat').getImmediate()!; + const installations = container + .getProvider('installations-exp') + .getImmediate()!; + return new InstallationsCompat(app, installations); + }, + ComponentType.PUBLIC + ) + ); + + instance.registerVersion(name, version); +} + +registerInstallations(firebase as _FirebaseNamespace); + +/** + * Define extension behavior of `registerInstallations` + */ +declare module '@firebase/app-compat' { + interface FirebaseNamespace { + installations(app?: FirebaseApp): FirebaseInstallationsCompat; + } + interface FirebaseApp { + installations(): FirebaseInstallationsCompat; + } +} diff --git a/packages-exp/installations-compat/src/installationsCompat.test.ts b/packages-exp/installations-compat/src/installationsCompat.test.ts new file mode 100644 index 00000000000..1f3442c4d86 --- /dev/null +++ b/packages-exp/installations-compat/src/installationsCompat.test.ts @@ -0,0 +1,85 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import './testing/setup'; +import { getFakeApp, getFakeInstallations } from './testing/util'; +import { InstallationsCompat } from './installationsCompat'; +import * as modularApi from '@firebase/installations-exp'; +import { expect } from 'chai'; +import { stub } from 'sinon'; + +describe('Installations Compat', () => { + let installationsCompat!: InstallationsCompat; + const installations = getFakeInstallations(); + before(() => { + installationsCompat = new InstallationsCompat(getFakeApp(), installations); + }); + + it('getId calls modular getId()', async () => { + const fakeFid = 'fake-fid'; + const modularGetIdStub = stub(modularApi, 'getId').callsFake(() => + Promise.resolve(fakeFid) + ); + + const res = await installationsCompat.getId(); + + expect(res).to.equal(fakeFid); + expect(modularGetIdStub).to.have.been.calledWithExactly(installations); + }); + + it('getToken calls modular getToken()', async () => { + const fakeToken = 'fake-token'; + const modularGetTokenStub = stub(modularApi, 'getToken').callsFake(() => + Promise.resolve(fakeToken) + ); + + const res = await installationsCompat.getToken(); + + expect(res).to.equal(fakeToken); + expect(modularGetTokenStub).to.have.been.calledWithExactly( + installations, + undefined + ); + }); + + it('delete calls modular deleteInstallations()', async () => { + const modularDeleteStub = stub( + modularApi, + 'deleteInstallations' + ).callsFake(() => Promise.resolve()); + + await installationsCompat.delete(); + + expect(modularDeleteStub).to.have.been.calledWithExactly(installations); + }); + + it('onIdChange calls modular onIdChange()', () => { + const fakeIdChangeCallbackFn = stub(); + const fakeIdChangeUnsubscribeFn = stub(); + const modularOnIdChangeStub = stub(modularApi, 'onIdChange').callsFake( + () => fakeIdChangeUnsubscribeFn + ); + + const res = installationsCompat.onIdChange(fakeIdChangeCallbackFn); + + expect(res).to.equal(fakeIdChangeUnsubscribeFn); + expect(modularOnIdChangeStub).to.have.been.calledWith( + installations, + fakeIdChangeCallbackFn + ); + }); +}); diff --git a/packages-exp/installations-compat/src/installationsCompat.ts b/packages-exp/installations-compat/src/installationsCompat.ts new file mode 100644 index 00000000000..600bf11c0bd --- /dev/null +++ b/packages-exp/installations-compat/src/installationsCompat.ts @@ -0,0 +1,49 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FirebaseInstallations as FirebaseInstallationsCompat } from '@firebase/installations-types'; +import { FirebaseApp, _FirebaseService } from '@firebase/app-compat'; +import { + FirebaseInstallations, + deleteInstallations, + getId, + getToken, + IdChangeCallbackFn, + IdChangeUnsubscribeFn, + onIdChange +} from '@firebase/installations-exp'; + +export class InstallationsCompat + implements FirebaseInstallationsCompat, _FirebaseService { + constructor( + public app: FirebaseApp, + readonly _delegate: FirebaseInstallations + ) {} + + getId(): Promise { + return getId(this._delegate); + } + getToken(forceRefresh?: boolean): Promise { + return getToken(this._delegate, forceRefresh); + } + delete(): Promise { + return deleteInstallations(this._delegate); + } + onIdChange(callback: IdChangeCallbackFn): IdChangeUnsubscribeFn { + return onIdChange(this._delegate, callback); + } +} diff --git a/packages-exp/installations-compat/src/testing/setup.ts b/packages-exp/installations-compat/src/testing/setup.ts new file mode 100644 index 00000000000..463089fa69b --- /dev/null +++ b/packages-exp/installations-compat/src/testing/setup.ts @@ -0,0 +1,26 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { use } from 'chai'; +import { restore } from 'sinon'; +import * as sinonChai from 'sinon-chai'; + +use(sinonChai); + +afterEach(async () => { + restore(); +}); diff --git a/packages-exp/installations-compat/src/testing/util.ts b/packages-exp/installations-compat/src/testing/util.ts new file mode 100644 index 00000000000..2b57f444ce2 --- /dev/null +++ b/packages-exp/installations-compat/src/testing/util.ts @@ -0,0 +1,56 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FirebaseApp } from '@firebase/app-compat'; +import { FirebaseInstallations } from '@firebase/installations-exp'; + +const appName = 'testApp'; +const apiKey = 'AIzaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaA'; +const projectId = 'fis-test-app'; +const appId = '1:777777777777:web:aaaaaaaaaaaaaaaa'; + +export function getFakeApp(): FirebaseApp { + return { + name: appName, + options: { + apiKey, + projectId, + authDomain: 'authDomain', + messagingSenderId: 'messagingSenderId', + databaseURL: 'databaseUrl', + storageBucket: 'storageBucket', + appId + }, + automaticDataCollectionEnabled: true, + delete: async () => {}, + installations: (() => null as unknown) as any + }; +} + +export function getFakeInstallations(): FirebaseInstallations { + return { + app: getFakeApp(), + appConfig: { + appName, + projectId, + apiKey, + appId + }, + platformLoggerProvider: null, + _delete: () => Promise.resolve() + }; +} diff --git a/packages-exp/installations-compat/tsconfig.json b/packages-exp/installations-compat/tsconfig.json new file mode 100644 index 00000000000..420eda97a1d --- /dev/null +++ b/packages-exp/installations-compat/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../config/tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "downlevelIteration": true, + "resolveJsonModule": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "exclude": ["dist/**/*"] +} diff --git a/packages-exp/installations-exp/api-extractor.json b/packages-exp/installations-exp/api-extractor.json index f291311f711..8a3c6cb251e 100644 --- a/packages-exp/installations-exp/api-extractor.json +++ b/packages-exp/installations-exp/api-extractor.json @@ -3,6 +3,8 @@ // Point it to your entry point d.ts file. "mainEntryPointFilePath": "/dist/src/index.d.ts", "dtsRollup": { - "enabled": true + "enabled": true, + "untrimmedFilePath": "/dist/.d.ts", + "publicTrimmedFilePath": "/dist/-public.d.ts" } } \ No newline at end of file diff --git a/packages-exp/installations-exp/package.json b/packages-exp/installations-exp/package.json index fdad30a789a..10982e9b9a1 100644 --- a/packages-exp/installations-exp/package.json +++ b/packages-exp/installations-exp/package.json @@ -1,21 +1,22 @@ { "name": "@firebase/installations-exp", - "version": "0.0.800", + "version": "0.0.900", "private": true, "author": "Firebase (https://firebase.google.com/)", "main": "dist/index.cjs.js", - "module": "dist/index.esm.js", - "browser": "dist/index.esm.js", - "esm2017": "dist/index.esm2017.js", - "typings": "dist/installations-exp.d.ts", + "module": "dist/index.esm2017.js", + "browser": "dist/index.esm2017.js", + "typings": "dist/src/index.d.ts", "license": "Apache-2.0", - "files": ["dist"], + "files": [ + "dist" + ], "scripts": { "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", "build": "rollup -c && yarn api-report", "build:deps": "lerna run --scope @firebase/installations-exp --include-dependencies build", - "build:release": "rollup -c rollup.config.release.js && yarn api-report", + "build:release": "rollup -c rollup.config.release.js && yarn api-report && yarn typings:public", "dev": "rollup -c -w", "test": "yarn type-check && yarn test:karma && yarn lint", "test:ci": "node ../../scripts/run_tests_in_ci.js", @@ -25,11 +26,12 @@ "serve": "yarn serve:build && yarn serve:host", "serve:build": "rollup -c test-app/rollup.config.js", "serve:host": "http-server -c-1 test-app", - "prepare": "yarn build:release", "api-report": "api-extractor run --local --verbose", "predoc": "node ../../scripts/exp/remove-exp.js temp", "doc": "api-documenter markdown --input temp --output docs", - "build:doc": "yarn build && yarn doc" + "build:doc": "yarn build && yarn doc", + "typings:public": "node ../../scripts/exp/use_typings.js ./dist/installations-exp-public.d.ts", + "typings:internal": "node ../../scripts/exp/use_typings.js ./dist/src/index.d.ts" }, "repository": { "directory": "packages-exp/installations-exp", @@ -40,24 +42,23 @@ "url": "https://github.com/firebase/firebase-js-sdk/issues" }, "devDependencies": { - "@firebase/app-exp": "0.0.800", - "rollup": "2.29.0", - "@rollup/plugin-commonjs": "15.1.0", + "@firebase/app-exp": "0.0.900", + "rollup": "2.52.2", + "@rollup/plugin-commonjs": "17.1.0", "@rollup/plugin-json": "4.1.0", - "@rollup/plugin-node-resolve": "9.0.0", - "rollup-plugin-typescript2": "0.27.3", + "@rollup/plugin-node-resolve": "11.2.0", + "rollup-plugin-typescript2": "0.30.0", "rollup-plugin-uglify": "6.0.4", - "typescript": "4.0.2" + "typescript": "4.2.2" }, "peerDependencies": { - "@firebase/app-exp": "0.x", - "@firebase/app-types-exp": "0.x" + "@firebase/app-exp": "0.x" }, "dependencies": { - "@firebase/installations-types-exp": "0.0.800", - "@firebase/util": "0.3.2", - "@firebase/component": "0.1.19", + "@firebase/util": "1.1.0", + "@firebase/component": "0.5.4", "idb": "3.0.2", - "tslib": "^1.11.1" - } -} + "tslib": "^2.1.0" + }, + "esm5": "dist/index.esm.js" +} \ No newline at end of file diff --git a/packages-exp/installations-exp/rollup.shared.js b/packages-exp/installations-exp/rollup.shared.js index ca79f888314..329ad9eb73b 100644 --- a/packages-exp/installations-exp/rollup.shared.js +++ b/packages-exp/installations-exp/rollup.shared.js @@ -16,7 +16,10 @@ */ import pkg from './package.json'; -const deps = Object.keys({ ...pkg.peerDependencies, ...pkg.dependencies }); +const deps = [ + ...Object.keys({ ...pkg.peerDependencies, ...pkg.dependencies }), + '@firebase/app' +]; /** * ES5 Builds @@ -26,7 +29,7 @@ export const es5BuildsNoPlugin = [ input: 'src/index.ts', output: [ { file: pkg.main, format: 'cjs', sourcemap: true }, - { file: pkg.module, format: 'es', sourcemap: true } + { file: pkg.esm5, format: 'es', sourcemap: true } ], external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) } @@ -39,7 +42,7 @@ export const es2017BuildsNoPlugin = [ { input: 'src/index.ts', output: { - file: pkg.esm2017, + file: pkg.browser, format: 'es', sourcemap: true }, diff --git a/packages-exp/installations-exp/src/api/delete-installations.ts b/packages-exp/installations-exp/src/api/delete-installations.ts index 5e3d6b21123..e41163939b1 100644 --- a/packages-exp/installations-exp/src/api/delete-installations.ts +++ b/packages-exp/installations-exp/src/api/delete-installations.ts @@ -20,10 +20,11 @@ import { remove, update } from '../helpers/idb-manager'; import { RequestStatus } from '../interfaces/installation-entry'; import { ERROR_FACTORY, ErrorCode } from '../util/errors'; import { FirebaseInstallationsImpl } from '../interfaces/installation-impl'; -import { FirebaseInstallations } from '@firebase/installations-types-exp'; +import { FirebaseInstallations } from '../interfaces/public-types'; /** * Deletes the Firebase Installation and all associated data. + * @param installations - The `Installations` instance. * * @public */ diff --git a/packages-exp/installations-exp/src/api/get-id.ts b/packages-exp/installations-exp/src/api/get-id.ts index 83a3871d78d..cc3550fa1c5 100644 --- a/packages-exp/installations-exp/src/api/get-id.ts +++ b/packages-exp/installations-exp/src/api/get-id.ts @@ -18,11 +18,12 @@ import { getInstallationEntry } from '../helpers/get-installation-entry'; import { refreshAuthToken } from '../helpers/refresh-auth-token'; import { FirebaseInstallationsImpl } from '../interfaces/installation-impl'; -import { FirebaseInstallations } from '@firebase/installations-types-exp'; +import { FirebaseInstallations } from '../interfaces/public-types'; /** * Creates a Firebase Installation if there isn't one for the app and * returns the Installation ID. + * @param installations - The `Installations` instance. * * @public */ diff --git a/packages-exp/installations-exp/src/api/get-installations.ts b/packages-exp/installations-exp/src/api/get-installations.ts index d2032710ab8..529bea79ecf 100644 --- a/packages-exp/installations-exp/src/api/get-installations.ts +++ b/packages-exp/installations-exp/src/api/get-installations.ts @@ -15,16 +15,18 @@ * limitations under the License. */ -import { FirebaseApp } from '@firebase/app-types-exp'; -import { FirebaseInstallations } from '@firebase/installations-types-exp'; -import { _getProvider } from '@firebase/app-exp'; +import { FirebaseApp, getApp, _getProvider } from '@firebase/app-exp'; +import { FirebaseInstallations } from '../interfaces/public-types'; /** * Returns an instance of FirebaseInstallations associated with the given FirebaseApp instance. + * @param app - The `FirebaseApp` instance. * * @public */ -export function getInstallations(app: FirebaseApp): FirebaseInstallations { +export function getInstallations( + app: FirebaseApp = getApp() +): FirebaseInstallations { const installationsImpl = _getProvider( app, 'installations-exp' diff --git a/packages-exp/installations-exp/src/api/get-token.ts b/packages-exp/installations-exp/src/api/get-token.ts index 53cb89899dd..248ab68c7ec 100644 --- a/packages-exp/installations-exp/src/api/get-token.ts +++ b/packages-exp/installations-exp/src/api/get-token.ts @@ -21,10 +21,12 @@ import { FirebaseInstallationsImpl, AppConfig } from '../interfaces/installation-impl'; -import { FirebaseInstallations } from '@firebase/installations-types-exp'; +import { FirebaseInstallations } from '../interfaces/public-types'; /** * Returns an Installation auth token, identifying the current Firebase Installation. + * @param installations - The `Installations` instance. + * @param forceRefresh - Force refresh regardless of token expiration. * * @public */ diff --git a/packages-exp/installations-exp/src/api/on-id-change.ts b/packages-exp/installations-exp/src/api/on-id-change.ts index a9e4360dfd4..4d0750f61b1 100644 --- a/packages-exp/installations-exp/src/api/on-id-change.ts +++ b/packages-exp/installations-exp/src/api/on-id-change.ts @@ -17,7 +17,7 @@ import { addCallback, removeCallback } from '../helpers/fid-changed'; import { FirebaseInstallationsImpl } from '../interfaces/installation-impl'; -import { FirebaseInstallations } from '@firebase/installations-types-exp'; +import { FirebaseInstallations } from '../interfaces/public-types'; /** * An user defined callback function that gets called when Installations ID changes. @@ -35,6 +35,9 @@ export type IdChangeUnsubscribeFn = () => void; /** * Sets a new callback that will get called when Installation ID changes. * Returns an unsubscribe function that will remove the callback when called. + * @param installations - The `Installations` instance. + * @param callback - The callback function that is invoked when FID changes. + * @returns A function that can be called to unsubscribe. * * @public */ diff --git a/packages-exp/installations-exp/src/functions/config.ts b/packages-exp/installations-exp/src/functions/config.ts index 9a0544b4ba6..efd48f4468f 100644 --- a/packages-exp/installations-exp/src/functions/config.ts +++ b/packages-exp/installations-exp/src/functions/config.ts @@ -16,7 +16,6 @@ */ import { _registerComponent, _getProvider } from '@firebase/app-exp'; -import { _FirebaseService } from '@firebase/app-types-exp'; import { Component, ComponentType, @@ -24,7 +23,7 @@ import { ComponentContainer } from '@firebase/component'; import { getId, getToken } from '../api/index'; -import { _FirebaseInstallationsInternal } from '@firebase/installations-types-exp'; +import { _FirebaseInstallationsInternal } from '../interfaces/public-types'; import { FirebaseInstallationsImpl } from '../interfaces/installation-impl'; import { extractAppConfig } from '../helpers/extract-app-config'; diff --git a/packages-exp/installations-exp/src/helpers/extract-app-config.ts b/packages-exp/installations-exp/src/helpers/extract-app-config.ts index e1390d4ab83..b4c693f1b46 100644 --- a/packages-exp/installations-exp/src/helpers/extract-app-config.ts +++ b/packages-exp/installations-exp/src/helpers/extract-app-config.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { FirebaseApp, FirebaseOptions } from '@firebase/app-types-exp'; +import { FirebaseApp, FirebaseOptions } from '@firebase/app-exp'; import { FirebaseError } from '@firebase/util'; import { AppConfig } from '../interfaces/installation-impl'; import { ERROR_FACTORY, ErrorCode } from '../util/errors'; diff --git a/packages-exp/installations-exp/src/helpers/get-installation-entry.ts b/packages-exp/installations-exp/src/helpers/get-installation-entry.ts index a0e1cc425ce..6b57563eb80 100644 --- a/packages-exp/installations-exp/src/helpers/get-installation-entry.ts +++ b/packages-exp/installations-exp/src/helpers/get-installation-entry.ts @@ -138,7 +138,7 @@ async function registerInstallation( ); return set(appConfig, registeredInstallationEntry); } catch (e) { - if (isServerError(e) && e.serverCode === 409) { + if (isServerError(e) && e.customData.serverCode === 409) { // Server returned a "FID can not be used" error. // Generate a new ID next time. await remove(appConfig); diff --git a/packages-exp/installations-exp/src/helpers/refresh-auth-token.ts b/packages-exp/installations-exp/src/helpers/refresh-auth-token.ts index 1ad5dc5da50..ac2a0ffc02d 100644 --- a/packages-exp/installations-exp/src/helpers/refresh-auth-token.ts +++ b/packages-exp/installations-exp/src/helpers/refresh-auth-token.ts @@ -150,7 +150,10 @@ async function fetchAuthTokenFromServer( await set(installations.appConfig, updatedInstallationEntry); return authToken; } catch (e) { - if (isServerError(e) && (e.serverCode === 401 || e.serverCode === 404)) { + if ( + isServerError(e) && + (e.customData.serverCode === 401 || e.customData.serverCode === 404) + ) { // Server returned a "FID not found" or a "Invalid authentication" error. // Generate a new ID next time. await remove(installations.appConfig); diff --git a/packages-exp/installations-exp/src/index.ts b/packages-exp/installations-exp/src/index.ts index 98351b54056..5aa6674dea3 100644 --- a/packages-exp/installations-exp/src/index.ts +++ b/packages-exp/installations-exp/src/index.ts @@ -1,3 +1,9 @@ +/** + * Firebase Installations + * + * @packageDocumentation + */ + /** * @license * Copyright 2019 Google LLC @@ -20,6 +26,7 @@ import { registerVersion } from '@firebase/app-exp'; import { name, version } from '../package.json'; export * from './api'; +export * from './interfaces/public-types'; registerInstallations(); registerVersion(name, version); diff --git a/packages-exp/installations-exp/src/interfaces/installation-impl.ts b/packages-exp/installations-exp/src/interfaces/installation-impl.ts index 280e51a7478..f4b923caf81 100644 --- a/packages-exp/installations-exp/src/interfaces/installation-impl.ts +++ b/packages-exp/installations-exp/src/interfaces/installation-impl.ts @@ -16,8 +16,8 @@ */ import { Provider } from '@firebase/component'; -import { _FirebaseService } from '@firebase/app-types-exp'; -import { FirebaseInstallations } from '@firebase/installations-types-exp'; +import { _FirebaseService } from '@firebase/app-exp'; +import { FirebaseInstallations } from '../interfaces/public-types'; export interface FirebaseInstallationsImpl extends FirebaseInstallations, diff --git a/packages-exp/installations-exp/src/interfaces/public-types.ts b/packages-exp/installations-exp/src/interfaces/public-types.ts new file mode 100644 index 00000000000..cea3a584aaf --- /dev/null +++ b/packages-exp/installations-exp/src/interfaces/public-types.ts @@ -0,0 +1,49 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Public interface of the FirebaseInstallations SDK. + * + * @public + */ +export interface FirebaseInstallations {} + +/** + * An interface for Firebase internal SDKs use only. + * + * @internal + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export interface _FirebaseInstallationsInternal { + /** + * Creates a Firebase Installation if there isn't one for the app and + * returns the Installation ID. + */ + getId(): Promise; + + /** + * Returns an Authentication Token for the current Firebase Installation. + */ + getToken(forceRefresh?: boolean): Promise; +} + +declare module '@firebase/component' { + interface NameServiceMapping { + 'installations-exp': FirebaseInstallations; + 'installations-exp-internal': _FirebaseInstallationsInternal; + } +} diff --git a/packages-exp/installations-exp/src/testing/fake-generators.ts b/packages-exp/installations-exp/src/testing/fake-generators.ts index 5d8a59a6fa6..0240b263943 100644 --- a/packages-exp/installations-exp/src/testing/fake-generators.ts +++ b/packages-exp/installations-exp/src/testing/fake-generators.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { FirebaseApp } from '@firebase/app-types-exp'; +import { FirebaseApp } from '@firebase/app-exp'; import { Component, ComponentContainer, diff --git a/packages-exp/installations-exp/src/util/errors.ts b/packages-exp/installations-exp/src/util/errors.ts index 6332ff65901..25cc69b1ff0 100644 --- a/packages-exp/installations-exp/src/util/errors.ts +++ b/packages-exp/installations-exp/src/util/errors.ts @@ -61,7 +61,7 @@ export interface ServerErrorData { serverStatus: string; } -export type ServerError = FirebaseError & ServerErrorData; +export type ServerError = FirebaseError & { customData: ServerErrorData }; /** Returns true if error is a FirebaseError that is based on an error from the server. */ export function isServerError(error: unknown): error is ServerError { diff --git a/packages-exp/installations-types-exp/api-extractor.json b/packages-exp/installations-types-exp/api-extractor.json deleted file mode 100644 index 42f37a88c4b..00000000000 --- a/packages-exp/installations-types-exp/api-extractor.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "../../config/api-extractor.json", - // Point it to your entry point d.ts file. - "mainEntryPointFilePath": "/index.d.ts" -} \ No newline at end of file diff --git a/packages-exp/installations-types-exp/index.d.ts b/packages-exp/installations-types-exp/index.d.ts deleted file mode 100644 index 1803c45d422..00000000000 --- a/packages-exp/installations-types-exp/index.d.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Public interface of the FirebaseInstallations SDK. - * - * @public - */ -export interface FirebaseInstallations {} - -/** - * An interface for Firebase internal SDKs use only. - * - * @internal - */ -export interface _FirebaseInstallationsInternal { - /** - * Creates a Firebase Installation if there isn't one for the app and - * returns the Installation ID. - */ - getId(): Promise; - - /** - * Returns an Authentication Token for the current Firebase Installation. - */ - getToken(forceRefresh?: boolean): Promise; -} - -declare module '@firebase/component' { - interface NameServiceMapping { - 'installations-exp': FirebaseInstallations; - 'installations-exp-internal': _FirebaseInstallationsInternal; - } -} diff --git a/packages-exp/installations-types-exp/package.json b/packages-exp/installations-types-exp/package.json deleted file mode 100644 index 77628cffd78..00000000000 --- a/packages-exp/installations-types-exp/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "@firebase/installations-types-exp", - "private": true, - "version": "0.0.800", - "description": "@firebase/installations-exp Types", - "author": "Firebase (https://firebase.google.com/)", - "license": "Apache-2.0", - "scripts": { - "test": "tsc", - "test:ci": "node ../../scripts/run_tests_in_ci.js", - "api-report": "api-extractor run --local --verbose", - "predoc": "node ../../scripts/exp/remove-exp.js temp", - "doc": "api-documenter markdown --input temp --output docs", - "build:doc": "yarn api-report && yarn doc" - }, - "files": [ - "index.d.ts" - ], - "peerDependencies": { - "@firebase/app-types": "0.x" - }, - "repository": { - "directory": "packages/installations-types-exp", - "type": "git", - "url": "https://github.com/firebase/firebase-js-sdk.git" - }, - "bugs": { - "url": "https://github.com/firebase/firebase-js-sdk/issues" - }, - "devDependencies": { - "typescript": "4.0.2" - } -} diff --git a/packages-exp/installations-types-exp/tsconfig.json b/packages-exp/installations-types-exp/tsconfig.json deleted file mode 100644 index 9ec79aa816b..00000000000 --- a/packages-exp/installations-types-exp/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "../installations-exp/tsconfig.json", - "compilerOptions": { - "noEmit": true - }, - "exclude": ["dist/**/*"] -} diff --git a/packages-exp/messaging-compat/.eslintrc.js b/packages-exp/messaging-compat/.eslintrc.js new file mode 100644 index 00000000000..67f0cbcecbf --- /dev/null +++ b/packages-exp/messaging-compat/.eslintrc.js @@ -0,0 +1,28 @@ +/* eslint-disable @typescript-eslint/no-require-imports */ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const path = require('path'); + +module.exports = { + extends: '../../config/.eslintrc.js', + parserOptions: { + project: 'tsconfig.json', + // to make vscode-eslint work with monorepo + // https://github.com/typescript-eslint/typescript-eslint/issues/251#issuecomment-463943250 + tsconfigRootDir: __dirname + } +}; diff --git a/packages-exp/messaging-compat/README.md b/packages-exp/messaging-compat/README.md new file mode 100644 index 00000000000..9e7d29db35d --- /dev/null +++ b/packages-exp/messaging-compat/README.md @@ -0,0 +1,5 @@ +# @firebase/messaging-compat + +This is the compat package that recreates the v8 APIs. + +**This package is not intended for direct usage, and should only be used via the officially supported [firebase](https://www.npmjs.com/package/firebase) package.** diff --git a/packages-exp/messaging-compat/karma.conf.js b/packages-exp/messaging-compat/karma.conf.js new file mode 100644 index 00000000000..322599066d1 --- /dev/null +++ b/packages-exp/messaging-compat/karma.conf.js @@ -0,0 +1,32 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const karmaBase = require('../../config/karma.base'); + +const files = ['test/**/*']; + +module.exports = function (config) { + const karmaConfig = { + ...karmaBase, + files, + frameworks: ['mocha'] + }; + + config.set(karmaConfig); +}; + +module.exports.files = files; diff --git a/packages-exp/messaging-compat/package.json b/packages-exp/messaging-compat/package.json new file mode 100644 index 00000000000..733b466c538 --- /dev/null +++ b/packages-exp/messaging-compat/package.json @@ -0,0 +1,56 @@ +{ + "name": "@firebase/messaging-compat", + "version": "0.0.900", + "license": "Apache-2.0", + "description": "", + "private": true, + "author": "Firebase (https://firebase.google.com/)", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm2017.js", + "module": "dist/index.esm2017.js", + "typings": "dist/src/index.d.ts", + "sw": "dist/index.sw.esm2017.js", + "files": [ + "dist" + ], + "scripts": { + "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "build": "rollup -c", + "build:deps": "lerna run --scope @firebase/'messaging-compat' --include-dependencies build", + "build:release": "rollup -c rollup.config.release.js && yarn add-compat-overloads", + "dev": "rollup -c -w", + "test": "run-p test:karma", + "test:ci": "node ../../scripts/run_tests_in_ci.js", + "test:karma": "karma start --single-run", + "test:debug": "karma start --browsers=Chrome --auto-watch", + "type-check": "tsc --noEmit", + "add-compat-overloads": "ts-node-script ../../scripts/exp/create-overloads.ts -i ../messaging-exp/dist/index-public.d.ts -o dist/src/index.d.ts -a -r FirebaseMessaging:MessagingCompat -r FirebaseApp:FirebaseAppCompat --moduleToEnhance @firebase/messaging" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + }, + "dependencies": { + "@firebase/messaging-exp": "0.0.900", + "@firebase/component": "0.5.4", + "@firebase/installations-exp": "0.0.900", + "@firebase/util": "1.1.0", + "tslib": "^2.1.0" + }, + "devDependencies": { + "@firebase/app-compat": "0.0.900", + "@rollup/plugin-json": "4.1.0", + "rollup-plugin-typescript2": "0.30.0", + "ts-essentials": "7.0.1", + "typescript": "4.2.2" + }, + "repository": { + "directory": "packages/messaging", + "type": "git", + "url": "https://github.com/firebase/firebase-js-sdk.git" + }, + "bugs": { + "url": "https://github.com/firebase/firebase-js-sdk/issues" + }, + "esm5": "dist/index.esm.js" +} \ No newline at end of file diff --git a/packages-exp/messaging-compat/rollup.config.js b/packages-exp/messaging-compat/rollup.config.js new file mode 100644 index 00000000000..e27be804d8f --- /dev/null +++ b/packages-exp/messaging-compat/rollup.config.js @@ -0,0 +1,77 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import json from '@rollup/plugin-json'; +import pkg from './package.json'; +import typescript from 'typescript'; +import typescriptPlugin from 'rollup-plugin-typescript2'; + +const deps = Object.keys( + Object.assign({}, pkg.peerDependencies, pkg.dependencies) +); + +/** + * ES5 Builds + */ +const es5BuildPlugins = [ + typescriptPlugin({ + typescript + }), + json() +]; + +const es5Builds = [ + { + input: 'src/index.ts', + output: [ + { file: pkg.main, format: 'cjs', sourcemap: true }, + { file: pkg.esm5, format: 'es', sourcemap: true } + ], + plugins: es5BuildPlugins, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + } +]; + +/** + * ES2017 Builds + */ +const es2017BuildPlugins = [ + typescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017' + } + } + }), + json({ preferConst: true }) +]; + +const es2017Builds = [ + { + input: 'src/index.ts', + output: { + file: pkg.browser, + format: 'es', + sourcemap: true + }, + plugins: es2017BuildPlugins, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + } +]; + +export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/messaging-compat/rollup.config.release.js b/packages-exp/messaging-compat/rollup.config.release.js new file mode 100644 index 00000000000..419a0b88248 --- /dev/null +++ b/packages-exp/messaging-compat/rollup.config.release.js @@ -0,0 +1,91 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; +import json from '@rollup/plugin-json'; +import pkg from './package.json'; +import typescript from 'typescript'; +import typescriptPlugin from 'rollup-plugin-typescript2'; + +const deps = [ + ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), + '@firebase/app' +]; + +/** + * ES5 Builds + */ +const es5BuildPlugins = [ + typescriptPlugin({ + typescript, + clean: true, + abortOnError: false, + transformers: [importPathTransformer] + }), + json() +]; + +const es5Builds = [ + { + input: 'src/index.ts', + output: [ + { file: pkg.main, format: 'cjs', sourcemap: true }, + { file: pkg.esm5, format: 'es', sourcemap: true } + ], + plugins: es5BuildPlugins, + treeshake: { + moduleSideEffects: false + }, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + } +]; + +/** + * ES2017 Builds + */ +const es2017BuildPlugins = [ + typescriptPlugin({ + typescript, + abortOnError: false, + clean: true, + transformers: [importPathTransformer], + tsconfigOverride: { + compilerOptions: { + target: 'es2017' + } + } + }), + json({ preferConst: true }) +]; + +const es2017Builds = [ + { + input: 'src/index.ts', + output: { + file: pkg.browser, + format: 'es', + sourcemap: true + }, + plugins: es2017BuildPlugins, + treeshake: { + moduleSideEffects: false + }, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + } +]; + +export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/messaging-compat/src/index.ts b/packages-exp/messaging-compat/src/index.ts new file mode 100644 index 00000000000..d7d06b58dcf --- /dev/null +++ b/packages-exp/messaging-compat/src/index.ts @@ -0,0 +1,40 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { name, version } from '../package.json'; + +import firebase from '@firebase/app-compat'; +import { registerMessagingCompat } from './registerMessagingCompat'; +import { MessagingCompat } from './messaging-compat'; + +registerMessagingCompat(); +firebase.registerVersion(name, version); + +/** + * Define extension behavior of `registerMessaging` + */ +declare module '@firebase/app-compat' { + interface FirebaseNamespace { + messaging: { + (app?: FirebaseApp): MessagingCompat; + isSupported(): boolean; + }; + } + interface FirebaseApp { + messaging(): MessagingCompat; + } +} diff --git a/packages-exp/messaging-compat/src/messaging-compat.ts b/packages-exp/messaging-compat/src/messaging-compat.ts new file mode 100644 index 00000000000..b54e61c798e --- /dev/null +++ b/packages-exp/messaging-compat/src/messaging-compat.ts @@ -0,0 +1,119 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + FirebaseApp as AppCompat, + _FirebaseService +} from '@firebase/app-compat'; +import { + FirebaseMessaging, + MessagePayload, + deleteToken, + getToken, + onMessage +} from '@firebase/messaging-exp'; +import { NextFn, Observer, Unsubscribe } from '@firebase/util'; + +import { onBackgroundMessage } from '@firebase/messaging-exp/sw'; + +export interface MessagingCompat { + getToken(options?: { + vapidKey?: string; + serviceWorkerRegistration?: ServiceWorkerRegistration; + }): Promise; + + deleteToken(): Promise; + + onMessage( + nextOrObserver: NextFn | Observer + ): Unsubscribe; + + onBackgroundMessage( + nextOrObserver: NextFn | Observer + ): Unsubscribe; +} + +export function isSupported(): boolean { + if (self && 'ServiceWorkerGlobalScope' in self) { + // Running in ServiceWorker context + return isSwSupported(); + } else { + // Assume we are in the window context. + return isWindowSupported(); + } +} + +/** + * Checks to see if the required APIs exist. + */ +function isWindowSupported(): boolean { + return ( + 'indexedDB' in window && + indexedDB !== null && + navigator.cookieEnabled && + 'serviceWorker' in navigator && + 'PushManager' in window && + 'Notification' in window && + 'fetch' in window && + ServiceWorkerRegistration.prototype.hasOwnProperty('showNotification') && + PushSubscription.prototype.hasOwnProperty('getKey') + ); +} + +/** + * Checks to see if the required APIs exist within SW Context. + */ +function isSwSupported(): boolean { + return ( + 'indexedDB' in self && + indexedDB !== null && + 'PushManager' in self && + 'Notification' in self && + ServiceWorkerRegistration.prototype.hasOwnProperty('showNotification') && + PushSubscription.prototype.hasOwnProperty('getKey') + ); +} + +export class MessagingCompatImpl implements MessagingCompat, _FirebaseService { + constructor(readonly app: AppCompat, readonly _delegate: FirebaseMessaging) { + this.app = app; + this._delegate = _delegate; + } + + async getToken(options?: { + vapidKey?: string; + serviceWorkerRegistration?: ServiceWorkerRegistration; + }): Promise { + return getToken(this._delegate, options); + } + + async deleteToken(): Promise { + return deleteToken(this._delegate); + } + + onMessage( + nextOrObserver: NextFn | Observer + ): Unsubscribe { + return onMessage(this._delegate, nextOrObserver); + } + + onBackgroundMessage( + nextOrObserver: NextFn | Observer + ): Unsubscribe { + return onBackgroundMessage(this._delegate, nextOrObserver); + } +} diff --git a/packages-exp/messaging-compat/src/registerMessagingCompat.ts b/packages-exp/messaging-compat/src/registerMessagingCompat.ts new file mode 100644 index 00000000000..0dbd5d1ceef --- /dev/null +++ b/packages-exp/messaging-compat/src/registerMessagingCompat.ts @@ -0,0 +1,60 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Component, + ComponentContainer, + ComponentType, + InstanceFactory +} from '@firebase/component'; +import firebase, { _FirebaseNamespace } from '@firebase/app-compat'; + +import { MessagingCompatImpl } from './messaging-compat'; + +declare module '@firebase/component' { + interface NameServiceMapping { + 'messaging-compat': MessagingCompatImpl; + } +} + +const messagingCompatFactory: InstanceFactory<'messaging-compat'> = ( + container: ComponentContainer +) => { + if (!!navigator) { + // in window + return new MessagingCompatImpl( + container.getProvider('app-compat').getImmediate(), + container.getProvider('messaging-exp').getImmediate() + ); + } else { + // in sw + return new MessagingCompatImpl( + container.getProvider('app-compat').getImmediate(), + container.getProvider('messaging-sw-exp').getImmediate() + ); + } +}; + +export function registerMessagingCompat(): void { + (firebase as _FirebaseNamespace).INTERNAL.registerComponent( + new Component( + 'messaging-compat', + messagingCompatFactory, + ComponentType.PUBLIC + ) + ); +} diff --git a/packages-exp/messaging-compat/test/fakes.ts b/packages-exp/messaging-compat/test/fakes.ts new file mode 100644 index 00000000000..4225fd5c0e1 --- /dev/null +++ b/packages-exp/messaging-compat/test/fakes.ts @@ -0,0 +1,41 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FirebaseApp } from '@firebase/app-compat'; +import { FirebaseMessaging } from '@firebase/messaging-exp'; + +export function getFakeApp(): FirebaseApp { + return { + name: 'appName', + options: { + apiKey: 'apiKey', + projectId: 'projectId', + authDomain: 'authDomain', + messagingSenderId: 'messagingSenderId', + databaseURL: 'databaseUrl', + storageBucket: 'storageBucket', + appId: '1:777777777777:web:d93b5ca1475efe57' + }, + automaticDataCollectionEnabled: true, + delete: async () => {}, + messaging: (() => null as unknown) as FirebaseApp['messaging'] + }; +} + +export function getFakeModularMessaging(): FirebaseMessaging { + return {}; +} diff --git a/packages-exp/messaging-compat/test/messaging-compat.test.ts b/packages-exp/messaging-compat/test/messaging-compat.test.ts new file mode 100644 index 00000000000..9c76fe1ce10 --- /dev/null +++ b/packages-exp/messaging-compat/test/messaging-compat.test.ts @@ -0,0 +1,61 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as messagingModule from '@firebase/messaging-exp'; +import * as messagingModuleInSw from '@firebase/messaging-exp/sw'; + +import { getFakeApp, getFakeModularMessaging } from './fakes'; + +import { MessagingCompatImpl } from '../src/messaging-compat'; +import { expect } from 'chai'; +import { stub } from 'sinon'; + +describe('messagingCompat', () => { + const messagingCompat = new MessagingCompatImpl( + getFakeApp(), + getFakeModularMessaging() + ); + + //Stubs + const getTokenStub = stub(messagingModule, 'getToken'); + const deleteTokenStub = stub(messagingModule, 'deleteToken'); + const onMessageStub = stub(messagingModule, 'onMessage'); + const onBackgroundMessageStub = stub( + messagingModuleInSw, + 'onBackgroundMessage' + ); + + it('routes messagingCompat.getToken to modular SDK', () => { + void messagingCompat.getToken(); + expect(getTokenStub.called).to.be.true; + }); + + it('routes messagingCompat.deleteToken to modular SDK', () => { + void messagingCompat.deleteToken(); + expect(deleteTokenStub.called).to.be.true; + }); + + it('routes messagingCompat.onMessage to modular SDK', () => { + messagingCompat.onMessage(_ => {}); + expect(onMessageStub.called).to.be.true; + }); + + it('routes messagingCompat.onBackgroundMessage to modular SDK', () => { + messagingCompat.onBackgroundMessage(_ => {}); + expect(onBackgroundMessageStub.called).to.be.true; + }); +}); diff --git a/packages-exp/messaging-compat/tsconfig.json b/packages-exp/messaging-compat/tsconfig.json new file mode 100644 index 00000000000..4b63b47c5b5 --- /dev/null +++ b/packages-exp/messaging-compat/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../config/tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "noUnusedLocals": true, + "lib": [ + "dom", + "es2017" + ], + "downlevelIteration": true + }, + "exclude": [ + "dist/**/*" + ] +} diff --git a/packages-exp/messaging-exp/.eslintrc.js b/packages-exp/messaging-exp/.eslintrc.js new file mode 100644 index 00000000000..ca80aa0f69a --- /dev/null +++ b/packages-exp/messaging-exp/.eslintrc.js @@ -0,0 +1,26 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +module.exports = { + extends: '../../config/.eslintrc.js', + parserOptions: { + project: 'tsconfig.json', + // to make vscode-eslint work with monorepo + // https://github.com/typescript-eslint/typescript-eslint/issues/251#issuecomment-463943250 + tsconfigRootDir: __dirname + } +}; diff --git a/packages-exp/messaging-exp/README.md b/packages-exp/messaging-exp/README.md new file mode 100644 index 00000000000..8f3fd52738a --- /dev/null +++ b/packages-exp/messaging-exp/README.md @@ -0,0 +1,5 @@ +# @firebase/messaging + +This is the Firebase Cloud Messaging component of the Firebase JS SDK. + +**This package is not intended for direct usage, and should only be used via the officially supported [firebase](https://www.npmjs.com/package/firebase) package.** diff --git a/packages-exp/messaging-exp/api-extractor.json b/packages-exp/messaging-exp/api-extractor.json new file mode 100644 index 00000000000..d68a256c8c5 --- /dev/null +++ b/packages-exp/messaging-exp/api-extractor.json @@ -0,0 +1,22 @@ +{ + "extends": "../../config/api-extractor.json", + // Point it to your entry point d.ts file. + "mainEntryPointFilePath": "/dist/index.d.ts", + "additionalEntryPoints": [{ + "modulePath": "sw", + "filePath": "/dist/index.sw.d.ts" + }], + "dtsRollup": { + /** + * rollup is not supported when multiple entry points are present. + * npm script api-report:rollup is used to generate dts rollup. + */ + "enabled": false + }, + "apiReport": { + /** + * apiReport is handled by npm script api-report:rollup + */ + "enabled": false +} +} diff --git a/packages-exp/messaging-exp/karma.conf.js b/packages-exp/messaging-exp/karma.conf.js new file mode 100644 index 00000000000..c9bc6b770c9 --- /dev/null +++ b/packages-exp/messaging-exp/karma.conf.js @@ -0,0 +1,32 @@ +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const karmaBase = require('../../config/karma.base'); + +const files = [`src/**/*.test.ts`]; + +module.exports = function (config) { + const karmaConfig = { + ...karmaBase, + files, + frameworks: ['mocha'] + }; + + config.set(karmaConfig); +}; + +module.exports.files = files; diff --git a/packages-exp/messaging-exp/package.json b/packages-exp/messaging-exp/package.json new file mode 100644 index 00000000000..ca8535f3367 --- /dev/null +++ b/packages-exp/messaging-exp/package.json @@ -0,0 +1,60 @@ +{ + "name": "@firebase/messaging-exp", + "private": true, + "version": "0.0.900", + "description": "", + "author": "Firebase (https://firebase.google.com/)", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm2017.js", + "module": "dist/index.esm2017.js", + "typings": "dist/index.d.ts", + "sw": "dist/index.sw.esm2017.js", + "files": [ + "dist", + "sw/package.json" + ], + "scripts": { + "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "build": "rollup -c && yarn api-report", + "build:deps": "lerna run --scope @firebase/'{app-exp,messaging-exp}' --include-dependencies build", + "build:release": "rollup -c rollup.config.release.js && yarn api-report && yarn typings:public", + "dev": "rollup -c -w", + "test": "run-p test:karma type-check lint ", + "test:integration": "run-p test:karma type-check lint && cd ../../integration/messaging && npm run-script test", + "test:ci": "node ../../scripts/run_tests_in_ci.js", + "test:karma": "karma start --single-run", + "test:debug": "karma start --browsers=Chrome --auto-watch", + "api-report": "yarn api-report:rollup && yarn api-report:api-json", + "api-report:rollup": "ts-node-script ../../repo-scripts/prune-dts/extract-public-api.ts --package messaging-exp --packageRoot . --typescriptDts ./dist/index.d.ts --rollupDts ./dist/private.d.ts --untrimmedRollupDts ./dist/internal.d.ts --publicDts ./dist/index-public.d.ts", + "api-report:api-json": "api-extractor run --local --verbose", + "type-check": "tsc --noEmit", + "typings:public": "node ../../scripts/exp/use_typings.js ./dist/index-public.d.ts" + }, + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-exp": "0.x" + }, + "dependencies": { + "@firebase/component": "0.5.4", + "@firebase/installations-exp": "0.0.900", + "@firebase/util": "1.1.0", + "idb": "3.0.2", + "tslib": "^2.1.0" + }, + "devDependencies": { + "@rollup/plugin-json": "4.1.0", + "rollup-plugin-typescript2": "0.30.0", + "ts-essentials": "7.0.1", + "typescript": "4.2.2" + }, + "repository": { + "directory": "packages/messaging", + "type": "git", + "url": "https://github.com/firebase/firebase-js-sdk.git" + }, + "bugs": { + "url": "https://github.com/firebase/firebase-js-sdk/issues" + }, + "esm5": "dist/index.esm.js" +} diff --git a/packages-exp/messaging-exp/rollup.config.js b/packages-exp/messaging-exp/rollup.config.js new file mode 100644 index 00000000000..5d7b3338874 --- /dev/null +++ b/packages-exp/messaging-exp/rollup.config.js @@ -0,0 +1,84 @@ +/** + * @license + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import json from '@rollup/plugin-json'; +import pkg from './package.json'; +import typescript from 'typescript'; +import typescriptPlugin from 'rollup-plugin-typescript2'; + +const deps = Object.keys( + Object.assign({}, pkg.peerDependencies, pkg.dependencies) +); + +/** + * ES5 Builds + */ +const es5BuildPlugins = [ + typescriptPlugin({ + typescript + }), + json() +]; + +const es5Builds = [ + { + input: 'src/index.ts', + output: [ + { file: pkg.main, format: 'cjs', sourcemap: true }, + { file: pkg.esm5, format: 'es', sourcemap: true } + ], + plugins: es5BuildPlugins, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + } +]; + +/** + * ES2017 Builds + */ +const es2017BuildPlugins = [ + typescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017' + } + } + }), + json({ preferConst: true }) +]; + +const es2017Builds = [ + { + input: 'src/index.ts', + output: { + file: pkg.browser, + format: 'es', + sourcemap: true + }, + plugins: es2017BuildPlugins, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + }, + // sw builds + { + input: 'src/index.sw.ts', + output: { file: pkg.sw, format: 'es', sourcemap: true }, + plugins: es2017BuildPlugins, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + } +]; + +export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/messaging-exp/rollup.config.release.js b/packages-exp/messaging-exp/rollup.config.release.js new file mode 100644 index 00000000000..03ccf1d097f --- /dev/null +++ b/packages-exp/messaging-exp/rollup.config.release.js @@ -0,0 +1,102 @@ +/** + * @license + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; +import json from '@rollup/plugin-json'; +import pkg from './package.json'; +import typescript from 'typescript'; +import typescriptPlugin from 'rollup-plugin-typescript2'; + +const deps = [ + ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), + '@firebase/app' +]; + +/** + * ES5 Builds + */ +const es5BuildPlugins = [ + typescriptPlugin({ + typescript, + clean: true, + abortOnError: false, + transformers: [importPathTransformer] + }), + json() +]; + +const es5Builds = [ + { + input: 'src/index.ts', + output: [ + { file: pkg.main, format: 'cjs', sourcemap: true }, + { file: pkg.esm5, format: 'es', sourcemap: true } + ], + plugins: es5BuildPlugins, + treeshake: { + moduleSideEffects: (id, external) => id === '@firebase/installations' + }, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + } +]; + +/** + * ES2017 Builds + */ +const es2017BuildPlugins = [ + typescriptPlugin({ + typescript, + abortOnError: false, + clean: true, + transformers: [importPathTransformer], + tsconfigOverride: { + compilerOptions: { + target: 'es2017' + } + } + }), + json({ preferConst: true }) +]; + +const es2017Builds = [ + { + input: 'src/index.ts', + output: { + file: pkg.browser, + format: 'es', + sourcemap: true + }, + plugins: es2017BuildPlugins, + treeshake: { + moduleSideEffects: (id, external) => id === '@firebase/installations' + }, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + }, + + // sw builds + { + input: 'src/index.sw.ts', + output: { file: pkg.sw, format: 'es', sourcemap: true }, + plugins: es2017BuildPlugins, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), + treeshake: { + moduleSideEffects: (id, external) => id === '@firebase/installations' + } + } +]; + +export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/messaging-exp/src/api.ts b/packages-exp/messaging-exp/src/api.ts new file mode 100644 index 00000000000..f3e025877cc --- /dev/null +++ b/packages-exp/messaging-exp/src/api.ts @@ -0,0 +1,143 @@ +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FirebaseApp, _getProvider, getApp } from '@firebase/app-exp'; +import { + FirebaseMessaging, + GetTokenOptions, + MessagePayload +} from './interfaces/public-types'; +import { + NextFn, + Observer, + Unsubscribe, + getModularInstance +} from '@firebase/util'; + +import { MessagingService } from './messaging-service'; +import { deleteToken as _deleteToken } from './api/deleteToken'; +import { getToken as _getToken } from './api/getToken'; +import { onBackgroundMessage as _onBackgroundMessage } from './api/onBackgroundMessage'; +import { onMessage as _onMessage } from './api/onMessage'; + +/** + * Retrieves a Firebase Cloud Messaging instance. + * + * @returns The Firebase Cloud Messaging instance associated with the provided firebase app. + * + * @public + */ +export function getMessagingInWindow( + app: FirebaseApp = getApp() +): FirebaseMessaging { + return _getProvider(getModularInstance(app), 'messaging-exp').getImmediate(); +} + +/** + * Retrieves a Firebase Cloud Messaging instance. + * + * @returns The Firebase Cloud Messaging instance associated with the provided firebase app. + * + * @public + */ +export function getMessagingInSw( + app: FirebaseApp = getApp() +): FirebaseMessaging { + return _getProvider( + getModularInstance(app), + 'messaging-sw-exp' + ).getImmediate(); +} + +/** + * Subscribes the `FirebaseMessaging` instance to push notifications. Returns an Firebase Cloud + * Messaging registration token that can be used to send push messages to that `FirebaseMessaging` + * instance. + * + * If a notification permission isn't already granted, this method asks the user for permission. The + * returned promise rejects if the user does not allow the app to show notifications. + * + * @param messaging - The `FirebaseMessaging` instance. + * @param options - Provides an optional vapid key and an optinoal service worker registration + * + * @returns The promise resolves with an FCM registration token. + * + * @public + */ +export async function getToken( + messaging: FirebaseMessaging, + options?: GetTokenOptions +): Promise { + messaging = getModularInstance(messaging); + return _getToken(messaging as MessagingService, options); +} + +/** + * Deletes the registration token associated with this `FirebaseMessaging` instance and unsubscribes + * the `FirebaseMessaging` instance from the push subscription. + * + * @param messaging - The `FirebaseMessaging` instance. + * + * @returns The promise resolves when the token has been successfully deleted. + * + * @public + */ +export function deleteToken(messaging: FirebaseMessaging): Promise { + messaging = getModularInstance(messaging); + return _deleteToken(messaging as MessagingService); +} + +/** + * When a push message is received and the user is currently on a page for your origin, the + * message is passed to the page and an `onMessage()` event is dispatched with the payload of + * the push message. + * + * + * @param messaging - The `FirebaseMessaging` instance. + * @param nextOrObserver - This function, or observer object with `next` defined, + * is called when a message is received and the user is currently viewing your page. + * @returns To stop listening for messages execute this returned function. + * + * @public + */ +export function onMessage( + messaging: FirebaseMessaging, + nextOrObserver: NextFn | Observer +): Unsubscribe { + messaging = getModularInstance(messaging); + return _onMessage(messaging as MessagingService, nextOrObserver); +} + +/** + * Called when a message is received while the app is in the background. An app is considered to be + * in the background if no active window is displayed. + * + * @param messaging - The `FirebaseMessaging` instance. + * @param nextOrObserver - This function, or observer object with `next` defined, is called when a + * message is received and the app is currently in the background. + * + * @returns To stop listening for messages execute this returned function + * + * @public + */ +export function onBackgroundMessage( + messaging: FirebaseMessaging, + nextOrObserver: NextFn | Observer +): Unsubscribe { + messaging = getModularInstance(messaging); + return _onBackgroundMessage(messaging as MessagingService, nextOrObserver); +} diff --git a/packages-exp/messaging-exp/src/api/deleteToken.ts b/packages-exp/messaging-exp/src/api/deleteToken.ts new file mode 100644 index 00000000000..5a1725f2f0d --- /dev/null +++ b/packages-exp/messaging-exp/src/api/deleteToken.ts @@ -0,0 +1,36 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ERROR_FACTORY, ErrorCode } from '../util/errors'; + +import { MessagingService } from '../messaging-service'; +import { deleteTokenInternal } from '../internals/token-manager'; +import { registerDefaultSw } from '../helpers/registerDefaultSw'; + +export async function deleteToken( + messaging: MessagingService +): Promise { + if (!navigator) { + throw ERROR_FACTORY.create(ErrorCode.AVAILABLE_IN_WINDOW); + } + + if (!messaging.swRegistration) { + await registerDefaultSw(messaging); + } + + return deleteTokenInternal(messaging); +} diff --git a/packages-exp/messaging-exp/src/api/getToken.ts b/packages-exp/messaging-exp/src/api/getToken.ts new file mode 100644 index 00000000000..58c9fea90c4 --- /dev/null +++ b/packages-exp/messaging-exp/src/api/getToken.ts @@ -0,0 +1,46 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ERROR_FACTORY, ErrorCode } from '../util/errors'; + +import { MessagingService } from '../messaging-service'; +import { getTokenInternal } from '../internals/token-manager'; +import { updateSwReg } from '../helpers/updateSwReg'; +import { updateVapidKey } from '../helpers/updateVapidKey'; +import { GetTokenOptions } from '../interfaces/public-types'; + +export async function getToken( + messaging: MessagingService, + options?: GetTokenOptions +): Promise { + if (!navigator) { + throw ERROR_FACTORY.create(ErrorCode.AVAILABLE_IN_WINDOW); + } + + if (Notification.permission === 'default') { + await Notification.requestPermission(); + } + + if (Notification.permission !== 'granted') { + throw ERROR_FACTORY.create(ErrorCode.PERMISSION_BLOCKED); + } + + await updateVapidKey(messaging, options?.vapidKey); + await updateSwReg(messaging, options?.serviceWorkerRegistration); + + return getTokenInternal(messaging); +} diff --git a/packages-exp/messaging-exp/src/api/isSupported.ts b/packages-exp/messaging-exp/src/api/isSupported.ts new file mode 100644 index 00000000000..dca882fa44d --- /dev/null +++ b/packages-exp/messaging-exp/src/api/isSupported.ts @@ -0,0 +1,63 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { validateIndexedDBOpenable } from '@firebase/util'; + +/** + * Checks if all required APIs exist in the browser. + * @returns a Promise that resolves to a boolean. + * + * @public + */ +export async function isWindowSupported(): Promise { + // firebase-js-sdk/issues/2393 reveals that idb#open in Safari iframe and Firefox private browsing + // might be prohibited to run. In these contexts, an error would be thrown during the messaging + // instantiating phase, informing the developers to import/call isSupported for special handling. + return ( + (await validateIndexedDBOpenable()) && + 'indexedDB' in window && + indexedDB !== null && + navigator.cookieEnabled && + 'serviceWorker' in navigator && + 'PushManager' in window && + 'Notification' in window && + 'fetch' in window && + ServiceWorkerRegistration.prototype.hasOwnProperty('showNotification') && + PushSubscription.prototype.hasOwnProperty('getKey') + ); +} + +/** + * Checks whether all required APIs exist within SW Context + * @returns a Promise that resolves to a boolean. + * + * @public + */ +export async function isSwSupported(): Promise { + // firebase-js-sdk/issues/2393 reveals that idb#open in Safari iframe and Firefox private browsing + // might be prohibited to run. In these contexts, an error would be thrown during the messaging + // instantiating phase, informing the developers to import/call isSupported for special handling. + return ( + (await validateIndexedDBOpenable()) && + 'indexedDB' in self && + indexedDB !== null && + 'PushManager' in self && + 'Notification' in self && + ServiceWorkerRegistration.prototype.hasOwnProperty('showNotification') && + PushSubscription.prototype.hasOwnProperty('getKey') + ); +} diff --git a/packages-exp/messaging-exp/src/api/onBackgroundMessage.ts b/packages-exp/messaging-exp/src/api/onBackgroundMessage.ts new file mode 100644 index 00000000000..214e3a005c5 --- /dev/null +++ b/packages-exp/messaging-exp/src/api/onBackgroundMessage.ts @@ -0,0 +1,41 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ERROR_FACTORY, ErrorCode } from '../util/errors'; + +import { + MessagePayload, + NextFn, + Observer, + Unsubscribe +} from '../interfaces/public-types'; +import { MessagingService } from '../messaging-service'; + +export function onBackgroundMessage( + messaging: MessagingService, + nextOrObserver: NextFn | Observer +): Unsubscribe { + if (self.document !== undefined) { + throw ERROR_FACTORY.create(ErrorCode.AVAILABLE_IN_SW); + } + + messaging.onBackgroundMessageHandler = nextOrObserver; + + return () => { + messaging.onBackgroundMessageHandler = null; + }; +} diff --git a/packages-exp/messaging-exp/src/api/onMessage.ts b/packages-exp/messaging-exp/src/api/onMessage.ts new file mode 100644 index 00000000000..cde191ac7f4 --- /dev/null +++ b/packages-exp/messaging-exp/src/api/onMessage.ts @@ -0,0 +1,41 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ERROR_FACTORY, ErrorCode } from '../util/errors'; + +import { + MessagePayload, + NextFn, + Observer, + Unsubscribe +} from '../interfaces/public-types'; +import { MessagingService } from '../messaging-service'; + +export function onMessage( + messaging: MessagingService, + nextOrObserver: NextFn | Observer +): Unsubscribe { + if (!navigator) { + throw ERROR_FACTORY.create(ErrorCode.AVAILABLE_IN_WINDOW); + } + + messaging.onMessageHandler = nextOrObserver; + + return () => { + messaging.onMessageHandler = null; + }; +} diff --git a/packages-exp/messaging-exp/src/helpers/array-base64-translator.test.ts b/packages-exp/messaging-exp/src/helpers/array-base64-translator.test.ts new file mode 100644 index 00000000000..c161b365dbc --- /dev/null +++ b/packages-exp/messaging-exp/src/helpers/array-base64-translator.test.ts @@ -0,0 +1,84 @@ +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import '../testing/setup'; + +import { arrayToBase64, base64ToArray } from './array-base64-translator'; + +import { expect } from 'chai'; + +// prettier-ignore +const TEST_P256_ARRAY = new Uint8Array([ + 4, 181, 98, 240, 48, 62, 75, 119, 193, 227, 154, 69, 250, 216, 53, 110, + 157, 120, 62, 76, 213, 249, 11, 62, 12, 19, 149, 36, 5, 82, 140, 37, 141, + 134, 132, 98, 87, 152, 175, 98, 53, 83, 196, 242, 202, 155, 19, 173, 157, + 216, 45, 147, 20, 12, 151, 160, 147, 159, 205, 219, 75, 133, 156, 129, 152 +]); +const TEST_P256_BASE64 = + 'BLVi8DA-S3fB45pF-tg1bp14PkzV-Qs-DBOVJAVSjCWNhoRi' + + 'V5ivYjVTxPLKmxOtndgtkxQMl6CTn83bS4WcgZg'; + +// prettier-ignore +const TEST_AUTH_ARRAY = new Uint8Array([ + 255, 237, 107, 177, 171, 78, 84, 131, 221, 231, 87, 188, 22, 232, 71, 15 +]); +const TEST_AUTH_BASE64 = '_-1rsatOVIPd51e8FuhHDw'; + +// prettier-ignore +const TEST_VAPID_ARRAY = new Uint8Array([4, 48, 191, 217, 11, 218, 74, 124, 103, 143, 63, 182, 203, + 91, 0, 68, 221, 68, 172, 74, 89, 133, 198, 252, 145, 164, 136, 243, 186, 75, 198, 32, 45, 64, 240, + 120, 141, 173, 240, 131, 253, 83, 209, 193, 129, 50, 155, 126, 189, 23, 127, 232, 109, 75, 101, + 229, 92, 85, 137, 80, 121, 35, 229, 118, 207]); +const TEST_VAPID_BASE64 = + 'BDC_2QvaSnxnjz-2y1sARN1ErEpZhcb8kaSI87pLxiAtQPB4ja3wg_1T0cGBMpt' + + '-vRd_6G1LZeVcVYlQeSPlds8'; + +describe('arrayToBase64', () => { + it('array to base64 translation succeed', () => { + expect(arrayToBase64(TEST_P256_ARRAY)).to.equal(TEST_P256_BASE64); + expect(arrayToBase64(TEST_AUTH_ARRAY)).to.equal(TEST_AUTH_BASE64); + expect(arrayToBase64(TEST_VAPID_ARRAY)).to.equal(TEST_VAPID_BASE64); + }); +}); + +describe('base64ToArray', () => { + it('base64 to array translation succeed', () => { + expect(isEqual(base64ToArray(TEST_P256_BASE64), TEST_P256_ARRAY)).to.equal( + true + ); + expect(isEqual(base64ToArray(TEST_AUTH_BASE64), TEST_AUTH_ARRAY)).to.equal( + true + ); + expect( + isEqual(base64ToArray(TEST_VAPID_BASE64), TEST_VAPID_ARRAY) + ).to.equal(true); + }); +}); + +function isEqual(a: Uint8Array, b: Uint8Array): boolean { + if (a.length !== b.length) { + return false; + } + + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) { + return false; + } + } + + return true; +} diff --git a/packages-exp/messaging-exp/src/helpers/array-base64-translator.ts b/packages-exp/messaging-exp/src/helpers/array-base64-translator.ts new file mode 100644 index 00000000000..bbade845ae4 --- /dev/null +++ b/packages-exp/messaging-exp/src/helpers/array-base64-translator.ts @@ -0,0 +1,37 @@ +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export function arrayToBase64(array: Uint8Array | ArrayBuffer): string { + const uint8Array = new Uint8Array(array); + const base64String = btoa(String.fromCharCode(...uint8Array)); + return base64String.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); +} + +export function base64ToArray(base64String: string): Uint8Array { + const padding = '='.repeat((4 - (base64String.length % 4)) % 4); + const base64 = (base64String + padding) + .replace(/\-/g, '+') + .replace(/_/g, '/'); + + const rawData = atob(base64); + const outputArray = new Uint8Array(rawData.length); + + for (let i = 0; i < rawData.length; ++i) { + outputArray[i] = rawData.charCodeAt(i); + } + return outputArray; +} diff --git a/packages-exp/messaging-exp/src/helpers/externalizePayload.test.ts b/packages-exp/messaging-exp/src/helpers/externalizePayload.test.ts new file mode 100644 index 00000000000..8d2f6b0860a --- /dev/null +++ b/packages-exp/messaging-exp/src/helpers/externalizePayload.test.ts @@ -0,0 +1,106 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { MessagePayload } from '../interfaces/public-types'; +import { MessagePayloadInternal } from '../interfaces/internal-message-payload'; +import { expect } from 'chai'; +import { externalizePayload } from './externalizePayload'; + +describe('externalizePayload', () => { + it('externalizes internalMessage with only notification payload', () => { + const internalPayload: MessagePayloadInternal = { + notification: { + title: 'title', + body: 'body', + image: 'image' + }, + from: 'from', + // eslint-disable-next-line camelcase + collapse_key: 'collapse' + }; + + const payload: MessagePayload = { + notification: { title: 'title', body: 'body', image: 'image' }, + from: 'from', + collapseKey: 'collapse' + }; + expect(externalizePayload(internalPayload)).to.deep.equal(payload); + }); + + it('externalizes internalMessage with only data payload', () => { + const internalPayload: MessagePayloadInternal = { + data: { + foo: 'foo', + bar: 'bar', + baz: 'baz' + }, + from: 'from', + // eslint-disable-next-line camelcase + collapse_key: 'collapse' + }; + + const payload: MessagePayload = { + data: { foo: 'foo', bar: 'bar', baz: 'baz' }, + from: 'from', + collapseKey: 'collapse' + }; + expect(externalizePayload(internalPayload)).to.deep.equal(payload); + }); + + it('externalizes internalMessage with all three payloads', () => { + const internalPayload: MessagePayloadInternal = { + notification: { + title: 'title', + body: 'body', + image: 'image' + }, + data: { + foo: 'foo', + bar: 'bar', + baz: 'baz' + }, + fcmOptions: { + link: 'link', + // eslint-disable-next-line camelcase + analytics_label: 'label' + }, + from: 'from', + // eslint-disable-next-line camelcase + collapse_key: 'collapse' + }; + + const payload: MessagePayload = { + notification: { + title: 'title', + body: 'body', + image: 'image' + }, + data: { + foo: 'foo', + bar: 'bar', + baz: 'baz' + }, + fcmOptions: { + link: 'link', + analyticsLabel: 'label' + }, + from: 'from', + collapseKey: 'collapse' + }; + expect(externalizePayload(internalPayload)).to.deep.equal(payload); + }); +}); diff --git a/packages-exp/messaging-exp/src/helpers/externalizePayload.ts b/packages-exp/messaging-exp/src/helpers/externalizePayload.ts new file mode 100644 index 00000000000..2d7cb5626f5 --- /dev/null +++ b/packages-exp/messaging-exp/src/helpers/externalizePayload.ts @@ -0,0 +1,94 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { MessagePayload } from '../interfaces/public-types'; +import { MessagePayloadInternal } from '../interfaces/internal-message-payload'; + +export function externalizePayload( + internalPayload: MessagePayloadInternal +): MessagePayload { + const payload: MessagePayload = { + from: internalPayload.from, + // eslint-disable-next-line camelcase + collapseKey: internalPayload.collapse_key + } as MessagePayload; + + propagateNotificationPayload(payload, internalPayload); + propagateDataPayload(payload, internalPayload); + propagateFcmOptions(payload, internalPayload); + + return payload; +} + +function propagateNotificationPayload( + payload: MessagePayload, + messagePayloadInternal: MessagePayloadInternal +): void { + if (!messagePayloadInternal.notification) { + return; + } + + payload.notification = {}; + + const title = messagePayloadInternal.notification!.title; + if (!!title) { + payload.notification!.title = title; + } + + const body = messagePayloadInternal.notification!.body; + if (!!body) { + payload.notification!.body = body; + } + + const image = messagePayloadInternal.notification!.image; + if (!!image) { + payload.notification!.image = image; + } +} + +function propagateDataPayload( + payload: MessagePayload, + messagePayloadInternal: MessagePayloadInternal +): void { + if (!messagePayloadInternal.data) { + return; + } + + payload.data = messagePayloadInternal.data as { [key: string]: string }; +} + +function propagateFcmOptions( + payload: MessagePayload, + messagePayloadInternal: MessagePayloadInternal +): void { + if (!messagePayloadInternal.fcmOptions) { + return; + } + + payload.fcmOptions = {}; + + const link = messagePayloadInternal.fcmOptions!.link; + if (!!link) { + payload.fcmOptions!.link = link; + } + + // eslint-disable-next-line camelcase + const analyticsLabel = messagePayloadInternal.fcmOptions!.analytics_label; + if (!!analyticsLabel) { + payload.fcmOptions!.analyticsLabel = analyticsLabel; + } +} diff --git a/packages-exp/messaging-exp/src/helpers/extract-app-config.test.ts b/packages-exp/messaging-exp/src/helpers/extract-app-config.test.ts new file mode 100644 index 00000000000..f3f91d604ab --- /dev/null +++ b/packages-exp/messaging-exp/src/helpers/extract-app-config.test.ts @@ -0,0 +1,80 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import '../testing/setup'; + +import { AppConfig } from '../interfaces/app-config'; +import { FirebaseApp } from '@firebase/app-exp'; +import { expect } from 'chai'; +import { extractAppConfig } from './extract-app-config'; +import { getFakeApp } from '../testing/fakes/firebase-dependencies'; + +describe('extractAppConfig', () => { + it('returns AppConfig if the argument is a FirebaseApp object that includes an appId', () => { + const firebaseApp = getFakeApp(); + const expected: AppConfig = { + appName: 'appName', + apiKey: 'apiKey', + projectId: 'projectId', + appId: '1:777777777777:web:d93b5ca1475efe57', + senderId: '1234567890' + }; + expect(extractAppConfig(firebaseApp)).to.deep.equal(expected); + }); + + it('throws if a necessary value is missing', () => { + expect(() => + extractAppConfig((undefined as unknown) as FirebaseApp) + ).to.throw('Missing App configuration value: "App Configuration Object"'); + + let firebaseApp = getFakeApp(); + delete firebaseApp.options; + expect(() => extractAppConfig(firebaseApp)).to.throw( + 'Missing App configuration value: "App Configuration Object"' + ); + + firebaseApp = getFakeApp(); + delete firebaseApp.name; + expect(() => extractAppConfig(firebaseApp)).to.throw( + 'Missing App configuration value: "App Name"' + ); + + firebaseApp = getFakeApp(); + delete firebaseApp.options.projectId; + expect(() => extractAppConfig(firebaseApp)).to.throw( + 'Missing App configuration value: "projectId"' + ); + + firebaseApp = getFakeApp(); + delete firebaseApp.options.apiKey; + expect(() => extractAppConfig(firebaseApp)).to.throw( + 'Missing App configuration value: "apiKey"' + ); + + firebaseApp = getFakeApp(); + delete firebaseApp.options.appId; + expect(() => extractAppConfig(firebaseApp)).to.throw( + 'Missing App configuration value: "appId"' + ); + + firebaseApp = getFakeApp(); + delete firebaseApp.options.messagingSenderId; + expect(() => extractAppConfig(firebaseApp)).to.throw( + 'Missing App configuration value: "messagingSenderId"' + ); + }); +}); diff --git a/packages-exp/messaging-exp/src/helpers/extract-app-config.ts b/packages-exp/messaging-exp/src/helpers/extract-app-config.ts new file mode 100644 index 00000000000..c80a32ee14d --- /dev/null +++ b/packages-exp/messaging-exp/src/helpers/extract-app-config.ts @@ -0,0 +1,61 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ERROR_FACTORY, ErrorCode } from '../util/errors'; +import { FirebaseApp, FirebaseOptions } from '@firebase/app-exp'; + +import { AppConfig } from '../interfaces/app-config'; +import { FirebaseError } from '@firebase/util'; + +export function extractAppConfig(app: FirebaseApp): AppConfig { + if (!app || !app.options) { + throw getMissingValueError('App Configuration Object'); + } + + if (!app.name) { + throw getMissingValueError('App Name'); + } + + // Required app config keys + const configKeys: ReadonlyArray = [ + 'projectId', + 'apiKey', + 'appId', + 'messagingSenderId' + ]; + + const { options } = app; + for (const keyName of configKeys) { + if (!options[keyName]) { + throw getMissingValueError(keyName); + } + } + + return { + appName: app.name, + projectId: options.projectId!, + apiKey: options.apiKey!, + appId: options.appId!, + senderId: options.messagingSenderId! + }; +} + +function getMissingValueError(valueName: string): FirebaseError { + return ERROR_FACTORY.create(ErrorCode.MISSING_APP_CONFIG_VALUES, { + valueName + }); +} diff --git a/packages-exp/messaging-exp/src/helpers/is-console-message.ts b/packages-exp/messaging-exp/src/helpers/is-console-message.ts new file mode 100644 index 00000000000..151713be132 --- /dev/null +++ b/packages-exp/messaging-exp/src/helpers/is-console-message.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { CONSOLE_CAMPAIGN_ID } from '../util/constants'; +import { ConsoleMessageData } from '../interfaces/internal-message-payload'; + +export function isConsoleMessage(data: unknown): data is ConsoleMessageData { + // This message has a campaign ID, meaning it was sent using the Firebase Console. + return typeof data === 'object' && !!data && CONSOLE_CAMPAIGN_ID in data; +} diff --git a/packages-exp/messaging-exp/src/helpers/logToScion.ts b/packages-exp/messaging-exp/src/helpers/logToScion.ts new file mode 100644 index 00000000000..75966ae5dd0 --- /dev/null +++ b/packages-exp/messaging-exp/src/helpers/logToScion.ts @@ -0,0 +1,56 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + CONSOLE_CAMPAIGN_ID, + CONSOLE_CAMPAIGN_NAME, + CONSOLE_CAMPAIGN_TIME +} from '../util/constants'; +import { + ConsoleMessageData, + MessageType +} from '../interfaces/internal-message-payload'; + +import { MessagingService } from '../messaging-service'; + +export async function logToScion( + messaging: MessagingService, + messageType: MessageType, + data: ConsoleMessageData +): Promise { + const eventType = getEventType(messageType); + const analytics = await messaging.firebaseDependencies.analyticsProvider.get(); + analytics.logEvent(eventType, { + /* eslint-disable camelcase */ + message_id: data[CONSOLE_CAMPAIGN_ID], + message_name: data[CONSOLE_CAMPAIGN_NAME], + message_time: data[CONSOLE_CAMPAIGN_TIME], + message_device_time: Math.floor(Date.now() / 1000) + /* eslint-enable camelcase */ + }); +} + +function getEventType(messageType: MessageType): string { + switch (messageType) { + case MessageType.NOTIFICATION_CLICKED: + return 'notification_open'; + case MessageType.PUSH_RECEIVED: + return 'notification_foreground'; + default: + throw new Error(); + } +} diff --git a/packages-exp/messaging-exp/src/helpers/migrate-old-database.test.ts b/packages-exp/messaging-exp/src/helpers/migrate-old-database.test.ts new file mode 100644 index 00000000000..020295ca2fd --- /dev/null +++ b/packages-exp/messaging-exp/src/helpers/migrate-old-database.test.ts @@ -0,0 +1,204 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import '../testing/setup'; + +import { + V2TokenDetails, + V3TokenDetails, + V4TokenDetails, + migrateOldDatabase +} from './migrate-old-database'; + +import { FakePushSubscription } from '../testing/fakes/service-worker'; +import { base64ToArray } from './array-base64-translator'; +import { expect } from 'chai'; +import { getFakeTokenDetails } from '../testing/fakes/token-details'; +import { openDb } from 'idb'; + +describe('migrateOldDb', () => { + it("does nothing if old DB didn't exist", async () => { + const tokenDetails = await migrateOldDatabase('1234567890'); + expect(tokenDetails).to.be.null; + }); + + it('does nothing if old DB was too old', async () => { + await put(1, { + swScope: '/scope-value', + fcmSenderId: '1234567890', + fcmToken: 'token-value' + }); + + const tokenDetails = await migrateOldDatabase('1234567890'); + expect(tokenDetails).to.be.null; + }); + + describe('version 2', () => { + beforeEach(async () => { + const v2TokenDetails: V2TokenDetails = { + fcmToken: 'token-value', + swScope: '/scope-value', + vapidKey: base64ToArray('dmFwaWQta2V5LXZhbHVl'), + fcmSenderId: '1234567890', + fcmPushSet: '7654321', + auth: 'YXV0aC12YWx1ZQ', + p256dh: 'cDI1Ni12YWx1ZQ', + endpoint: 'https://example.org', + subscription: new FakePushSubscription() + }; + + await put(2, v2TokenDetails); + }); + + it('can get a value from old DB', async () => { + const tokenDetails = await migrateOldDatabase('1234567890'); + + const expectedTokenDetails = getFakeTokenDetails(); + // Ignore createTime difference. + expectedTokenDetails.createTime = tokenDetails!.createTime; + + expect(tokenDetails).to.deep.equal(expectedTokenDetails); + }); + + it('only migrates once', async () => { + await migrateOldDatabase('1234567890'); + const tokenDetails = await migrateOldDatabase('1234567890'); + + expect(tokenDetails).to.be.null; + }); + + it('does not get a value that has a different sender ID', async () => { + const tokenDetails = await migrateOldDatabase('321321321'); + expect(tokenDetails).to.be.null; + }); + + it('does not migrate an entry with missing optional values', async () => { + const v2TokenDetails: V2TokenDetails = { + fcmToken: 'token-value', + swScope: '/scope-value', + vapidKey: base64ToArray('dmFwaWQta2V5LXZhbHVl'), + fcmSenderId: '1234567890', + fcmPushSet: '7654321', + subscription: new FakePushSubscription() + }; + await put(2, v2TokenDetails); + + const tokenDetails = await migrateOldDatabase('1234567890'); + expect(tokenDetails).to.be.null; + }); + }); + + describe('version 3', () => { + beforeEach(async () => { + const v3TokenDetails: V3TokenDetails = { + createTime: 1234567890, + fcmToken: 'token-value', + swScope: '/scope-value', + vapidKey: base64ToArray('dmFwaWQta2V5LXZhbHVl'), + fcmSenderId: '1234567890', + fcmPushSet: '7654321', + auth: base64ToArray('YXV0aC12YWx1ZQ'), + p256dh: base64ToArray('cDI1Ni12YWx1ZQ'), + endpoint: 'https://example.org' + }; + + await put(3, v3TokenDetails); + }); + + it('can get a value from old DB', async () => { + const tokenDetails = await migrateOldDatabase('1234567890'); + + const expectedTokenDetails = getFakeTokenDetails(); + + expect(tokenDetails).to.deep.equal(expectedTokenDetails); + }); + + it('only migrates once', async () => { + await migrateOldDatabase('1234567890'); + const tokenDetails = await migrateOldDatabase('1234567890'); + + expect(tokenDetails).to.be.null; + }); + + it('does not get a value that has a different sender ID', async () => { + const tokenDetails = await migrateOldDatabase('321321321'); + expect(tokenDetails).to.be.null; + }); + }); + + describe('version 4', () => { + beforeEach(async () => { + const v4TokenDetails: V4TokenDetails = { + createTime: 1234567890, + fcmToken: 'token-value', + swScope: '/scope-value', + vapidKey: base64ToArray('dmFwaWQta2V5LXZhbHVl'), + fcmSenderId: '1234567890', + auth: base64ToArray('YXV0aC12YWx1ZQ'), + p256dh: base64ToArray('cDI1Ni12YWx1ZQ'), + endpoint: 'https://example.org' + }; + + await put(4, v4TokenDetails); + }); + + it('can get a value from old DB', async () => { + const tokenDetails = await migrateOldDatabase('1234567890'); + + const expectedTokenDetails = getFakeTokenDetails(); + + expect(tokenDetails).to.deep.equal(expectedTokenDetails); + }); + + it('only migrates once', async () => { + await migrateOldDatabase('1234567890'); + const tokenDetails = await migrateOldDatabase('1234567890'); + + expect(tokenDetails).to.be.null; + }); + + it('does not get a value that has a different sender ID', async () => { + const tokenDetails = await migrateOldDatabase('321321321'); + expect(tokenDetails).to.be.null; + }); + }); +}); + +async function put(version: number, value: object): Promise { + const db = await openDb('fcm_token_details_db', version, upgradeDb => { + if (upgradeDb.oldVersion === 0) { + const objectStore = upgradeDb.createObjectStore( + 'fcm_token_object_Store', + { + keyPath: 'swScope' + } + ); + objectStore.createIndex('fcmSenderId', 'fcmSenderId', { + unique: false + }); + objectStore.createIndex('fcmToken', 'fcmToken', { unique: true }); + } + }); + + try { + const tx = db.transaction('fcm_token_object_Store', 'readwrite'); + await tx.objectStore('fcm_token_object_Store').put(value); + await tx.complete; + } finally { + db.close(); + } +} diff --git a/packages-exp/messaging-exp/src/helpers/migrate-old-database.ts b/packages-exp/messaging-exp/src/helpers/migrate-old-database.ts new file mode 100644 index 00000000000..40fdece6171 --- /dev/null +++ b/packages-exp/messaging-exp/src/helpers/migrate-old-database.ts @@ -0,0 +1,193 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { deleteDb, openDb } from 'idb'; + +import { TokenDetails } from '../interfaces/token-details'; +import { arrayToBase64 } from './array-base64-translator'; + +// https://github.com/firebase/firebase-js-sdk/blob/7857c212f944a2a9eb421fd4cb7370181bc034b5/packages/messaging/src/interfaces/token-details.ts +export interface V2TokenDetails { + fcmToken: string; + swScope: string; + vapidKey: string | Uint8Array; + subscription: PushSubscription; + fcmSenderId: string; + fcmPushSet: string; + createTime?: number; + endpoint?: string; + auth?: string; + p256dh?: string; +} + +// https://github.com/firebase/firebase-js-sdk/blob/6b5b15ce4ea3df5df5df8a8b33a4e41e249c7715/packages/messaging/src/interfaces/token-details.ts +export interface V3TokenDetails { + fcmToken: string; + swScope: string; + vapidKey: Uint8Array; + fcmSenderId: string; + fcmPushSet: string; + endpoint: string; + auth: ArrayBuffer; + p256dh: ArrayBuffer; + createTime: number; +} + +// https://github.com/firebase/firebase-js-sdk/blob/9567dba664732f681fa7fe60f5b7032bb1daf4c9/packages/messaging/src/interfaces/token-details.ts +export interface V4TokenDetails { + fcmToken: string; + swScope: string; + vapidKey: Uint8Array; + fcmSenderId: string; + endpoint: string; + auth: ArrayBufferLike; + p256dh: ArrayBufferLike; + createTime: number; +} + +const OLD_DB_NAME = 'fcm_token_details_db'; +/** + * The last DB version of 'fcm_token_details_db' was 4. This is one higher, so that the upgrade + * callback is called for all versions of the old DB. + */ +const OLD_DB_VERSION = 5; +const OLD_OBJECT_STORE_NAME = 'fcm_token_object_Store'; + +export async function migrateOldDatabase( + senderId: string +): Promise { + if ('databases' in indexedDB) { + // indexedDb.databases() is an IndexedDB v3 API and does not exist in all browsers. TODO: Remove + // typecast when it lands in TS types. + const databases = await (indexedDB as { + databases(): Promise>; + }).databases(); + const dbNames = databases.map(db => db.name); + + if (!dbNames.includes(OLD_DB_NAME)) { + // old DB didn't exist, no need to open. + return null; + } + } + + let tokenDetails: TokenDetails | null = null; + + const db = await openDb(OLD_DB_NAME, OLD_DB_VERSION, async db => { + if (db.oldVersion < 2) { + // Database too old, skip migration. + return; + } + + if (!db.objectStoreNames.contains(OLD_OBJECT_STORE_NAME)) { + // Database did not exist. Nothing to do. + return; + } + + const objectStore = db.transaction.objectStore(OLD_OBJECT_STORE_NAME); + const value = await objectStore.index('fcmSenderId').get(senderId); + await objectStore.clear(); + + if (!value) { + // No entry in the database, nothing to migrate. + return; + } + + if (db.oldVersion === 2) { + const oldDetails = value as V2TokenDetails; + + if (!oldDetails.auth || !oldDetails.p256dh || !oldDetails.endpoint) { + return; + } + + tokenDetails = { + token: oldDetails.fcmToken, + createTime: oldDetails.createTime ?? Date.now(), + subscriptionOptions: { + auth: oldDetails.auth, + p256dh: oldDetails.p256dh, + endpoint: oldDetails.endpoint, + swScope: oldDetails.swScope, + vapidKey: + typeof oldDetails.vapidKey === 'string' + ? oldDetails.vapidKey + : arrayToBase64(oldDetails.vapidKey) + } + }; + } else if (db.oldVersion === 3) { + const oldDetails = value as V3TokenDetails; + + tokenDetails = { + token: oldDetails.fcmToken, + createTime: oldDetails.createTime, + subscriptionOptions: { + auth: arrayToBase64(oldDetails.auth), + p256dh: arrayToBase64(oldDetails.p256dh), + endpoint: oldDetails.endpoint, + swScope: oldDetails.swScope, + vapidKey: arrayToBase64(oldDetails.vapidKey) + } + }; + } else if (db.oldVersion === 4) { + const oldDetails = value as V4TokenDetails; + + tokenDetails = { + token: oldDetails.fcmToken, + createTime: oldDetails.createTime, + subscriptionOptions: { + auth: arrayToBase64(oldDetails.auth), + p256dh: arrayToBase64(oldDetails.p256dh), + endpoint: oldDetails.endpoint, + swScope: oldDetails.swScope, + vapidKey: arrayToBase64(oldDetails.vapidKey) + } + }; + } + }); + db.close(); + + // Delete all old databases. + await deleteDb(OLD_DB_NAME); + await deleteDb('fcm_vapid_details_db'); + await deleteDb('undefined'); + + return checkTokenDetails(tokenDetails) ? tokenDetails : null; +} + +function checkTokenDetails( + tokenDetails: TokenDetails | null +): tokenDetails is TokenDetails { + if (!tokenDetails || !tokenDetails.subscriptionOptions) { + return false; + } + const { subscriptionOptions } = tokenDetails; + return ( + typeof tokenDetails.createTime === 'number' && + tokenDetails.createTime > 0 && + typeof tokenDetails.token === 'string' && + tokenDetails.token.length > 0 && + typeof subscriptionOptions.auth === 'string' && + subscriptionOptions.auth.length > 0 && + typeof subscriptionOptions.p256dh === 'string' && + subscriptionOptions.p256dh.length > 0 && + typeof subscriptionOptions.endpoint === 'string' && + subscriptionOptions.endpoint.length > 0 && + typeof subscriptionOptions.swScope === 'string' && + subscriptionOptions.swScope.length > 0 && + typeof subscriptionOptions.vapidKey === 'string' && + subscriptionOptions.vapidKey.length > 0 + ); +} diff --git a/packages-exp/messaging-exp/src/helpers/register.ts b/packages-exp/messaging-exp/src/helpers/register.ts new file mode 100644 index 00000000000..edb2e81e32f --- /dev/null +++ b/packages-exp/messaging-exp/src/helpers/register.ts @@ -0,0 +1,119 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Component, + ComponentContainer, + ComponentType, + InstanceFactory +} from '@firebase/component'; +import { ERROR_FACTORY, ErrorCode } from '../util/errors'; +import { isSwSupported, isWindowSupported } from '../api/isSupported'; +import { + onNotificationClick, + onPush, + onSubChange +} from '../listeners/sw-listeners'; + +import { MessagingService } from '../messaging-service'; +import { ServiceWorkerGlobalScope } from '../util/sw-types'; +import { _registerComponent } from '@firebase/app-exp'; +import { messageEventListener } from '../listeners/window-listener'; + +const WindowMessagingFactory: InstanceFactory<'messaging-exp'> = ( + container: ComponentContainer +) => { + // Conscious decision to make this async check non-blocking during the messaging instance + // initialization phase for performance consideration. An error would be thrown latter for + // developer's information. Developers can then choose to import and call `isSupported` for + // special handling. + isWindowSupported() + .then(isSupported => { + if (!isSupported) { + throw ERROR_FACTORY.create(ErrorCode.UNSUPPORTED_BROWSER); + } + }) + .catch(_ => { + throw ERROR_FACTORY.create(ErrorCode.INDEXED_DB_UNSUPPORTED); + }); + + const messaging = new MessagingService( + container.getProvider('app-exp').getImmediate(), + container.getProvider('installations-exp-internal').getImmediate(), + container.getProvider('analytics-internal') + ); + + navigator.serviceWorker.addEventListener('message', e => + messageEventListener(messaging as MessagingService, e) + ); + + return messaging; +}; + +declare const self: ServiceWorkerGlobalScope; +const SwMessagingFactory: InstanceFactory<'messaging-exp'> = ( + container: ComponentContainer +) => { + // Conscious decision to make this async check non-blocking during the messaging instance + // initialization phase for performance consideration. An error would be thrown latter for + // developer's information. Developers can then choose to import and call `isSupported` for + // special handling. + isSwSupported() + .then(isSupported => { + if (!isSupported) { + throw ERROR_FACTORY.create(ErrorCode.UNSUPPORTED_BROWSER); + } + }) + .catch(_ => { + throw ERROR_FACTORY.create(ErrorCode.INDEXED_DB_UNSUPPORTED); + }); + + const messaging = new MessagingService( + container.getProvider('app-exp').getImmediate(), + container.getProvider('installations-exp-internal').getImmediate(), + container.getProvider('analytics-internal') + ); + + self.addEventListener('push', e => { + e.waitUntil(onPush(e, messaging as MessagingService)); + }); + self.addEventListener('pushsubscriptionchange', e => { + e.waitUntil(onSubChange(e, messaging as MessagingService)); + }); + self.addEventListener('notificationclick', e => { + e.waitUntil(onNotificationClick(e)); + }); + + return messaging; +}; + +export function registerMessagingInWindow(): void { + _registerComponent( + new Component('messaging-exp', WindowMessagingFactory, ComponentType.PUBLIC) + ); +} + +/** + * The messaging instance registered in sw is named differently than that of in client. This is + * because both `registerMessagingInWindow` and `registerMessagingInSw` would be called in + * `messaging-compat` and component with the same name can only be registered once. + */ +export function registerMessagingInSw(): void { + _registerComponent( + new Component('messaging-sw-exp', SwMessagingFactory, ComponentType.PUBLIC) + ); +} diff --git a/packages-exp/messaging-exp/src/helpers/registerDefaultSw.ts b/packages-exp/messaging-exp/src/helpers/registerDefaultSw.ts new file mode 100644 index 00000000000..2d929596c68 --- /dev/null +++ b/packages-exp/messaging-exp/src/helpers/registerDefaultSw.ts @@ -0,0 +1,47 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DEFAULT_SW_PATH, DEFAULT_SW_SCOPE } from '../util/constants'; +import { ERROR_FACTORY, ErrorCode } from '../util/errors'; + +import { MessagingService } from '../messaging-service'; + +export async function registerDefaultSw( + messaging: MessagingService +): Promise { + try { + messaging.swRegistration = await navigator.serviceWorker.register( + DEFAULT_SW_PATH, + { + scope: DEFAULT_SW_SCOPE + } + ); + + // The timing when browser updates sw when sw has an update is unreliable from experiment. It + // leads to version conflict when the SDK upgrades to a newer version in the main page, but sw + // is stuck with the old version. For example, + // https://github.com/firebase/firebase-js-sdk/issues/2590 The following line reliably updates + // sw if there was an update. + messaging.swRegistration.update().catch(() => { + /* it is non blocking and we don't care if it failed */ + }); + } catch (e) { + throw ERROR_FACTORY.create(ErrorCode.FAILED_DEFAULT_REGISTRATION, { + browserErrorMessage: e.message + }); + } +} diff --git a/packages-exp/messaging-exp/src/helpers/sleep.test.ts b/packages-exp/messaging-exp/src/helpers/sleep.test.ts new file mode 100644 index 00000000000..b7c4e228f10 --- /dev/null +++ b/packages-exp/messaging-exp/src/helpers/sleep.test.ts @@ -0,0 +1,39 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import '../testing/setup'; + +import { SinonFakeTimers, useFakeTimers } from 'sinon'; + +import { expect } from 'chai'; +import { sleep } from './sleep'; + +describe('sleep', () => { + let clock: SinonFakeTimers; + + beforeEach(() => { + clock = useFakeTimers({ shouldAdvanceTime: true }); + }); + + it('returns a promise that resolves after a given amount of time', async () => { + const t0 = clock.now; + await sleep(100); + const t1 = clock.now; + + expect(t1 - t0).to.equal(100); + }); +}); diff --git a/packages-exp/messaging-exp/src/helpers/sleep.ts b/packages-exp/messaging-exp/src/helpers/sleep.ts new file mode 100644 index 00000000000..2bd1eb9283b --- /dev/null +++ b/packages-exp/messaging-exp/src/helpers/sleep.ts @@ -0,0 +1,23 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** Returns a promise that resolves after given time passes. */ +export function sleep(ms: number): Promise { + return new Promise(resolve => { + setTimeout(resolve, ms); + }); +} diff --git a/packages-exp/messaging-exp/src/helpers/updateSwReg.ts b/packages-exp/messaging-exp/src/helpers/updateSwReg.ts new file mode 100644 index 00000000000..927599aaa73 --- /dev/null +++ b/packages-exp/messaging-exp/src/helpers/updateSwReg.ts @@ -0,0 +1,40 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ERROR_FACTORY, ErrorCode } from '../util/errors'; + +import { MessagingService } from '../messaging-service'; +import { registerDefaultSw } from './registerDefaultSw'; + +export async function updateSwReg( + messaging: MessagingService, + swRegistration?: ServiceWorkerRegistration | undefined +): Promise { + if (!swRegistration && !messaging.swRegistration) { + await registerDefaultSw(messaging); + } + + if (!swRegistration && !!messaging.swRegistration) { + return; + } + + if (!(swRegistration instanceof ServiceWorkerRegistration)) { + throw ERROR_FACTORY.create(ErrorCode.INVALID_SW_REGISTRATION); + } + + messaging.swRegistration = swRegistration; +} diff --git a/packages-exp/messaging-exp/src/helpers/updateVapidKey.ts b/packages-exp/messaging-exp/src/helpers/updateVapidKey.ts new file mode 100644 index 00000000000..9ad3b4663b4 --- /dev/null +++ b/packages-exp/messaging-exp/src/helpers/updateVapidKey.ts @@ -0,0 +1,30 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DEFAULT_VAPID_KEY } from '../util/constants'; +import { MessagingService } from '../messaging-service'; + +export async function updateVapidKey( + messaging: MessagingService, + vapidKey?: string | undefined +): Promise { + if (!!vapidKey) { + messaging.vapidKey = vapidKey; + } else if (!messaging.vapidKey) { + messaging.vapidKey = DEFAULT_VAPID_KEY; + } +} diff --git a/packages-exp/messaging-exp/src/index.sw.ts b/packages-exp/messaging-exp/src/index.sw.ts new file mode 100644 index 00000000000..0aa3d7130d9 --- /dev/null +++ b/packages-exp/messaging-exp/src/index.sw.ts @@ -0,0 +1,32 @@ +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import '@firebase/installations-exp'; + +import { FirebaseMessaging } from './interfaces/public-types'; +import { registerMessagingInSw } from './helpers/register'; + +export { onBackgroundMessage, getMessagingInSw as getMessaging } from './api'; +export { isSwSupported as isSupported } from './api/isSupported'; + +declare module '@firebase/component' { + interface NameServiceMapping { + 'messaging-sw-exp': FirebaseMessaging; + } +} + +registerMessagingInSw(); diff --git a/packages-exp/messaging-exp/src/index.ts b/packages-exp/messaging-exp/src/index.ts new file mode 100644 index 00000000000..f2a2e1ae202 --- /dev/null +++ b/packages-exp/messaging-exp/src/index.ts @@ -0,0 +1,44 @@ +/** + * Firebase Cloud Messaging + * + * @packageDocumentation + */ + +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import '@firebase/installations-exp'; + +import { FirebaseMessaging } from './interfaces/public-types'; +import { registerMessagingInWindow } from './helpers/register'; + +export { + getToken, + deleteToken, + onMessage, + getMessagingInWindow as getMessaging +} from './api'; +export { isWindowSupported as isSupported } from './api/isSupported'; +export * from './interfaces/public-types'; + +declare module '@firebase/component' { + interface NameServiceMapping { + 'messaging-exp': FirebaseMessaging; + } +} + +registerMessagingInWindow(); diff --git a/packages-exp/messaging-exp/src/interfaces/app-config.ts b/packages-exp/messaging-exp/src/interfaces/app-config.ts new file mode 100644 index 00000000000..4a887eeb3cc --- /dev/null +++ b/packages-exp/messaging-exp/src/interfaces/app-config.ts @@ -0,0 +1,25 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface AppConfig { + readonly appName: string; + readonly projectId: string; + readonly apiKey: string; + readonly appId: string; + /** Only used for old DB migration. */ + readonly senderId: string; +} diff --git a/packages-exp/messaging-exp/src/interfaces/internal-dependencies.ts b/packages-exp/messaging-exp/src/interfaces/internal-dependencies.ts new file mode 100644 index 00000000000..6d88720ef46 --- /dev/null +++ b/packages-exp/messaging-exp/src/interfaces/internal-dependencies.ts @@ -0,0 +1,29 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AppConfig } from './app-config'; +import { FirebaseAnalyticsInternalName } from '@firebase/analytics-interop-types'; +import { FirebaseApp } from '@firebase/app-exp'; +import { Provider } from '@firebase/component'; +import { _FirebaseInstallationsInternal } from '@firebase/installations-exp'; + +export interface FirebaseInternalDependencies { + app: FirebaseApp; + appConfig: AppConfig; + installations: _FirebaseInstallationsInternal; + analyticsProvider: Provider; +} diff --git a/packages-exp/messaging-exp/src/interfaces/internal-message-payload.ts b/packages-exp/messaging-exp/src/interfaces/internal-message-payload.ts new file mode 100644 index 00000000000..92f7b19fa02 --- /dev/null +++ b/packages-exp/messaging-exp/src/interfaces/internal-message-payload.ts @@ -0,0 +1,64 @@ +/** + * @license + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +import { + CONSOLE_CAMPAIGN_ANALYTICS_ENABLED, + CONSOLE_CAMPAIGN_ID, + CONSOLE_CAMPAIGN_NAME, + CONSOLE_CAMPAIGN_TIME +} from '../util/constants'; + +export interface MessagePayloadInternal { + notification?: NotificationPayloadInternal; + data?: unknown; + fcmOptions?: FcmOptionsInternal; + messageType?: MessageType; + isFirebaseMessaging?: boolean; + from: string; + // eslint-disable-next-line camelcase + collapse_key: string; +} + +export interface NotificationPayloadInternal extends NotificationOptions { + title: string; + // Supported in the Legacy Send API. + // See:https://firebase.google.com/docs/cloud-messaging/xmpp-server-ref. + // eslint-disable-next-line camelcase + click_action?: string; +} + +// Defined in +// https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#webpushfcmoptions. Note +// that the keys are sent to the clients in snake cases which we need to convert to camel so it can +// be exposed as a type to match the Firebase API convention. +export interface FcmOptionsInternal { + link?: string; + + // eslint-disable-next-line camelcase + analytics_label?: string; +} + +export enum MessageType { + PUSH_RECEIVED = 'push-received', + NOTIFICATION_CLICKED = 'notification-clicked' +} + +/** Additional data of a message sent from the FN Console. */ +export interface ConsoleMessageData { + [CONSOLE_CAMPAIGN_ID]: string; + [CONSOLE_CAMPAIGN_TIME]: string; + [CONSOLE_CAMPAIGN_NAME]?: string; + [CONSOLE_CAMPAIGN_ANALYTICS_ENABLED]?: '1'; +} diff --git a/packages-exp/messaging-exp/src/interfaces/public-types.ts b/packages-exp/messaging-exp/src/interfaces/public-types.ts new file mode 100644 index 00000000000..37dae04255d --- /dev/null +++ b/packages-exp/messaging-exp/src/interfaces/public-types.ts @@ -0,0 +1,142 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Display notification details. They are sent through the + * {@link https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#notification | Send API} + * + * @public + */ +export interface NotificationPayload { + /** + * The notification's title. + */ + title?: string; + + /** + * The notification's body text. + */ + body?: string; + + /** + * The URL of an image that is downloaded on the device and displayed in the notification. + */ + image?: string; +} + +/** + * Options for features provided by the FCM SDK for Web. See {@link + * https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#webpushfcmoptions | + * WebpushFcmOptions} + * + * @public + */ +export interface FcmOptions { + /** + * The link to open when the user clicks on the notification. + */ + link?: string; + + /** + * The label associated with the message's analytics data. + */ + analyticsLabel?: string; +} + +/** + * Message payload that contains the notification payload that is represented with + * {@link NotificationPayload} and the data payload that contains an arbitrary + * number of key-value pairs sent by developers through the + * {@link https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#notification | Send API} + * + * @public + */ +export interface MessagePayload { + /** + * {@inheritdoc NotificationPayload} + */ + notification?: NotificationPayload; + + /** + * Arbitrary key/value payload. + */ + data?: { [key: string]: string }; + + /** + * {@inheritdoc FcmOptions} + */ + fcmOptions?: FcmOptions; + + /** + * The sender of this message. + */ + from: string; + + /** + * The collapse key of the message. See + * {@link https://firebase.google.com/docs/cloud-messaging/concept-options#collapsible_and_non-collapsible_messages | Non-collapsible and collapsible messages} + */ + collapseKey: string; +} + +/** + * Options for {@link getToken} + * + * @public + */ +export interface GetTokenOptions { + /** + * The public server key provided to push services. It is used to + * authenticate the push subscribers to receive push messages only from sending servers that hold + * the corresponding private key. If it is not provided, a default VAPID key is used. Note that some + * push services (Chrome Push Service) require a non-default VAPID key. Therefore, it is recommended + * to generate and import a VAPID key for your project with + * {@link https://firebase.google.com/docs/cloud-messaging/js/client#configure_web_credentials_with_fcm | Configure Web Credentials with FCM}. + * See + * {@link https://developers.google.com/web/fundamentals/push-notifications/web-push-protocol | The Web Push Protocol} + * for details on web push services. + */ + vapidKey?: string; + /** + * The service worker registration for receiving push + * messaging. If the registration is not provided explicitly, you need to have a + * `firebase-messaging-sw.js` at your root location. See + * {@link https://firebase.google.com/docs/cloud-messaging/js/client#retrieve-the-current-registration-token | Retrieve the current registration token} + * for more details. + */ + serviceWorkerRegistration?: ServiceWorkerRegistration; +} + +/** + * Public interface of the Firebase Cloud Messaging SDK. + * + * @public + */ +export interface FirebaseMessaging {} + +/** + * @internal + */ +export type _FirebaseMessagingName = 'messaging'; + +export { NextFn, Observer, Unsubscribe } from '@firebase/util'; + +declare module '@firebase/component' { + interface NameServiceMapping { + 'messaging-exp': FirebaseMessaging; + } +} diff --git a/packages-exp/messaging-exp/src/interfaces/token-details.ts b/packages-exp/messaging-exp/src/interfaces/token-details.ts new file mode 100644 index 00000000000..791c94d267b --- /dev/null +++ b/packages-exp/messaging-exp/src/interfaces/token-details.ts @@ -0,0 +1,34 @@ +/** + * @license + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface TokenDetails { + token: string; + createTime: number; + /** Does not exist in Safari since it's not using Push API. */ + subscriptionOptions?: SubscriptionOptions; +} + +/** + * Additional options and values required by a Push API subscription. + */ +export interface SubscriptionOptions { + vapidKey: string; + swScope: string; + endpoint: string; + auth: string; + p256dh: string; +} diff --git a/packages-exp/messaging-exp/src/internals/idb-manager.test.ts b/packages-exp/messaging-exp/src/internals/idb-manager.test.ts new file mode 100644 index 00000000000..c66f11ad440 --- /dev/null +++ b/packages-exp/messaging-exp/src/internals/idb-manager.test.ts @@ -0,0 +1,124 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import '../testing/setup'; + +import * as migrateOldDatabaseModule from '../helpers/migrate-old-database'; + +import { dbGet, dbRemove, dbSet } from '../internals/idb-manager'; + +import { FirebaseInternalDependencies } from '../interfaces/internal-dependencies'; +import { Stub } from '../testing/sinon-types'; +import { TokenDetails } from '../interfaces/token-details'; +import { expect } from 'chai'; +import { getFakeFirebaseDependencies } from '../testing/fakes/firebase-dependencies'; +import { getFakeTokenDetails } from '../testing/fakes/token-details'; +import { stub } from 'sinon'; + +describe('idb manager', () => { + let firebaseDependencies: FirebaseInternalDependencies; + let tokenDetailsA: TokenDetails; + let tokenDetailsB: TokenDetails; + + beforeEach(() => { + firebaseDependencies = getFakeFirebaseDependencies(); + tokenDetailsA = getFakeTokenDetails(); + tokenDetailsB = getFakeTokenDetails(); + tokenDetailsA.token = 'TOKEN_A'; + tokenDetailsB.token = 'TOKEN_B'; + }); + + describe('get / set', () => { + it('sets a value and then gets the same value back', async () => { + await dbSet(firebaseDependencies, tokenDetailsA); + const value = await dbGet(firebaseDependencies); + expect(value).to.deep.equal(tokenDetailsA); + }); + + it('gets undefined for a key that does not exist', async () => { + const value = await dbGet(firebaseDependencies); + expect(value).to.be.undefined; + }); + + it('sets and gets multiple values with different keys', async () => { + const firebaseDependenciesB = getFakeFirebaseDependencies({ + appId: 'different-app-id' + }); + await dbSet(firebaseDependencies, tokenDetailsA); + await dbSet(firebaseDependenciesB, tokenDetailsB); + expect(await dbGet(firebaseDependencies)).to.deep.equal(tokenDetailsA); + expect(await dbGet(firebaseDependenciesB)).to.deep.equal(tokenDetailsB); + }); + + it('overwrites a value', async () => { + await dbSet(firebaseDependencies, tokenDetailsA); + await dbSet(firebaseDependencies, tokenDetailsB); + expect(await dbGet(firebaseDependencies)).to.deep.equal(tokenDetailsB); + }); + + describe('old DB migration', () => { + let migrateOldDatabaseStub: Stub< + typeof migrateOldDatabaseModule['migrateOldDatabase'] + >; + + beforeEach(() => { + migrateOldDatabaseStub = stub( + migrateOldDatabaseModule, + 'migrateOldDatabase' + ).resolves(tokenDetailsA); + }); + + it('gets value from old DB if there is one', async () => { + await dbGet(firebaseDependencies); + + expect(migrateOldDatabaseStub).to.have.been.calledOnceWith( + firebaseDependencies.appConfig.senderId + ); + }); + + it('does not call migrateOldDatabase a second time', async () => { + await dbGet(firebaseDependencies); + await dbGet(firebaseDependencies); + + expect(migrateOldDatabaseStub).to.have.been.calledOnceWith( + firebaseDependencies.appConfig.senderId + ); + }); + + it('does not call migrateOldDatabase if there is already a value in the DB', async () => { + await dbSet(firebaseDependencies, tokenDetailsA); + + await dbGet(firebaseDependencies); + + expect(migrateOldDatabaseStub).not.to.have.been.called; + }); + }); + }); + + describe('remove', () => { + it('deletes a key', async () => { + await dbSet(firebaseDependencies, tokenDetailsA); + await dbRemove(firebaseDependencies); + expect(await dbGet(firebaseDependencies)).to.be.undefined; + }); + + it('does not throw if key does not exist', async () => { + await dbRemove(firebaseDependencies); + expect(await dbGet(firebaseDependencies)).to.be.undefined; + }); + }); +}); diff --git a/packages-exp/messaging-exp/src/internals/idb-manager.ts b/packages-exp/messaging-exp/src/internals/idb-manager.ts new file mode 100644 index 00000000000..4ddebf5ae96 --- /dev/null +++ b/packages-exp/messaging-exp/src/internals/idb-manager.ts @@ -0,0 +1,106 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DB, deleteDb, openDb } from 'idb'; + +import { FirebaseInternalDependencies } from '../interfaces/internal-dependencies'; +import { TokenDetails } from '../interfaces/token-details'; +import { migrateOldDatabase } from '../helpers/migrate-old-database'; + +// Exported for tests. +export const DATABASE_NAME = 'firebase-messaging-database'; +const DATABASE_VERSION = 1; +const OBJECT_STORE_NAME = 'firebase-messaging-store'; + +let dbPromise: Promise | null = null; +function getDbPromise(): Promise { + if (!dbPromise) { + dbPromise = openDb(DATABASE_NAME, DATABASE_VERSION, upgradeDb => { + // We don't use 'break' in this switch statement, the fall-through behavior is what we want, + // because if there are multiple versions between the old version and the current version, we + // want ALL the migrations that correspond to those versions to run, not only the last one. + // eslint-disable-next-line default-case + switch (upgradeDb.oldVersion) { + case 0: + upgradeDb.createObjectStore(OBJECT_STORE_NAME); + } + }); + } + return dbPromise; +} + +/** Gets record(s) from the objectStore that match the given key. */ +export async function dbGet( + firebaseDependencies: FirebaseInternalDependencies +): Promise { + const key = getKey(firebaseDependencies); + const db = await getDbPromise(); + const tokenDetails = await db + .transaction(OBJECT_STORE_NAME) + .objectStore(OBJECT_STORE_NAME) + .get(key); + + if (tokenDetails) { + return tokenDetails; + } else { + // Check if there is a tokenDetails object in the old DB. + const oldTokenDetails = await migrateOldDatabase( + firebaseDependencies.appConfig.senderId + ); + if (oldTokenDetails) { + await dbSet(firebaseDependencies, oldTokenDetails); + return oldTokenDetails; + } + } +} + +/** Assigns or overwrites the record for the given key with the given value. */ +export async function dbSet( + firebaseDependencies: FirebaseInternalDependencies, + tokenDetails: TokenDetails +): Promise { + const key = getKey(firebaseDependencies); + const db = await getDbPromise(); + const tx = db.transaction(OBJECT_STORE_NAME, 'readwrite'); + await tx.objectStore(OBJECT_STORE_NAME).put(tokenDetails, key); + await tx.complete; + return tokenDetails; +} + +/** Removes record(s) from the objectStore that match the given key. */ +export async function dbRemove( + firebaseDependencies: FirebaseInternalDependencies +): Promise { + const key = getKey(firebaseDependencies); + const db = await getDbPromise(); + const tx = db.transaction(OBJECT_STORE_NAME, 'readwrite'); + await tx.objectStore(OBJECT_STORE_NAME).delete(key); + await tx.complete; +} + +/** Deletes the DB. Useful for tests. */ +export async function dbDelete(): Promise { + if (dbPromise) { + (await dbPromise).close(); + await deleteDb(DATABASE_NAME); + dbPromise = null; + } +} + +function getKey({ appConfig }: FirebaseInternalDependencies): string { + return appConfig.appId; +} diff --git a/packages-exp/messaging-exp/src/internals/requests.test.ts b/packages-exp/messaging-exp/src/internals/requests.test.ts new file mode 100644 index 00000000000..0f4375e69a5 --- /dev/null +++ b/packages-exp/messaging-exp/src/internals/requests.test.ts @@ -0,0 +1,218 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import '../testing/setup'; + +import { + ApiRequestBody, + requestDeleteToken, + requestGetToken, + requestUpdateToken +} from './requests'; + +import { ENDPOINT } from '../util/constants'; +import { FirebaseInternalDependencies } from '../interfaces/internal-dependencies'; +import { Stub } from '../testing/sinon-types'; +import { TokenDetails } from '../interfaces/token-details'; +import { compareHeaders } from '../testing/compare-headers'; +import { expect } from 'chai'; +import { getFakeFirebaseDependencies } from '../testing/fakes/firebase-dependencies'; +import { getFakeTokenDetails } from '../testing/fakes/token-details'; +import { stub } from 'sinon'; + +describe('API', () => { + let tokenDetails: TokenDetails; + let firebaseDependencies: FirebaseInternalDependencies; + let fetchStub: Stub; + + beforeEach(() => { + tokenDetails = getFakeTokenDetails(); + firebaseDependencies = getFakeFirebaseDependencies(); + fetchStub = stub(self, 'fetch'); + }); + + describe('getToken', () => { + it('calls the createRegistration server API with correct parameters', async () => { + fetchStub.resolves( + new Response(JSON.stringify({ token: 'fcm-token-from-server' })) + ); + + const response = await requestGetToken( + firebaseDependencies, + tokenDetails.subscriptionOptions! + ); + + const expectedHeaders = new Headers({ + 'Content-Type': 'application/json', + Accept: 'application/json', + 'x-goog-api-key': 'apiKey', + 'x-goog-firebase-installations-auth': `FIS authToken` + }); + const expectedBody: ApiRequestBody = { + web: { + endpoint: 'https://example.org', + auth: 'YXV0aC12YWx1ZQ', + p256dh: 'cDI1Ni12YWx1ZQ', + applicationPubKey: 'dmFwaWQta2V5LXZhbHVl' + } + }; + const expectedRequest: RequestInit = { + method: 'POST', + headers: expectedHeaders, + body: JSON.stringify(expectedBody) + }; + const expectedEndpoint = `${ENDPOINT}/projects/projectId/registrations`; + + expect(response).to.equal('fcm-token-from-server'); + expect(fetchStub).to.be.calledOnceWith(expectedEndpoint, expectedRequest); + // TODO: expect fis.getToken to be called. There is some issue w/ stubbing the fis module. + const actualHeaders = fetchStub.lastCall.lastArg.headers; + compareHeaders(expectedHeaders, actualHeaders); + }); + + it('throws if there is a problem with the response', async () => { + fetchStub.rejects(new Error('Fetch failed')); + await expect( + requestGetToken(firebaseDependencies, tokenDetails.subscriptionOptions!) + ).to.be.rejectedWith('Fetch failed'); + + fetchStub.resolves( + new Response(JSON.stringify({ error: { message: 'error message' } })) + ); + await expect( + requestGetToken(firebaseDependencies, tokenDetails.subscriptionOptions!) + ).to.be.rejectedWith('messaging/token-subscribe-failed'); + + fetchStub.resolves( + new Response( + JSON.stringify({ + /* no token */ + }) + ) + ); + await expect( + requestGetToken(firebaseDependencies, tokenDetails.subscriptionOptions!) + ).to.be.rejectedWith('messaging/token-subscribe-no-token'); + }); + }); + + describe('updateToken', () => { + it('calls the updateRegistration server API with correct parameters', async () => { + fetchStub.resolves( + new Response(JSON.stringify({ token: 'fcm-token-from-server' })) + ); + + const response = await requestUpdateToken( + firebaseDependencies, + tokenDetails + ); + + const expectedHeaders = new Headers({ + 'Content-Type': 'application/json', + Accept: 'application/json', + 'x-goog-api-key': 'apiKey', + 'x-goog-firebase-installations-auth': `FIS authToken` + }); + const expectedBody: ApiRequestBody = { + web: { + endpoint: 'https://example.org', + auth: 'YXV0aC12YWx1ZQ', + p256dh: 'cDI1Ni12YWx1ZQ', + applicationPubKey: 'dmFwaWQta2V5LXZhbHVl' + } + }; + const expectedRequest: RequestInit = { + method: 'PATCH', + headers: expectedHeaders, + body: JSON.stringify(expectedBody) + }; + const expectedEndpoint = `${ENDPOINT}/projects/projectId/registrations/token-value`; + + expect(response).to.equal('fcm-token-from-server'); + expect(fetchStub).to.be.calledOnceWith(expectedEndpoint, expectedRequest); + const actualHeaders = fetchStub.lastCall.lastArg.headers; + compareHeaders(expectedHeaders, actualHeaders); + }); + + it('throws if there is a problem with the response', async () => { + fetchStub.rejects(new Error('Fetch failed')); + await expect( + requestUpdateToken(firebaseDependencies, tokenDetails) + ).to.be.rejectedWith('Fetch failed'); + + fetchStub.resolves( + new Response(JSON.stringify({ error: { message: 'error message' } })) + ); + await expect( + requestUpdateToken(firebaseDependencies, tokenDetails) + ).to.be.rejectedWith('messaging/token-update-failed'); + + fetchStub.resolves( + new Response( + JSON.stringify({ + /* no token */ + }) + ) + ); + await expect( + requestUpdateToken(firebaseDependencies, tokenDetails) + ).to.be.rejectedWith('messaging/token-update-no-token'); + }); + }); + + describe('deleteToken', () => { + it('calls the deleteRegistration server API with correct parameters', async () => { + fetchStub.resolves(new Response(JSON.stringify({}))); + + const response = await requestDeleteToken( + firebaseDependencies, + tokenDetails.token + ); + + const expectedHeaders = new Headers({ + 'Content-Type': 'application/json', + Accept: 'application/json', + 'x-goog-api-key': 'apiKey', + 'x-goog-firebase-installations-auth': `FIS authToken` + }); + const expectedRequest: RequestInit = { + method: 'DELETE', + headers: expectedHeaders + }; + const expectedEndpoint = `${ENDPOINT}/projects/projectId/registrations/token-value`; + + expect(response).to.be.undefined; + expect(fetchStub).to.be.calledOnceWith(expectedEndpoint, expectedRequest); + const actualHeaders = fetchStub.lastCall.lastArg.headers; + compareHeaders(expectedHeaders, actualHeaders); + }); + + it('throws if there is a problem with the response', async () => { + fetchStub.rejects(new Error('Fetch failed')); + await expect( + requestDeleteToken(firebaseDependencies, tokenDetails.token) + ).to.be.rejectedWith('Fetch failed'); + + fetchStub.resolves( + new Response(JSON.stringify({ error: { message: 'error message' } })) + ); + await expect( + requestDeleteToken(firebaseDependencies, tokenDetails.token) + ).to.be.rejectedWith('messaging/token-unsubscribe-failed'); + }); + }); +}); diff --git a/packages-exp/messaging-exp/src/internals/requests.ts b/packages-exp/messaging-exp/src/internals/requests.ts new file mode 100644 index 00000000000..b93f8623601 --- /dev/null +++ b/packages-exp/messaging-exp/src/internals/requests.ts @@ -0,0 +1,186 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DEFAULT_VAPID_KEY, ENDPOINT } from '../util/constants'; +import { ERROR_FACTORY, ErrorCode } from '../util/errors'; +import { SubscriptionOptions, TokenDetails } from '../interfaces/token-details'; + +import { AppConfig } from '../interfaces/app-config'; +import { FirebaseInternalDependencies } from '../interfaces/internal-dependencies'; + +export interface ApiResponse { + token?: string; + error?: { message: string }; +} + +export interface ApiRequestBody { + web: { + endpoint: string; + p256dh: string; + auth: string; + applicationPubKey?: string; + }; +} + +export async function requestGetToken( + firebaseDependencies: FirebaseInternalDependencies, + subscriptionOptions: SubscriptionOptions +): Promise { + const headers = await getHeaders(firebaseDependencies); + const body = getBody(subscriptionOptions); + + const subscribeOptions = { + method: 'POST', + headers, + body: JSON.stringify(body) + }; + + let responseData: ApiResponse; + try { + const response = await fetch( + getEndpoint(firebaseDependencies.appConfig), + subscribeOptions + ); + responseData = await response.json(); + } catch (err) { + throw ERROR_FACTORY.create(ErrorCode.TOKEN_SUBSCRIBE_FAILED, { + errorInfo: err + }); + } + + if (responseData.error) { + const message = responseData.error.message; + throw ERROR_FACTORY.create(ErrorCode.TOKEN_SUBSCRIBE_FAILED, { + errorInfo: message + }); + } + + if (!responseData.token) { + throw ERROR_FACTORY.create(ErrorCode.TOKEN_SUBSCRIBE_NO_TOKEN); + } + + return responseData.token; +} + +export async function requestUpdateToken( + firebaseDependencies: FirebaseInternalDependencies, + tokenDetails: TokenDetails +): Promise { + const headers = await getHeaders(firebaseDependencies); + const body = getBody(tokenDetails.subscriptionOptions!); + + const updateOptions = { + method: 'PATCH', + headers, + body: JSON.stringify(body) + }; + + let responseData: ApiResponse; + try { + const response = await fetch( + `${getEndpoint(firebaseDependencies.appConfig)}/${tokenDetails.token}`, + updateOptions + ); + responseData = await response.json(); + } catch (err) { + throw ERROR_FACTORY.create(ErrorCode.TOKEN_UPDATE_FAILED, { + errorInfo: err + }); + } + + if (responseData.error) { + const message = responseData.error.message; + throw ERROR_FACTORY.create(ErrorCode.TOKEN_UPDATE_FAILED, { + errorInfo: message + }); + } + + if (!responseData.token) { + throw ERROR_FACTORY.create(ErrorCode.TOKEN_UPDATE_NO_TOKEN); + } + + return responseData.token; +} + +export async function requestDeleteToken( + firebaseDependencies: FirebaseInternalDependencies, + token: string +): Promise { + const headers = await getHeaders(firebaseDependencies); + + const unsubscribeOptions = { + method: 'DELETE', + headers + }; + + try { + const response = await fetch( + `${getEndpoint(firebaseDependencies.appConfig)}/${token}`, + unsubscribeOptions + ); + const responseData: ApiResponse = await response.json(); + if (responseData.error) { + const message = responseData.error.message; + throw ERROR_FACTORY.create(ErrorCode.TOKEN_UNSUBSCRIBE_FAILED, { + errorInfo: message + }); + } + } catch (err) { + throw ERROR_FACTORY.create(ErrorCode.TOKEN_UNSUBSCRIBE_FAILED, { + errorInfo: err + }); + } +} + +function getEndpoint({ projectId }: AppConfig): string { + return `${ENDPOINT}/projects/${projectId!}/registrations`; +} + +async function getHeaders({ + appConfig, + installations +}: FirebaseInternalDependencies): Promise { + const authToken = await installations.getToken(); + + return new Headers({ + 'Content-Type': 'application/json', + Accept: 'application/json', + 'x-goog-api-key': appConfig.apiKey!, + 'x-goog-firebase-installations-auth': `FIS ${authToken}` + }); +} + +function getBody({ + p256dh, + auth, + endpoint, + vapidKey +}: SubscriptionOptions): ApiRequestBody { + const body: ApiRequestBody = { + web: { + endpoint, + auth, + p256dh + } + }; + + if (vapidKey !== DEFAULT_VAPID_KEY) { + body.web.applicationPubKey = vapidKey; + } + + return body; +} diff --git a/packages-exp/messaging-exp/src/internals/token-manager.test.ts b/packages-exp/messaging-exp/src/internals/token-manager.test.ts new file mode 100644 index 00000000000..2129d82a34a --- /dev/null +++ b/packages-exp/messaging-exp/src/internals/token-manager.test.ts @@ -0,0 +1,194 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import '../testing/setup'; + +import * as apiModule from './requests'; + +import { dbGet, dbSet } from './idb-manager'; +import { deleteTokenInternal, getTokenInternal } from './token-manager'; +import { + getFakeAnalyticsProvider, + getFakeApp, + getFakeInstallations +} from '../testing/fakes/firebase-dependencies'; +import { spy, stub, useFakeTimers } from 'sinon'; + +import { FakeServiceWorkerRegistration } from '../testing/fakes/service-worker'; +import { MessagingService } from '../messaging-service'; +import { Stub } from '../testing/sinon-types'; +import { TokenDetails } from '../interfaces/token-details'; +// import { arrayToBase64 } from '../helpers/array-base64-translator'; +import { expect } from 'chai'; +import { getFakeTokenDetails } from '../testing/fakes/token-details'; + +describe('Token Manager', () => { + let tokenDetails: TokenDetails; + let messaging: MessagingService; + let requestGetTokenStub: Stub; + let requestUpdateTokenStub: Stub; + let requestDeleteTokenStub: Stub; + + beforeEach(() => { + tokenDetails = getFakeTokenDetails(); + messaging = new MessagingService( + getFakeApp(), + getFakeInstallations(), + getFakeAnalyticsProvider() + ); + // base64 value of 'vapid-key-value' set in fakeTokenDetails + messaging.vapidKey = 'dmFwaWQta2V5LXZhbHVl'; + messaging.swRegistration = new FakeServiceWorkerRegistration(); + + requestGetTokenStub = stub(apiModule, 'requestGetToken').resolves( + 'token-value' // new token. + ); + requestUpdateTokenStub = stub(apiModule, 'requestUpdateToken').resolves( + tokenDetails.token // same as current token. + ); + requestDeleteTokenStub = stub(apiModule, 'requestDeleteToken').resolves(); + useFakeTimers({ now: 1234567890 }); + }); + + describe('getTokenInternal', () => { + it('gets a new token if there is none', async () => { + // Act + const token = await getTokenInternal(messaging); + + // Assert + expect(token).to.equal('token-value'); + expect(requestGetTokenStub).to.have.been.calledOnceWith( + messaging.firebaseDependencies, + tokenDetails.subscriptionOptions + ); + expect(requestUpdateTokenStub).not.to.have.been.called; + expect(requestDeleteTokenStub).not.to.have.been.called; + + const tokenFromDb = await dbGet(messaging.firebaseDependencies); + expect(token).to.equal(tokenFromDb!.token); + expect(tokenFromDb).to.deep.equal({ + ...tokenDetails, + token: 'token-value' + }); + }); + + it('returns the token if it is valid', async () => { + // Arrange + await dbSet(messaging.firebaseDependencies, tokenDetails); + + // Act + const token = await getTokenInternal(messaging); + + // Assert + expect(token).to.equal(tokenDetails.token); + expect(requestGetTokenStub).not.to.have.been.called; + expect(requestUpdateTokenStub).not.to.have.been.called; + expect(requestDeleteTokenStub).not.to.have.been.called; + + const tokenFromDb = await dbGet(messaging.firebaseDependencies); + expect(tokenFromDb).to.deep.equal(tokenDetails); + }); + + it('update the token if it was last updated more than a week ago', async () => { + // Change create time to be older than a week. + tokenDetails.createTime = Date.now() - 8 * 24 * 60 * 60 * 1000; // 8 days + + await dbSet(messaging.firebaseDependencies, tokenDetails); + + const token = await getTokenInternal(messaging); + const expectedTokenDetails: TokenDetails = { + ...tokenDetails, + createTime: Date.now() + }; + + expect(token).to.equal(tokenDetails.token); // Same token. + expect(requestGetTokenStub).not.to.have.been.called; + expect(requestUpdateTokenStub).to.have.been.calledOnceWith( + messaging.firebaseDependencies, + expectedTokenDetails + ); + expect(requestDeleteTokenStub).not.to.have.been.called; + + const tokenFromDb = await dbGet(messaging.firebaseDependencies); + expect(token).to.equal(tokenFromDb!.token); + expect(tokenFromDb).to.deep.equal(expectedTokenDetails); + }); + + it('deletes the token if the update fails', async () => { + // Arrange + // Change create time to be older than a week. + tokenDetails.createTime = Date.now() - 8 * 24 * 60 * 60 * 1000; // 8 days + + await dbSet(messaging.firebaseDependencies, tokenDetails); + + requestUpdateTokenStub.rejects(new Error('Update failed.')); + + // Act + await expect(getTokenInternal(messaging)).to.be.rejectedWith( + 'Update failed.' + ); + + // Assert + const expectedTokenDetails: TokenDetails = { + ...tokenDetails, + createTime: Date.now() + }; + + expect(requestGetTokenStub).not.to.have.been.called; + expect(requestUpdateTokenStub).to.have.been.calledOnceWith( + messaging.firebaseDependencies, + expectedTokenDetails + ); + expect(requestDeleteTokenStub).to.have.been.calledOnceWith( + messaging.firebaseDependencies, + tokenDetails.token + ); + + const tokenFromDb = await dbGet(messaging.firebaseDependencies); + expect(tokenFromDb).to.be.undefined; + }); + }); + + describe('deleteToken', () => { + it('returns if there is no token in the db', async () => { + await deleteTokenInternal(messaging); + + expect(requestGetTokenStub).not.to.have.been.called; + expect(requestUpdateTokenStub).not.to.have.been.called; + expect(requestDeleteTokenStub).not.to.have.been.called; + }); + + it('removes token from the db, calls requestDeleteToken and unsubscribes the push subscription', async () => { + const unsubscribeSpy = spy( + await messaging.swRegistration!.pushManager.subscribe(), + 'unsubscribe' + ); + await dbSet(messaging.firebaseDependencies, tokenDetails); + + await deleteTokenInternal(messaging); + + expect(await dbGet(messaging.firebaseDependencies)).to.be.undefined; + expect(requestGetTokenStub).not.to.have.been.called; + expect(requestUpdateTokenStub).not.to.have.been.called; + expect(requestDeleteTokenStub).not.to.have.been.calledOnceWith( + messaging.firebaseDependencies, + tokenDetails + ); + expect(unsubscribeSpy).to.have.been.called; + }); + }); +}); diff --git a/packages-exp/messaging-exp/src/internals/token-manager.ts b/packages-exp/messaging-exp/src/internals/token-manager.ts new file mode 100644 index 00000000000..1f14a9e055c --- /dev/null +++ b/packages-exp/messaging-exp/src/internals/token-manager.ts @@ -0,0 +1,184 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SubscriptionOptions, TokenDetails } from '../interfaces/token-details'; +import { + arrayToBase64, + base64ToArray +} from '../helpers/array-base64-translator'; +import { dbGet, dbRemove, dbSet } from './idb-manager'; +import { + requestDeleteToken, + requestGetToken, + requestUpdateToken +} from './requests'; + +import { FirebaseInternalDependencies } from '../interfaces/internal-dependencies'; +import { MessagingService } from '../messaging-service'; + +// UpdateRegistration will be called once every week. +const TOKEN_EXPIRATION_MS = 7 * 24 * 60 * 60 * 1000; // 7 days + +export async function getTokenInternal( + messaging: MessagingService +): Promise { + const pushSubscription = await getPushSubscription( + messaging.swRegistration!, + messaging.vapidKey! + ); + + const subscriptionOptions: SubscriptionOptions = { + vapidKey: messaging.vapidKey!, + swScope: messaging.swRegistration!.scope, + endpoint: pushSubscription.endpoint, + auth: arrayToBase64(pushSubscription.getKey('auth')!), + p256dh: arrayToBase64(pushSubscription.getKey('p256dh')!) + }; + + const tokenDetails = await dbGet(messaging.firebaseDependencies); + if (!tokenDetails) { + // No token, get a new one. + return getNewToken(messaging.firebaseDependencies, subscriptionOptions); + } else if ( + !isTokenValid(tokenDetails.subscriptionOptions!, subscriptionOptions) + ) { + // Invalid token, get a new one. + try { + await requestDeleteToken( + messaging.firebaseDependencies!, + tokenDetails.token + ); + } catch (e) { + // Suppress errors because of #2364 + console.warn(e); + } + + return getNewToken(messaging.firebaseDependencies!, subscriptionOptions); + } else if (Date.now() >= tokenDetails.createTime + TOKEN_EXPIRATION_MS) { + // Weekly token refresh + return updateToken(messaging, { + token: tokenDetails.token, + createTime: Date.now(), + subscriptionOptions + }); + } else { + // Valid token, nothing to do. + return tokenDetails.token; + } +} + +/** + * This method deletes the token from the database, unsubscribes the token from FCM, and unregisters + * the push subscription if it exists. + */ +export async function deleteTokenInternal( + messaging: MessagingService +): Promise { + const tokenDetails = await dbGet(messaging.firebaseDependencies); + if (tokenDetails) { + await requestDeleteToken( + messaging.firebaseDependencies, + tokenDetails.token + ); + await dbRemove(messaging.firebaseDependencies); + } + + // Unsubscribe from the push subscription. + const pushSubscription = await messaging.swRegistration!.pushManager.getSubscription(); + if (pushSubscription) { + return pushSubscription.unsubscribe(); + } + + // If there's no SW, consider it a success. + return true; +} + +async function updateToken( + messaging: MessagingService, + tokenDetails: TokenDetails +): Promise { + try { + const updatedToken = await requestUpdateToken( + messaging.firebaseDependencies, + tokenDetails + ); + + const updatedTokenDetails: TokenDetails = { + ...tokenDetails, + token: updatedToken, + createTime: Date.now() + }; + + await dbSet(messaging.firebaseDependencies, updatedTokenDetails); + return updatedToken; + } catch (e) { + await deleteTokenInternal(messaging); + throw e; + } +} + +async function getNewToken( + firebaseDependencies: FirebaseInternalDependencies, + subscriptionOptions: SubscriptionOptions +): Promise { + const token = await requestGetToken( + firebaseDependencies, + subscriptionOptions + ); + const tokenDetails: TokenDetails = { + token, + createTime: Date.now(), + subscriptionOptions + }; + await dbSet(firebaseDependencies, tokenDetails); + return tokenDetails.token; +} + +/** + * Gets a PushSubscription for the current user. + */ +async function getPushSubscription( + swRegistration: ServiceWorkerRegistration, + vapidKey: string +): Promise { + const subscription = await swRegistration.pushManager.getSubscription(); + if (subscription) { + return subscription; + } + + return swRegistration.pushManager.subscribe({ + userVisibleOnly: true, + // Chrome <= 75 doesn't support base64-encoded VAPID key. For backward compatibility, VAPID key + // submitted to pushManager#subscribe must be of type Uint8Array. + applicationServerKey: base64ToArray(vapidKey) + }); +} + +/** + * Checks if the saved tokenDetails object matches the configuration provided. + */ +function isTokenValid( + dbOptions: SubscriptionOptions, + currentOptions: SubscriptionOptions +): boolean { + const isVapidKeyEqual = currentOptions.vapidKey === dbOptions.vapidKey; + const isEndpointEqual = currentOptions.endpoint === dbOptions.endpoint; + const isAuthEqual = currentOptions.auth === dbOptions.auth; + const isP256dhEqual = currentOptions.p256dh === dbOptions.p256dh; + + return isVapidKeyEqual && isEndpointEqual && isAuthEqual && isP256dhEqual; +} diff --git a/packages-exp/messaging-exp/src/listeners/sw-listeners.test.ts b/packages-exp/messaging-exp/src/listeners/sw-listeners.test.ts new file mode 100644 index 00000000000..f02231cde1a --- /dev/null +++ b/packages-exp/messaging-exp/src/listeners/sw-listeners.test.ts @@ -0,0 +1,474 @@ +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import '../testing/setup'; + +import * as tokenManagementModule from '../internals/token-manager'; + +import { + CONSOLE_CAMPAIGN_ANALYTICS_ENABLED, + CONSOLE_CAMPAIGN_ID, + CONSOLE_CAMPAIGN_NAME, + CONSOLE_CAMPAIGN_TIME, + FCM_MSG +} from '../util/constants'; +import { DeepPartial, ValueOf, Writable } from 'ts-essentials'; +import { + FakeEvent, + FakePushSubscription, + mockServiceWorker, + restoreServiceWorker +} from '../testing/fakes/service-worker'; +import { + MessagePayloadInternal, + MessageType +} from '../interfaces/internal-message-payload'; +import { + NotificationEvent, + ServiceWorkerGlobalScope, + ServiceWorkerGlobalScopeEventMap, + WindowClient +} from '../util/sw-types'; +import { + getFakeAnalyticsProvider, + getFakeApp, + getFakeInstallations +} from '../testing/fakes/firebase-dependencies'; +import { onNotificationClick, onPush, onSubChange } from './sw-listeners'; +import { spy, stub } from 'sinon'; + +import { MessagingService } from '../messaging-service'; +import { Stub } from '../testing/sinon-types'; +import { expect } from 'chai'; + +const LOCAL_HOST = self.location.host; +const TEST_LINK = 'https://' + LOCAL_HOST + '/test-link.org'; +const TEST_CLICK_ACTION = 'https://' + LOCAL_HOST + '/test-click-action.org'; + +// Add fake SW types. +declare const self: ServiceWorkerGlobalScope; + +// internal message payload (parsed directly from the push event) that contains and only contains +// notification payload. +const DISPLAY_MESSAGE: MessagePayloadInternal = { + notification: { + title: 'title', + body: 'body' + }, + fcmOptions: { + link: TEST_LINK + }, + from: 'from', + // eslint-disable-next-line camelcase + collapse_key: 'collapse' +}; + +describe('SwController', () => { + let addEventListenerStub: Stub; + // eslint-disable-next-line @typescript-eslint/ban-types + let eventListenerMap: Map; + let messaging: MessagingService; + let getTokenStub: Stub; + let deleteTokenStub: Stub< + typeof tokenManagementModule['deleteTokenInternal'] + >; + + beforeEach(() => { + mockServiceWorker(); + + stub(Notification, 'permission').value('granted'); + + // Instead of calling actual addEventListener, add the event to the eventListeners list. Actual + // event listeners can't be used as the tests are not running in a Service Worker, which means + // Push events do not exist. + addEventListenerStub = stub(self, 'addEventListener').callsFake( + (type, listener) => { + eventListenerMap.set(type, listener); + } + ); + eventListenerMap = new Map(); + + getTokenStub = stub(tokenManagementModule, 'getTokenInternal').resolves( + 'token-value' + ); + deleteTokenStub = stub( + tokenManagementModule, + 'deleteTokenInternal' + ).resolves(true); + + messaging = new MessagingService( + getFakeApp(), + getFakeInstallations(), + getFakeAnalyticsProvider() + ); + + self.addEventListener('push', e => { + e.waitUntil(onPush(e, messaging as MessagingService)); + }); + self.addEventListener('pushsubscriptionchange', e => { + e.waitUntil(onSubChange(e, messaging as MessagingService)); + }); + self.addEventListener('notificationclick', e => { + e.waitUntil(onNotificationClick(e)); + }); + }); + + afterEach(() => { + restoreServiceWorker(); + }); + + it('sets event listeners on initialization', () => { + expect(addEventListenerStub).to.have.been.calledThrice; + expect(addEventListenerStub).to.have.been.calledWith('push'); + expect(addEventListenerStub).to.have.been.calledWith( + 'pushsubscriptionchange' + ); + expect(addEventListenerStub).to.have.been.calledWith('notificationclick'); + }); + + describe('onPush', () => { + it('does nothing if push is not from FCM', async () => { + const showNotificationSpy = spy(self.registration, 'showNotification'); + const matchAllSpy = spy(self.clients, 'matchAll'); + + await callEventListener(makeEvent('push', {})); + + await callEventListener( + makeEvent('push', { + data: {} + }) + ); + + expect(showNotificationSpy).not.to.have.been.called; + expect(matchAllSpy).not.to.have.been.called; + }); + + it('sends a message to window clients if a window client is visible', async () => { + const client: Writable = (await self.clients.openWindow( + 'https://example.org' + ))!; + client.visibilityState = 'visible'; + const postMessageSpy = spy(client, 'postMessage'); + + await callEventListener( + makeEvent('push', { + data: { + json: () => DISPLAY_MESSAGE + } + }) + ); + + const expectedMessage: MessagePayloadInternal = { + ...DISPLAY_MESSAGE, + messageType: MessageType.PUSH_RECEIVED + }; + expect(postMessageSpy).to.have.been.calledOnceWith(expectedMessage); + }); + + it('does not send a message to window clients if window clients are hidden', async () => { + const client = (await self.clients.openWindow('https://example.org'))!; + const postMessageSpy = spy(client, 'postMessage'); + const showNotificationSpy = spy(self.registration, 'showNotification'); + + await callEventListener( + makeEvent('push', { + data: { + json: () => DISPLAY_MESSAGE + } + }) + ); + + expect(postMessageSpy).not.to.have.been.called; + expect(showNotificationSpy).to.have.been.calledWith('title', { + ...DISPLAY_MESSAGE.notification, + data: { + [FCM_MSG]: DISPLAY_MESSAGE + } + }); + }); + + it('displays a notification if a window client does not exist', async () => { + const showNotificationSpy = spy(self.registration, 'showNotification'); + + await callEventListener( + makeEvent('push', { + data: { + json: () => DISPLAY_MESSAGE + } + }) + ); + + expect(showNotificationSpy).to.have.been.calledWith('title', { + ...DISPLAY_MESSAGE.notification, + data: { + ...DISPLAY_MESSAGE.notification!.data, + [FCM_MSG]: DISPLAY_MESSAGE + } + }); + }); + + it('warns if there are more action buttons than the browser limit', async () => { + // This doesn't exist on Firefox: + // https://developer.mozilla.org/en-US/docs/Web/API/notification/maxActions + if (!Notification.maxActions) { + return; + } + stub(Notification, 'maxActions').value(1); + + const warnStub = stub(console, 'warn'); + + await callEventListener( + makeEvent('push', { + data: { + json: () => ({ + notification: { + ...DISPLAY_MESSAGE, + actions: [ + { action: 'like', title: 'Like' }, + { action: 'favorite', title: 'Favorite' } + ] + } + }) + } + }) + ); + + expect(warnStub).to.have.been.calledOnceWith( + 'This browser only supports 1 actions. The remaining actions will not be displayed.' + ); + }); + }); + + describe('onNotificationClick', () => { + let NOTIFICATION_CLICK_PAYLOAD: DeepPartial; + + beforeEach(() => { + NOTIFICATION_CLICK_PAYLOAD = { + notification: new Notification('title', { + ...DISPLAY_MESSAGE.notification, + data: { + ...DISPLAY_MESSAGE.notification!.data, + [FCM_MSG]: DISPLAY_MESSAGE + } + }) + }; + }); + + it('does nothing if notification is not from FCM', async () => { + delete NOTIFICATION_CLICK_PAYLOAD.notification!.data![FCM_MSG]; + + const event = makeEvent('notificationclick', NOTIFICATION_CLICK_PAYLOAD); + const stopImmediatePropagationSpy = spy( + event, + 'stopImmediatePropagation' + ); + + await callEventListener(event); + + expect(stopImmediatePropagationSpy).not.to.have.been.called; + }); + + it('does nothing if an action button was clicked', async () => { + const event = makeEvent('notificationclick', NOTIFICATION_CLICK_PAYLOAD); + event.action = 'actionName'; + const stopImmediatePropagationSpy = spy( + event, + 'stopImmediatePropagation' + ); + + await callEventListener(event); + + expect(stopImmediatePropagationSpy).not.to.have.been.called; + }); + + it('calls stopImmediatePropagation and notification.close', async () => { + const event = makeEvent('notificationclick', NOTIFICATION_CLICK_PAYLOAD); + const stopImmediatePropagationSpy = spy( + event, + 'stopImmediatePropagation' + ); + const notificationCloseSpy = spy(event.notification, 'close'); + + await callEventListener(event); + + expect(stopImmediatePropagationSpy).to.have.been.called; + expect(notificationCloseSpy).to.have.been.called; + }); + + it('does not redirect if there is no link', async () => { + // Remove link. + delete NOTIFICATION_CLICK_PAYLOAD.notification!.data![FCM_MSG].fcmOptions; + + const event = makeEvent('notificationclick', NOTIFICATION_CLICK_PAYLOAD); + const stopImmediatePropagationSpy = spy( + event, + 'stopImmediatePropagation' + ); + const notificationCloseSpy = spy(event.notification, 'close'); + const matchAllSpy = spy(self.clients, 'matchAll'); + + await callEventListener(event); + + expect(stopImmediatePropagationSpy).to.have.been.called; + expect(notificationCloseSpy).to.have.been.called; + expect(matchAllSpy).not.to.have.been.called; + }); + + it('does not redirect if link is not from origin', async () => { + // Remove link. + NOTIFICATION_CLICK_PAYLOAD.notification!.data![FCM_MSG].fcmOptions.link = + 'https://www.youtube.com'; + + const event = makeEvent('notificationclick', NOTIFICATION_CLICK_PAYLOAD); + const stopImmediatePropagationSpy = spy( + event, + 'stopImmediatePropagation' + ); + const notificationCloseSpy = spy(event.notification, 'close'); + const matchAllSpy = spy(self.clients, 'matchAll'); + + await callEventListener(event); + + expect(stopImmediatePropagationSpy).to.have.been.called; + expect(notificationCloseSpy).to.have.been.called; + expect(matchAllSpy).not.to.have.been.called; + }); + + it('focuses on and sends the message to an open WindowClient', async () => { + const client: Writable = (await self.clients.openWindow( + TEST_LINK + ))!; + const focusSpy = spy(client, 'focus'); + const matchAllSpy = spy(self.clients, 'matchAll'); + const openWindowSpy = spy(self.clients, 'openWindow'); + const postMessageSpy = spy(client, 'postMessage'); + + const event = makeEvent('notificationclick', NOTIFICATION_CLICK_PAYLOAD); + + await callEventListener(event); + + expect(matchAllSpy).to.have.been.called; + expect(openWindowSpy).not.to.have.been.called; + expect(focusSpy).to.have.been.called; + expect(postMessageSpy).to.have.been.calledWith({ + ...DISPLAY_MESSAGE, + messageType: MessageType.NOTIFICATION_CLICKED + }); + }); + + it("opens a new client if there isn't one already open", async () => { + const matchAllSpy = spy(self.clients, 'matchAll'); + const openWindowSpy = spy(self.clients, 'openWindow'); + + const event = makeEvent('notificationclick', NOTIFICATION_CLICK_PAYLOAD); + + await callEventListener(event); + + expect(matchAllSpy).to.have.been.called; + expect(openWindowSpy).to.have.been.calledWith(TEST_LINK); + }); + + it('works with click_action', async () => { + // Replace link with the deprecated click_action. + delete NOTIFICATION_CLICK_PAYLOAD.notification!.data![FCM_MSG].fcmOptions; + NOTIFICATION_CLICK_PAYLOAD.notification!.data![ + FCM_MSG + ].notification.click_action = TEST_CLICK_ACTION; // eslint-disable-line camelcase + + const matchAllSpy = spy(self.clients, 'matchAll'); + const openWindowSpy = spy(self.clients, 'openWindow'); + + const event = makeEvent('notificationclick', NOTIFICATION_CLICK_PAYLOAD); + + await callEventListener(event); + + expect(matchAllSpy).to.have.been.called; + expect(openWindowSpy).to.have.been.calledWith(TEST_CLICK_ACTION); + }); + + it('redirects to origin if message was sent from the FN Console', async () => { + // Remove link. + delete NOTIFICATION_CLICK_PAYLOAD.notification!.data![FCM_MSG].fcmOptions; + // Add FN data. + NOTIFICATION_CLICK_PAYLOAD.notification!.data![FCM_MSG].data = { + [CONSOLE_CAMPAIGN_ID]: '123456', + [CONSOLE_CAMPAIGN_NAME]: 'Campaign Name', + [CONSOLE_CAMPAIGN_TIME]: '1234567890', + [CONSOLE_CAMPAIGN_ANALYTICS_ENABLED]: '1' + }; + + const matchAllSpy = spy(self.clients, 'matchAll'); + const openWindowSpy = spy(self.clients, 'openWindow'); + + const event = makeEvent('notificationclick', NOTIFICATION_CLICK_PAYLOAD); + + await callEventListener(event); + + expect(matchAllSpy).to.have.been.called; + expect(openWindowSpy).to.have.been.calledWith(self.location.origin); + }); + }); + + describe('onSubChange', () => { + it('calls deleteToken if there is no new subscription', async () => { + const event = makeEvent('pushsubscriptionchange', { + oldSubscription: new FakePushSubscription(), + newSubscription: undefined + }); + + await callEventListener(event); + + expect(deleteTokenStub).to.have.been.called; + expect(getTokenStub).not.to.have.been.called; + }); + + it('calls deleteToken and getToken if subscription changed', async () => { + const event = makeEvent('pushsubscriptionchange', { + oldSubscription: new FakePushSubscription(), + newSubscription: new FakePushSubscription() + }); + + await callEventListener(event); + + expect(deleteTokenStub).to.have.been.called; + expect(getTokenStub).to.have.been.called; + }); + }); + + async function callEventListener( + event: ValueOf + ): Promise { + const listener = eventListenerMap.get(event.type); + if (!listener) { + throw new Error(`Event listener for ${event.type} was not defined.`); + } + + const waitUntil = spy(event, 'waitUntil'); + listener(event); + await waitUntil.getCall(0).args[0]; + } +}); + +/** Makes fake push events. */ +function makeEvent( + type: K, + data: DeepPartial +): Writable { + const event = new FakeEvent(type); + Object.assign(event, data); + return (event as unknown) as ServiceWorkerGlobalScopeEventMap[K]; +} diff --git a/packages-exp/messaging-exp/src/listeners/sw-listeners.ts b/packages-exp/messaging-exp/src/listeners/sw-listeners.ts new file mode 100644 index 00000000000..7dcb9cccc9c --- /dev/null +++ b/packages-exp/messaging-exp/src/listeners/sw-listeners.ts @@ -0,0 +1,270 @@ +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DEFAULT_VAPID_KEY, FCM_MSG } from '../util/constants'; +import { + MessagePayloadInternal, + MessageType, + NotificationPayloadInternal +} from '../interfaces/internal-message-payload'; +import { + NotificationEvent, + PushEvent, + PushSubscriptionChangeEvent, + ServiceWorkerGlobalScope, + WindowClient +} from '../util/sw-types'; +import { + deleteTokenInternal, + getTokenInternal +} from '../internals/token-manager'; + +import { MessagingService } from '../messaging-service'; +import { dbGet } from '../internals/idb-manager'; +import { externalizePayload } from '../helpers/externalizePayload'; +import { isConsoleMessage } from '../helpers/is-console-message'; +import { sleep } from '../helpers/sleep'; + +// Let TS know that this is a service worker +declare const self: ServiceWorkerGlobalScope; + +export async function onSubChange( + event: PushSubscriptionChangeEvent, + messaging: MessagingService +): Promise { + const { newSubscription } = event; + if (!newSubscription) { + // Subscription revoked, delete token + await deleteTokenInternal(messaging); + return; + } + + const tokenDetails = await dbGet(messaging.firebaseDependencies); + await deleteTokenInternal(messaging); + + messaging.vapidKey = + tokenDetails?.subscriptionOptions?.vapidKey ?? DEFAULT_VAPID_KEY; + await getTokenInternal(messaging); +} + +export async function onPush( + event: PushEvent, + messaging: MessagingService +): Promise { + const internalPayload = getMessagePayloadInternal(event); + if (!internalPayload) { + // Failed to get parsed MessagePayload from the PushEvent. Skip handling the push. + return; + } + + // foreground handling: eventually passed to onMessage hook + const clientList = await getClientList(); + if (hasVisibleClients(clientList)) { + return sendMessagePayloadInternalToWindows(clientList, internalPayload); + } + + // background handling: display if possible and pass to onBackgroundMessage hook + if (!!internalPayload.notification) { + await showNotification(wrapInternalPayload(internalPayload)); + } + + if (!messaging) { + return; + } + + if (!!messaging.onBackgroundMessageHandler) { + const payload = externalizePayload(internalPayload); + + if (typeof messaging.onBackgroundMessageHandler === 'function') { + messaging.onBackgroundMessageHandler(payload); + } else { + messaging.onBackgroundMessageHandler.next(payload); + } + } +} + +export async function onNotificationClick( + event: NotificationEvent +): Promise { + const internalPayload: MessagePayloadInternal = + event.notification?.data?.[FCM_MSG]; + + if (!internalPayload) { + return; + } else if (event.action) { + // User clicked on an action button. This will allow developers to act on action button clicks + // by using a custom onNotificationClick listener that they define. + return; + } + + // Prevent other listeners from receiving the event + event.stopImmediatePropagation(); + event.notification.close(); + + // Note clicking on a notification with no link set will focus the Chrome's current tab. + const link = getLink(internalPayload); + if (!link) { + return; + } + + // FM should only open/focus links from app's origin. + const url = new URL(link, self.location.href); + const originUrl = new URL(self.location.origin); + + if (url.host !== originUrl.host) { + return; + } + + let client = await getWindowClient(url); + + if (!client) { + client = await self.clients.openWindow(link); + + // Wait three seconds for the client to initialize and set up the message handler so that it + // can receive the message. + await sleep(3000); + } else { + client = await client.focus(); + } + + if (!client) { + // Window Client will not be returned if it's for a third party origin. + return; + } + + internalPayload.messageType = MessageType.NOTIFICATION_CLICKED; + internalPayload.isFirebaseMessaging = true; + return client.postMessage(internalPayload); +} + +function wrapInternalPayload( + internalPayload: MessagePayloadInternal +): NotificationPayloadInternal { + const wrappedInternalPayload: NotificationPayloadInternal = { + ...((internalPayload.notification as unknown) as NotificationPayloadInternal) + }; + + // Put the message payload under FCM_MSG name so we can identify the notification as being an FCM + // notification vs a notification from somewhere else (i.e. normal web push or developer generated + // notification). + wrappedInternalPayload.data = { + [FCM_MSG]: internalPayload + }; + + return wrappedInternalPayload; +} + +function getMessagePayloadInternal({ + data +}: PushEvent): MessagePayloadInternal | null { + if (!data) { + return null; + } + + try { + return data.json(); + } catch (err) { + // Not JSON so not an FCM message. + return null; + } +} + +/** + * @param url The URL to look for when focusing a client. + * @return Returns an existing window client or a newly opened WindowClient. + */ +async function getWindowClient(url: URL): Promise { + const clientList = await getClientList(); + + for (const client of clientList) { + const clientUrl = new URL(client.url, self.location.href); + + if (url.host === clientUrl.host) { + return client; + } + } + + return null; +} + +/** + * @returns If there is currently a visible WindowClient, this method will resolve to true, + * otherwise false. + */ +function hasVisibleClients(clientList: WindowClient[]): boolean { + return clientList.some( + client => + client.visibilityState === 'visible' && + // Ignore chrome-extension clients as that matches the background pages of extensions, which + // are always considered visible for some reason. + !client.url.startsWith('chrome-extension://') + ); +} + +function sendMessagePayloadInternalToWindows( + clientList: WindowClient[], + internalPayload: MessagePayloadInternal +): void { + internalPayload.isFirebaseMessaging = true; + internalPayload.messageType = MessageType.PUSH_RECEIVED; + + for (const client of clientList) { + client.postMessage(internalPayload); + } +} + +function getClientList(): Promise { + return self.clients.matchAll({ + type: 'window', + includeUncontrolled: true + // TS doesn't know that "type: 'window'" means it'll return WindowClient[] + }) as Promise; +} + +function showNotification( + notificationPayloadInternal: NotificationPayloadInternal +): Promise { + // Note: Firefox does not support the maxActions property. + // https://developer.mozilla.org/en-US/docs/Web/API/notification/maxActions + const { actions } = notificationPayloadInternal; + const { maxActions } = Notification; + if (actions && maxActions && actions.length > maxActions) { + console.warn( + `This browser only supports ${maxActions} actions. The remaining actions will not be displayed.` + ); + } + + return self.registration.showNotification( + /* title= */ notificationPayloadInternal.title ?? '', + notificationPayloadInternal + ); +} + +function getLink(payload: MessagePayloadInternal): string | null { + // eslint-disable-next-line camelcase + const link = payload.fcmOptions?.link ?? payload.notification?.click_action; + if (link) { + return link; + } + + if (isConsoleMessage(payload.data)) { + // Notification created in the Firebase Console. Redirect to origin. + return self.location.origin; + } else { + return null; + } +} diff --git a/packages-exp/messaging-exp/src/listeners/window-listener.ts b/packages-exp/messaging-exp/src/listeners/window-listener.ts new file mode 100644 index 00000000000..e40113376af --- /dev/null +++ b/packages-exp/messaging-exp/src/listeners/window-listener.ts @@ -0,0 +1,58 @@ +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + MessagePayloadInternal, + MessageType +} from '../interfaces/internal-message-payload'; + +import { CONSOLE_CAMPAIGN_ANALYTICS_ENABLED } from '../util/constants'; +import { MessagingService } from '../messaging-service'; +import { externalizePayload } from '../helpers/externalizePayload'; +import { isConsoleMessage } from '../helpers/is-console-message'; +import { logToScion } from '../helpers/logToScion'; + +export async function messageEventListener( + messaging: MessagingService, + event: MessageEvent +): Promise { + const internalPayload = event.data as MessagePayloadInternal; + + if (!internalPayload.isFirebaseMessaging) { + return; + } + + if ( + messaging.onMessageHandler && + internalPayload.messageType === MessageType.PUSH_RECEIVED + ) { + if (typeof messaging.onMessageHandler === 'function') { + messaging.onMessageHandler(externalizePayload(internalPayload)); + } else { + messaging.onMessageHandler.next(externalizePayload(internalPayload)); + } + } + + // Log to Scion if applicable + const dataPayload = internalPayload.data; + if ( + isConsoleMessage(dataPayload) && + dataPayload[CONSOLE_CAMPAIGN_ANALYTICS_ENABLED] === '1' + ) { + await logToScion(messaging, internalPayload.messageType!, dataPayload); + } +} diff --git a/packages-exp/messaging-exp/src/messaging-service.ts b/packages-exp/messaging-exp/src/messaging-service.ts new file mode 100644 index 00000000000..7d3e67edea2 --- /dev/null +++ b/packages-exp/messaging-exp/src/messaging-service.ts @@ -0,0 +1,62 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FirebaseApp, _FirebaseService } from '@firebase/app-exp'; + +import { FirebaseAnalyticsInternalName } from '@firebase/analytics-interop-types'; +import { FirebaseInternalDependencies } from './interfaces/internal-dependencies'; +import { MessagePayload, NextFn, Observer } from './interfaces/public-types'; +import { Provider } from '@firebase/component'; +import { _FirebaseInstallationsInternal } from '@firebase/installations-exp'; +import { extractAppConfig } from './helpers/extract-app-config'; + +export class MessagingService implements _FirebaseService { + readonly app!: FirebaseApp; + readonly firebaseDependencies!: FirebaseInternalDependencies; + + swRegistration?: ServiceWorkerRegistration; + vapidKey?: string; + + onBackgroundMessageHandler: + | NextFn + | Observer + | null = null; + + onMessageHandler: + | NextFn + | Observer + | null = null; + + constructor( + app: FirebaseApp, + installations: _FirebaseInstallationsInternal, + analyticsProvider: Provider + ) { + const appConfig = extractAppConfig(app); + + this.firebaseDependencies = { + app, + appConfig, + installations, + analyticsProvider + }; + } + + _delete(): Promise { + return Promise.resolve(); + } +} diff --git a/packages-exp/messaging-exp/src/testing/compare-headers.test.ts b/packages-exp/messaging-exp/src/testing/compare-headers.test.ts new file mode 100644 index 00000000000..ad171740ace --- /dev/null +++ b/packages-exp/messaging-exp/src/testing/compare-headers.test.ts @@ -0,0 +1,46 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import './setup'; + +import { AssertionError, expect } from 'chai'; + +import { compareHeaders } from './compare-headers'; + +describe('compareHeaders', () => { + it("doesn't fail if headers contain the same entries", () => { + const headers1 = new Headers({ a: '123', b: '456' }); + const headers2 = new Headers({ a: '123', b: '456' }); + compareHeaders(headers1, headers2); + }); + + it('fails if headers contain different keys', () => { + const headers1 = new Headers({ a: '123', b: '456', extraKey: '789' }); + const headers2 = new Headers({ a: '123', b: '456' }); + expect(() => { + compareHeaders(headers1, headers2); + }).to.throw(AssertionError); + }); + + it('fails if headers contain different values', () => { + const headers1 = new Headers({ a: '123', b: '456' }); + const headers2 = new Headers({ a: '123', b: 'differentValue' }); + expect(() => { + compareHeaders(headers1, headers2); + }).to.throw(AssertionError); + }); +}); diff --git a/packages-exp/messaging-exp/src/testing/compare-headers.ts b/packages-exp/messaging-exp/src/testing/compare-headers.ts new file mode 100644 index 00000000000..6f760caf32d --- /dev/null +++ b/packages-exp/messaging-exp/src/testing/compare-headers.ts @@ -0,0 +1,40 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import './setup'; + +import { expect } from 'chai'; + +// Trick TS since it's set to target ES5. +declare class HeadersWithEntries extends Headers { + entries?(): Iterable<[string, string]>; +} + +// Chai doesn't check if Headers objects contain the same entries, so we need to do that manually. +export function compareHeaders( + expectedHeaders: HeadersWithEntries, + actualHeaders: HeadersWithEntries +): void { + const expected = makeMap(expectedHeaders); + const actual = makeMap(actualHeaders); + expect(actual).to.deep.equal(expected); +} + +function makeMap(headers: HeadersWithEntries): Map { + expect(headers.entries).not.to.be.undefined; + return new Map(headers.entries!()); +} diff --git a/packages-exp/messaging-exp/src/testing/fakes/firebase-dependencies.ts b/packages-exp/messaging-exp/src/testing/fakes/firebase-dependencies.ts new file mode 100644 index 00000000000..683b7b30e22 --- /dev/null +++ b/packages-exp/messaging-exp/src/testing/fakes/firebase-dependencies.ts @@ -0,0 +1,78 @@ +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + FirebaseAnalyticsInternal, + FirebaseAnalyticsInternalName +} from '@firebase/analytics-interop-types'; + +import { FirebaseInternalDependencies } from '../../interfaces/internal-dependencies'; +import { FirebaseOptions } from '@firebase/app-exp'; +import { Provider } from '@firebase/component'; +import { _FirebaseInstallationsInternal } from '@firebase/installations-exp'; +import { extractAppConfig } from '../../helpers/extract-app-config'; + +export function getFakeFirebaseDependencies( + options: FirebaseOptions = {} +): FirebaseInternalDependencies { + const app = getFakeApp(options); + return { + app, + appConfig: extractAppConfig(app), + installations: getFakeInstallations(), + analyticsProvider: getFakeAnalyticsProvider() + }; +} + +export function getFakeApp(options: FirebaseOptions = {}): any { + options = { + apiKey: 'apiKey', + projectId: 'projectId', + authDomain: 'authDomain', + messagingSenderId: '1234567890', + databaseURL: 'databaseUrl', + storageBucket: 'storageBucket', + appId: '1:777777777777:web:d93b5ca1475efe57', + ...options + }; + return { + name: 'appName', + options, + automaticDataCollectionEnabled: true, + delete: async () => {}, + messaging: (() => null as unknown) as any, + installations: () => getFakeInstallations() + }; +} + +export function getFakeInstallations(): _FirebaseInstallationsInternal { + return { + getId: async () => 'FID', + getToken: async () => 'authToken' + }; +} + +export function getFakeAnalyticsProvider(): Provider { + const analytics: FirebaseAnalyticsInternal = { + logEvent() {} + }; + + return ({ + get: async () => analytics, + getImmediate: () => analytics + } as unknown) as Provider; +} diff --git a/packages-exp/messaging-exp/src/testing/fakes/service-worker.ts b/packages-exp/messaging-exp/src/testing/fakes/service-worker.ts new file mode 100644 index 00000000000..8cc09811e0e --- /dev/null +++ b/packages-exp/messaging-exp/src/testing/fakes/service-worker.ts @@ -0,0 +1,219 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Client, + Clients, + ExtendableEvent, + ServiceWorkerGlobalScope, + WindowClient +} from '../../util/sw-types'; + +import { Writable } from 'ts-essentials'; + +// Add fake SW types. +declare const self: Window & Writable; + +// When trying to stub self.clients self.registration, Sinon complains that these properties do not +// exist. This is because we're not actually running these tests in a service worker context. + +// Here we're adding placeholders for Sinon to overwrite, which prevents the "Cannot stub +// non-existent own property" errors. + +// Casting to any is needed because TS also thinks that we're in a SW context and considers these +// properties readonly. + +// Missing function types are implemented from interfaces, so types are actually defined. +/* eslint-disable @typescript-eslint/explicit-function-return-type */ + +// const originalSwRegistration = ServiceWorkerRegistration; +export function mockServiceWorker(): void { + self.clients = new FakeClients(); + self.registration = new FakeServiceWorkerRegistration(); +} + +export function restoreServiceWorker(): void { + self.clients = new FakeClients(); + self.registration = new FakeServiceWorkerRegistration(); +} + +class FakeClients implements Clients { + private readonly clients: Client[] = []; + + async get(id: string) { + return this.clients.find(c => id === c.id) ?? null; + } + + async matchAll({ type = 'all' } = {}) { + if (type === 'all') { + return this.clients; + } + return this.clients.filter(c => c.type === type); + } + + async openWindow(url: string) { + const windowClient = new FakeWindowClient(); + windowClient.url = url; + this.clients.push(windowClient); + return windowClient; + } + + async claim() {} +} + +let currentId = 0; +class FakeWindowClient implements WindowClient { + readonly id: string; + readonly type = 'window'; + focused = false; + visibilityState: VisibilityState = 'hidden'; + url = 'https://example.org'; + + constructor() { + this.id = (currentId++).toString(); + } + + async focus() { + this.focused = true; + return this; + } + + async navigate(url: string) { + this.url = url; + return this; + } + + postMessage() {} +} + +export class FakeServiceWorkerRegistration + implements ServiceWorkerRegistration { + active = null; + installing = null; + waiting = null; + onupdatefound = null; + pushManager = new FakePushManager(); + scope = '/scope-value'; + + // Unused in FCM Web SDK, no need to mock these. + navigationPreload = (null as unknown) as NavigationPreloadManager; + sync = (null as unknown) as SyncManager; + updateViaCache = (null as unknown) as ServiceWorkerUpdateViaCache; + + async getNotifications() { + return []; + } + + async showNotification() {} + + async update() {} + + async unregister() { + return true; + } + + addEventListener() {} + removeEventListener() {} + dispatchEvent() { + return true; + } +} + +class FakePushManager implements PushManager { + private subscription: FakePushSubscription | null = null; + + async permissionState() { + return 'granted' as const; + } + + async getSubscription() { + return this.subscription; + } + + async subscribe() { + if (!this.subscription) { + this.subscription = new FakePushSubscription(); + } + return this.subscription!; + } +} + +export class FakePushSubscription implements PushSubscription { + endpoint = 'https://example.org'; + expirationTime = 1234567890; + auth = 'auth-value'; // Encoded: 'YXV0aC12YWx1ZQ' + p256 = 'p256-value'; // Encoded: 'cDI1Ni12YWx1ZQ' + + getKey(name: PushEncryptionKeyName) { + const encoder = new TextEncoder(); + return encoder.encode(name === 'auth' ? this.auth : this.p256); + } + + async unsubscribe() { + return true; + } + + // Unused in FCM + toJSON = (null as unknown) as () => PushSubscriptionJSON; + options = (null as unknown) as PushSubscriptionOptions; +} + +/** + * Most of the fields in here are unused / deprecated. They are only added here to match the TS + * Event interface. + */ +export class FakeEvent implements ExtendableEvent { + NONE = Event.NONE; + AT_TARGET = Event.AT_TARGET; + BUBBLING_PHASE = Event.BUBBLING_PHASE; + CAPTURING_PHASE = Event.CAPTURING_PHASE; + bubbles: boolean; + cancelable: boolean; + composed: boolean; + timeStamp = 123456; + isTrusted = true; + eventPhase = Event.NONE; + target = null; + currentTarget = null; + srcElement = null; + cancelBubble = false; + defaultPrevented = false; + returnValue = false; + + preventDefault() { + this.defaultPrevented = true; + this.returnValue = true; + } + + stopPropagation() {} + + stopImmediatePropagation() {} + + initEvent() {} + + waitUntil() {} + + composedPath() { + return []; + } + + constructor(public type: string, options: EventInit = {}) { + this.bubbles = options.bubbles ?? false; + this.cancelable = options.cancelable ?? false; + this.composed = options.composed ?? false; + } +} diff --git a/packages-exp/messaging-exp/src/testing/fakes/token-details.ts b/packages-exp/messaging-exp/src/testing/fakes/token-details.ts new file mode 100644 index 00000000000..73eea06b2e5 --- /dev/null +++ b/packages-exp/messaging-exp/src/testing/fakes/token-details.ts @@ -0,0 +1,36 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FakePushSubscription } from './service-worker'; +import { TokenDetails } from '../../interfaces/token-details'; +import { arrayToBase64 } from '../../helpers/array-base64-translator'; + +export function getFakeTokenDetails(): TokenDetails { + const subscription = new FakePushSubscription(); + + return { + token: 'token-value', + createTime: 1234567890, + subscriptionOptions: { + swScope: '/scope-value', + vapidKey: arrayToBase64(new TextEncoder().encode('vapid-key-value')), // 'dmFwaWQta2V5LXZhbHVl', + endpoint: subscription.endpoint, + auth: arrayToBase64(subscription.getKey('auth')), // 'YXV0aC12YWx1ZQ' + p256dh: arrayToBase64(subscription.getKey('p256dh')) // 'cDI1Ni12YWx1ZQ' + } + }; +} diff --git a/packages-exp/messaging-exp/src/testing/setup.ts b/packages-exp/messaging-exp/src/testing/setup.ts new file mode 100644 index 00000000000..61b3524ca74 --- /dev/null +++ b/packages-exp/messaging-exp/src/testing/setup.ts @@ -0,0 +1,33 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as chaiAsPromised from 'chai-as-promised'; +import * as sinonChai from 'sinon-chai'; + +import { dbDelete } from '../internals/idb-manager'; +import { deleteDb } from 'idb'; +import { restore } from 'sinon'; +import { use } from 'chai'; + +use(chaiAsPromised); +use(sinonChai); + +afterEach(async () => { + restore(); + await dbDelete(); + await deleteDb('fcm_token_details_db'); +}); diff --git a/packages-exp/messaging-exp/src/testing/sinon-types.ts b/packages-exp/messaging-exp/src/testing/sinon-types.ts new file mode 100644 index 00000000000..13eafbac969 --- /dev/null +++ b/packages-exp/messaging-exp/src/testing/sinon-types.ts @@ -0,0 +1,30 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SinonSpy, SinonStub } from 'sinon'; + +// Helper types for Sinon stubs and spies. + +export type Stub any> = SinonStub< + Parameters, + ReturnType +>; + +export type Spy any> = SinonSpy< + Parameters, + ReturnType +>; diff --git a/packages-exp/messaging-exp/src/util/constants.ts b/packages-exp/messaging-exp/src/util/constants.ts new file mode 100644 index 00000000000..4bafce33b0a --- /dev/null +++ b/packages-exp/messaging-exp/src/util/constants.ts @@ -0,0 +1,34 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const DEFAULT_SW_PATH = '/firebase-messaging-sw.js'; +export const DEFAULT_SW_SCOPE = '/firebase-cloud-messaging-push-scope'; + +export const DEFAULT_VAPID_KEY = + 'BDOU99-h67HcA6JeFXHbSNMu7e2yNNu3RzoMj8TM4W88jITfq7ZmPvIM1Iv-4_l2LxQcYwhqby2xGpWwzjfAnG4'; + +export const ENDPOINT = 'https://fcmregistrations.googleapis.com/v1'; + +/** Key of FCM Payload in Notification's data field. */ +export const FCM_MSG = 'FCM_MSG'; + +export const CONSOLE_CAMPAIGN_ID = 'google.c.a.c_id'; +export const CONSOLE_CAMPAIGN_NAME = 'google.c.a.c_l'; +export const CONSOLE_CAMPAIGN_TIME = 'google.c.a.ts'; +/** Set to '1' if Analytics is enabled for the campaign */ +export const CONSOLE_CAMPAIGN_ANALYTICS_ENABLED = 'google.c.a.e'; +export const TAG = 'FirebaseMessaging: '; diff --git a/packages-exp/messaging-exp/src/util/errors.ts b/packages-exp/messaging-exp/src/util/errors.ts new file mode 100644 index 00000000000..14b001b6dc2 --- /dev/null +++ b/packages-exp/messaging-exp/src/util/errors.ts @@ -0,0 +1,96 @@ +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ErrorFactory, ErrorMap } from '@firebase/util'; + +export const enum ErrorCode { + MISSING_APP_CONFIG_VALUES = 'missing-app-config-values', + AVAILABLE_IN_WINDOW = 'only-available-in-window', + AVAILABLE_IN_SW = 'only-available-in-sw', + PERMISSION_DEFAULT = 'permission-default', + PERMISSION_BLOCKED = 'permission-blocked', + UNSUPPORTED_BROWSER = 'unsupported-browser', + INDEXED_DB_UNSUPPORTED = 'indexed-db-unsupported', + FAILED_DEFAULT_REGISTRATION = 'failed-service-worker-registration', + TOKEN_SUBSCRIBE_FAILED = 'token-subscribe-failed', + TOKEN_SUBSCRIBE_NO_TOKEN = 'token-subscribe-no-token', + TOKEN_UNSUBSCRIBE_FAILED = 'token-unsubscribe-failed', + TOKEN_UPDATE_FAILED = 'token-update-failed', + TOKEN_UPDATE_NO_TOKEN = 'token-update-no-token', + INVALID_BG_HANDLER = 'invalid-bg-handler', + USE_SW_AFTER_GET_TOKEN = 'use-sw-after-get-token', + INVALID_SW_REGISTRATION = 'invalid-sw-registration', + USE_VAPID_KEY_AFTER_GET_TOKEN = 'use-vapid-key-after-get-token', + INVALID_VAPID_KEY = 'invalid-vapid-key' +} + +export const ERROR_MAP: ErrorMap = { + [ErrorCode.MISSING_APP_CONFIG_VALUES]: + 'Missing App configuration value: "{$valueName}"', + [ErrorCode.AVAILABLE_IN_WINDOW]: + 'This method is available in a Window context.', + [ErrorCode.AVAILABLE_IN_SW]: + 'This method is available in a service worker context.', + [ErrorCode.PERMISSION_DEFAULT]: + 'The notification permission was not granted and dismissed instead.', + [ErrorCode.PERMISSION_BLOCKED]: + 'The notification permission was not granted and blocked instead.', + [ErrorCode.UNSUPPORTED_BROWSER]: + "This browser doesn't support the API's required to use the firebase SDK.", + [ErrorCode.INDEXED_DB_UNSUPPORTED]: + "This browser doesn't support indexedDb.open() (ex. Safari iFrame, Firefox Private Browsing, etc)", + [ErrorCode.FAILED_DEFAULT_REGISTRATION]: + 'We are unable to register the default service worker. {$browserErrorMessage}', + [ErrorCode.TOKEN_SUBSCRIBE_FAILED]: + 'A problem occurred while subscribing the user to FCM: {$errorInfo}', + [ErrorCode.TOKEN_SUBSCRIBE_NO_TOKEN]: + 'FCM returned no token when subscribing the user to push.', + [ErrorCode.TOKEN_UNSUBSCRIBE_FAILED]: + 'A problem occurred while unsubscribing the ' + + 'user from FCM: {$errorInfo}', + [ErrorCode.TOKEN_UPDATE_FAILED]: + 'A problem occurred while updating the user from FCM: {$errorInfo}', + [ErrorCode.TOKEN_UPDATE_NO_TOKEN]: + 'FCM returned no token when updating the user to push.', + [ErrorCode.USE_SW_AFTER_GET_TOKEN]: + 'The useServiceWorker() method may only be called once and must be ' + + 'called before calling getToken() to ensure your service worker is used.', + [ErrorCode.INVALID_SW_REGISTRATION]: + 'The input to useServiceWorker() must be a ServiceWorkerRegistration.', + [ErrorCode.INVALID_BG_HANDLER]: + 'The input to setBackgroundMessageHandler() must be a function.', + [ErrorCode.INVALID_VAPID_KEY]: 'The public VAPID key must be a string.', + [ErrorCode.USE_VAPID_KEY_AFTER_GET_TOKEN]: + 'The usePublicVapidKey() method may only be called once and must be ' + + 'called before calling getToken() to ensure your VAPID key is used.' +}; + +interface ErrorParams { + [ErrorCode.MISSING_APP_CONFIG_VALUES]: { + valueName: string; + }; + [ErrorCode.FAILED_DEFAULT_REGISTRATION]: { browserErrorMessage: string }; + [ErrorCode.TOKEN_SUBSCRIBE_FAILED]: { errorInfo: string }; + [ErrorCode.TOKEN_UNSUBSCRIBE_FAILED]: { errorInfo: string }; + [ErrorCode.TOKEN_UPDATE_FAILED]: { errorInfo: string }; +} + +export const ERROR_FACTORY = new ErrorFactory( + 'messaging', + 'Messaging', + ERROR_MAP +); diff --git a/packages-exp/messaging-exp/src/util/sw-types.ts b/packages-exp/messaging-exp/src/util/sw-types.ts new file mode 100644 index 00000000000..587cc248f50 --- /dev/null +++ b/packages-exp/messaging-exp/src/util/sw-types.ts @@ -0,0 +1,114 @@ +/** + * @license + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Subset of Web Worker types from lib.webworker.d.ts + * https://github.com/Microsoft/TypeScript/blob/master/lib/lib.webworker.d.ts + * + * Since it's not possible to have both "dom" and "webworker" libs in a single project, we have to + * manually declare the web worker types we need. + */ + +/* eslint-disable @typescript-eslint/no-explicit-any */ // These types are from TS + +// Not the whole interface, just the parts we're currently using. If TS claims that something does +// not exist on this, feel free to add it. +export interface ServiceWorkerGlobalScope { + readonly location: WorkerLocation; + readonly clients: Clients; + readonly registration: ServiceWorkerRegistration; + addEventListener( + type: K, + listener: ( + this: ServiceWorkerGlobalScope, + ev: ServiceWorkerGlobalScopeEventMap[K] + ) => any, + options?: boolean | AddEventListenerOptions + ): void; +} + +// Same as the previous interface +export interface ServiceWorkerGlobalScopeEventMap { + notificationclick: NotificationEvent; + push: PushEvent; + pushsubscriptionchange: PushSubscriptionChangeEvent; +} + +export interface Client { + readonly id: string; + readonly type: ClientTypes; + readonly url: string; + postMessage(message: any, transfer?: Transferable[]): void; +} + +export interface ClientQueryOptions { + includeReserved?: boolean; + includeUncontrolled?: boolean; + type?: ClientTypes; +} + +export interface WindowClient extends Client { + readonly focused: boolean; + readonly visibilityState: VisibilityState; + focus(): Promise; + navigate(url: string): Promise; +} + +export interface Clients { + claim(): Promise; + get(id: string): Promise; + matchAll(options?: ClientQueryOptions): Promise; + openWindow(url: string): Promise; +} + +export interface ExtendableEvent extends Event { + waitUntil(f: Promise): void; +} + +export interface NotificationEvent extends ExtendableEvent { + readonly action: string; + readonly notification: Notification; +} + +interface PushMessageData { + arrayBuffer(): ArrayBuffer; + blob(): Blob; + json(): any; + text(): string; +} + +export interface PushEvent extends ExtendableEvent { + readonly data: PushMessageData | null; +} + +export interface PushSubscriptionChangeEvent extends ExtendableEvent { + readonly newSubscription: PushSubscription | null; + readonly oldSubscription: PushSubscription | null; +} + +interface WorkerLocation { + readonly hash: string; + readonly host: string; + readonly hostname: string; + readonly href: string; + readonly origin: string; + readonly pathname: string; + readonly port: string; + readonly protocol: string; + readonly search: string; + toString(): string; +} diff --git a/packages-exp/messaging-exp/sw/package.json b/packages-exp/messaging-exp/sw/package.json new file mode 100644 index 00000000000..10abf5a7d17 --- /dev/null +++ b/packages-exp/messaging-exp/sw/package.json @@ -0,0 +1,7 @@ +{ + "name": "@firebase/messaging-exp", + "description": "", + "author": "Firebase (https://firebase.google.com/)", + "module": "../dist/index.sw.esm2017.js", + "typings": "../dist/index.sw.d.ts" +} diff --git a/packages-exp/messaging-exp/tsconfig.json b/packages-exp/messaging-exp/tsconfig.json new file mode 100644 index 00000000000..4b63b47c5b5 --- /dev/null +++ b/packages-exp/messaging-exp/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../config/tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "noUnusedLocals": true, + "lib": [ + "dom", + "es2017" + ], + "downlevelIteration": true + }, + "exclude": [ + "dist/**/*" + ] +} diff --git a/packages-exp/performance-compat/.eslintrc.js b/packages-exp/performance-compat/.eslintrc.js new file mode 100644 index 00000000000..ca80aa0f69a --- /dev/null +++ b/packages-exp/performance-compat/.eslintrc.js @@ -0,0 +1,26 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +module.exports = { + extends: '../../config/.eslintrc.js', + parserOptions: { + project: 'tsconfig.json', + // to make vscode-eslint work with monorepo + // https://github.com/typescript-eslint/typescript-eslint/issues/251#issuecomment-463943250 + tsconfigRootDir: __dirname + } +}; diff --git a/packages-exp/performance-compat/README.md b/packages-exp/performance-compat/README.md new file mode 100644 index 00000000000..eb4da067f01 --- /dev/null +++ b/packages-exp/performance-compat/README.md @@ -0,0 +1,5 @@ +# @firebase/performance-compat + +This is the compat package that recreates the v8 APIs. + +**This package is not intended for direct usage, and should only be used via the officially supported [firebase](https://www.npmjs.com/package/firebase) package.** diff --git a/packages-exp/performance-compat/karma.conf.js b/packages-exp/performance-compat/karma.conf.js new file mode 100644 index 00000000000..c0737457c55 --- /dev/null +++ b/packages-exp/performance-compat/karma.conf.js @@ -0,0 +1,35 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const karmaBase = require('../../config/karma.base'); + +const files = ['test/**/*', 'src/**/*.test.ts']; + +module.exports = function (config) { + const karmaConfig = Object.assign({}, karmaBase, { + // files to load into karma + files: files, + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + preprocessors: { '**/*.ts': ['webpack', 'sourcemap'] }, + frameworks: ['mocha'] + }); + + config.set(karmaConfig); +}; + +module.exports.files = files; diff --git a/packages-exp/performance-compat/package.json b/packages-exp/performance-compat/package.json new file mode 100644 index 00000000000..9807dd95d89 --- /dev/null +++ b/packages-exp/performance-compat/package.json @@ -0,0 +1,64 @@ +{ + "name": "@firebase/performance-compat", + "version": "0.0.900", + "description": "The compatibility package of Firebase Performance", + "author": "Firebase (https://firebase.google.com/)", + "private": true, + "main": "dist/index.cjs.js", + "browser": "dist/index.esm2017.js", + "module": "dist/index.esm2017.js", + "files": [ + "dist" + ], + "scripts": { + "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "build": "rollup -c", + "build:release": "rollup -c rollup.config.release.js && yarn add-compat-overloads", + "build:deps": "lerna run --scope @firebase/performance-compat --include-dependencies build", + "dev": "rollup -c -w", + "test": "run-p lint test:all", + "test:all": "run-p test:browser", + "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:all", + "test:browser": "karma start --single-run", + "test:browser:debug": "karma start --browsers Chrome --auto-watch", + "prettier": "prettier --write '{src,test}/**/*.{js,ts}'", + "add-compat-overloads": "ts-node-script ../../scripts/exp/create-overloads.ts -i ../performance-exp/dist/src/index.d.ts -o dist/src/index.d.ts -a -r FirebasePerformance:FirebasePerformanceCompat -r FirebaseApp:FirebaseAppCompat --moduleToEnhance @firebase/performance" + }, + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-compat": "0.x" + }, + "dependencies": { + "@firebase/performance-exp": "0.0.900", + "@firebase/performance-types": "0.0.13", + "@firebase/util": "1.1.0", + "@firebase/logger": "0.2.6", + "@firebase/component": "0.5.4", + "tslib": "^2.1.0" + }, + "devDependencies": { + "rollup": "2.52.2", + "@rollup/plugin-json": "4.1.0", + "rollup-plugin-replace": "2.2.0", + "rollup-plugin-typescript2": "0.30.0", + "typescript": "4.2.2", + "@firebase/app-compat": "0.0.900" + }, + "repository": { + "directory": "packages-exp/performance-compat", + "type": "git", + "url": "https://github.com/firebase/firebase-js-sdk.git" + }, + "bugs": { + "url": "https://github.com/firebase/firebase-js-sdk/issues" + }, + "typings": "dist/src/index.d.ts", + "nyc": { + "extension": [ + ".ts" + ], + "reportDir": "./coverage/node" + }, + "esm5": "dist/index.esm5.js" +} \ No newline at end of file diff --git a/packages-exp/performance-compat/rollup.config.js b/packages-exp/performance-compat/rollup.config.js new file mode 100644 index 00000000000..4df9d8a2f48 --- /dev/null +++ b/packages-exp/performance-compat/rollup.config.js @@ -0,0 +1,60 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import typescriptPlugin from 'rollup-plugin-typescript2'; +import typescript from 'typescript'; +import json from '@rollup/plugin-json'; +import { es5BuildsNoPlugin, es2017BuildsNoPlugin } from './rollup.shared.js'; + +/** + * ES5 Builds + */ +const es5BuildPlugins = [ + typescriptPlugin({ + typescript + }), + json() +]; + +const es5Builds = es5BuildsNoPlugin.map(build => ({ + ...build, + plugins: es5BuildPlugins +})); + +/** + * ES2017 Builds + */ +const es2017BuildPlugins = [ + typescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017' + } + } + }), + json({ + preferConst: true + }) +]; + +const es2017Builds = es2017BuildsNoPlugin.map(build => ({ + ...build, + plugins: es2017BuildPlugins +})); + +export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/performance-compat/rollup.config.release.js b/packages-exp/performance-compat/rollup.config.release.js new file mode 100644 index 00000000000..b0b1dc5154a --- /dev/null +++ b/packages-exp/performance-compat/rollup.config.release.js @@ -0,0 +1,67 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import typescriptPlugin from 'rollup-plugin-typescript2'; +import typescript from 'typescript'; +import json from '@rollup/plugin-json'; +import { es5BuildsNoPlugin, es2017BuildsNoPlugin } from './rollup.shared.js'; +import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; + +/** + * ES5 Builds + */ +const es5BuildPlugins = [ + typescriptPlugin({ + typescript, + clean: true, + abortOnError: false, + transformers: [importPathTransformer] + }), + json() +]; + +const es5Builds = es5BuildsNoPlugin.map(build => ({ + ...build, + plugins: es5BuildPlugins +})); + +/** + * ES2017 Builds + */ +const es2017BuildPlugins = [ + typescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017' + } + }, + clean: true, + abortOnError: false, + transformers: [importPathTransformer] + }), + json({ + preferConst: true + }) +]; + +const es2017Builds = es2017BuildsNoPlugin.map(build => ({ + ...build, + plugins: es2017BuildPlugins +})); + +export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/performance-compat/rollup.shared.js b/packages-exp/performance-compat/rollup.shared.js new file mode 100644 index 00000000000..6d5afe89732 --- /dev/null +++ b/packages-exp/performance-compat/rollup.shared.js @@ -0,0 +1,52 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import pkg from './package.json'; + +const deps = [ + ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), + '@firebase/performance' +]; + +export const es5BuildsNoPlugin = [ + /** + * Browser Builds + */ + { + input: 'src/index.ts', + output: [ + { file: pkg.main, format: 'cjs', sourcemap: true }, + { file: pkg.esm5, format: 'es', sourcemap: true } + ], + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + } +]; + +export const es2017BuildsNoPlugin = [ + /** + * Browser Builds + */ + { + input: 'src/index.ts', + output: { + file: pkg.browser, + format: 'es', + sourcemap: true + }, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + } +]; diff --git a/packages-exp/performance-compat/src/index.ts b/packages-exp/performance-compat/src/index.ts new file mode 100644 index 00000000000..0a2109ee3f0 --- /dev/null +++ b/packages-exp/performance-compat/src/index.ts @@ -0,0 +1,68 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import firebase, { _FirebaseNamespace } from '@firebase/app-compat'; +import { + Component, + ComponentContainer, + ComponentType +} from '@firebase/component'; +import { PerformanceCompatImpl } from './performance'; +import { name as packageName, version } from '../package.json'; +import { FirebasePerformance as FirebasePerformanceCompat } from '@firebase/performance-types'; + +// TODO: move it to the future performance-compat-types package +declare module '@firebase/component' { + interface NameServiceMapping { + 'performance-compat': FirebasePerformanceCompat; + } +} + +function registerPerformanceCompat(firebaseInstance: _FirebaseNamespace): void { + firebaseInstance.INTERNAL.registerComponent( + new Component( + 'performance-compat', + performanceFactory, + ComponentType.PUBLIC + ) + ); + + firebaseInstance.registerVersion(packageName, version); +} + +function performanceFactory( + container: ComponentContainer +): PerformanceCompatImpl { + const app = container.getProvider('app-compat').getImmediate(); + // The following call will always succeed. + const performance = container.getProvider('performance-exp').getImmediate(); + + return new PerformanceCompatImpl(app, performance); +} + +registerPerformanceCompat(firebase as _FirebaseNamespace); + +declare module '@firebase/app-compat' { + interface FirebaseNamespace { + performance: { + (app?: FirebaseApp): FirebasePerformanceCompat; + }; + } + interface FirebaseApp { + performance(): FirebasePerformanceCompat; + } +} diff --git a/packages-exp/performance-compat/src/performance.test.ts b/packages-exp/performance-compat/src/performance.test.ts new file mode 100644 index 00000000000..1ef81428e7b --- /dev/null +++ b/packages-exp/performance-compat/src/performance.test.ts @@ -0,0 +1,66 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import '../test/setup'; +import { expect } from 'chai'; +import { stub } from 'sinon'; +import { + getFakeApp, + getFakeModularPerformance, + getFakeModularPerformanceTrace +} from '../test/util'; +import * as perfModularApi from '@firebase/performance-exp'; +import { PerformanceCompatImpl } from './performance'; + +describe('Performance Compat', () => { + let performanceCompat!: PerformanceCompatImpl; + let fakeModularPerformance!: perfModularApi.FirebasePerformance; + + beforeEach(() => { + fakeModularPerformance = getFakeModularPerformance(); + performanceCompat = new PerformanceCompatImpl( + getFakeApp(), + fakeModularPerformance + ); + }); + + it('sets instrumnetation flag on the modular package', () => { + // Default value of the flag is true. + performanceCompat.instrumentationEnabled = false; + + expect(fakeModularPerformance.instrumentationEnabled).to.be.false; + }); + + it('sets data collection flag on the modular package', () => { + // Default value of the flag is true. + performanceCompat.dataCollectionEnabled = false; + + expect(fakeModularPerformance.dataCollectionEnabled).to.be.false; + }); + + it('calls modular trace api when trace is called on compat api', () => { + const modularTraceStub = stub(perfModularApi, 'trace').callsFake(() => + getFakeModularPerformanceTrace() + ); + performanceCompat.trace('test'); + + expect(modularTraceStub).to.have.been.calledWithExactly( + fakeModularPerformance, + 'test' + ); + }); +}); diff --git a/packages-exp/performance-compat/src/performance.ts b/packages-exp/performance-compat/src/performance.ts new file mode 100644 index 00000000000..ddc82c88d5a --- /dev/null +++ b/packages-exp/performance-compat/src/performance.ts @@ -0,0 +1,53 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + trace, + FirebasePerformance, + // The PerformanceTrace type has not changed between modular and non-modular packages. + PerformanceTrace +} from '@firebase/performance-exp'; +import { FirebasePerformance as FirebasePerformanceCompat } from '@firebase/performance-types'; +import { FirebaseApp, _FirebaseService } from '@firebase/app-compat'; + +export class PerformanceCompatImpl + implements FirebasePerformanceCompat, _FirebaseService { + constructor( + public app: FirebaseApp, + readonly _delegate: FirebasePerformance + ) {} + + get instrumentationEnabled(): boolean { + return this._delegate.instrumentationEnabled; + } + + set instrumentationEnabled(val: boolean) { + this._delegate.instrumentationEnabled = val; + } + + get dataCollectionEnabled(): boolean { + return this._delegate.dataCollectionEnabled; + } + + set dataCollectionEnabled(val: boolean) { + this._delegate.dataCollectionEnabled = val; + } + + trace(traceName: string): PerformanceTrace { + return trace(this._delegate, traceName); + } +} diff --git a/packages-exp/performance-compat/test/setup.ts b/packages-exp/performance-compat/test/setup.ts new file mode 100644 index 00000000000..b1e3136529f --- /dev/null +++ b/packages-exp/performance-compat/test/setup.ts @@ -0,0 +1,26 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { use } from 'chai'; +import { restore } from 'sinon'; +import * as sinonChai from 'sinon-chai'; + +use(sinonChai); + +afterEach(async () => { + restore(); +}); diff --git a/packages-exp/performance-compat/test/util.ts b/packages-exp/performance-compat/test/util.ts new file mode 100644 index 00000000000..5bc5e3e56ff --- /dev/null +++ b/packages-exp/performance-compat/test/util.ts @@ -0,0 +1,50 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FirebaseApp } from '@firebase/app-compat'; +import { + FirebasePerformance, + PerformanceTrace +} from '@firebase/performance-exp'; + +export function getFakeApp(): FirebaseApp { + return { + name: 'appName', + options: { + apiKey: 'apiKey', + projectId: 'projectId', + authDomain: 'authDomain', + messagingSenderId: 'messagingSenderId', + databaseURL: 'databaseUrl', + storageBucket: 'storageBucket', + appId: '1:777777777777:web:d93b5ca1475efe57' + }, + automaticDataCollectionEnabled: true, + delete: async () => {} + }; +} + +export function getFakeModularPerformance(): FirebasePerformance { + return { + instrumentationEnabled: true, + dataCollectionEnabled: true + }; +} + +export function getFakeModularPerformanceTrace(): PerformanceTrace { + return {} as PerformanceTrace; +} diff --git a/packages-exp/performance-compat/tsconfig.json b/packages-exp/performance-compat/tsconfig.json new file mode 100644 index 00000000000..72e0736c2b7 --- /dev/null +++ b/packages-exp/performance-compat/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../config/tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "resolveJsonModule": true, + "downlevelIteration": true + }, + "exclude": ["dist/**/*"] +} diff --git a/packages-exp/performance-exp/package.json b/packages-exp/performance-exp/package.json index 3f991657b19..6172a5ed695 100644 --- a/packages-exp/performance-exp/package.json +++ b/packages-exp/performance-exp/package.json @@ -1,17 +1,18 @@ { "name": "@firebase/performance-exp", - "version": "0.0.800", + "version": "0.0.900", "description": "Firebase performance for web", "author": "Firebase (https://firebase.google.com/)", "private": true, "main": "dist/index.cjs.js", - "browser": "dist/index.esm.js", - "module": "dist/index.esm.js", - "esm2017": "dist/index.esm2017.js", - "files": ["dist"], + "browser": "dist/index.esm2017.js", + "module": "dist/index.esm2017.js", + "files": [ + "dist" + ], "scripts": { "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts'", + "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", "build": "rollup -c && yarn api-report", "build:deps": "lerna run --scope @firebase/performance-exp --include-dependencies build", "build:release": "rollup -c rollup.config.release.js", @@ -20,7 +21,6 @@ "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:browser", "test:browser": "karma start --single-run", "test:debug": "karma start --browsers=Chrome --auto-watch", - "prepare": "yarn build:release", "prettier": "prettier --write '{src,test}/**/*.{js,ts}'", "api-report": "api-extractor run --local --verbose", "predoc": "node ../../scripts/exp/remove-exp.js temp", @@ -28,24 +28,22 @@ "build:doc": "yarn build && yarn doc" }, "peerDependencies": { - "@firebase/app-exp": "0.x", - "@firebase/app-types-exp": "0.x" + "@firebase/app-exp": "0.x" }, "dependencies": { "@firebase/logger": "0.2.6", - "@firebase/installations-exp": "0.0.800", - "@firebase/util": "0.3.2", - "@firebase/performance-types-exp": "0.0.800", - "@firebase/component": "0.1.19", - "tslib": "^1.11.1" + "@firebase/installations-exp": "0.0.900", + "@firebase/util": "1.1.0", + "@firebase/component": "0.5.4", + "tslib": "^2.1.0" }, "license": "Apache-2.0", "devDependencies": { - "@firebase/app-exp": "0.0.800", - "rollup": "2.29.0", + "@firebase/app-exp": "0.0.900", + "rollup": "2.52.2", "@rollup/plugin-json": "4.1.0", - "rollup-plugin-typescript2": "0.27.3", - "typescript": "4.0.2" + "rollup-plugin-typescript2": "0.30.0", + "typescript": "4.2.2" }, "repository": { "directory": "packages/performance-exp", @@ -61,5 +59,6 @@ ".ts" ], "reportDir": "./coverage/node" - } -} + }, + "esm5": "dist/index.esm.js" +} \ No newline at end of file diff --git a/packages-exp/performance-exp/rollup.shared.js b/packages-exp/performance-exp/rollup.shared.js index cbb57224a81..461af4f05ad 100644 --- a/packages-exp/performance-exp/rollup.shared.js +++ b/packages-exp/performance-exp/rollup.shared.js @@ -17,9 +17,10 @@ import pkg from './package.json'; -const deps = Object.keys( - Object.assign({}, pkg.peerDependencies, pkg.dependencies) -); +const deps = [ + ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), + '@firebase/app' +]; /** * ES5 Builds @@ -29,7 +30,7 @@ export const es5BuildsNoPlugin = [ input: 'src/index.ts', output: [ { file: pkg.main, format: 'cjs', sourcemap: true }, - { file: pkg.module, format: 'es', sourcemap: true } + { file: pkg.esm5, format: 'es', sourcemap: true } ], external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) } @@ -41,7 +42,7 @@ export const es5BuildsNoPlugin = [ export const es2017BuildsNoPlugin = [ { input: 'src/index.ts', - output: [{ file: pkg.esm2017, format: 'es', sourcemap: true }], + output: [{ file: pkg.browser, format: 'es', sourcemap: true }], external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) } ]; diff --git a/packages-exp/performance-exp/src/controllers/perf.test.ts b/packages-exp/performance-exp/src/controllers/perf.test.ts index 6efc944870f..369fabe12ce 100644 --- a/packages-exp/performance-exp/src/controllers/perf.test.ts +++ b/packages-exp/performance-exp/src/controllers/perf.test.ts @@ -22,8 +22,8 @@ import { Api, setupApi } from '../services/api_service'; import * as initializationService from '../services/initialization_service'; import { SettingsService } from '../services/settings_service'; import { consoleLogger } from '../utils/console_logger'; -import { FirebaseApp } from '@firebase/app-types-exp'; -import { _FirebaseInstallationsInternal } from '@firebase/installations-types-exp'; +import { FirebaseApp } from '@firebase/app-exp'; +import { _FirebaseInstallationsInternal } from '@firebase/installations-exp'; import '../../test/setup'; describe('Firebase Performance Test', () => { diff --git a/packages-exp/performance-exp/src/controllers/perf.ts b/packages-exp/performance-exp/src/controllers/perf.ts index 9434c0f2a94..5f2a70c0bc4 100644 --- a/packages-exp/performance-exp/src/controllers/perf.ts +++ b/packages-exp/performance-exp/src/controllers/perf.ts @@ -19,12 +19,9 @@ import { setupOobResources } from '../services/oob_resources_service'; import { SettingsService } from '../services/settings_service'; import { getInitializationPromise } from '../services/initialization_service'; import { Api } from '../services/api_service'; -import { FirebaseApp } from '@firebase/app-types-exp'; -import { _FirebaseInstallationsInternal } from '@firebase/installations-types-exp'; -import { - PerformanceSettings, - FirebasePerformance -} from '@firebase/performance-types-exp'; +import { FirebaseApp } from '@firebase/app-exp'; +import { _FirebaseInstallationsInternal } from '@firebase/installations-exp'; +import { PerformanceSettings, FirebasePerformance } from '../public_types'; import { validateIndexedDBOpenable } from '@firebase/util'; import { setupTransportService } from '../services/transport_service'; import { consoleLogger } from '../utils/console_logger'; diff --git a/packages-exp/performance-exp/src/index.test.ts b/packages-exp/performance-exp/src/index.test.ts new file mode 100644 index 00000000000..b4610acc7dd --- /dev/null +++ b/packages-exp/performance-exp/src/index.test.ts @@ -0,0 +1,50 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from 'chai'; +import { stub } from 'sinon'; +import { initializePerformance } from './index'; +import { ERROR_FACTORY, ErrorCode } from './utils/errors'; +import * as firebase from '@firebase/app-exp'; +import { Provider } from '@firebase/component'; +import '../test/setup'; + +const fakeFirebaseConfig = { + apiKey: 'api-key', + authDomain: 'project-id.firebaseapp.com', + databaseURL: 'https://project-id.firebaseio.com', + projectId: 'project-id', + storageBucket: 'project-id.appspot.com', + messagingSenderId: 'sender-id', + appId: '1:111:web:a1234' +}; + +const fakeFirebaseApp = ({ + options: fakeFirebaseConfig +} as unknown) as firebase.FirebaseApp; + +describe('Firebase Performance > initializePerformance()', () => { + it('throws if a perf instance has already been created', () => { + stub(firebase, '_getProvider').returns(({ + isInitialized: () => true + } as unknown) as Provider<'performance-exp'>); + const expectedError = ERROR_FACTORY.create(ErrorCode.ALREADY_INITIALIZED); + expect(() => initializePerformance(fakeFirebaseApp)).to.throw( + expectedError.message + ); + }); +}); diff --git a/packages-exp/performance-exp/src/index.ts b/packages-exp/performance-exp/src/index.ts index b976ac9d398..07d00b70dac 100644 --- a/packages-exp/performance-exp/src/index.ts +++ b/packages-exp/performance-exp/src/index.ts @@ -1,3 +1,9 @@ +/** + * Firebase Performance Monitoring + * + * @packageDocumentation + */ + /** * @license * Copyright 2020 Google LLC @@ -15,19 +21,20 @@ * limitations under the License. */ -import { FirebaseApp } from '@firebase/app-types-exp'; import { FirebasePerformance, PerformanceSettings, PerformanceTrace -} from '@firebase/performance-types-exp'; +} from './public_types'; import { ERROR_FACTORY, ErrorCode } from './utils/errors'; import { setupApi } from './services/api_service'; import { PerformanceController } from './controllers/perf'; import { _registerComponent, _getProvider, - registerVersion + registerVersion, + FirebaseApp, + getApp } from '@firebase/app-exp'; import { InstanceFactory, @@ -38,28 +45,52 @@ import { import { name, version } from '../package.json'; import { Trace } from './resources/trace'; import '@firebase/installations-exp'; +import { getModularInstance } from '@firebase/util'; const DEFAULT_ENTRY_NAME = '[DEFAULT]'; /** * Returns a FirebasePerformance instance for the given app. - * @param app - The FirebaseApp to use. - * @param settings - Optional settings for the Performance instance. + * @param app - The `FirebaseApp` to use. * @public */ export function getPerformance( + app: FirebaseApp = getApp() +): FirebasePerformance { + app = getModularInstance(app); + const provider = _getProvider(app, 'performance-exp'); + const perfInstance = provider.getImmediate() as PerformanceController; + return perfInstance; +} + +/** + * Returns a FirebasePerformance instance for the given app. Can only be called once. + * @param app - The `FirebaseApp` to use. + * @param settings - Optional settings for the `FirebasePerformance` instance. + * @public + */ +export function initializePerformance( app: FirebaseApp, settings?: PerformanceSettings ): FirebasePerformance { + app = getModularInstance(app); const provider = _getProvider(app, 'performance-exp'); - const perfInstance = provider.getImmediate() as PerformanceController; - perfInstance._init(settings); + + // throw if an instance was already created. + // It could happen if initializePerformance() is called more than once, or getPerformance() is called first. + if (provider.isInitialized()) { + throw ERROR_FACTORY.create(ErrorCode.ALREADY_INITIALIZED); + } + + const perfInstance = provider.initialize({ + options: settings + }) as PerformanceController; return perfInstance; } /** - * Returns a new PerformanceTrace instance. - * @param performance - The FirebasePerformance instance to use. + * Returns a new `PerformanceTrace` instance. + * @param performance - The `FirebasePerformance` instance to use. * @param name - The name of the trace. * @public */ @@ -67,11 +98,13 @@ export function trace( performance: FirebasePerformance, name: string ): PerformanceTrace { + performance = getModularInstance(performance); return new Trace(performance as PerformanceController, name); } const factory: InstanceFactory<'performance-exp'> = ( - container: ComponentContainer + container: ComponentContainer, + { options: settings }: { options?: PerformanceSettings } ) => { // Dependencies const app = container.getProvider('app-exp').getImmediate(); @@ -86,7 +119,10 @@ const factory: InstanceFactory<'performance-exp'> = ( throw ERROR_FACTORY.create(ErrorCode.NO_WINDOW); } setupApi(window); - return new PerformanceController(app, installations); + const perfInstance = new PerformanceController(app, installations); + perfInstance._init(settings); + + return perfInstance; }; function registerPerformance(): void { @@ -97,3 +133,5 @@ function registerPerformance(): void { registerPerformance(); registerVersion(name, version); + +export { FirebasePerformance, PerformanceSettings, PerformanceTrace }; diff --git a/packages-exp/performance-exp/src/public_types.ts b/packages-exp/performance-exp/src/public_types.ts new file mode 100644 index 00000000000..756fca2febf --- /dev/null +++ b/packages-exp/performance-exp/src/public_types.ts @@ -0,0 +1,135 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Defines configuration options for the Performance Monitoring SDK. + * + * @public + */ +export interface PerformanceSettings { + /** Whether to collect custom events. */ + dataCollectionEnabled?: boolean; + + /** Whether to collect out of the box events. */ + instrumentationEnabled?: boolean; +} + +/** + * The Firebase Performance Monitoring service interface. + * + * @public + */ +export interface FirebasePerformance { + /** + * Controls the logging of automatic traces and HTTP/S network monitoring. + */ + instrumentationEnabled: boolean; + + /** + * Controls the logging of custom traces. + */ + dataCollectionEnabled: boolean; +} + +/** + * The interface representing a `Trace`. + * + * @public + */ +export interface PerformanceTrace { + /** + * Starts the timing for the trace instance. + */ + start(): void; + /** + * Stops the timing of the trace instance and logs the data of the instance. + */ + stop(): void; + /** + * Records a trace from given parameters. This provides a direct way to use trace without a need to + * start/stop. This is useful for use cases in which the trace cannot directly be used + * (e.g. if the duration was captured before the Performance SDK was loaded). + * + * @param startTime - trace start time since epoch in millisec. + * @param duration - The duraction of the trace in millisec. + * @param options - An object which can optionally hold maps of custom metrics and + * custom attributes. + */ + record( + startTime: number, + duration: number, + options?: { + metrics?: { [key: string]: number }; + attributes?: { [key: string]: string }; + } + ): void; + /** + * Adds to the value of a custom metric. If a custom metric with the provided name does not + * exist, it creates one with that name and the value equal to the given number. The value will be floored down to an + * integer. + * + * @param metricName - The name of the custom metric. + * @param num - The number to be added to the value of the custom metric. If not provided, it + * uses a default value of one. + */ + incrementMetric(metricName: string, num?: number): void; + /** + * Sets the value of the specified custom metric to the given number regardless of whether + * a metric with that name already exists on the trace instance or not. The value will be floored down to an + * integer. + * + * @param metricName - Name of the custom metric. + * @param num - Value to of the custom metric. + */ + putMetric(metricName: string, num: number): void; + /** + * Returns the value of the custom metric by that name. If a custom metric with that name does + * not exist will return zero. + * + * @param metricName - Name of the custom metric. + */ + getMetric(metricName: string): number; + /** + * Set a custom attribute of a trace to a certain value. + * + * @param attr - Name of the custom attribute. + * @param value - Value of the custom attribute. + */ + putAttribute(attr: string, value: string): void; + /** + * Retrieves the value which a custom attribute is set to. + * + * @param attr - Name of the custom attribute. + */ + getAttribute(attr: string): string | undefined; + /** + * Removes the specified custom attribute from a trace instance. + * + * @param attr - Name of the custom attribute. + */ + removeAttribute(attr: string): void; + /** + * Returns a map of all custom attributes of a trace instance. + */ + getAttributes(): { [key: string]: string }; +} + +declare module '@firebase/component' { + interface NameServiceMapping { + 'performance-exp': FirebasePerformance; + } +} diff --git a/packages-exp/performance-exp/src/resources/network_request.test.ts b/packages-exp/performance-exp/src/resources/network_request.test.ts index 068fbb58b92..20b6598f3f9 100644 --- a/packages-exp/performance-exp/src/resources/network_request.test.ts +++ b/packages-exp/performance-exp/src/resources/network_request.test.ts @@ -21,7 +21,7 @@ import { expect } from 'chai'; import { Api, setupApi } from '../services/api_service'; import * as perfLogger from '../services/perf_logger'; -import { FirebaseApp } from '@firebase/app-types-exp'; +import { FirebaseApp } from '@firebase/app-exp'; import { PerformanceController } from '../controllers/perf'; import { FirebaseInstallations } from '@firebase/installations-types'; import '../../test/setup'; diff --git a/packages-exp/performance-exp/src/resources/trace.test.ts b/packages-exp/performance-exp/src/resources/trace.test.ts index a4104cf4723..c3d2f9c61db 100644 --- a/packages-exp/performance-exp/src/resources/trace.test.ts +++ b/packages-exp/performance-exp/src/resources/trace.test.ts @@ -21,7 +21,7 @@ import { expect } from 'chai'; import { Api, setupApi } from '../services/api_service'; import * as perfLogger from '../services/perf_logger'; import { PerformanceController } from '../controllers/perf'; -import { FirebaseApp } from '@firebase/app-types-exp'; +import { FirebaseApp } from '@firebase/app-exp'; import { FirebaseInstallations } from '@firebase/installations-types'; import '../../test/setup'; diff --git a/packages-exp/performance-exp/src/resources/trace.ts b/packages-exp/performance-exp/src/resources/trace.ts index 2da51f56a6b..67958572984 100644 --- a/packages-exp/performance-exp/src/resources/trace.ts +++ b/packages-exp/performance-exp/src/resources/trace.ts @@ -35,7 +35,7 @@ import { isValidMetricName, convertMetricValueToInteger } from '../utils/metric_utils'; -import { PerformanceTrace } from '@firebase/performance-types-exp'; +import { PerformanceTrace } from '../public_types'; import { PerformanceController } from '../controllers/perf'; const enum TraceState { diff --git a/packages-exp/performance-exp/src/services/iid_service.test.ts b/packages-exp/performance-exp/src/services/iid_service.test.ts index 4451b0642b0..3f1e195506e 100644 --- a/packages-exp/performance-exp/src/services/iid_service.test.ts +++ b/packages-exp/performance-exp/src/services/iid_service.test.ts @@ -24,7 +24,7 @@ import { getAuthTokenPromise } from './iid_service'; import '../../test/setup'; -import { _FirebaseInstallationsInternal } from '@firebase/installations-types-exp'; +import { _FirebaseInstallationsInternal } from '@firebase/installations-exp'; describe('Firebase Perofmrance > iid_service', () => { const IID = 'fid'; diff --git a/packages-exp/performance-exp/src/services/iid_service.ts b/packages-exp/performance-exp/src/services/iid_service.ts index 8899e12d1cb..6d336e3c034 100644 --- a/packages-exp/performance-exp/src/services/iid_service.ts +++ b/packages-exp/performance-exp/src/services/iid_service.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { _FirebaseInstallationsInternal } from '@firebase/installations-types-exp'; +import { _FirebaseInstallationsInternal } from '@firebase/installations-exp'; let iid: string | undefined; let authToken: string | undefined; diff --git a/packages-exp/performance-exp/src/services/initialization_service.test.ts b/packages-exp/performance-exp/src/services/initialization_service.test.ts index 9efde63ca37..242bbdff4a0 100644 --- a/packages-exp/performance-exp/src/services/initialization_service.test.ts +++ b/packages-exp/performance-exp/src/services/initialization_service.test.ts @@ -22,7 +22,7 @@ import { isPerfInitialized } from './initialization_service'; import { setupApi } from './api_service'; -import { FirebaseApp } from '@firebase/app-types-exp'; +import { FirebaseApp } from '@firebase/app-exp'; import '../../test/setup'; import { FirebaseInstallations } from '@firebase/installations-types'; import { PerformanceController } from '../controllers/perf'; diff --git a/packages-exp/performance-exp/src/services/oob_resources_service.test.ts b/packages-exp/performance-exp/src/services/oob_resources_service.test.ts index 35e4f0c93a5..c0210b24116 100644 --- a/packages-exp/performance-exp/src/services/oob_resources_service.test.ts +++ b/packages-exp/performance-exp/src/services/oob_resources_service.test.ts @@ -30,7 +30,7 @@ import { setupOobResources } from './oob_resources_service'; import { Trace } from '../resources/trace'; import '../../test/setup'; import { PerformanceController } from '../controllers/perf'; -import { FirebaseApp } from '@firebase/app-types-exp'; +import { FirebaseApp } from '@firebase/app-exp'; import { FirebaseInstallations } from '@firebase/installations-types'; describe('Firebase Performance > oob_resources_service', () => { diff --git a/packages-exp/performance-exp/src/services/perf_logger.test.ts b/packages-exp/performance-exp/src/services/perf_logger.test.ts index 994a72f9d5e..2f2a70c35f4 100644 --- a/packages-exp/performance-exp/src/services/perf_logger.test.ts +++ b/packages-exp/performance-exp/src/services/perf_logger.test.ts @@ -22,7 +22,7 @@ import * as iidService from './iid_service'; import { expect } from 'chai'; import { Api, setupApi } from './api_service'; import { SettingsService } from './settings_service'; -import { FirebaseApp } from '@firebase/app-types-exp'; +import { FirebaseApp } from '@firebase/app-exp'; import * as initializationService from './initialization_service'; import { SDK_VERSION } from '../constants'; import * as attributeUtils from '../utils/attributes_utils'; diff --git a/packages-exp/performance-exp/src/services/perf_logger.ts b/packages-exp/performance-exp/src/services/perf_logger.ts index 3a2a65fdfb0..ac506dab516 100644 --- a/packages-exp/performance-exp/src/services/perf_logger.ts +++ b/packages-exp/performance-exp/src/services/perf_logger.ts @@ -32,7 +32,7 @@ import { } from './initialization_service'; import { transportHandler } from './transport_service'; import { SDK_VERSION } from '../constants'; -import { FirebaseApp } from '@firebase/app-types-exp'; +import { FirebaseApp } from '@firebase/app-exp'; import { getAppId } from '../utils/app_utils'; const enum ResourceType { diff --git a/packages-exp/performance-exp/src/services/remote_config_service.test.ts b/packages-exp/performance-exp/src/services/remote_config_service.test.ts index ddd1e3a3ecb..9baf2d3a8a8 100644 --- a/packages-exp/performance-exp/src/services/remote_config_service.test.ts +++ b/packages-exp/performance-exp/src/services/remote_config_service.test.ts @@ -22,7 +22,7 @@ import { CONFIG_EXPIRY_LOCAL_STORAGE_KEY } from '../constants'; import { setupApi, Api } from './api_service'; import * as iidService from './iid_service'; import { getConfig } from './remote_config_service'; -import { FirebaseApp } from '@firebase/app-types-exp'; +import { FirebaseApp } from '@firebase/app-exp'; import '../../test/setup'; import { FirebaseInstallations } from '@firebase/installations-types'; import { PerformanceController } from '../controllers/perf'; diff --git a/packages-exp/performance-exp/src/services/transport_service.test.ts b/packages-exp/performance-exp/src/services/transport_service.test.ts index 871f9bb2782..43986e00817 100644 --- a/packages-exp/performance-exp/src/services/transport_service.test.ts +++ b/packages-exp/performance-exp/src/services/transport_service.test.ts @@ -31,6 +31,8 @@ describe('Firebase Performance > transport_service', () => { let fetchStub: SinonStub<[RequestInfo, RequestInit?], Promise>; const INITIAL_SEND_TIME_DELAY_MS = 5.5 * 1000; const DEFAULT_SEND_INTERVAL_MS = 10 * 1000; + const MAX_EVENT_COUNT_PER_REQUEST = 1000; + const TRANSPORT_DELAY_INTERVAL = 10000; // Starts date at timestamp 1 instead of 0, otherwise it causes validation errors. let clock: SinonFakeTimers; const testTransportHandler = transportHandler((...args) => { @@ -55,7 +57,7 @@ describe('Firebase Performance > transport_service', () => { }).to.throw; }); - it('does not attempt to log an event to cc after INITIAL_SEND_TIME_DELAY_MS if queue is empty', () => { + it('does not attempt to log an event after INITIAL_SEND_TIME_DELAY_MS if queue is empty', () => { fetchStub.resolves( new Response('', { status: 200, @@ -67,7 +69,7 @@ describe('Firebase Performance > transport_service', () => { expect(fetchStub).to.not.have.been.called; }); - it('attempts to log an event to cc after DEFAULT_SEND_INTERVAL_MS if queue not empty', () => { + it('attempts to log an event after DEFAULT_SEND_INTERVAL_MS if queue not empty', async () => { fetchStub.resolves( new Response('', { status: 200, @@ -82,32 +84,100 @@ describe('Firebase Performance > transport_service', () => { }); it('successful send a meesage to transport', () => { - const transportDelayInterval = 30000; const setting = SettingsService.getInstance(); const flTransportFullUrl = setting.flTransportEndpointUrl + '?key=' + setting.transportKey; fetchStub.withArgs(flTransportFullUrl, match.any).resolves( // DELETE_REQUEST means event dispatch is successful. - new Response( - '{\ - "nextRequestWaitMillis": "' + - transportDelayInterval + - '",\ - "logResponseDetails": [\ - {\ - "responseAction": "DELETE_REQUEST"\ - }\ - ]\ - }', - { - status: 200, - headers: { 'Content-type': 'application/json' } - } - ) + generateSuccessResponse() ); testTransportHandler('event1'); clock.tick(INITIAL_SEND_TIME_DELAY_MS); expect(fetchStub).to.have.been.calledOnce; }); + + it('sends up to the maximum event limit in one request', async () => { + // Arrange + const setting = SettingsService.getInstance(); + const flTransportFullUrl = + setting.flTransportEndpointUrl + '?key=' + setting.transportKey; + + // Returns successful response from fl for logRequests. + const response = generateSuccessResponse(); + stub(response, 'json').resolves(JSON.parse(generateSuccessResponseBody())); + fetchStub.resolves(response); + + // Act + // Generate 1020 events, which should be dispatched in two batches (1000 events and 20 events). + for (let i = 0; i < 1020; i++) { + testTransportHandler('event' + i); + } + // Wait for first and second event dispatch to happen. + clock.tick(INITIAL_SEND_TIME_DELAY_MS); + // This is to resolve the floating promise chain in transport service. + await Promise.resolve().then().then().then(); + clock.tick(DEFAULT_SEND_INTERVAL_MS); + + // Assert + // Expects the first logRequest which contains first 1000 events. + const firstLogRequest = generateLogRequest('5501'); + for (let i = 0; i < MAX_EVENT_COUNT_PER_REQUEST; i++) { + firstLogRequest['log_event'].push({ + 'source_extension_json_proto3': 'event' + i, + 'event_time_ms': '1' + }); + } + expect(fetchStub).which.to.have.been.calledWith(flTransportFullUrl, { + method: 'POST', + body: JSON.stringify(firstLogRequest) + }); + // Expects the second logRequest which contains remaining 20 events; + const secondLogRequest = generateLogRequest('15501'); + for (let i = 0; i < 20; i++) { + secondLogRequest['log_event'].push({ + 'source_extension_json_proto3': + 'event' + (MAX_EVENT_COUNT_PER_REQUEST + i), + 'event_time_ms': '1' + }); + } + expect(fetchStub).calledWith(flTransportFullUrl, { + method: 'POST', + body: JSON.stringify(secondLogRequest) + }); + }); + + function generateLogRequest(requestTimeMs: string): any { + return { + 'request_time_ms': requestTimeMs, + 'client_info': { + 'client_type': 1, + 'js_client_info': {} + }, + 'log_source': 462, + 'log_event': [] as any + }; + } + + function generateSuccessResponse(): Response { + return new Response(generateSuccessResponseBody(), { + status: 200, + headers: { 'Content-type': 'application/json' } + }); + } + + function generateSuccessResponseBody(): string { + return ( + '{\ + "nextRequestWaitMillis": "' + + TRANSPORT_DELAY_INTERVAL + + '",\ + "logResponseDetails": [\ + {\ + "responseAction": "DELETE_REQUEST"\ + }\ + ]\ + }' + ); + } }); diff --git a/packages-exp/performance-exp/src/services/transport_service.ts b/packages-exp/performance-exp/src/services/transport_service.ts index cc1328fe637..87f8efab773 100644 --- a/packages-exp/performance-exp/src/services/transport_service.ts +++ b/packages-exp/performance-exp/src/services/transport_service.ts @@ -23,6 +23,7 @@ const DEFAULT_SEND_INTERVAL_MS = 10 * 1000; const INITIAL_SEND_TIME_DELAY_MS = 5.5 * 1000; // If end point does not work, the call will be tried for these many times. const DEFAULT_REMAINING_TRIES = 3; +const MAX_EVENT_COUNT_PER_REQUEST = 1000; let remainingTries = DEFAULT_REMAINING_TRIES; interface LogResponseDetails { @@ -90,9 +91,10 @@ function processQueue(timeOffset: number): void { } function dispatchQueueEvents(): void { - // Capture a snapshot of the queue and empty the "official queue". - const staged = [...queue]; - queue = []; + // Extract events up to the maximum cap of single logRequest from top of "official queue". + // The staged events will be used for current logRequest attempt, remaining events will be kept + // for next attempt. + const staged = queue.splice(0, MAX_EVENT_COUNT_PER_REQUEST); /* eslint-disable camelcase */ // We will pass the JSON serialized event to the backend. diff --git a/packages-exp/performance-exp/src/utils/app_utils.ts b/packages-exp/performance-exp/src/utils/app_utils.ts index aff8e0c1379..0e34c74b61c 100644 --- a/packages-exp/performance-exp/src/utils/app_utils.ts +++ b/packages-exp/performance-exp/src/utils/app_utils.ts @@ -16,7 +16,7 @@ */ import { ERROR_FACTORY, ErrorCode } from './errors'; -import { FirebaseApp } from '@firebase/app-types-exp'; +import { FirebaseApp } from '@firebase/app-exp'; export function getAppId(firebaseApp: FirebaseApp): string { const appId = firebaseApp.options?.appId; diff --git a/packages-exp/performance-exp/src/utils/errors.ts b/packages-exp/performance-exp/src/utils/errors.ts index 942a5571d87..92e4053144b 100644 --- a/packages-exp/performance-exp/src/utils/errors.ts +++ b/packages-exp/performance-exp/src/utils/errors.ts @@ -33,7 +33,8 @@ export const enum ErrorCode { INVALID_ATTRIBUTE_NAME = 'invalid attribute name', INVALID_ATTRIBUTE_VALUE = 'invalid attribute value', INVALID_CUSTOM_METRIC_NAME = 'invalid custom metric name', - INVALID_STRING_MERGER_PARAMETER = 'invalid String merger input' + INVALID_STRING_MERGER_PARAMETER = 'invalid String merger input', + ALREADY_INITIALIZED = 'already initialized' } const ERROR_DESCRIPTION_MAP: { readonly [key in ErrorCode]: string } = { @@ -58,7 +59,8 @@ const ERROR_DESCRIPTION_MAP: { readonly [key in ErrorCode]: string } = { [ErrorCode.INVALID_CUSTOM_METRIC_NAME]: 'Custom metric name {$customMetricName} is invalid', [ErrorCode.INVALID_STRING_MERGER_PARAMETER]: - 'Input for String merger is invalid, contact support team to resolve.' + 'Input for String merger is invalid, contact support team to resolve.', + [ErrorCode.ALREADY_INITIALIZED]: 'Performance can only be initialized once.' }; interface ErrorParams { diff --git a/packages-exp/performance-exp/src/utils/string_merger.test.ts b/packages-exp/performance-exp/src/utils/string_merger.test.ts index ded5472e580..a82799f315e 100644 --- a/packages-exp/performance-exp/src/utils/string_merger.test.ts +++ b/packages-exp/performance-exp/src/utils/string_merger.test.ts @@ -19,13 +19,11 @@ import { expect } from 'chai'; import { mergeStrings } from './string_merger'; import { FirebaseError } from '@firebase/util'; -// import { ERROR_FACTORY, ErrorCode } from './errors'; import '../../test/setup'; describe('Firebase Performance > string_merger', () => { describe('#mergeStrings', () => { it('Throws exception when string length has | diff | > 1', () => { - // const expectedError = ERROR_FACTORY.create(ErrorCode.INVALID_STRING_MERGER_PARAMETER); expect(() => mergeStrings('', '123')).to.throw( FirebaseError, 'performance/invalid String merger input' diff --git a/packages-exp/performance-types-exp/api-extractor.json b/packages-exp/performance-types-exp/api-extractor.json deleted file mode 100644 index 42f37a88c4b..00000000000 --- a/packages-exp/performance-types-exp/api-extractor.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "../../config/api-extractor.json", - // Point it to your entry point d.ts file. - "mainEntryPointFilePath": "/index.d.ts" -} \ No newline at end of file diff --git a/packages-exp/performance-types-exp/index.d.ts b/packages-exp/performance-types-exp/index.d.ts deleted file mode 100644 index cf65771a0f1..00000000000 --- a/packages-exp/performance-types-exp/index.d.ts +++ /dev/null @@ -1,123 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @public - */ -export interface PerformanceSettings { - /** Whether to collect custom events. */ - dataCollectionEnabled?: boolean; - - /** Whether to collect out of the box events. */ - instrumentationEnabled?: boolean; -} - -export interface FirebasePerformance { - /** - * Controls the logging of automatic traces and HTTP/S network monitoring. - */ - instrumentationEnabled: boolean; - - /** - * Controls the logging of custom traces. - */ - dataCollectionEnabled: boolean; -} - -export interface PerformanceTrace { - /** - * Starts the timing for the trace instance. - */ - start(): void; - /** - * Stops the timing of the trace instance and logs the data of the instance. - */ - stop(): void; - /** - * Records a trace from given parameters. This provides a direct way to use trace without a need to - * start/stop. This is useful for use cases in which the trace cannot directly be used - * (e.g. if the duration was captured before the Performance SDK was loaded). - * - * @param startTime trace start time since epoch in millisec. - * @param duration The duraction of the trace in millisec. - * @param options An object which can optionally hold maps of custom metrics and - * custom attributes. - */ - record( - startTime: number, - duration: number, - options?: { - metrics?: { [key: string]: number }; - attributes?: { [key: string]: string }; - } - ): void; - /** - * Adds to the value of a custom metric. If a custom metric with the provided name does not - * exist, it creates one with that name and the value equal to the given number. The value will be floored down to an - * integer. - * - * @param metricName The name of the custom metric. - * @param num The number to be added to the value of the custom metric. If not provided, it - * uses a default value of one. - */ - incrementMetric(metricName: string, num?: number): void; - /** - * Sets the value of the specified custom metric to the given number regardless of whether - * a metric with that name already exists on the trace instance or not. The value will be floored down to an - * integer. - * - * @param metricName Name of the custom metric. - * @param num Value to of the custom metric. - */ - putMetric(metricName: string, num: number): void; - /** - * Returns the value of the custom metric by that name. If a custom metric with that name does - * not exist will return zero. - * - * @param metricName Name of the custom metric. - */ - getMetric(metricName: string): number; - /** - * Set a custom attribute of a trace to a certain value. - * - * @param attr Name of the custom attribute. - * @param value Value of the custom attribute. - */ - putAttribute(attr: string, value: string): void; - /** - * Retrieves the value which a custom attribute is set to. - * - * @param attr Name of the custom attribute. - */ - getAttribute(attr: string): string | undefined; - /** - * Removes the specified custom attribute from a trace instance. - * - * @param attr Name of the custom attribute. - */ - removeAttribute(attr: string): void; - /** - * Returns a map of all custom attributes of a trace instance. - */ - getAttributes(): { [key: string]: string }; -} - -declare module '@firebase/component' { - interface NameServiceMapping { - 'performance-exp': FirebasePerformance; - } -} diff --git a/packages-exp/performance-types-exp/package.json b/packages-exp/performance-types-exp/package.json deleted file mode 100644 index 17e2c820650..00000000000 --- a/packages-exp/performance-types-exp/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "@firebase/performance-types-exp", - "version": "0.0.800", - "description": "@firebase/performance Types", - "author": "Firebase (https://firebase.google.com/)", - "private": true, - "license": "Apache-2.0", - "scripts": { - "test": "tsc", - "test:ci": "node ../../scripts/run_tests_in_ci.js", - "api-report": "api-extractor run --local --verbose", - "predoc": "node ../../scripts/exp/remove-exp.js temp", - "doc": "api-documenter markdown --input temp --output docs", - "build:doc": "yarn api-report && yarn doc" - }, - "files": [ - "index.d.ts" - ], - "devDependencies": { - "typescript": "4.0.2" - }, - "repository": { - "directory": "packages/performance-types-exp", - "type": "git", - "url": "https://github.com/firebase/firebase-js-sdk.git" - }, - "bugs": { - "url": "https://github.com/firebase/firebase-js-sdk/issues" - } -} diff --git a/packages-exp/remote-config-compat/.eslintrc.js b/packages-exp/remote-config-compat/.eslintrc.js new file mode 100644 index 00000000000..ca80aa0f69a --- /dev/null +++ b/packages-exp/remote-config-compat/.eslintrc.js @@ -0,0 +1,26 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +module.exports = { + extends: '../../config/.eslintrc.js', + parserOptions: { + project: 'tsconfig.json', + // to make vscode-eslint work with monorepo + // https://github.com/typescript-eslint/typescript-eslint/issues/251#issuecomment-463943250 + tsconfigRootDir: __dirname + } +}; diff --git a/packages-exp/remote-config-compat/README.md b/packages-exp/remote-config-compat/README.md new file mode 100644 index 00000000000..2813f0ae9d4 --- /dev/null +++ b/packages-exp/remote-config-compat/README.md @@ -0,0 +1,5 @@ +# @firebase/remote-compat + +This is the compat package that recreates the v8 APIs. + +**This package is not intended for direct usage, and should only be used via the officially supported [firebase](https://www.npmjs.com/package/firebase) package.** diff --git a/packages-exp/remote-config-compat/karma.conf.js b/packages-exp/remote-config-compat/karma.conf.js new file mode 100644 index 00000000000..c0737457c55 --- /dev/null +++ b/packages-exp/remote-config-compat/karma.conf.js @@ -0,0 +1,35 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const karmaBase = require('../../config/karma.base'); + +const files = ['test/**/*', 'src/**/*.test.ts']; + +module.exports = function (config) { + const karmaConfig = Object.assign({}, karmaBase, { + // files to load into karma + files: files, + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + preprocessors: { '**/*.ts': ['webpack', 'sourcemap'] }, + frameworks: ['mocha'] + }); + + config.set(karmaConfig); +}; + +module.exports.files = files; diff --git a/packages-exp/remote-config-compat/package.json b/packages-exp/remote-config-compat/package.json new file mode 100644 index 00000000000..84d54734382 --- /dev/null +++ b/packages-exp/remote-config-compat/package.json @@ -0,0 +1,63 @@ +{ + "name": "@firebase/remote-config-compat", + "version": "0.0.900", + "description": "The compatibility package of Remote Config", + "author": "Firebase (https://firebase.google.com/)", + "private": true, + "main": "dist/index.cjs.js", + "browser": "dist/index.esm2017.js", + "module": "dist/index.esm2017.js", + "files": [ + "dist" + ], + "scripts": { + "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "build": "rollup -c", + "build:release": "rollup -c rollup.config.release.js && yarn add-compat-overloads", + "build:deps": "lerna run --scope @firebase/remote-config-compat --include-dependencies build", + "dev": "rollup -c -w", + "test": "run-p lint test:all", + "test:all": "run-p test:browser", + "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:all", + "test:browser": "karma start --single-run", + "test:browser:debug": "karma start --browsers Chrome --auto-watch", + "add-compat-overloads": "ts-node-script ../../scripts/exp/create-overloads.ts -i ../remote-config-exp/dist/remote-config-exp-public.d.ts -o dist/src/index.d.ts -a -r RemoteConfig:RemoteConfigCompat -r FirebaseApp:FirebaseAppCompat --moduleToEnhance @firebase/remote-config" + }, + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-compat": "0.x" + }, + "dependencies": { + "@firebase/remote-config-exp": "0.0.900", + "@firebase/remote-config-types": "0.1.9", + "@firebase/util": "1.1.0", + "@firebase/logger": "0.2.6", + "@firebase/component": "0.5.4", + "tslib": "^2.1.0" + }, + "devDependencies": { + "rollup": "2.52.2", + "@rollup/plugin-json": "4.1.0", + "rollup-plugin-replace": "2.2.0", + "rollup-plugin-typescript2": "0.30.0", + "typescript": "4.2.2", + "@firebase/app-compat": "0.0.900" + }, + "repository": { + "directory": "packages-exp/remote-config-compat", + "type": "git", + "url": "https://github.com/firebase/firebase-js-sdk.git" + }, + "bugs": { + "url": "https://github.com/firebase/firebase-js-sdk/issues" + }, + "typings": "dist/src/index.d.ts", + "nyc": { + "extension": [ + ".ts" + ], + "reportDir": "./coverage/node" + }, + "esm5": "dist/index.esm5.js" +} \ No newline at end of file diff --git a/packages-exp/remote-config-compat/rollup.config.js b/packages-exp/remote-config-compat/rollup.config.js new file mode 100644 index 00000000000..4df9d8a2f48 --- /dev/null +++ b/packages-exp/remote-config-compat/rollup.config.js @@ -0,0 +1,60 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import typescriptPlugin from 'rollup-plugin-typescript2'; +import typescript from 'typescript'; +import json from '@rollup/plugin-json'; +import { es5BuildsNoPlugin, es2017BuildsNoPlugin } from './rollup.shared.js'; + +/** + * ES5 Builds + */ +const es5BuildPlugins = [ + typescriptPlugin({ + typescript + }), + json() +]; + +const es5Builds = es5BuildsNoPlugin.map(build => ({ + ...build, + plugins: es5BuildPlugins +})); + +/** + * ES2017 Builds + */ +const es2017BuildPlugins = [ + typescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017' + } + } + }), + json({ + preferConst: true + }) +]; + +const es2017Builds = es2017BuildsNoPlugin.map(build => ({ + ...build, + plugins: es2017BuildPlugins +})); + +export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/remote-config-compat/rollup.config.release.js b/packages-exp/remote-config-compat/rollup.config.release.js new file mode 100644 index 00000000000..b0b1dc5154a --- /dev/null +++ b/packages-exp/remote-config-compat/rollup.config.release.js @@ -0,0 +1,67 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import typescriptPlugin from 'rollup-plugin-typescript2'; +import typescript from 'typescript'; +import json from '@rollup/plugin-json'; +import { es5BuildsNoPlugin, es2017BuildsNoPlugin } from './rollup.shared.js'; +import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; + +/** + * ES5 Builds + */ +const es5BuildPlugins = [ + typescriptPlugin({ + typescript, + clean: true, + abortOnError: false, + transformers: [importPathTransformer] + }), + json() +]; + +const es5Builds = es5BuildsNoPlugin.map(build => ({ + ...build, + plugins: es5BuildPlugins +})); + +/** + * ES2017 Builds + */ +const es2017BuildPlugins = [ + typescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017' + } + }, + clean: true, + abortOnError: false, + transformers: [importPathTransformer] + }), + json({ + preferConst: true + }) +]; + +const es2017Builds = es2017BuildsNoPlugin.map(build => ({ + ...build, + plugins: es2017BuildPlugins +})); + +export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/remote-config-compat/rollup.shared.js b/packages-exp/remote-config-compat/rollup.shared.js new file mode 100644 index 00000000000..3a873a01807 --- /dev/null +++ b/packages-exp/remote-config-compat/rollup.shared.js @@ -0,0 +1,52 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import pkg from './package.json'; + +const deps = [ + ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), + '@firebase/remote-config' +]; + +export const es5BuildsNoPlugin = [ + /** + * Browser Builds + */ + { + input: 'src/index.ts', + output: [ + { file: pkg.main, format: 'cjs', sourcemap: true }, + { file: pkg.esm5, format: 'es', sourcemap: true } + ], + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + } +]; + +export const es2017BuildsNoPlugin = [ + /** + * Browser Builds + */ + { + input: 'src/index.ts', + output: { + file: pkg.browser, + format: 'es', + sourcemap: true + }, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + } +]; diff --git a/packages-exp/remote-config-compat/src/index.ts b/packages-exp/remote-config-compat/src/index.ts new file mode 100644 index 00000000000..88de17406cf --- /dev/null +++ b/packages-exp/remote-config-compat/src/index.ts @@ -0,0 +1,74 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import firebase, { _FirebaseNamespace } from '@firebase/app-compat'; +import { + Component, + ComponentContainer, + ComponentType, + InstanceFactoryOptions +} from '@firebase/component'; +import { RemoteConfigCompatImpl } from './remoteConfig'; +import { name as packageName, version } from '../package.json'; +import { RemoteConfig as RemoteConfigCompat } from '@firebase/remote-config-types'; + +// TODO: move it to remote-config-types package +declare module '@firebase/component' { + interface NameServiceMapping { + 'remoteConfig-compat': RemoteConfigCompat; + } +} + +function registerRemoteConfigCompat( + firebaseInstance: _FirebaseNamespace +): void { + firebaseInstance.INTERNAL.registerComponent( + new Component( + 'remoteConfig-compat', + remoteConfigFactory, + ComponentType.PUBLIC + ).setMultipleInstances(true) + ); + + firebaseInstance.registerVersion(packageName, version); +} + +function remoteConfigFactory( + container: ComponentContainer, + { instanceIdentifier: namespace }: InstanceFactoryOptions +): RemoteConfigCompatImpl { + const app = container.getProvider('app-compat').getImmediate(); + // The following call will always succeed because rc `import {...} from '@firebase/remote-config-exp'` + const remoteConfig = container.getProvider('remote-config-exp').getImmediate({ + identifier: namespace + }); + + return new RemoteConfigCompatImpl(app, remoteConfig); +} + +registerRemoteConfigCompat(firebase as _FirebaseNamespace); + +declare module '@firebase/app-compat' { + interface FirebaseNamespace { + remoteConfig: { + (app?: FirebaseApp): RemoteConfigCompat; + }; + } + interface FirebaseApp { + remoteConfig(): RemoteConfigCompat; + } +} diff --git a/packages-exp/remote-config-compat/src/remoteConfig.test.ts b/packages-exp/remote-config-compat/src/remoteConfig.test.ts new file mode 100644 index 00000000000..0d85990935e --- /dev/null +++ b/packages-exp/remote-config-compat/src/remoteConfig.test.ts @@ -0,0 +1,161 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import '../test/setup'; +import { expect } from 'chai'; +import { stub } from 'sinon'; +import { RemoteConfigCompatImpl } from './remoteConfig'; +import { getFakeApp, getFakeModularRemoteConfig } from '../test/util'; +import * as modularApi from '@firebase/remote-config-exp'; + +describe('Remote Config Compat', () => { + let remoteConfig!: RemoteConfigCompatImpl; + const fakeModularRemoteConfig = getFakeModularRemoteConfig(); + before(() => { + remoteConfig = new RemoteConfigCompatImpl( + getFakeApp(), + fakeModularRemoteConfig + ); + }); + + it('activate() calls modular activate()', async () => { + const modularActivateStub = stub(modularApi, 'activate').callsFake(() => + Promise.resolve(true) + ); + const res = await remoteConfig.activate(); + + expect(res).to.equal(res); + expect(modularActivateStub).to.have.been.calledWithExactly( + fakeModularRemoteConfig + ); + }); + + it('ensureInitialized() calls modular ensureInitialized()', async () => { + const modularEnsureInitializedStub = stub( + modularApi, + 'ensureInitialized' + ).callsFake(() => Promise.resolve()); + await remoteConfig.ensureInitialized(); + + expect(modularEnsureInitializedStub).to.have.been.calledWithExactly( + fakeModularRemoteConfig + ); + }); + + it('fetch() calls modular fetchConfig()', async () => { + const modularFetchStub = stub(modularApi, 'fetchConfig').callsFake(() => + Promise.resolve() + ); + await remoteConfig.fetch(); + + expect(modularFetchStub).to.have.been.calledWithExactly( + fakeModularRemoteConfig + ); + }); + + it('fetchAndActivate() calls modular fetchAndActivate()', async () => { + const modularFetchAndActivateStub = stub( + modularApi, + 'fetchAndActivate' + ).callsFake(() => Promise.resolve(true)); + const res = await remoteConfig.fetchAndActivate(); + + expect(res).to.equal(true); + expect(modularFetchAndActivateStub).to.have.been.calledWithExactly( + fakeModularRemoteConfig + ); + }); + + it('getAll() calls modular getAll()', () => { + const allValues = {}; + const modularGetAllStub = stub(modularApi, 'getAll').callsFake( + () => allValues + ); + + const res = remoteConfig.getAll(); + + expect(res).to.equal(allValues); + expect(modularGetAllStub).to.have.been.calledWithExactly( + fakeModularRemoteConfig + ); + }); + + it('getBoolean() calls modular getBoolean()', () => { + const modularGetBoolean = stub(modularApi, 'getBoolean').callsFake( + () => false + ); + + const res = remoteConfig.getBoolean('myKey'); + + expect(res).to.equal(false); + expect(modularGetBoolean).to.have.been.calledWithExactly( + fakeModularRemoteConfig, + 'myKey' + ); + }); + + it('getNumber() calls modular getNumber()', () => { + const modularGetNumber = stub(modularApi, 'getNumber').callsFake(() => 123); + const res = remoteConfig.getNumber('myNumKey'); + + expect(res).to.equal(123); + expect(modularGetNumber).to.have.been.calledWithExactly( + fakeModularRemoteConfig, + 'myNumKey' + ); + }); + + it('getString() calls modular getString()', () => { + const modularGetString = stub(modularApi, 'getString').callsFake( + () => 'abc' + ); + const res = remoteConfig.getString('myStrKey'); + + expect(res).to.equal('abc'); + expect(modularGetString).to.have.been.calledWithExactly( + fakeModularRemoteConfig, + 'myStrKey' + ); + }); + + it('getValue() calls modular getValue()', () => { + const fakeValue = {} as modularApi.Value; + const modularGetValue = stub(modularApi, 'getValue').callsFake( + () => fakeValue + ); + const res = remoteConfig.getValue('myValKey'); + + expect(res).to.equal(fakeValue); + expect(modularGetValue).to.have.been.calledWithExactly( + fakeModularRemoteConfig, + 'myValKey' + ); + }); + + it('setLogLevel() calls modular setLogLevel()', () => { + const modularSetLogLevel = stub( + modularApi, + 'setLogLevel' + ).callsFake(() => {}); + remoteConfig.setLogLevel('debug'); + + expect(modularSetLogLevel).to.have.been.calledWithExactly( + fakeModularRemoteConfig, + 'debug' + ); + }); +}); diff --git a/packages-exp/remote-config-compat/src/remoteConfig.ts b/packages-exp/remote-config-compat/src/remoteConfig.ts new file mode 100644 index 00000000000..61b02ce62d8 --- /dev/null +++ b/packages-exp/remote-config-compat/src/remoteConfig.ts @@ -0,0 +1,114 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FirebaseApp, _FirebaseService } from '@firebase/app-compat'; +import { + Value as ValueCompat, + FetchStatus as FetchSTatusCompat, + Settings as SettingsCompat, + LogLevel as RemoteConfigLogLevel, + RemoteConfig as RemoteConfigCompat +} from '@firebase/remote-config-types'; +import { + RemoteConfig, + setLogLevel, + activate, + ensureInitialized, + fetchAndActivate, + fetchConfig, + getAll, + getBoolean, + getNumber, + getString, + getValue +} from '@firebase/remote-config-exp'; + +export class RemoteConfigCompatImpl + implements RemoteConfigCompat, _FirebaseService { + constructor(public app: FirebaseApp, readonly _delegate: RemoteConfig) {} + + get defaultConfig(): { [key: string]: string | number | boolean } { + return this._delegate.defaultConfig; + } + + set defaultConfig(value: { [key: string]: string | number | boolean }) { + this._delegate.defaultConfig = value; + } + + get fetchTimeMillis(): number { + return this._delegate.fetchTimeMillis; + } + + get lastFetchStatus(): FetchSTatusCompat { + return this._delegate.lastFetchStatus; + } + + get settings(): SettingsCompat { + return this._delegate.settings; + } + + set settings(value: SettingsCompat) { + this._delegate.settings = value; + } + + activate(): Promise { + return activate(this._delegate); + } + + ensureInitialized(): Promise { + return ensureInitialized(this._delegate); + } + + /** + * @throws a {@link ErrorCode.FETCH_CLIENT_TIMEOUT} if the request takes longer than + * {@link Settings.fetchTimeoutInSeconds} or + * {@link DEFAULT_FETCH_TIMEOUT_SECONDS}. + */ + fetch(): Promise { + return fetchConfig(this._delegate); + } + + fetchAndActivate(): Promise { + return fetchAndActivate(this._delegate); + } + + getAll(): { [key: string]: ValueCompat } { + return getAll(this._delegate); + } + + getBoolean(key: string): boolean { + return getBoolean(this._delegate, key); + } + + getNumber(key: string): number { + return getNumber(this._delegate, key); + } + + getString(key: string): string { + return getString(this._delegate, key); + } + + getValue(key: string): ValueCompat { + return getValue(this._delegate, key); + } + + // Based on packages/firestore/src/util/log.ts but not static because we need per-instance levels + // to differentiate 2p and 3p use-cases. + setLogLevel(logLevel: RemoteConfigLogLevel): void { + setLogLevel(this._delegate, logLevel); + } +} diff --git a/packages-exp/remote-config-compat/test/setup.ts b/packages-exp/remote-config-compat/test/setup.ts new file mode 100644 index 00000000000..b1e3136529f --- /dev/null +++ b/packages-exp/remote-config-compat/test/setup.ts @@ -0,0 +1,26 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { use } from 'chai'; +import { restore } from 'sinon'; +import * as sinonChai from 'sinon-chai'; + +use(sinonChai); + +afterEach(async () => { + restore(); +}); diff --git a/packages-exp/remote-config-compat/test/util.ts b/packages-exp/remote-config-compat/test/util.ts new file mode 100644 index 00000000000..387b6bdd4b1 --- /dev/null +++ b/packages-exp/remote-config-compat/test/util.ts @@ -0,0 +1,49 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FirebaseApp } from '@firebase/app-compat'; +import { RemoteConfig } from '@firebase/remote-config-exp'; + +export function getFakeApp(): FirebaseApp { + return { + name: 'appName', + options: { + apiKey: 'apiKey', + projectId: 'projectId', + authDomain: 'authDomain', + messagingSenderId: 'messagingSenderId', + databaseURL: 'databaseUrl', + storageBucket: 'storageBucket', + appId: '1:777777777777:web:d93b5ca1475efe57' + }, + automaticDataCollectionEnabled: true, + delete: async () => {}, + remoteConfig: (() => null as unknown) as FirebaseApp['remoteConfig'] + }; +} + +export function getFakeModularRemoteConfig(): RemoteConfig { + return { + defaultConfig: {}, + fetchTimeMillis: 0, + lastFetchStatus: 'no-fetch-yet', + settings: { + fetchTimeoutMillis: 0, + minimumFetchIntervalMillis: 0 + } + }; +} diff --git a/packages-exp/remote-config-compat/tsconfig.json b/packages-exp/remote-config-compat/tsconfig.json new file mode 100644 index 00000000000..72e0736c2b7 --- /dev/null +++ b/packages-exp/remote-config-compat/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../config/tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "resolveJsonModule": true, + "downlevelIteration": true + }, + "exclude": ["dist/**/*"] +} diff --git a/packages-exp/remote-config-exp/.eslintrc.js b/packages-exp/remote-config-exp/.eslintrc.js new file mode 100644 index 00000000000..5a8c4b909c2 --- /dev/null +++ b/packages-exp/remote-config-exp/.eslintrc.js @@ -0,0 +1,26 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +module.exports = { + 'extends': '../../config/.eslintrc.js', + 'parserOptions': { + project: 'tsconfig.json', + // to make vscode-eslint work with monorepo + // https://github.com/typescript-eslint/typescript-eslint/issues/251#issuecomment-463943250 + tsconfigRootDir: __dirname + } +}; diff --git a/packages-exp/remote-config-exp/.npmignore b/packages-exp/remote-config-exp/.npmignore new file mode 100644 index 00000000000..6de0b6d2896 --- /dev/null +++ b/packages-exp/remote-config-exp/.npmignore @@ -0,0 +1 @@ +# This file is left intentionally blank \ No newline at end of file diff --git a/packages-exp/remote-config-exp/README.md b/packages-exp/remote-config-exp/README.md new file mode 100644 index 00000000000..1b21bbecc51 --- /dev/null +++ b/packages-exp/remote-config-exp/README.md @@ -0,0 +1,27 @@ +# @firebase/remote-config + +This is the [Remote Config](https://firebase.google.com/docs/remote-config/) component of the +[Firebase JS SDK](https://www.npmjs.com/package/firebase). + +**This package is not intended for direct usage, and should only be used via the officially +supported [firebase](https://www.npmjs.com/package/firebase) package.** + +## Contributing + +Setup: + +1. Run `yarn` in repo root + +Format: + +1. Run `yarn prettier` in RC package + +Unit test: + +1. Run `yarn test` in RC package + +End-to-end test: + +1. Run `yarn build` in RC package +1. Run `yarn build` in Firebase package +1. Open test_app/index.html in a browser diff --git a/packages-exp/remote-config-exp/api-extractor.json b/packages-exp/remote-config-exp/api-extractor.json new file mode 100644 index 00000000000..8a3c6cb251e --- /dev/null +++ b/packages-exp/remote-config-exp/api-extractor.json @@ -0,0 +1,10 @@ +{ + "extends": "../../config/api-extractor.json", + // Point it to your entry point d.ts file. + "mainEntryPointFilePath": "/dist/src/index.d.ts", + "dtsRollup": { + "enabled": true, + "untrimmedFilePath": "/dist/.d.ts", + "publicTrimmedFilePath": "/dist/-public.d.ts" + } +} \ No newline at end of file diff --git a/packages-exp/remote-config-exp/karma.conf.js b/packages-exp/remote-config-exp/karma.conf.js new file mode 100644 index 00000000000..5006cd5a4ea --- /dev/null +++ b/packages-exp/remote-config-exp/karma.conf.js @@ -0,0 +1,36 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const karma = require('karma'); +const path = require('path'); +const karmaBase = require('../../config/karma.base'); + +const files = [`test/**/*`]; + +module.exports = function (config) { + const karmaConfig = Object.assign({}, karmaBase, { + // files to load into karma + files: files, + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['mocha'] + }); + + config.set(karmaConfig); +}; + +module.exports.files = files; diff --git a/packages-exp/remote-config-exp/package.json b/packages-exp/remote-config-exp/package.json new file mode 100644 index 00000000000..e07410960cb --- /dev/null +++ b/packages-exp/remote-config-exp/package.json @@ -0,0 +1,65 @@ +{ + "name": "@firebase/remote-config-exp", + "version": "0.0.900", + "description": "The Remote Config package of the Firebase JS SDK", + "author": "Firebase (https://firebase.google.com/)", + "private": true, + "main": "dist/index.cjs.js", + "browser": "dist/index.esm2017.js", + "module": "dist/index.esm2017.js", + "files": [ + "dist" + ], + "scripts": { + "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "build": "rollup -c && yarn api-report", + "build:deps": "lerna run --scope @firebase/remote-config-exp --include-dependencies build", + "build:release": "rollup -c rollup.config.release.js && yarn api-report && yarn typings:public", + "dev": "rollup -c -w", + "test": "run-p lint test:browser", + "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:browser", + "test:browser": "karma start --single-run", + "test:debug": "karma start --browsers=Chrome --auto-watch", + "prettier": "prettier --write '{src,test}/**/*.{js,ts}'", + "api-report": "api-extractor run --local --verbose", + "predoc": "node ../../scripts/exp/remove-exp.js temp", + "doc": "api-documenter markdown --input temp --output docs", + "build:doc": "yarn build && yarn doc", + "typings:public": "node ../../scripts/exp/use_typings.js ./dist/remote-config-exp-public.d.ts", + "typings:internal": "node ../../scripts/exp/use_typings.js ./dist/src/index.d.ts" + }, + "peerDependencies": { + "@firebase/app-exp": "0.x" + }, + "dependencies": { + "@firebase/installations-exp": "0.0.900", + "@firebase/logger": "0.2.6", + "@firebase/util": "1.1.0", + "@firebase/component": "0.5.4", + "tslib": "^2.1.0" + }, + "license": "Apache-2.0", + "devDependencies": { + "@firebase/app-exp": "0.0.900", + "rollup": "2.52.2", + "rollup-plugin-typescript2": "0.30.0", + "typescript": "4.2.2" + }, + "repository": { + "directory": "packages/remote-config", + "type": "git", + "url": "https://github.com/firebase/firebase-js-sdk.git" + }, + "bugs": { + "url": "https://github.com/firebase/firebase-js-sdk/issues" + }, + "typings": "dist/src/index.d.ts", + "nyc": { + "extension": [ + ".ts" + ], + "reportDir": "./coverage/node" + }, + "esm5": "dist/index.esm.js" +} \ No newline at end of file diff --git a/packages-exp/remote-config-exp/rollup.config.js b/packages-exp/remote-config-exp/rollup.config.js new file mode 100644 index 00000000000..b57e5ad73ee --- /dev/null +++ b/packages-exp/remote-config-exp/rollup.config.js @@ -0,0 +1,58 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import json from '@rollup/plugin-json'; // Enables package.json import in TypeScript. +import typescriptPlugin from 'rollup-plugin-typescript2'; +import typescript from 'typescript'; +import { es2017BuildsNoPlugin, es5BuildsNoPlugin } from './rollup.shared'; + +/** + * ES5 Builds + */ +const es5BuildPlugins = [ + typescriptPlugin({ + typescript + }), + json() +]; + +const es5Builds = es5BuildsNoPlugin.map(build => ({ + ...build, + plugins: es5BuildPlugins +})); + +/** + * ES2017 Builds + */ +const es2017BuildPlugins = [ + typescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017' + } + } + }), + json({ preferConst: true }) +]; + +const es2017Builds = es2017BuildsNoPlugin.map(build => ({ + ...build, + plugins: es2017BuildPlugins +})); + +export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/remote-config-exp/rollup.config.release.js b/packages-exp/remote-config-exp/rollup.config.release.js new file mode 100644 index 00000000000..7e38601f87c --- /dev/null +++ b/packages-exp/remote-config-exp/rollup.config.release.js @@ -0,0 +1,73 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import typescriptPlugin from 'rollup-plugin-typescript2'; +import typescript from 'typescript'; +import json from '@rollup/plugin-json'; +import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; +import { es2017BuildsNoPlugin, es5BuildsNoPlugin } from './rollup.shared'; + +/** + * ES5 Builds + */ +const es5BuildPlugins = [ + typescriptPlugin({ + typescript, + clean: true, + abortOnError: false, + transformers: [importPathTransformer] + }), + json() +]; + +const es5Builds = es5BuildsNoPlugin.map(build => ({ + ...build, + plugins: es5BuildPlugins, + treeshake: { + moduleSideEffects: (id, external) => id === '@firebase/installations' + } +})); + +/** + * ES2017 Builds + */ +const es2017BuildPlugins = [ + typescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017' + } + }, + abortOnError: false, + clean: true, + transformers: [importPathTransformer] + }), + json({ + preferConst: true + }) +]; + +const es2017Builds = es2017BuildsNoPlugin.map(build => ({ + ...build, + plugins: es2017BuildPlugins, + treeshake: { + moduleSideEffects: (id, external) => id === '@firebase/installations' + } +})); + +export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/remote-config-exp/rollup.shared.js b/packages-exp/remote-config-exp/rollup.shared.js new file mode 100644 index 00000000000..397949ded6b --- /dev/null +++ b/packages-exp/remote-config-exp/rollup.shared.js @@ -0,0 +1,54 @@ +/** + * @license + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import pkg from './package.json'; + +const deps = [ + ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), + '@firebase/app' +]; + +export const es5BuildsNoPlugin = [ + /** + * Browser Builds + */ + { + input: 'src/index.ts', + output: [ + { file: pkg.main, format: 'cjs', sourcemap: true }, + { file: pkg.esm5, format: 'es', sourcemap: true } + ], + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + } +]; + +/** + * ES2017 Builds + */ +export const es2017BuildsNoPlugin = [ + { + /** + * Browser Build + */ + input: 'src/index.ts', + output: { + file: pkg.browser, + format: 'es', + sourcemap: true + }, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + } +]; diff --git a/packages-exp/remote-config-exp/src/api.ts b/packages-exp/remote-config-exp/src/api.ts new file mode 100644 index 00000000000..bf7937faeb0 --- /dev/null +++ b/packages-exp/remote-config-exp/src/api.ts @@ -0,0 +1,260 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { _getProvider, FirebaseApp, getApp } from '@firebase/app-exp'; +import { + LogLevel as RemoteConfigLogLevel, + RemoteConfig, + Value +} from './public_types'; +import { RemoteConfigAbortSignal } from './client/remote_config_fetch_client'; +import { RC_COMPONENT_NAME } from './constants'; +import { ErrorCode, hasErrorCode } from './errors'; +import { RemoteConfig as RemoteConfigImpl } from './remote_config'; +import { Value as ValueImpl } from './value'; +import { LogLevel as FirebaseLogLevel } from '@firebase/logger'; +import { getModularInstance } from '@firebase/util'; + +/** + * + * @param app - The `FirebaseApp` instance. + * @returns A `RemoteConfig` instance. + * + * @public + */ +export function getRemoteConfig(app: FirebaseApp = getApp()): RemoteConfig { + app = getModularInstance(app); + const rcProvider = _getProvider(app, RC_COMPONENT_NAME); + return rcProvider.getImmediate(); +} + +/** + * Makes the last fetched config available to the getters. + * @param remoteConfig - The `RemoteConfig` instance. + * @returns A promise which resolves to true if the current call activated the fetched configs. + * If the fetched configs were already activated, the promise will resolve to false. + * + * @public + */ +export async function activate(remoteConfig: RemoteConfig): Promise { + const rc = getModularInstance(remoteConfig) as RemoteConfigImpl; + const [lastSuccessfulFetchResponse, activeConfigEtag] = await Promise.all([ + rc._storage.getLastSuccessfulFetchResponse(), + rc._storage.getActiveConfigEtag() + ]); + if ( + !lastSuccessfulFetchResponse || + !lastSuccessfulFetchResponse.config || + !lastSuccessfulFetchResponse.eTag || + lastSuccessfulFetchResponse.eTag === activeConfigEtag + ) { + // Either there is no successful fetched config, or is the same as current active + // config. + return false; + } + await Promise.all([ + rc._storageCache.setActiveConfig(lastSuccessfulFetchResponse.config), + rc._storage.setActiveConfigEtag(lastSuccessfulFetchResponse.eTag) + ]); + return true; +} + +/** + * Ensures the last activated config are available to the getters. + * @param remoteConfig - The `RemoteConfig` instance. + * + * @returns A promise that resolves when the last activated config is available to the getters. + * @public + */ +export function ensureInitialized(remoteConfig: RemoteConfig): Promise { + const rc = getModularInstance(remoteConfig) as RemoteConfigImpl; + if (!rc._initializePromise) { + rc._initializePromise = rc._storageCache.loadFromStorage().then(() => { + rc._isInitializationComplete = true; + }); + } + return rc._initializePromise; +} + +/** + * Fetches and caches configuration from the Remote Config service. + * @param remoteConfig - The `RemoteConfig` instance. + * @public + */ +export async function fetchConfig(remoteConfig: RemoteConfig): Promise { + const rc = getModularInstance(remoteConfig) as RemoteConfigImpl; + // Aborts the request after the given timeout, causing the fetch call to + // reject with an AbortError. + // + //

Aborting after the request completes is a no-op, so we don't need a + // corresponding clearTimeout. + // + // Locating abort logic here because: + // * it uses a developer setting (timeout) + // * it applies to all retries (like curl's max-time arg) + // * it is consistent with the Fetch API's signal input + const abortSignal = new RemoteConfigAbortSignal(); + + setTimeout(async () => { + // Note a very low delay, eg < 10ms, can elapse before listeners are initialized. + abortSignal.abort(); + }, rc.settings.fetchTimeoutMillis); + + // Catches *all* errors thrown by client so status can be set consistently. + try { + await rc._client.fetch({ + cacheMaxAgeMillis: rc.settings.minimumFetchIntervalMillis, + signal: abortSignal + }); + + await rc._storageCache.setLastFetchStatus('success'); + } catch (e) { + const lastFetchStatus = hasErrorCode(e, ErrorCode.FETCH_THROTTLE) + ? 'throttle' + : 'failure'; + await rc._storageCache.setLastFetchStatus(lastFetchStatus); + throw e; + } +} + +/** + * Gets all config. + * + * @param remoteConfig - The `RemoteConfig` instance. + * @returns All config. + * + * @public + */ +export function getAll(remoteConfig: RemoteConfig): Record { + const rc = getModularInstance(remoteConfig) as RemoteConfigImpl; + return getAllKeys( + rc._storageCache.getActiveConfig(), + rc.defaultConfig + ).reduce((allConfigs, key) => { + allConfigs[key] = getValue(remoteConfig, key); + return allConfigs; + }, {} as Record); +} + +/** + * Gets the value for the given key as a boolean. + * + * Convenience method for calling remoteConfig.getValue(key).asBoolean(). + * + * @param remoteConfig - The `RemoteConfig` instance. + * @param key - The name of the parameter. + * + * @returns The value for the given key as a boolean. + * @public + */ +export function getBoolean(remoteConfig: RemoteConfig, key: string): boolean { + return getValue(getModularInstance(remoteConfig), key).asBoolean(); +} + +/** + * Gets the value for the given key as a number. + * + * Convenience method for calling remoteConfig.getValue(key).asNumber(). + * + * @param remoteConfig - The `RemoteConfig` instance. + * @param key - The name of the parameter. + * + * @returns The value for the given key as a number. + * + * @public + */ +export function getNumber(remoteConfig: RemoteConfig, key: string): number { + return getValue(getModularInstance(remoteConfig), key).asNumber(); +} + +/** + * Gets the value for the given key as a string. + * Convenience method for calling remoteConfig.getValue(key).asString(). + * + * @param remoteConfig - The `RemoteConfig` instance. + * @param key - The name of the parameter. + * + * @returns The value for the given key as a string. + * + * @public + */ +export function getString(remoteConfig: RemoteConfig, key: string): string { + return getValue(getModularInstance(remoteConfig), key).asString(); +} + +/** + * Gets the {@link Value} for the given key. + * + * @param remoteConfig - The `RemoteConfig` instance. + * @param key - The name of the parameter. + * + * @returns The value for the given key. + * + * @public + */ +export function getValue(remoteConfig: RemoteConfig, key: string): Value { + const rc = getModularInstance(remoteConfig) as RemoteConfigImpl; + if (!rc._isInitializationComplete) { + rc._logger.debug( + `A value was requested for key "${key}" before SDK initialization completed.` + + ' Await on ensureInitialized if the intent was to get a previously activated value.' + ); + } + const activeConfig = rc._storageCache.getActiveConfig(); + if (activeConfig && activeConfig[key] !== undefined) { + return new ValueImpl('remote', activeConfig[key]); + } else if (rc.defaultConfig && rc.defaultConfig[key] !== undefined) { + return new ValueImpl('default', String(rc.defaultConfig[key])); + } + rc._logger.debug( + `Returning static value for key "${key}".` + + ' Define a default or remote value if this is unintentional.' + ); + return new ValueImpl('static'); +} + +/** + * Defines the log level to use. + * + * @param remoteConfig - The `RemoteConfig` instance. + * @param logLevel - The log level to set. + * + * @public + */ +export function setLogLevel( + remoteConfig: RemoteConfig, + logLevel: RemoteConfigLogLevel +): void { + const rc = getModularInstance(remoteConfig) as RemoteConfigImpl; + switch (logLevel) { + case 'debug': + rc._logger.logLevel = FirebaseLogLevel.DEBUG; + break; + case 'silent': + rc._logger.logLevel = FirebaseLogLevel.SILENT; + break; + default: + rc._logger.logLevel = FirebaseLogLevel.ERROR; + } +} + +/** + * Dedupes and returns an array of all the keys of the received objects. + */ +function getAllKeys(obj1: {} = {}, obj2: {} = {}): string[] { + return Object.keys({ ...obj1, ...obj2 }); +} diff --git a/packages-exp/remote-config-exp/src/api2.ts b/packages-exp/remote-config-exp/src/api2.ts new file mode 100644 index 00000000000..7d0689a8561 --- /dev/null +++ b/packages-exp/remote-config-exp/src/api2.ts @@ -0,0 +1,41 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { RemoteConfig } from './public_types'; +import { activate, fetchConfig } from './api'; +import { getModularInstance } from '@firebase/util'; + +// This API is put in a separate file, so we can stub fetchConfig and activate in tests. +// It's not possible to stub standalone functions from the same module. +/** + * + * Performs fetch and activate operations, as a convenience. + * + * @param remoteConfig - The remote config instance. + * + * @returns A promise which resolves to true if the current call activated the fetched configs. + * If the fetched configs were already activated, the promise will resolve to false. + * + * @public + */ +export async function fetchAndActivate( + remoteConfig: RemoteConfig +): Promise { + remoteConfig = getModularInstance(remoteConfig); + await fetchConfig(remoteConfig); + return activate(remoteConfig); +} diff --git a/packages-exp/remote-config-exp/src/client/caching_client.ts b/packages-exp/remote-config-exp/src/client/caching_client.ts new file mode 100644 index 00000000000..aea61acfd1f --- /dev/null +++ b/packages-exp/remote-config-exp/src/client/caching_client.ts @@ -0,0 +1,123 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { StorageCache } from '../storage/storage_cache'; +import { + FetchResponse, + RemoteConfigFetchClient, + FetchRequest +} from './remote_config_fetch_client'; +import { Storage } from '../storage/storage'; +import { Logger } from '@firebase/logger'; + +/** + * Implements the {@link RemoteConfigClient} abstraction with success response caching. + * + *

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Defers default language specification to server logic for consistency. + * + * @param navigatorLanguage Enables tests to override read-only {@link NavigatorLanguage}. + */ +export function getUserLanguage( + navigatorLanguage: NavigatorLanguage = navigator +): string { + return ( + // Most reliable, but only supported in Chrome/Firefox. + (navigatorLanguage.languages && navigatorLanguage.languages[0]) || + // Supported in most browsers, but returns the language of the browser + // UI, not the language set in browser settings. + navigatorLanguage.language + // Polyfill otherwise. + ); +} diff --git a/packages-exp/remote-config-exp/src/public_types.ts b/packages-exp/remote-config-exp/src/public_types.ts new file mode 100644 index 00000000000..5a820debae3 --- /dev/null +++ b/packages-exp/remote-config-exp/src/public_types.ts @@ -0,0 +1,135 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * The Firebase Remote Config service interface. + * + * @public + */ +export interface RemoteConfig { + /** + * Defines configuration for the Remote Config SDK. + */ + settings: Settings; + + /** + * Object containing default values for conigs. + */ + defaultConfig: { [key: string]: string | number | boolean }; + + /** + * The Unix timestamp in milliseconds of the last successful fetch, or negative one if + * the {@link RemoteConfig} instance either hasn't fetched or initialization + * is incomplete. + */ + fetchTimeMillis: number; + + /** + * The status of the last fetch attempt. + */ + lastFetchStatus: FetchStatus; +} + +/** + * Indicates the source of a value. + * + *

    + *
  • "static" indicates the value was defined by a static constant.
  • + *
  • "default" indicates the value was defined by default config.
  • + *
  • "remote" indicates the value was defined by fetched config.
  • + *
+ * + * @public + */ +export type ValueSource = 'static' | 'default' | 'remote'; + +/** + * Wraps a value with metadata and type-safe getters. + * + * @public + */ +export interface Value { + /** + * Gets the value as a boolean. + * + * The following values (case insensitive) are interpreted as true: + * "1", "true", "t", "yes", "y", "on". Other values are interpreted as false. + */ + asBoolean(): boolean; + + /** + * Gets the value as a number. Comparable to calling Number(value) || 0. + */ + asNumber(): number; + + /** + * Gets the value as a string. + */ + asString(): string; + + /** + * Gets the {@link ValueSource} for the given key. + */ + getSource(): ValueSource; +} + +/** + * Defines configuration options for the Remote Config SDK. + * + * @public + */ +export interface Settings { + /** + * Defines the maximum age in milliseconds of an entry in the config cache before + * it is considered stale. Defaults to 43200000 (Twelve hours). + */ + minimumFetchIntervalMillis: number; + + /** + * Defines the maximum amount of milliseconds to wait for a response when fetching + * configuration from the Remote Config server. Defaults to 60000 (One minute). + */ + fetchTimeoutMillis: number; +} + +/** + * Summarizes the outcome of the last attempt to fetch config from the Firebase Remote Config server. + * + *
    + *
  • "no-fetch-yet" indicates the {@link RemoteConfig} instance has not yet attempted + * to fetch config, or that SDK initialization is incomplete.
  • + *
  • "success" indicates the last attempt succeeded.
  • + *
  • "failure" indicates the last attempt failed.
  • + *
  • "throttle" indicates the last attempt was rate-limited.
  • + *
+ * + * @public + */ +export type FetchStatus = 'no-fetch-yet' | 'success' | 'failure' | 'throttle'; + +/** + * Defines levels of Remote Config logging. + * + * @public + */ +export type LogLevel = 'debug' | 'error' | 'silent'; + +declare module '@firebase/component' { + interface NameServiceMapping { + 'remote-config-exp': RemoteConfig; + } +} diff --git a/packages-exp/remote-config-exp/src/register.ts b/packages-exp/remote-config-exp/src/register.ts new file mode 100644 index 00000000000..ea71928ffcc --- /dev/null +++ b/packages-exp/remote-config-exp/src/register.ts @@ -0,0 +1,125 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + _registerComponent, + registerVersion, + SDK_VERSION +} from '@firebase/app-exp'; +import { + Component, + ComponentType, + ComponentContainer, + InstanceFactoryOptions +} from '@firebase/component'; +import { Logger, LogLevel as FirebaseLogLevel } from '@firebase/logger'; +import { RemoteConfig } from './public_types'; +import { name as packageName, version } from '../package.json'; +import { ensureInitialized } from './api'; +import { CachingClient } from './client/caching_client'; +import { RestClient } from './client/rest_client'; +import { RetryingClient } from './client/retrying_client'; +import { RC_COMPONENT_NAME } from './constants'; +import { ErrorCode, ERROR_FACTORY } from './errors'; +import { RemoteConfig as RemoteConfigImpl } from './remote_config'; +import { Storage } from './storage/storage'; +import { StorageCache } from './storage/storage_cache'; +// This needs to be in the same file that calls `getProvider()` on the component +// or it will get tree-shaken out. +import '@firebase/installations-exp'; + +export function registerRemoteConfig(): void { + _registerComponent( + new Component( + RC_COMPONENT_NAME, + remoteConfigFactory, + ComponentType.PUBLIC + ).setMultipleInstances(true) + ); + + registerVersion(packageName, version); + + function remoteConfigFactory( + container: ComponentContainer, + { instanceIdentifier: namespace }: InstanceFactoryOptions + ): RemoteConfig { + /* Dependencies */ + // getImmediate for FirebaseApp will always succeed + const app = container.getProvider('app-exp').getImmediate(); + // The following call will always succeed because rc has `import '@firebase/installations'` + const installations = container + .getProvider('installations-exp-internal') + .getImmediate(); + + // Guards against the SDK being used in non-browser environments. + if (typeof window === 'undefined') { + throw ERROR_FACTORY.create(ErrorCode.REGISTRATION_WINDOW); + } + + // Normalizes optional inputs. + const { projectId, apiKey, appId } = app.options; + if (!projectId) { + throw ERROR_FACTORY.create(ErrorCode.REGISTRATION_PROJECT_ID); + } + if (!apiKey) { + throw ERROR_FACTORY.create(ErrorCode.REGISTRATION_API_KEY); + } + if (!appId) { + throw ERROR_FACTORY.create(ErrorCode.REGISTRATION_APP_ID); + } + namespace = namespace || 'firebase'; + + const storage = new Storage(appId, app.name, namespace); + const storageCache = new StorageCache(storage); + + const logger = new Logger(packageName); + + // Sets ERROR as the default log level. + // See RemoteConfig#setLogLevel for corresponding normalization to ERROR log level. + logger.logLevel = FirebaseLogLevel.ERROR; + + const restClient = new RestClient( + installations, + // Uses the JS SDK version, by which the RC package version can be deduced, if necessary. + SDK_VERSION, + namespace, + projectId, + apiKey, + appId + ); + const retryingClient = new RetryingClient(restClient, storage); + const cachingClient = new CachingClient( + retryingClient, + storage, + storageCache, + logger + ); + + const remoteConfigInstance = new RemoteConfigImpl( + app, + cachingClient, + storageCache, + storage, + logger + ); + + // Starts warming cache. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + ensureInitialized(remoteConfigInstance); + + return remoteConfigInstance; + } +} diff --git a/packages-exp/remote-config-exp/src/remote_config.ts b/packages-exp/remote-config-exp/src/remote_config.ts new file mode 100644 index 00000000000..43319da4888 --- /dev/null +++ b/packages-exp/remote-config-exp/src/remote_config.ts @@ -0,0 +1,88 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FirebaseApp } from '@firebase/app-exp'; +import { + RemoteConfig as RemoteConfigType, + FetchStatus, + Settings +} from './public_types'; +import { StorageCache } from './storage/storage_cache'; +import { RemoteConfigFetchClient } from './client/remote_config_fetch_client'; +import { Storage } from './storage/storage'; +import { Logger } from '@firebase/logger'; + +const DEFAULT_FETCH_TIMEOUT_MILLIS = 60 * 1000; // One minute +const DEFAULT_CACHE_MAX_AGE_MILLIS = 12 * 60 * 60 * 1000; // Twelve hours. + +/** + * Encapsulates business logic mapping network and storage dependencies to the public SDK API. + * + * See {@link https://github.com/FirebasePrivate/firebase-js-sdk/blob/master/packages/firebase/index.d.ts|interface documentation} for method descriptions. + */ +export class RemoteConfig implements RemoteConfigType { + /** + * Tracks completion of initialization promise. + * @internal + */ + _isInitializationComplete = false; + + /** + * De-duplicates initialization calls. + * @internal + */ + _initializePromise?: Promise; + + settings: Settings = { + fetchTimeoutMillis: DEFAULT_FETCH_TIMEOUT_MILLIS, + minimumFetchIntervalMillis: DEFAULT_CACHE_MAX_AGE_MILLIS + }; + + defaultConfig: { [key: string]: string | number | boolean } = {}; + + get fetchTimeMillis(): number { + return this._storageCache.getLastSuccessfulFetchTimestampMillis() || -1; + } + + get lastFetchStatus(): FetchStatus { + return this._storageCache.getLastFetchStatus() || 'no-fetch-yet'; + } + + constructor( + // Required by FirebaseServiceFactory interface. + readonly app: FirebaseApp, + // JS doesn't support private yet + // (https://github.com/tc39/proposal-class-fields#private-fields), so we hint using an + // underscore prefix. + /** + * @internal + */ + readonly _client: RemoteConfigFetchClient, + /** + * @internal + */ + readonly _storageCache: StorageCache, + /** + * @internal + */ + readonly _storage: Storage, + /** + * @internal + */ + readonly _logger: Logger + ) {} +} diff --git a/packages-exp/remote-config-exp/src/storage/storage.ts b/packages-exp/remote-config-exp/src/storage/storage.ts new file mode 100644 index 00000000000..f5f457161b1 --- /dev/null +++ b/packages-exp/remote-config-exp/src/storage/storage.ts @@ -0,0 +1,260 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FetchStatus } from '@firebase/remote-config-types'; +import { + FetchResponse, + FirebaseRemoteConfigObject +} from '../client/remote_config_fetch_client'; +import { ERROR_FACTORY, ErrorCode } from '../errors'; +import { FirebaseError } from '@firebase/util'; + +/** + * Converts an error event associated with a {@link IDBRequest} to a {@link FirebaseError}. + */ +function toFirebaseError(event: Event, errorCode: ErrorCode): FirebaseError { + const originalError = (event.target as IDBRequest).error || undefined; + return ERROR_FACTORY.create(errorCode, { + originalErrorMessage: originalError && originalError.message + }); +} + +/** + * A general-purpose store keyed by app + namespace + {@link + * ProjectNamespaceKeyFieldValue}. + * + *

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

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

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

+

Remote Config Test App

+
+
+
+
Firebase config
+
+ +
+
+
RC defaults
+
+ +
+
+
RC settings
+
+ +
+
+
Log level
+
+ +
+
+
+
+
Controls
+
+ + + + +
+
+
+
Value getters
+
+
+ key: + +
+ + + + +
+
+
+
General getters
+
+ + + + +
+
+
+
+
Output
+
+
+
+
+
+
+ + + diff --git a/packages-exp/remote-config-exp/test_app/index.js b/packages-exp/remote-config-exp/test_app/index.js new file mode 100644 index 00000000000..5fe575bbbb4 --- /dev/null +++ b/packages-exp/remote-config-exp/test_app/index.js @@ -0,0 +1,224 @@ +/** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const FB_CONFIG_PLACEHOLDER = `{ + apiKey: ..., + authDomain: ..., + databaseURL: ..., + projectId: ..., + storageBucket: ..., + messagingSenderId: ..., + appId: ... +}`; +const DEFAULTS_PLACEHOLDER = `{ + key1: 'value1', + key2: 'value2', + ... +}`; +const SETTINGS_PLACEHOLDER = `{ + minimumFetchIntervalMillis: 43200000, + fetchTimeoutMillis: 5000 +}`; +const SUCCESS_MESSAGE = 'Done '; + +let rcInstance; +const outputBox = document.getElementById('output-box'); + +window.onload = function () { + document.querySelector( + '#firebase-config' + ).placeholder = FB_CONFIG_PLACEHOLDER; + document.querySelector('#rc-defaults').placeholder = DEFAULTS_PLACEHOLDER; + document.querySelector('#rc-settings').placeholder = SETTINGS_PLACEHOLDER; +}; + +function initializeFirebase() { + const val = document.querySelector('#firebase-config').value; + const app = firebase.app.initializeApp(parseObjFromStr(val)); + rcInstance = firebase.remoteConfig.getRemoteConfig(app); + return Promise.resolve(); +} + +function setDefaults() { + rcInstance.defaultConfig = parseObjFromStr( + document.querySelector('#rc-defaults').value + ); + return SUCCESS_MESSAGE; +} + +function setSettings() { + const newSettings = parseObjFromStr( + document.querySelector('#rc-settings').value, + true + ); + const currentSettings = rcInstance.settings; + rcInstance.settings = Object.assign({}, currentSettings, newSettings); + return SUCCESS_MESSAGE; +} + +function setLogLevel() { + const newLogLevel = document.querySelector('#log-level-input').value; + firebase.remoteConfig.setLogLevel(rcInstance, newLogLevel); + return SUCCESS_MESSAGE; +} + +function activate() { + return firebase.remoteConfig.activate(rcInstance); +} + +function ensureInitialized() { + return firebase.remoteConfig.ensureInitialized(rcInstance); +} + +// Prefixed to avoid clobbering the browser's fetch function. +function rcFetch() { + return firebase.remoteConfig.fetchConfig(rcInstance); +} + +function fetchAndActivate() { + return firebase.remoteConfig.fetchAndActivate(rcInstance); +} + +function getString() { + return firebase.remoteConfig.getString(rcInstance, getKey()); +} + +function getBoolean() { + return firebase.remoteConfig.getBoolean(rcInstance, getKey()); +} + +function getNumber() { + return firebase.remoteConfig.getNumber(rcInstance, getKey()); +} + +function getValue() { + return firebase.remoteConfig.getValue(rcInstance, getKey()); +} + +function getAll() { + return firebase.remoteConfig.getAll(rcInstance); +} + +function getFetchTimeMillis() { + return rcInstance.fetchTimeMillis; +} + +function getLastFetchStatus() { + return rcInstance.lastFetchStatus; +} + +function getSettings() { + return rcInstance.settings; +} + +// Helper functions +function getKey() { + return document.querySelector('#key-input').value; +} + +function handlerWrapper(handler) { + try { + clearOutput(); + var returnValue = handler(); + if (returnValue instanceof Promise) { + handlePromise(returnValue); + } else if (returnValue instanceof Array) { + handleArray(returnValue); + } else if (returnValue instanceof Object) { + handleObject(returnValue); + } else { + displayOutput(returnValue); + } + } catch (error) { + displayOutputError(error); + } +} + +function clearOutput() { + outputBox.innerHTML = ''; +} + +function appendOutput(text) { + outputBox.innerHTML = outputBox.innerHTML + text; +} + +function displayOutput(text) { + outputBox.innerHTML = text; +} + +function displayOutputError(error) { + outputBox.innerHTML = `${error.message || error}`; +} + +function handlePromise(prom) { + const timerId = setInterval(() => appendOutput('.'), 400); + prom + .then(res => { + clearInterval(timerId); + appendOutput(SUCCESS_MESSAGE); + if (res != undefined) { + appendOutput('
'); + appendOutput('return value: ' + res); + } + }) + .catch(e => { + clearInterval(timerId); + appendOutput(`${e}`); + }); +} + +function handleArray(ar) { + displayOutput(ar.toString()); +} + +function handleObject(obj) { + appendOutput('{'); + for (const key in obj) { + if (obj[key] instanceof Function) { + appendOutput(`
${key}: ${obj[key]()},`); + } else if (obj[key] instanceof Object) { + appendOutput(key + ': '); + handleObject(obj[key]); + appendOutput(','); + } else { + appendOutput(`
${key}: ${obj[key]},`); + } + } + appendOutput('
}'); +} + +/** + * Parses string received from input elements into objects. These strings are not JSON + * compatible. + */ +function parseObjFromStr(str, valueIsNumber = false) { + const obj = {}; + str + .substring(str.indexOf('{') + 1, str.indexOf('}')) + .replace(/["']/g, '') // Get rid of curly braces and quotes + .trim() + .split(/[,]/g) // Results in a string of key value separated by a colon + .map(str => str.trim()) + .forEach(keyValue => { + const colIndex = keyValue.indexOf(':'); + const key = keyValue.substring(0, colIndex); + const val = keyValue.substring(colIndex + 1).trim(); + const value = valueIsNumber ? Number(val) : val; + obj[key] = value; + }); + return obj; +} diff --git a/packages-exp/remote-config-exp/tsconfig.json b/packages-exp/remote-config-exp/tsconfig.json new file mode 100644 index 00000000000..a4b8678284b --- /dev/null +++ b/packages-exp/remote-config-exp/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../config/tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "resolveJsonModule": true, + }, + "exclude": [ + "dist/**/*" + ] +} \ No newline at end of file diff --git a/packages/analytics-interop-types/package.json b/packages/analytics-interop-types/package.json index 4c6f5c20e1a..912dc29974c 100644 --- a/packages/analytics-interop-types/package.json +++ b/packages/analytics-interop-types/package.json @@ -20,6 +20,6 @@ "url": "https://github.com/firebase/firebase-js-sdk/issues" }, "devDependencies": { - "typescript": "4.0.2" + "typescript": "4.2.2" } } diff --git a/packages/analytics-types/index.d.ts b/packages/analytics-types/index.d.ts index 09708902516..b2632793d43 100644 --- a/packages/analytics-types/index.d.ts +++ b/packages/analytics-types/index.d.ts @@ -40,13 +40,421 @@ export interface FirebaseAnalytics { * Sends analytics event with given `eventParams`. This method * automatically associates this logged event with this Firebase web * app instance on this device. - * List of official event parameters can be found in + * List of recommended event parameters can be found in * {@link https://developers.google.com/gtagjs/reference/event * the gtag.js reference documentation}. */ logEvent( - eventName: EventNameString, - eventParams?: EventParams, + eventName: 'add_payment_info', + eventParams?: { + coupon?: EventParams['coupon']; + currency?: EventParams['currency']; + items?: EventParams['items']; + payment_type?: EventParams['payment_type']; + value?: EventParams['value']; + [key: string]: any; + }, + options?: AnalyticsCallOptions + ): void; + + /** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * the gtag.js reference documentation}. + */ + logEvent( + eventName: 'add_shipping_info', + eventParams?: { + coupon?: EventParams['coupon']; + currency?: EventParams['currency']; + items?: EventParams['items']; + shipping_tier?: EventParams['shipping_tier']; + value?: EventParams['value']; + [key: string]: any; + }, + options?: AnalyticsCallOptions + ): void; + + /** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * the gtag.js reference documentation}. + */ + logEvent( + eventName: 'add_to_cart' | 'add_to_wishlist' | 'remove_from_cart', + eventParams?: { + currency?: EventParams['currency']; + value?: EventParams['value']; + items?: EventParams['items']; + [key: string]: any; + }, + options?: AnalyticsCallOptions + ): void; + + /** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * the gtag.js reference documentation}. + */ + logEvent( + eventName: 'begin_checkout', + eventParams?: { + currency?: EventParams['currency']; + coupon?: EventParams['coupon']; + value?: EventParams['value']; + items?: EventParams['items']; + [key: string]: any; + }, + options?: AnalyticsCallOptions + ): void; + + /** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * the gtag.js reference documentation}. + */ + logEvent( + eventName: 'checkout_progress', + eventParams?: { + currency?: EventParams['currency']; + coupon?: EventParams['coupon']; + value?: EventParams['value']; + items?: EventParams['items']; + checkout_step?: EventParams['checkout_step']; + checkout_option?: EventParams['checkout_option']; + [key: string]: any; + }, + options?: AnalyticsCallOptions + ): void; + + /** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * the gtag.js reference documentation}. + */ + logEvent( + eventName: 'exception', + eventParams?: { + description?: EventParams['description']; + fatal?: EventParams['fatal']; + [key: string]: any; + }, + options?: AnalyticsCallOptions + ): void; + + /** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * the gtag.js reference documentation}. + */ + logEvent( + eventName: 'generate_lead', + eventParams?: { + value?: EventParams['value']; + currency?: EventParams['currency']; + transaction_id?: EventParams['transaction_id']; + [key: string]: any; + }, + options?: AnalyticsCallOptions + ): void; + + /** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * the gtag.js reference documentation}. + */ + logEvent( + eventName: 'login', + eventParams?: { + method?: EventParams['method']; + [key: string]: any; + }, + options?: AnalyticsCallOptions + ): void; + + /** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * the gtag.js reference documentation}. + */ + logEvent( + eventName: 'page_view', + eventParams?: { + page_title?: string; + page_location?: string; + page_path?: string; + [key: string]: any; + }, + options?: AnalyticsCallOptions + ): void; + + /** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * the gtag.js reference documentation}. + */ + logEvent( + eventName: 'purchase' | 'refund', + eventParams?: { + value?: EventParams['value']; + currency?: EventParams['currency']; + transaction_id: EventParams['transaction_id']; + tax?: EventParams['tax']; + shipping?: EventParams['shipping']; + items?: EventParams['items']; + coupon?: EventParams['coupon']; + affiliation?: EventParams['affiliation']; + [key: string]: any; + }, + options?: AnalyticsCallOptions + ): void; + + /** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * the gtag.js reference documentation}. + */ + logEvent( + eventName: 'screen_view', + eventParams?: { + app_name: string; + screen_name: EventParams['screen_name']; + app_id?: string; + app_version?: string; + app_installer_id?: string; + [key: string]: any; + }, + options?: AnalyticsCallOptions + ): void; + + /** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * the gtag.js reference documentation}. + */ + logEvent( + eventName: 'search' | 'view_search_results', + eventParams?: { + search_term?: EventParams['search_term']; + [key: string]: any; + }, + options?: AnalyticsCallOptions + ): void; + + /** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * the gtag.js reference documentation}. + */ + logEvent( + eventName: 'select_content', + eventParams?: { + items?: EventParams['items']; + promotions?: EventParams['promotions']; + content_type?: EventParams['content_type']; + content_id?: EventParams['content_id']; + [key: string]: any; + }, + options?: AnalyticsCallOptions + ): void; + + /** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * the gtag.js reference documentation}. + */ + logEvent( + eventName: 'select_item', + eventParams?: { + items?: EventParams['items']; + item_list_name?: EventParams['item_list_name']; + item_list_id?: EventParams['item_list_id']; + [key: string]: any; + }, + options?: AnalyticsCallOptions + ): void; + + /** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * the gtag.js reference documentation}. + */ + logEvent( + eventName: 'select_promotion' | 'view_promotion', + eventParams?: { + items?: EventParams['items']; + promotion_id?: EventParams['promotion_id']; + promotion_name?: EventParams['promotion_name']; + [key: string]: any; + }, + options?: AnalyticsCallOptions + ): void; + + /** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * the gtag.js reference documentation}. + */ + logEvent( + eventName: 'set_checkout_option', + eventParams?: { + checkout_step?: EventParams['checkout_step']; + checkout_option?: EventParams['checkout_option']; + [key: string]: any; + }, + options?: AnalyticsCallOptions + ): void; + + /** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * the gtag.js reference documentation}. + */ + logEvent( + eventName: 'share', + eventParams?: { + method?: EventParams['method']; + content_type?: EventParams['content_type']; + content_id?: EventParams['content_id']; + [key: string]: any; + }, + options?: AnalyticsCallOptions + ): void; + + /** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * the gtag.js reference documentation}. + */ + logEvent( + eventName: 'sign_up', + eventParams?: { + method?: EventParams['method']; + [key: string]: any; + }, + options?: AnalyticsCallOptions + ): void; + + /** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * the gtag.js reference documentation}. + */ + logEvent( + eventName: 'timing_complete', + eventParams?: { + name: string; + value: number; + event_category?: string; + event_label?: string; + [key: string]: any; + }, + options?: AnalyticsCallOptions + ): void; + + /** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * the gtag.js reference documentation}. + */ + logEvent( + eventName: 'view_cart' | 'view_item', + eventParams?: { + currency?: EventParams['currency']; + items?: EventParams['items']; + value?: EventParams['value']; + [key: string]: any; + }, + options?: AnalyticsCallOptions + ): void; + + /** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * the gtag.js reference documentation}. + */ + logEvent( + eventName: 'view_item_list', + eventParams?: { + items?: EventParams['items']; + item_list_name?: EventParams['item_list_name']; + item_list_id?: EventParams['item_list_id']; + [key: string]: any; + }, + options?: AnalyticsCallOptions + ): void; + + /** + * Sends analytics event with given `eventParams`. This method + * automatically associates this logged event with this Firebase web + * app instance on this device. + * List of recommended event parameters can be found in + * {@link https://developers.google.com/gtagjs/reference/event + * the gtag.js reference documentation}. + */ + logEvent( + eventName: CustomEventName, + eventParams?: { [key: string]: any }, options?: AnalyticsCallOptions ): void; @@ -75,6 +483,8 @@ export interface FirebaseAnalytics { setAnalyticsCollectionEnabled(enabled: boolean): void; } +export type CustomEventName = T extends EventNameString ? never : T; + /** * Specifies custom options for your Firebase Analytics instance. * You must set these before initializing `firebase.analytics()`. diff --git a/packages/analytics-types/package.json b/packages/analytics-types/package.json index 345be90ed88..14181a15688 100644 --- a/packages/analytics-types/package.json +++ b/packages/analytics-types/package.json @@ -20,6 +20,6 @@ "url": "https://github.com/firebase/firebase-js-sdk/issues" }, "devDependencies": { - "typescript": "4.0.2" + "typescript": "4.2.2" } } diff --git a/packages/analytics/CHANGELOG.md b/packages/analytics/CHANGELOG.md index 5691673be40..4643d41cd7c 100644 --- a/packages/analytics/CHANGELOG.md +++ b/packages/analytics/CHANGELOG.md @@ -1,5 +1,121 @@ # @firebase/analytics +## 0.6.14 + +### Patch Changes + +- Updated dependencies [[`56a6a9d4a`](https://github.com/firebase/firebase-js-sdk/commit/56a6a9d4af2766154584a0f66d3c4d8024d74ba5)]: + - @firebase/component@0.5.4 + - @firebase/installations@0.4.30 + +## 0.6.13 + +### Patch Changes + +- Updated dependencies [[`725ab4684`](https://github.com/firebase/firebase-js-sdk/commit/725ab4684ef0999a12f71e704c204a00fb030e5d)]: + - @firebase/component@0.5.3 + - @firebase/installations@0.4.29 + +## 0.6.12 + +### Patch Changes + +- Updated dependencies [[`4c4b6aed9`](https://github.com/firebase/firebase-js-sdk/commit/4c4b6aed9757c9a7e75fb698a15e53274f93880b)]: + - @firebase/component@0.5.2 + - @firebase/installations@0.4.28 + +## 0.6.11 + +### Patch Changes + +- Updated dependencies [[`5fbc5fb01`](https://github.com/firebase/firebase-js-sdk/commit/5fbc5fb0140d7da980fd7ebbfbae810f8c64ae19)]: + - @firebase/component@0.5.1 + - @firebase/installations@0.4.27 + +## 0.6.10 + +### Patch Changes + +- Updated dependencies [[`c34ac7a92`](https://github.com/firebase/firebase-js-sdk/commit/c34ac7a92a616915f38d192654db7770d81747ae), [`ac4ad08a2`](https://github.com/firebase/firebase-js-sdk/commit/ac4ad08a284397ec966e991dd388bb1fba857467)]: + - @firebase/component@0.5.0 + - @firebase/util@1.1.0 + - @firebase/installations@0.4.26 + +## 0.6.9 + +### Patch Changes + +- Updated dependencies [[`7354a0ed4`](https://github.com/firebase/firebase-js-sdk/commit/7354a0ed438f4e3df6577e4927e8c8f8f1fbbfda)]: + - @firebase/util@1.0.0 + - @firebase/component@0.4.1 + - @firebase/installations@0.4.25 + +## 0.6.8 + +### Patch Changes + +- Updated dependencies [[`f24d8961b`](https://github.com/firebase/firebase-js-sdk/commit/f24d8961b3b87821413297688803fc85113086b3)]: + - @firebase/component@0.4.0 + - @firebase/installations@0.4.24 + +## 0.6.7 + +### Patch Changes + +- Updated dependencies [[`de5f90501`](https://github.com/firebase/firebase-js-sdk/commit/de5f9050137acc9ed1490082e5aa429b5de3cb2a)]: + - @firebase/util@0.4.1 + - @firebase/component@0.3.1 + - @firebase/installations@0.4.23 + +## 0.6.6 + +### Patch Changes + +- Updated dependencies [[`5c1a83ed7`](https://github.com/firebase/firebase-js-sdk/commit/5c1a83ed70bae979322bd8751c0885d683ce4bf3)]: + - @firebase/component@0.3.0 + - @firebase/installations@0.4.22 + +## 0.6.5 + +### Patch Changes + +- Updated dependencies [[`ec95df3d0`](https://github.com/firebase/firebase-js-sdk/commit/ec95df3d07e5f091f2a7f7327e46417f64d04b4e)]: + - @firebase/util@0.4.0 + - @firebase/component@0.2.1 + - @firebase/installations@0.4.21 + +## 0.6.4 + +### Patch Changes + +- Updated dependencies [[`6afe42613`](https://github.com/firebase/firebase-js-sdk/commit/6afe42613ed3d7a842d378dc1a09a795811db2ac)]: + - @firebase/component@0.2.0 + - @firebase/installations@0.4.20 + +## 0.6.3 + +### Patch Changes + +- [`74bf52009`](https://github.com/firebase/firebase-js-sdk/commit/74bf52009b291a62deabfd865084d4e0fcacc483) [#4458](https://github.com/firebase/firebase-js-sdk/pull/4458) - Fixed a behavior causing `gtag` to be downloaded twice on Firebase Analytics initialization. This did not seem to affect the functionality of Firebase Analytics but adds noise to the logs when users are trying to debug. + +## 0.6.2 + +### Patch Changes + +- Updated dependencies [[`9cf727fcc`](https://github.com/firebase/firebase-js-sdk/commit/9cf727fcc3d049551b16ae0698ac33dc2fe45ada)]: + - @firebase/util@0.3.4 + - @firebase/component@0.1.21 + - @firebase/installations@0.4.19 + +## 0.6.1 + +### Patch Changes + +- Updated dependencies [[`a5768b0aa`](https://github.com/firebase/firebase-js-sdk/commit/a5768b0aa7d7ce732279931aa436e988c9f36487), [`7d916d905`](https://github.com/firebase/firebase-js-sdk/commit/7d916d905ba16816ac8ac7c8748c83831ff614ce)]: + - @firebase/component@0.1.20 + - @firebase/util@0.3.3 + - @firebase/installations@0.4.18 + ## 0.6.0 ### Minor Changes diff --git a/packages/analytics/index.test.ts b/packages/analytics/index.test.ts index 1dc31949c80..8b51306d86e 100644 --- a/packages/analytics/index.test.ts +++ b/packages/analytics/index.test.ts @@ -93,7 +93,12 @@ describe('FirebaseAnalytics instance tests', () => { ); }); it('Warns if config has no apiKey but does have a measurementId', () => { + // Since this is a warning and doesn't block the rest of initialization + // all the async stuff needs to be stubbed and cleaned up. const warnStub = stub(console, 'warn'); + const docStub = stub(document, 'createElement'); + stubFetch(200, { measurementId: fakeMeasurementId }); + stubIdbOpen(); const app = getFakeApp({ appId: fakeAppParams.appId, measurementId: fakeMeasurementId @@ -104,6 +109,11 @@ describe('FirebaseAnalytics instance tests', () => { `Falling back to the measurement ID ${fakeMeasurementId}` ); warnStub.restore(); + docStub.restore(); + fetchStub.restore(); + idbOpenStub.restore(); + delete window['gtag']; + delete window['dataLayer']; }); it('Throws if creating an instance with already-used appId', () => { const app = getFakeApp(fakeAppParams); @@ -210,6 +220,7 @@ describe('FirebaseAnalytics instance tests', () => { afterEach(() => { delete window['gtag']; delete window['dataLayer']; + removeGtagScript(); fetchStub.restore(); clock.restore(); warnStub.restore(); diff --git a/packages/analytics/package.json b/packages/analytics/package.json index d32695edc49..79e9525434a 100644 --- a/packages/analytics/package.json +++ b/packages/analytics/package.json @@ -1,12 +1,14 @@ { "name": "@firebase/analytics", - "version": "0.6.0", + "version": "0.6.14", "description": "A analytics package for new firebase packages", "author": "Firebase (https://firebase.google.com/)", "main": "dist/index.cjs.js", "module": "dist/index.esm.js", "esm2017": "dist/index.esm2017.js", - "files": ["dist"], + "files": [ + "dist" + ], "scripts": { "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", @@ -17,8 +19,7 @@ "test:all": "run-p test:browser test:integration", "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:all", "test:browser": "karma start --single-run --nocache", - "test:integration": "karma start ./karma.integration.conf.js --single-run --nocache", - "prepare": "yarn build" + "test:integration": "karma start ./karma.integration.conf.js --single-run --nocache" }, "peerDependencies": { "@firebase/app": "0.x", @@ -26,21 +27,21 @@ }, "dependencies": { "@firebase/analytics-types": "0.4.0", - "@firebase/installations": "0.4.17", + "@firebase/installations": "0.4.30", "@firebase/logger": "0.2.6", - "@firebase/util": "0.3.2", - "@firebase/component": "0.1.19", - "tslib": "^1.11.1" + "@firebase/util": "1.1.0", + "@firebase/component": "0.5.4", + "tslib": "^2.1.0" }, "license": "Apache-2.0", "devDependencies": { - "@firebase/app": "0.6.11", - "rollup": "2.29.0", - "@rollup/plugin-commonjs": "15.1.0", + "@firebase/app": "0.6.28", + "rollup": "2.52.2", + "@rollup/plugin-commonjs": "17.1.0", "@rollup/plugin-json": "4.1.0", - "@rollup/plugin-node-resolve": "9.0.0", - "rollup-plugin-typescript2": "0.27.3", - "typescript": "4.0.2" + "@rollup/plugin-node-resolve": "11.2.0", + "rollup-plugin-typescript2": "0.30.0", + "typescript": "4.2.2" }, "repository": { "directory": "packages/analytics", diff --git a/packages/analytics/src/constants.ts b/packages/analytics/src/constants.ts index 2b24c0f59ec..a9b1356f998 100644 --- a/packages/analytics/src/constants.ts +++ b/packages/analytics/src/constants.ts @@ -32,9 +32,11 @@ export enum GtagCommand { CONFIG = 'config' } -/* +/** * Officially recommended event names for gtag.js * Any other string is also allowed. + * + * @public */ export enum EventName { ADD_SHIPPING_INFO = 'add_shipping_info', @@ -42,7 +44,11 @@ export enum EventName { ADD_TO_CART = 'add_to_cart', ADD_TO_WISHLIST = 'add_to_wishlist', BEGIN_CHECKOUT = 'begin_checkout', - /** @deprecated */ + /** + * @deprecated + * This event name is deprecated and is unsupported in updated + * Enhanced Ecommerce reports. + */ CHECKOUT_PROGRESS = 'checkout_progress', EXCEPTION = 'exception', GENERATE_LEAD = 'generate_lead', diff --git a/packages/analytics/src/factory.ts b/packages/analytics/src/factory.ts index 3bd742304c0..294b8a223da 100644 --- a/packages/analytics/src/factory.ts +++ b/packages/analytics/src/factory.ts @@ -20,7 +20,10 @@ import { Gtag, SettingsOptions, DynamicConfig, - MinimalDynamicConfig + MinimalDynamicConfig, + AnalyticsCallOptions, + CustomParams, + EventParams } from '@firebase/analytics-types'; import { logEvent, @@ -29,12 +32,7 @@ import { setUserProperties, setAnalyticsCollectionEnabled } from './functions'; -import { - insertScriptTag, - getOrCreateDataLayer, - wrapOrCreateGtag, - findGtagScriptOnPage -} from './helpers'; +import { getOrCreateDataLayer, wrapOrCreateGtag } from './helpers'; import { AnalyticsError, ERROR_FACTORY } from './errors'; import { FirebaseApp } from '@firebase/app-types'; import { FirebaseInstallations } from '@firebase/installations-types'; @@ -61,9 +59,9 @@ let initializationPromisesMap: { * wait on all these to be complete in order to determine if it can selectively * wait for only certain initialization (FID) promises or if it must wait for all. */ -let dynamicConfigPromisesList: Array> = []; +let dynamicConfigPromisesList: Array< + Promise +> = []; /** * Maps fetched measurementIds to appId. Populated when the app's dynamic config @@ -202,10 +200,6 @@ export function factory( // Steps here should only be done once per page: creation or wrapping // of dataLayer and global gtag function. - // Detect if user has already put the gtag ' + }) + ]), + new DocParagraph({ configuration }, [ + new DocPlainText({ configuration, text: 'HTML escape: "' }) + ]), + new DocParagraph({ configuration }, [ + new DocPlainText({ + configuration, + text: '3 or more hyphens: - -- --- ---- ----- ------' + }) + ]) + ]); + + output.appendNodes([ + new DocHeading({ configuration, title: 'HTML tag' }), + new DocParagraph({ configuration }, [ + new DocHtmlStartTag({ configuration, name: 'b' }), + new DocPlainText({ configuration, text: 'bold' }), + new DocHtmlEndTag({ configuration, name: 'b' }) + ]) + ]); + + output.appendNodes([ + new DocHeading({ configuration, title: 'Table' }), + new DocTable( + { + configuration, + headerTitles: ['Header 1', 'Header 2'] + }, + [ + new DocTableRow({ configuration }, [ + new DocTableCell({ configuration }, [ + new DocParagraph({ configuration }, [ + new DocPlainText({ configuration, text: 'Cell 1' }) + ]) + ]), + new DocTableCell({ configuration }, [ + new DocParagraph({ configuration }, [ + new DocPlainText({ configuration, text: 'Cell 2' }) + ]) + ]) + ]) + ] + ) + ]); + + const stringBuilder: StringBuilder = new StringBuilder(); + const apiModel: ApiModel = new ApiModel(); + const markdownEmitter: CustomMarkdownEmitter = new CustomMarkdownEmitter( + apiModel + ); + markdownEmitter.emit(stringBuilder, output, { + contextApiItem: undefined, + onGetFilenameForApiItem: (apiItem: ApiItem) => { + return '#'; + } + }); + + expect(stringBuilder).toMatchSnapshot(); +}); diff --git a/repo-scripts/api-documenter/src/markdown/test/__snapshots__/CustomMarkdownEmitter.test.ts.snap b/repo-scripts/api-documenter/src/markdown/test/__snapshots__/CustomMarkdownEmitter.test.ts.snap new file mode 100644 index 00000000000..ae459a5a8d7 --- /dev/null +++ b/repo-scripts/api-documenter/src/markdown/test/__snapshots__/CustomMarkdownEmitter.test.ts.snap @@ -0,0 +1,62 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`render Markdown from TSDoc 1`] = ` +StringBuilder { + "_chunks": Array [ + " +## Simple bold test + +This is a bold word. + +## All whitespace bold + + + +## Newline bold + +line 1 line 2 + +## Newline bold with spaces + + line 1 line 2 line 3 + +## Adjacent bold regions + +onetwo three fournon-boldfive + +## Adjacent to other characters + +[a link](./index.md)boldnon-boldmore-non-bold + +## Unknown block tag + +boldnon-boldmore-non-bold + +## Bad characters + +\\\\*one\\\\*two\\\\*three\\\\*four + +## Characters that should be escaped + +Double-encoded JSON: \\"{ \\\\\\\\\\"A\\\\\\\\\\": 123}\\" + +HTML chars: <script>alert(\\"\\\\[You\\\\] are \\\\#1!\\");</script> + +HTML escape: &quot; + +3 or more hyphens: - -- \\\\-\\\\-\\\\- \\\\-\\\\-\\\\-- \\\\-\\\\-\\\\--- \\\\-\\\\-\\\\-\\\\-\\\\-\\\\- + +## HTML tag + +bold + +## Table + +| Header 1 | Header 2 | +| --- | --- | +| Cell 1 | Cell 2 | + +", + ], +} +`; diff --git a/repo-scripts/api-documenter/src/nodes/CustomDocNodeKind.ts b/repo-scripts/api-documenter/src/nodes/CustomDocNodeKind.ts new file mode 100644 index 00000000000..744673f484d --- /dev/null +++ b/repo-scripts/api-documenter/src/nodes/CustomDocNodeKind.ts @@ -0,0 +1,89 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { TSDocConfiguration, DocNodeKind } from '@microsoft/tsdoc'; +import { DocEmphasisSpan } from './DocEmphasisSpan'; +import { DocHeading } from './DocHeading'; +import { DocNoteBox } from './DocNoteBox'; +import { DocTable } from './DocTable'; +import { DocTableCell } from './DocTableCell'; +import { DocTableRow } from './DocTableRow'; + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +/** + * Identifies custom subclasses of {@link DocNode}. + */ +export const enum CustomDocNodeKind { + EmphasisSpan = 'EmphasisSpan', + Heading = 'Heading', + NoteBox = 'NoteBox', + Table = 'Table', + TableCell = 'TableCell', + TableRow = 'TableRow' +} + +export class CustomDocNodes { + private static _configuration: TSDocConfiguration | undefined; + + public static get configuration(): TSDocConfiguration { + if (CustomDocNodes._configuration === undefined) { + const configuration: TSDocConfiguration = new TSDocConfiguration(); + + configuration.docNodeManager.registerDocNodes( + '@micrososft/api-documenter', + [ + { + docNodeKind: CustomDocNodeKind.EmphasisSpan, + constructor: DocEmphasisSpan + }, + { docNodeKind: CustomDocNodeKind.Heading, constructor: DocHeading }, + { docNodeKind: CustomDocNodeKind.NoteBox, constructor: DocNoteBox }, + { docNodeKind: CustomDocNodeKind.Table, constructor: DocTable }, + { + docNodeKind: CustomDocNodeKind.TableCell, + constructor: DocTableCell + }, + { docNodeKind: CustomDocNodeKind.TableRow, constructor: DocTableRow } + ] + ); + + configuration.docNodeManager.registerAllowableChildren( + CustomDocNodeKind.EmphasisSpan, + [DocNodeKind.PlainText, DocNodeKind.SoftBreak] + ); + + configuration.docNodeManager.registerAllowableChildren( + DocNodeKind.Section, + [ + CustomDocNodeKind.Heading, + CustomDocNodeKind.NoteBox, + CustomDocNodeKind.Table + ] + ); + + configuration.docNodeManager.registerAllowableChildren( + DocNodeKind.Paragraph, + [CustomDocNodeKind.EmphasisSpan] + ); + + CustomDocNodes._configuration = configuration; + } + return CustomDocNodes._configuration; + } +} diff --git a/repo-scripts/api-documenter/src/nodes/DocEmphasisSpan.ts b/repo-scripts/api-documenter/src/nodes/DocEmphasisSpan.ts new file mode 100644 index 00000000000..51d6cc79e4e --- /dev/null +++ b/repo-scripts/api-documenter/src/nodes/DocEmphasisSpan.ts @@ -0,0 +1,58 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { + DocNode, + DocNodeContainer, + IDocNodeContainerParameters +} from '@microsoft/tsdoc'; +import { CustomDocNodeKind } from './CustomDocNodeKind'; + +/** + * Constructor parameters for {@link DocEmphasisSpan}. + */ +export interface IDocEmphasisSpanParameters + extends IDocNodeContainerParameters { + bold?: boolean; + italic?: boolean; +} + +/** + * Represents a span of text that is styled with CommonMark emphasis (italics), strong emphasis (boldface), + * or both. + */ +export class DocEmphasisSpan extends DocNodeContainer { + public readonly bold: boolean; + public readonly italic: boolean; + + public constructor( + parameters: IDocEmphasisSpanParameters, + children?: DocNode[] + ) { + super(parameters, children); + this.bold = !!parameters.bold; + this.italic = !!parameters.italic; + } + + /** @override */ + public get kind(): string { + return CustomDocNodeKind.EmphasisSpan; + } +} diff --git a/repo-scripts/api-documenter/src/nodes/DocHeading.ts b/repo-scripts/api-documenter/src/nodes/DocHeading.ts new file mode 100644 index 00000000000..9b0c9072b22 --- /dev/null +++ b/repo-scripts/api-documenter/src/nodes/DocHeading.ts @@ -0,0 +1,59 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { IDocNodeParameters, DocNode } from '@microsoft/tsdoc'; +import { CustomDocNodeKind } from './CustomDocNodeKind'; + +/** + * Constructor parameters for {@link DocHeading}. + */ +export interface IDocHeadingParameters extends IDocNodeParameters { + title: string; + level?: number; +} + +/** + * Represents a section header similar to an HTML `

` or `

` element. + */ +export class DocHeading extends DocNode { + public readonly title: string; + public readonly level: number; + + /** + * Don't call this directly. Instead use {@link TSDocParser} + * @internal + */ + public constructor(parameters: IDocHeadingParameters) { + super(parameters); + this.title = parameters.title; + this.level = parameters.level !== undefined ? parameters.level : 1; + + if (this.level < 1 || this.level > 5) { + throw new Error( + 'IDocHeadingParameters.level must be a number between 1 and 5' + ); + } + } + + /** @override */ + public get kind(): string { + return CustomDocNodeKind.Heading; + } +} diff --git a/repo-scripts/api-documenter/src/nodes/DocNoteBox.ts b/repo-scripts/api-documenter/src/nodes/DocNoteBox.ts new file mode 100644 index 00000000000..dd2133828f7 --- /dev/null +++ b/repo-scripts/api-documenter/src/nodes/DocNoteBox.ts @@ -0,0 +1,55 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { IDocNodeParameters, DocNode, DocSection } from '@microsoft/tsdoc'; +import { CustomDocNodeKind } from './CustomDocNodeKind'; + +/** + * Constructor parameters for {@link DocNoteBox}. + */ +export interface IDocNoteBoxParameters extends IDocNodeParameters {} + +/** + * Represents a note box, which is typically displayed as a bordered box containing informational text. + */ +export class DocNoteBox extends DocNode { + public readonly content: DocSection; + + public constructor( + parameters: IDocNoteBoxParameters, + sectionChildNodes?: ReadonlyArray + ) { + super(parameters); + this.content = new DocSection( + { configuration: this.configuration }, + sectionChildNodes + ); + } + + /** @override */ + public get kind(): string { + return CustomDocNodeKind.NoteBox; + } + + /** @override */ + protected onGetChildNodes(): ReadonlyArray { + return [this.content]; + } +} diff --git a/repo-scripts/api-documenter/src/nodes/DocTable.ts b/repo-scripts/api-documenter/src/nodes/DocTable.ts new file mode 100644 index 00000000000..49f319f0326 --- /dev/null +++ b/repo-scripts/api-documenter/src/nodes/DocTable.ts @@ -0,0 +1,101 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { IDocNodeParameters, DocNode } from '@microsoft/tsdoc'; +import { CustomDocNodeKind } from './CustomDocNodeKind'; +import { DocTableRow } from './DocTableRow'; +import { DocTableCell } from './DocTableCell'; + +/** + * Constructor parameters for {@link DocTable}. + */ +export interface IDocTableParameters extends IDocNodeParameters { + headerCells?: ReadonlyArray; + headerTitles?: string[]; +} + +/** + * Represents table, similar to an HTML `` element. + */ +export class DocTable extends DocNode { + public readonly header: DocTableRow; + + private _rows: DocTableRow[]; + + public constructor( + parameters: IDocTableParameters, + rows?: ReadonlyArray + ) { + super(parameters); + + this.header = new DocTableRow({ configuration: this.configuration }); + this._rows = []; + + if (parameters) { + if (parameters.headerTitles) { + if (parameters.headerCells) { + throw new Error( + 'IDocTableParameters.headerCells and IDocTableParameters.headerTitles' + + ' cannot both be specified' + ); + } + for (const cellText of parameters.headerTitles) { + this.header.addPlainTextCell(cellText); + } + } else if (parameters.headerCells) { + for (const cell of parameters.headerCells) { + this.header.addCell(cell); + } + } + } + + if (rows) { + for (const row of rows) { + this.addRow(row); + } + } + } + + /** @override */ + public get kind(): string { + return CustomDocNodeKind.Table; + } + + public get rows(): ReadonlyArray { + return this._rows; + } + + public addRow(row: DocTableRow): void { + this._rows.push(row); + } + + public createAndAddRow(): DocTableRow { + const row: DocTableRow = new DocTableRow({ + configuration: this.configuration + }); + this.addRow(row); + return row; + } + + /** @override */ + protected onGetChildNodes(): ReadonlyArray { + return [this.header, ...this._rows]; + } +} diff --git a/repo-scripts/api-documenter/src/nodes/DocTableCell.ts b/repo-scripts/api-documenter/src/nodes/DocTableCell.ts new file mode 100644 index 00000000000..6fa4f79159c --- /dev/null +++ b/repo-scripts/api-documenter/src/nodes/DocTableCell.ts @@ -0,0 +1,51 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { IDocNodeParameters, DocNode, DocSection } from '@microsoft/tsdoc'; +import { CustomDocNodeKind } from './CustomDocNodeKind'; + +/** + * Constructor parameters for {@link DocTableCell}. + */ +export interface IDocTableCellParameters extends IDocNodeParameters {} + +/** + * Represents table cell, similar to an HTML `` element. + */ +export class DocTableRow extends DocNode { + private readonly _cells: DocTableCell[]; + + public constructor( + parameters: IDocTableRowParameters, + cells?: ReadonlyArray + ) { + super(parameters); + + this._cells = []; + if (cells) { + for (const cell of cells) { + this.addCell(cell); + } + } + } + + /** @override */ + public get kind(): string { + return CustomDocNodeKind.TableRow; + } + + public get cells(): ReadonlyArray { + return this._cells; + } + + public addCell(cell: DocTableCell): void { + this._cells.push(cell); + } + + public createAndAddCell(): DocTableCell { + const newCell: DocTableCell = new DocTableCell({ + configuration: this.configuration + }); + this.addCell(newCell); + return newCell; + } + + public addPlainTextCell(cellContent: string): DocTableCell { + const cell: DocTableCell = this.createAndAddCell(); + cell.content.appendNodeInParagraph( + new DocPlainText({ + configuration: this.configuration, + text: cellContent + }) + ); + return cell; + } + + /** @override */ + protected onGetChildNodes(): ReadonlyArray { + return this._cells; + } +} diff --git a/repo-scripts/api-documenter/src/plugin/IApiDocumenterPluginManifest.ts b/repo-scripts/api-documenter/src/plugin/IApiDocumenterPluginManifest.ts new file mode 100644 index 00000000000..d8a44b95019 --- /dev/null +++ b/repo-scripts/api-documenter/src/plugin/IApiDocumenterPluginManifest.ts @@ -0,0 +1,96 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { MarkdownDocumenterFeature } from './MarkdownDocumenterFeature'; +import { PluginFeatureInitialization } from './PluginFeature'; + +/** + * Defines a "feature" that is provided by an API Documenter plugin. A feature is a user-defined module + * that customizes the behavior of API Documenter. + * + * @public + */ +export interface IFeatureDefinition { + /** + * The name of this feature, as it will appear in the config file. + * + * The name should consist of one or more words separated by hyphens. Each word should consist of lower case + * letters and numbers. Example: `my-feature` + */ + featureName: string; + + /** + * Determines the kind of feature. The specified value is the name of the base class that `subclass` inherits from. + * + * @remarks + * For now, `MarkdownDocumenterFeature` is the only supported value. + */ + kind: 'MarkdownDocumenterFeature'; + + /** + * Your subclass that extends from the base class. + */ + subclass: { + new ( + initialization: PluginFeatureInitialization + ): MarkdownDocumenterFeature; + }; +} + +/** + * The manifest for an API Documenter plugin. + * + * @remarks + * An API documenter plugin is an NPM package. By convention, the NPM package name should have the prefix + * `doc-plugin-`. Its main entry point should export an object named `apiDocumenterPluginManifest` which implements + * the `IApiDocumenterPluginManifest` interface. + * + * For example: + * ```ts + * class MyMarkdownDocumenter extends MarkdownDocumenterFeature { + * public onInitialized(): void { + * console.log('MyMarkdownDocumenter: onInitialized()'); + * } + * } + * + * export const apiDocumenterPluginManifest: IApiDocumenterPluginManifest = { + * manifestVersion: 1000, + * features: [ + * { + * featureName: 'my-markdown-documenter', + * kind: 'MarkdownDocumenterFeature', + * subclass: MyMarkdownDocumenter + * } + * ] + * }; + * ``` + * @public + */ +export interface IApiDocumenterPluginManifest { + /** + * The manifest version number. For now, this must always be `1000`. + */ + manifestVersion: 1000; + + /** + * The list of features provided by this plugin. + */ + features: IFeatureDefinition[]; +} diff --git a/repo-scripts/api-documenter/src/plugin/MarkdownDocumenterAccessor.ts b/repo-scripts/api-documenter/src/plugin/MarkdownDocumenterAccessor.ts new file mode 100644 index 00000000000..87e9ca09c0f --- /dev/null +++ b/repo-scripts/api-documenter/src/plugin/MarkdownDocumenterAccessor.ts @@ -0,0 +1,55 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { ApiItem } from 'api-extractor-model-me'; + +/** @internal */ +export interface IMarkdownDocumenterAccessorImplementation { + getLinkForApiItem(apiItem: ApiItem): string | undefined; +} + +/** + * Provides access to the documenter that is generating the output. + * + * @privateRemarks + * This class is wrapper that provides access to the underlying MarkdownDocumenter, while hiding the implementation + * details to ensure that the plugin API contract is stable. + * + * @public + */ +export class MarkdownDocumenterAccessor { + private _implementation: IMarkdownDocumenterAccessorImplementation; + + /** @internal */ + public constructor( + implementation: IMarkdownDocumenterAccessorImplementation + ) { + this._implementation = implementation; + } + + /** + * For a given `ApiItem`, return its markdown hyperlink. + * + * @returns The hyperlink, or `undefined` if the `ApiItem` object does not have a hyperlink. + */ + public getLinkForApiItem(apiItem: ApiItem): string | undefined { + return this._implementation.getLinkForApiItem(apiItem); + } +} diff --git a/repo-scripts/api-documenter/src/plugin/MarkdownDocumenterFeature.ts b/repo-scripts/api-documenter/src/plugin/MarkdownDocumenterFeature.ts new file mode 100644 index 00000000000..00fa9d03785 --- /dev/null +++ b/repo-scripts/api-documenter/src/plugin/MarkdownDocumenterFeature.ts @@ -0,0 +1,124 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { ApiItem, ApiModel } from 'api-extractor-model-me'; +import { TypeUuid } from '@rushstack/node-core-library'; +import { PluginFeature } from './PluginFeature'; +import { MarkdownDocumenterAccessor } from './MarkdownDocumenterAccessor'; + +/** + * Context object for {@link MarkdownDocumenterFeature}. + * Exposes various services that can be used by a plugin. + * + * @public + */ +export class MarkdownDocumenterFeatureContext { + /** + * Provides access to the `ApiModel` for the documentation being generated. + */ + public readonly apiModel: ApiModel; + + /** + * The full path to the output folder. + */ + public readonly outputFolder: string; + + /** + * Exposes functionality of the documenter. + */ + public readonly documenter: MarkdownDocumenterAccessor; + + /** @internal */ + public constructor(options: MarkdownDocumenterFeatureContext) { + this.apiModel = options.apiModel; + this.outputFolder = options.outputFolder; + this.documenter = options.documenter; + } +} + +/** + * Event arguments for MarkdownDocumenterFeature.onBeforeWritePage() + * @public + */ +export interface IMarkdownDocumenterFeatureOnBeforeWritePageArgs { + /** + * The API item corresponding to this page. + */ + readonly apiItem: ApiItem; + + /** + * The page content. The {@link MarkdownDocumenterFeature.onBeforeWritePage} handler can reassign this + * string to customize the page appearance. + */ + pageContent: string; + + /** + * The filename where the output will be written. + */ + readonly outputFilename: string; +} + +/** + * Event arguments for MarkdownDocumenterFeature.onFinished() + * @public + */ +export interface IMarkdownDocumenterFeatureOnFinishedArgs {} + +const uuidMarkdownDocumenterFeature: string = + '34196154-9eb3-4de0-a8c8-7e9539dfe216'; + +/** + * Inherit from this base class to implement an API Documenter plugin feature that customizes + * the generation of markdown output. + * + * @public + */ +export class MarkdownDocumenterFeature extends PluginFeature { + /** {@inheritdoc PluginFeature.context} */ + public context!: MarkdownDocumenterFeatureContext; + + /** + * This event occurs before each markdown file is written. It provides an opportunity to customize the + * content of the file. + * @virtual + */ + public onBeforeWritePage( + eventArgs: IMarkdownDocumenterFeatureOnBeforeWritePageArgs + ): void { + // (implemented by child class) + } + + /** + * This event occurs after all output files have been written. + * @virtual + */ + public onFinished(eventArgs: IMarkdownDocumenterFeatureOnFinishedArgs): void { + // (implemented by child class) + } + + public static [Symbol.hasInstance](instance: object): boolean { + return TypeUuid.isInstanceOf(instance, uuidMarkdownDocumenterFeature); + } +} + +TypeUuid.registerClass( + MarkdownDocumenterFeature, + uuidMarkdownDocumenterFeature +); diff --git a/repo-scripts/api-documenter/src/plugin/PluginFeature.ts b/repo-scripts/api-documenter/src/plugin/PluginFeature.ts new file mode 100644 index 00000000000..4fcef9f4eab --- /dev/null +++ b/repo-scripts/api-documenter/src/plugin/PluginFeature.ts @@ -0,0 +1,85 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { TypeUuid } from '@rushstack/node-core-library'; + +/** + * This is an internal part of the plugin infrastructure. + * + * @remarks + * This object is the constructor parameter for API Documenter plugin features. + * + * @public + */ +export class PluginFeatureInitialization { + /** @internal */ + public _context!: PluginFeatureContext; + + /** @internal */ + public constructor() { + // reserved for future use + } +} + +/** + * Context object for {@link PluginFeature}. + * Exposes various services that can be used by a plugin. + * + * @public + */ +export class PluginFeatureContext {} + +const uuidPluginFeature: string = '56876472-7134-4812-819e-533de0ee10e6'; + +/** + * The abstract base class for all API Documenter plugin features. + * @public + */ +export abstract class PluginFeature { + /** + * Exposes various services that can be used by a plugin. + */ + public context: PluginFeatureContext; + + /** + * The subclass should pass the `initialization` through to the base class. + * Do not put custom initialization code in the constructor. Instead perform your initialization in the + * `onInitialized()` event function. + * @internal + */ + public constructor(initialization: PluginFeatureInitialization) { + // reserved for future expansion + this.context = initialization._context; + } + + /** + * This event function is called after the feature is initialized, but before any processing occurs. + * @virtual + */ + public onInitialized(): void { + // (implemented by child class) + } + + public static [Symbol.hasInstance](instance: object): boolean { + return TypeUuid.isInstanceOf(instance, uuidPluginFeature); + } +} + +TypeUuid.registerClass(PluginFeature, uuidPluginFeature); diff --git a/repo-scripts/api-documenter/src/plugin/PluginLoader.ts b/repo-scripts/api-documenter/src/plugin/PluginLoader.ts new file mode 100644 index 00000000000..5b185a81ffe --- /dev/null +++ b/repo-scripts/api-documenter/src/plugin/PluginLoader.ts @@ -0,0 +1,163 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import * as path from 'path'; +import * as resolve from 'resolve'; + +import { + IApiDocumenterPluginManifest, + IFeatureDefinition +} from './IApiDocumenterPluginManifest'; +import { + MarkdownDocumenterFeature, + MarkdownDocumenterFeatureContext +} from './MarkdownDocumenterFeature'; +import { PluginFeatureInitialization } from './PluginFeature'; +import { DocumenterConfig } from '../documenters/DocumenterConfig'; + +interface ILoadedPlugin { + packageName: string; + manifest: IApiDocumenterPluginManifest; +} + +export class PluginLoader { + public markdownDocumenterFeature: MarkdownDocumenterFeature | undefined; + + public load( + documenterConfig: DocumenterConfig, + createContext: () => MarkdownDocumenterFeatureContext + ): void { + const configFileFolder: string = path.dirname( + documenterConfig.configFilePath + ); + for (const configPlugin of documenterConfig.configFile.plugins || []) { + try { + // Look for the package name in the same place as the config file + const resolvedEntryPointPath: string = resolve.sync( + configPlugin.packageName, + { + basedir: configFileFolder + } + ); + + // Load the package + const entryPoint: + | { apiDocumenterPluginManifest?: IApiDocumenterPluginManifest } + | undefined = require(resolvedEntryPointPath); + + if (!entryPoint) { + throw new Error('Invalid entry point'); + } + + if (!entryPoint.apiDocumenterPluginManifest) { + throw new Error( + `The package is not an API documenter plugin;` + + ` the "apiDocumenterPluginManifest" export was not found` + ); + } + + const manifest: IApiDocumenterPluginManifest = + entryPoint.apiDocumenterPluginManifest; + + if (manifest.manifestVersion !== 1000) { + throw new Error( + `The plugin is not compatible with this version of API Documenter;` + + ` unsupported manifestVersion` + ); + } + + const loadedPlugin: ILoadedPlugin = { + packageName: configPlugin.packageName, + manifest + }; + + const featureDefinitionsByName: Map< + string, + IFeatureDefinition + > = new Map(); + for (const featureDefinition of manifest.features) { + featureDefinitionsByName.set( + featureDefinition.featureName, + featureDefinition + ); + } + + for (const featureName of configPlugin.enabledFeatureNames) { + const featureDefinition: + | IFeatureDefinition + | undefined = featureDefinitionsByName.get(featureName); + if (!featureDefinition) { + throw new Error( + `The plugin ${loadedPlugin.packageName} does not have a feature with name "${featureName}"` + ); + } + + if (featureDefinition.kind === 'MarkdownDocumenterFeature') { + if (this.markdownDocumenterFeature) { + throw new Error('A MarkdownDocumenterFeature is already loaded'); + } + + const initialization: PluginFeatureInitialization = new PluginFeatureInitialization(); + initialization._context = createContext(); + + let markdownDocumenterFeature: + | MarkdownDocumenterFeature + | undefined = undefined; + try { + markdownDocumenterFeature = new featureDefinition.subclass( + initialization + ); + } catch (e) { + throw new Error( + `Failed to construct feature subclass:\n` + e.toString() + ); + } + if ( + !(markdownDocumenterFeature instanceof MarkdownDocumenterFeature) + ) { + throw new Error( + 'The constructed subclass was not an instance of MarkdownDocumenterFeature' + ); + } + + try { + markdownDocumenterFeature.onInitialized(); + } catch (e) { + throw new Error( + 'Error occurred during the onInitialized() event: ' + + e.toString() + ); + } + + this.markdownDocumenterFeature = markdownDocumenterFeature; + } else { + throw new Error( + `Unknown feature definition kind: "${featureDefinition.kind}"` + ); + } + } + } catch (e) { + throw new Error( + `Error loading plugin ${configPlugin.packageName}: ` + e.message + ); + } + } + } +} diff --git a/repo-scripts/api-documenter/src/schemas/api-documenter-template.json b/repo-scripts/api-documenter/src/schemas/api-documenter-template.json new file mode 100644 index 00000000000..1e57fd7f120 --- /dev/null +++ b/repo-scripts/api-documenter/src/schemas/api-documenter-template.json @@ -0,0 +1,92 @@ +/** + * Config file for API Documenter. For more info, please visit: https://api-extractor.com + */ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-documenter.schema.json", + + /** + * Specifies the output target. + * Supported values are "docfx" or "markdown" + */ + // "outputTarget": "markdown", + + /** + * Specifies what type of newlines API Documenter 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", + + /** + * This enables an experimental feature that will be officially released with the next major version + * of API Documenter. It requires DocFX 2.46 or newer. It enables documentation for namespaces and + * adds them to the table of contents. This will also affect file layout as namespaced items will be nested + * under a directory for the namespace instead of just within the package. + * + * This setting currently only affects the 'docfx' output target. It is equivalent to the `--new-docfx-namespaces` + * command-line parameter. + */ + // "newDocfxNamespaces": false, + + /** + * Describes plugin packages to be loaded, and which features to enable. + */ + "plugins": [ + // { + // "packageName": "doc-plugin-example", + // "enabledFeatureNames": [ "example-feature" ] + // } + ], + + /** + * Configures how the table of contents is generated. + */ + "tableOfContents": { + /** + * Allows hand-coded items to be injected into the table of contents. + * + * DEFAULT VALUE: (none) + */ + // "items": [ + // { "name": "Example Node", "href": "~/homepage/homepage.md" }, + // { + // "name": "API Reference", + // "items": [ + // { "name": "References" } + // ] + // } + // ], + /** + * Optional category name that is recommended to include in the `tocConfig`, + * along with one of the filters: `filterByApiItemName` or `filterByInlineTag`. + * Any items that are not matched to the mentioned filters will be placed under this + * catchAll category. If none provided the items will not be included in the final toc.yml file. + * + * DEFAULT VALUE: (none) + */ + // "catchAllCategory": "References", + /** + * When loading more than one api.json files that might include the same API items, + * toggle either to show duplicates or not. + * + * DEFAULT VALUE: false + */ + // "noDuplicateEntries": true, + /** + * Toggle either sorting of the API items should be made based on category name presence + * in the API item's name. + * + * DEFAULT VALUE: false + */ + // "filterByApiItemName": false, + /** + * Filter that can be used to sort the API items according to an inline custom tag + * that is present on them. + * + * DEFAULT VALUE: (none) + */ + // "filterByInlineTag": "@docCategory" + } +} diff --git a/repo-scripts/api-documenter/src/schemas/api-documenter.schema.json b/repo-scripts/api-documenter/src/schemas/api-documenter.schema.json new file mode 100644 index 00000000000..7282baf3b00 --- /dev/null +++ b/repo-scripts/api-documenter/src/schemas/api-documenter.schema.json @@ -0,0 +1,42 @@ +{ + "title": "API Documenter Configuration", + "description": "Describes how the API Documenter tool will process a project.", + "type": "object", + "properties": { + "$schema": { + "description": "Part of the JSON Schema standard, this optional keyword declares the URL of the schema that the file conforms to. Editors may download the schema and use it to perform syntax highlighting.", + "type": "string" + }, + + "outputTarget": { + "description": "Specifies what type of documentation will be generated", + "type": "string", + "enum": ["docfx", "markdown"] + }, + + "newlineKind": { + "description": "Specifies what type of newlines API Documenter 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\".", + "type": "string", + "enum": ["crlf", "lf", "os"], + "default": "crlf" + }, + + "newDocfxNamespaces": { + "description": "This enables an experimental feature that will be officially released with the next major version of API Documenter. It requires DocFX 2.46 or newer. It enables documentation for namespaces and adds them to the table of contents. This will also affect file layout as namespaced items will be nested under a directory for the namespace instead of just within the package.", + "type": "boolean" + }, + + "plugins": { + "description": "Specifies plugin packages to be loaded", + "type": "array" + }, + + "tableOfContents": { + "description": "Configures how the table of contents is generated.", + "type": "object", + "additionalProperties": true + } + }, + + "additionalProperties": false +} diff --git a/repo-scripts/api-documenter/src/start.ts b/repo-scripts/api-documenter/src/start.ts new file mode 100644 index 00000000000..55d50fb21f2 --- /dev/null +++ b/repo-scripts/api-documenter/src/start.ts @@ -0,0 +1,39 @@ +#!/usr/bin/env node + +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import * as os from 'os'; +import colors from 'colors'; + +import { PackageJsonLookup } from '@rushstack/node-core-library'; + +import { ApiDocumenterCommandLine } from './cli/ApiDocumenterCommandLine'; + +const myPackageVersion: string = PackageJsonLookup.loadOwnPackageJson(__dirname) + .version; + +console.log( + os.EOL + colors.bold(`@firebase/api-documenter ${myPackageVersion} ` + os.EOL) +); + +const parser: ApiDocumenterCommandLine = new ApiDocumenterCommandLine(); + +parser.execute().catch(console.error); // CommandLineParser.execute() should never reject the promise diff --git a/repo-scripts/api-documenter/src/toc.ts b/repo-scripts/api-documenter/src/toc.ts new file mode 100644 index 00000000000..2b71f3c8922 --- /dev/null +++ b/repo-scripts/api-documenter/src/toc.ts @@ -0,0 +1,107 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import yaml from 'js-yaml'; +import { ApiItem, ApiItemKind, ApiModel } from 'api-extractor-model-me'; +import { getFilenameForApiItem } from './documenters/MarkdownDocumenterHelpers'; +import { ModuleSource } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference'; +import { writeFileSync } from 'fs'; +import { resolve } from 'path'; + +export interface ITocGenerationOptions { + apiModel: ApiModel; + g3Path: string; + outputFolder: string; + addFileNameSuffix: boolean; + jsSdk: boolean; +} + +interface ITocItem { + title: string; + path: string; + section?: ITocItem[]; +} + +export function generateToc({ + apiModel, + g3Path, + outputFolder, + addFileNameSuffix, + jsSdk +}: ITocGenerationOptions) { + const toc = []; + + if (jsSdk) { + const firebaseToc: ITocItem = { + title: 'firebase', + path: `${g3Path}/index`, + section: [] + }; + toc.push(firebaseToc); + } + + generateTocRecursively(apiModel, g3Path, addFileNameSuffix, toc); + + writeFileSync( + resolve(outputFolder, 'toc.yaml'), + yaml.dump( + { toc }, + { + quotingType: '"' + } + ) + ); +} + +function generateTocRecursively( + apiItem: ApiItem, + g3Path: string, + addFileNameSuffix: boolean, + toc: ITocItem[] +) { + if (apiItem.kind === ApiItemKind.EntryPoint) { + // Entry point + const entryPointName = (apiItem.canonicalReference + .source! as ModuleSource).escapedPath.replace('@firebase/', ''); + const entryPointToc: ITocItem = { + title: entryPointName, + path: `${g3Path}/${getFilenameForApiItem(apiItem, addFileNameSuffix)}`, + section: [] + }; + + for (const member of apiItem.members) { + // only classes and interfaces have dedicated pages + if ( + member.kind === ApiItemKind.Class || + member.kind === ApiItemKind.Interface + ) { + const fileName = getFilenameForApiItem(member, addFileNameSuffix); + entryPointToc.section!.push({ + title: member.displayName, + path: `${g3Path}/${fileName}` + }); + } + } + + toc.push(entryPointToc); + } else { + // travel the api tree to find the next entry point + for (const member of apiItem.members) { + generateTocRecursively(member, g3Path, addFileNameSuffix, toc); + } + } +} diff --git a/repo-scripts/api-documenter/src/utils/IndentedWriter.ts b/repo-scripts/api-documenter/src/utils/IndentedWriter.ts new file mode 100644 index 00000000000..6b4fc8cbdf5 --- /dev/null +++ b/repo-scripts/api-documenter/src/utils/IndentedWriter.ts @@ -0,0 +1,243 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { StringBuilder, IStringBuilder } from '@rushstack/node-core-library'; + +/** + * A utility for writing indented text. + * + * @remarks + * + * Note that the indentation is inserted at the last possible opportunity. + * For example, this code... + * + * ```ts + * writer.write('begin\n'); + * writer.increaseIndent(); + * writer.write('one\ntwo\n'); + * writer.decreaseIndent(); + * writer.increaseIndent(); + * writer.decreaseIndent(); + * writer.write('end'); + * ``` + * + * ...would produce this output: + * + * ``` + * begin + * one + * two + * end + * ``` + */ +export class IndentedWriter { + /** + * The text characters used to create one level of indentation. + * Two spaces by default. + */ + public defaultIndentPrefix: string = ' '; + + private readonly _builder: IStringBuilder; + + private _latestChunk: string | undefined; + private _previousChunk: string | undefined; + private _atStartOfLine: boolean; + + private readonly _indentStack: string[]; + private _indentText: string; + + public constructor(builder?: IStringBuilder) { + this._builder = builder === undefined ? new StringBuilder() : builder; + + this._latestChunk = undefined; + this._previousChunk = undefined; + this._atStartOfLine = true; + + this._indentStack = []; + this._indentText = ''; + } + + /** + * Retrieves the output that was built so far. + */ + public getText(): string { + return this._builder.toString(); + } + + public toString(): string { + return this.getText(); + } + + /** + * Increases the indentation. Normally the indentation is two spaces, + * however an arbitrary prefix can optional be specified. (For example, + * the prefix could be "// " to indent and comment simultaneously.) + * Each call to IndentedWriter.increaseIndent() must be followed by a + * corresponding call to IndentedWriter.decreaseIndent(). + */ + public increaseIndent(indentPrefix?: string): void { + this._indentStack.push( + indentPrefix !== undefined ? indentPrefix : this.defaultIndentPrefix + ); + this._updateIndentText(); + } + + /** + * Decreases the indentation, reverting the effect of the corresponding call + * to IndentedWriter.increaseIndent(). + */ + public decreaseIndent(): void { + this._indentStack.pop(); + this._updateIndentText(); + } + + /** + * A shorthand for ensuring that increaseIndent()/decreaseIndent() occur + * in pairs. + */ + public indentScope(scope: () => void, indentPrefix?: string): void { + this.increaseIndent(indentPrefix); + scope(); + this.decreaseIndent(); + } + + /** + * Adds a newline if the file pointer is not already at the start of the line (or start of the stream). + */ + public ensureNewLine(): void { + const lastCharacter: string = this.peekLastCharacter(); + if (lastCharacter !== '\n' && lastCharacter !== '') { + this._writeNewLine(); + } + } + + /** + * Adds up to two newlines to ensure that there is a blank line above the current line. + */ + public ensureSkippedLine(): void { + if (this.peekLastCharacter() !== '\n') { + this._writeNewLine(); + } + + const secondLastCharacter: string = this.peekSecondLastCharacter(); + if (secondLastCharacter !== '\n' && secondLastCharacter !== '') { + this._writeNewLine(); + } + } + + /** + * Returns the last character that was written, or an empty string if no characters have been written yet. + */ + public peekLastCharacter(): string { + if (this._latestChunk !== undefined) { + return this._latestChunk.substr(-1, 1); + } + return ''; + } + + /** + * Returns the second to last character that was written, or an empty string if less than one characters + * have been written yet. + */ + public peekSecondLastCharacter(): string { + if (this._latestChunk !== undefined) { + if (this._latestChunk.length > 1) { + return this._latestChunk.substr(-2, 1); + } + if (this._previousChunk !== undefined) { + return this._previousChunk.substr(-1, 1); + } + } + return ''; + } + + /** + * Writes some text to the internal string buffer, applying indentation according + * to the current indentation level. If the string contains multiple newlines, + * each line will be indented separately. + */ + public write(message: string): void { + if (message.length === 0) { + return; + } + + // If there are no newline characters, then append the string verbatim + if (!/[\r\n]/.test(message)) { + this._writeLinePart(message); + return; + } + + // Otherwise split the lines and write each one individually + let first: boolean = true; + for (const linePart of message.split('\n')) { + if (!first) { + this._writeNewLine(); + } else { + first = false; + } + if (linePart) { + this._writeLinePart(linePart.replace(/[\r]/g, '')); + } + } + } + + /** + * A shorthand for writing an optional message, followed by a newline. + * Indentation is applied following the semantics of IndentedWriter.write(). + */ + public writeLine(message: string = ''): void { + if (message.length > 0) { + this.write(message); + } + this._writeNewLine(); + } + + /** + * Writes a string that does not contain any newline characters. + */ + private _writeLinePart(message: string): void { + if (message.length > 0) { + if (this._atStartOfLine && this._indentText.length > 0) { + this._write(this._indentText); + } + this._write(message); + this._atStartOfLine = false; + } + } + + private _writeNewLine(): void { + if (this._atStartOfLine && this._indentText.length > 0) { + this._write(this._indentText); + } + + this._write('\n'); + this._atStartOfLine = true; + } + + private _write(s: string): void { + this._previousChunk = this._latestChunk; + this._latestChunk = s; + this._builder.append(s); + } + + private _updateIndentText(): void { + this._indentText = this._indentStack.join(''); + } +} diff --git a/repo-scripts/api-documenter/src/utils/Utilities.ts b/repo-scripts/api-documenter/src/utils/Utilities.ts new file mode 100644 index 00000000000..d5a1bd39c13 --- /dev/null +++ b/repo-scripts/api-documenter/src/utils/Utilities.ts @@ -0,0 +1,48 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { ApiParameterListMixin, ApiItem } from 'api-extractor-model-me'; + +export class Utilities { + private static readonly _badFilenameCharsRegExp: RegExp = /[^a-z0-9_\-\.]/gi; + /** + * Generates a concise signature for a function. Example: "getArea(width, height)" + */ + public static getConciseSignature(apiItem: ApiItem): string { + if (ApiParameterListMixin.isBaseClassOf(apiItem)) { + return ( + apiItem.displayName + + '(' + + apiItem.parameters.map(x => x.name).join(', ') + + ')' + ); + } + return apiItem.displayName; + } + + /** + * Converts bad filename characters to underscores. + */ + public static getSafeFilenameForName(name: string): string { + // TODO: This can introduce naming collisions. + // We will fix that as part of https://github.com/microsoft/rushstack/issues/1308 + return name.replace(Utilities._badFilenameCharsRegExp, '_').toLowerCase(); + } +} diff --git a/repo-scripts/api-documenter/src/utils/test/IndentedWriter.test.ts b/repo-scripts/api-documenter/src/utils/test/IndentedWriter.test.ts new file mode 100644 index 00000000000..e313ffd8123 --- /dev/null +++ b/repo-scripts/api-documenter/src/utils/test/IndentedWriter.test.ts @@ -0,0 +1,99 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { IndentedWriter } from '../IndentedWriter'; +import { expect, use } from 'chai'; +import { jestSnapshotPlugin } from 'mocha-chai-jest-snapshot'; + +use(jestSnapshotPlugin()); + +it('01 Demo from docs', () => { + const indentedWriter: IndentedWriter = new IndentedWriter(); + indentedWriter.write('begin\n'); + indentedWriter.increaseIndent(); + indentedWriter.write('one\ntwo\n'); + indentedWriter.decreaseIndent(); + indentedWriter.increaseIndent(); + indentedWriter.decreaseIndent(); + indentedWriter.write('end'); + + expect(indentedWriter.toString()).toMatchSnapshot(); +}); + +it('02 Indent something', () => { + const indentedWriter: IndentedWriter = new IndentedWriter(); + indentedWriter.write('a'); + indentedWriter.write('b'); + indentedWriter.increaseIndent(); + indentedWriter.writeLine('c'); + indentedWriter.writeLine('d'); + indentedWriter.decreaseIndent(); + indentedWriter.writeLine('e'); + + indentedWriter.increaseIndent('>>> '); + indentedWriter.writeLine(); + indentedWriter.writeLine(); + indentedWriter.writeLine('g'); + indentedWriter.decreaseIndent(); + + expect(indentedWriter.toString()).toMatchSnapshot(); +}); + +it('03 Two kinds of indents', () => { + const indentedWriter: IndentedWriter = new IndentedWriter(); + + indentedWriter.writeLine('---'); + indentedWriter.indentScope(() => { + indentedWriter.write('a\nb'); + indentedWriter.indentScope(() => { + indentedWriter.write('c\nd\n'); + }); + indentedWriter.write('e\n'); + }, '> '); + indentedWriter.writeLine('---'); + + expect(indentedWriter.toString()).toMatchSnapshot(); +}); + +it('04 Edge cases for ensureNewLine()', () => { + let indentedWriter: IndentedWriter = new IndentedWriter(); + indentedWriter.ensureNewLine(); + indentedWriter.write('line'); + expect(indentedWriter.toString()).toMatchSnapshot(); + + indentedWriter = new IndentedWriter(); + indentedWriter.write('previous'); + indentedWriter.ensureNewLine(); + indentedWriter.write('line'); + expect(indentedWriter.toString()).toMatchSnapshot(); +}); + +it('04 Edge cases for ensureSkippedLine()', () => { + let indentedWriter: IndentedWriter = new IndentedWriter(); + indentedWriter.ensureSkippedLine(); + indentedWriter.write('line'); + expect(indentedWriter.toString()).toMatchSnapshot(); + + indentedWriter = new IndentedWriter(); + indentedWriter.write('previous'); + indentedWriter.ensureSkippedLine(); + indentedWriter.write('line'); + expect(indentedWriter.toString()).toMatchSnapshot(); +}); diff --git a/repo-scripts/api-documenter/src/utils/test/__snapshots__/IndentedWriter.test.ts.snap b/repo-scripts/api-documenter/src/utils/test/__snapshots__/IndentedWriter.test.ts.snap new file mode 100644 index 00000000000..62778d301c8 --- /dev/null +++ b/repo-scripts/api-documenter/src/utils/test/__snapshots__/IndentedWriter.test.ts.snap @@ -0,0 +1,46 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`01 Demo from docs 1`] = ` +"begin + one + two +end" +`; + +exports[`02 Indent something 1`] = ` +"abc + d +e +>>> +>>> +>>> g +" +`; + +exports[`03 Two kinds of indents 1`] = ` +"--- +> a +> bc +> d +> e +--- +" +`; + +exports[`04 Edge cases for ensureNewLine() 1`] = `"line"`; + +exports[`04 Edge cases for ensureNewLine() 2`] = ` +"previous +line" +`; + +exports[`04 Edge cases for ensureSkippedLine() 1`] = ` +" +line" +`; + +exports[`04 Edge cases for ensureSkippedLine() 2`] = ` +"previous + +line" +`; diff --git a/repo-scripts/api-documenter/tsconfig.json b/repo-scripts/api-documenter/tsconfig.json new file mode 100644 index 00000000000..e790468f48f --- /dev/null +++ b/repo-scripts/api-documenter/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../config/tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "downlevelIteration": true, + "module": "CommonJS", + "target": "ES6", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true + }, + "exclude": ["dist/**/*"] +} \ No newline at end of file diff --git a/repo-scripts/changelog-generator/package.json b/repo-scripts/changelog-generator/package.json index caab43e5e59..862d1df90be 100644 --- a/repo-scripts/changelog-generator/package.json +++ b/repo-scripts/changelog-generator/package.json @@ -13,18 +13,17 @@ "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", "build": "tsc", "build:dev": "tsc -w", - "test": "tsc -p . --noEmit", - "prepare": "yarn build" + "test": "tsc -p . --noEmit" }, "dependencies": { - "@changesets/types": "3.2.0", - "@changesets/get-github-info": "0.4.4", + "@changesets/types": "3.3.0", + "@changesets/get-github-info": "0.5.0", "node-fetch": "2.6.1", "@types/node-fetch": "2.5.7" }, "license": "Apache-2.0", "devDependencies": { - "typescript": "4.0.2" + "typescript": "4.2.2" }, "repository": { "directory": "repo-scripts/changelog-generator", diff --git a/repo-scripts/prune-dts/extract-public-api.ts b/repo-scripts/prune-dts/extract-public-api.ts new file mode 100644 index 00000000000..790bc684120 --- /dev/null +++ b/repo-scripts/prune-dts/extract-public-api.ts @@ -0,0 +1,224 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as fs from 'fs'; +import * as path from 'path'; + +import { Extractor, ExtractorConfig } from 'api-extractor-me'; +import * as tmp from 'tmp'; + +import { addBlankLines, pruneDts, removeUnusedImports } from './prune-dts'; +import * as yargs from 'yargs'; + +/* eslint-disable no-console */ + +// This script takes the output of the API Extractor, post-processes it using +// the pruned-dts script and then invokes API report to generate a report +// that only includes exported symbols. This is all done in temporary folders, +// all configuration is auto-generated for each run. + +const baseApiExtractorConfigFile: string = path.resolve( + __dirname, + '../../config/api-extractor.json' +); +const reportFolder = path.resolve(__dirname, '../../common/api-review'); +const tmpDir = tmp.dirSync().name; + +function writeTypescriptConfig(packageRoot: string): void { + const tsConfigJson = { + extends: path.resolve(packageRoot, './tsconfig.json'), + include: [path.resolve(packageRoot, './src')], + compilerOptions: { + downlevelIteration: true // Needed for FirebaseApp + } + }; + fs.writeFileSync( + path.resolve(tmpDir, 'tsconfig.json'), + JSON.stringify(tsConfigJson), + { encoding: 'utf-8' } + ); +} + +function writePackageJson(packageName: string): void { + const packageJson = { + 'name': `@firebase/${packageName}` + }; + const packageJsonPath = path.resolve(tmpDir, 'package.json'); + fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson), { + encoding: 'utf-8' + }); +} + +function loadApiExtractorConfig( + typescriptDtsPath: string, + rollupDtsPath: string, + untrimmedRollupDtsPath: string, + dtsRollupEnabled: boolean, + apiReportEnabled: boolean +): ExtractorConfig { + const apiExtractorJsonPath = path.resolve(tmpDir, 'api-extractor.json'); + const apiExtractorJson = { + extends: baseApiExtractorConfigFile, + mainEntryPointFilePath: typescriptDtsPath, + 'dtsRollup': { + 'enabled': dtsRollupEnabled, + publicTrimmedFilePath: rollupDtsPath, + untrimmedFilePath: untrimmedRollupDtsPath + }, + 'tsdocMetadata': { + 'enabled': false + }, + 'apiReport': { + 'enabled': apiReportEnabled, + reportFolder + }, + 'messages': { + 'extractorMessageReporting': { + 'ae-missing-release-tag': { + 'logLevel': 'none' + }, + 'ae-unresolved-link': { + 'logLevel': 'none' + }, + 'ae-forgotten-export': { + 'logLevel': apiReportEnabled ? 'error' : 'none' + } + }, + 'tsdocMessageReporting': { + 'tsdoc-undefined-tag': { + 'logLevel': 'none' + } + } + } + }; + fs.writeFileSync(apiExtractorJsonPath, JSON.stringify(apiExtractorJson), { + encoding: 'utf-8' + }); + console.log(apiExtractorJsonPath); + return ExtractorConfig.loadFileAndPrepare(apiExtractorJsonPath); +} + +/** + * Generates the Public API from generated DTS files. + * + * @param packageName - The name of the Firebase package (e.g. "database" or + * "firestore-lite") + * @param packageRoot - The root path of the package + * @param typescriptDtsPath - The .d.ts file generated by the Typescript + * compiler as we transpile our sources + * @param rollupDtsPath - A "bundled" version of our d.ts files that includes + * all public and private types + * @param untrimmedRollupDtsPath - A "bundled" version of our d.ts files that + * includes all public and private types, but also include exports marked as + * `@internal`. This file is used by compat APIs to use internal exports + * @param publicDtsPath - The output file for the customer-facing .d.ts file + * that only includes the public APIs + */ +export async function generateApi( + packageName: string, + packageRoot: string, + typescriptDtsPath: string, + rollupDtsPath: string, + untrimmedRollupDtsPath: string, + publicDtsPath: string +): Promise { + console.log(`Configuring API Extractor for #{packageName}`); + writeTypescriptConfig(packageRoot); + writePackageJson(packageName); + + let extractorConfig = loadApiExtractorConfig( + typescriptDtsPath, + rollupDtsPath, + untrimmedRollupDtsPath, + /* dtsRollupEnabled= */ true, + /* apiReportEnabled= */ false + ); + Extractor.invoke(extractorConfig, { + localBuild: true + }); + + console.log('Generated rollup DTS'); + pruneDts(rollupDtsPath, publicDtsPath); + console.log('Pruned DTS file'); + await addBlankLines(publicDtsPath); + console.log('Added blank lines after imports'); + await removeUnusedImports(publicDtsPath); + console.log('Removed unused imports'); + + extractorConfig = loadApiExtractorConfig( + publicDtsPath, + rollupDtsPath, + untrimmedRollupDtsPath, + /* dtsRollupEnabled= */ false, + /* apiReportEnabled= */ true + ); + Extractor.invoke(extractorConfig, { localBuild: true }); + console.log(`API report for ${packageName} written to ${reportFolder}`); +} + +const argv = yargs.options({ + package: { + type: 'string', + desc: + 'The name of the Firebase package (e.g. "database" or ' + + '"firestore-lite")', + require: true + }, + packageRoot: { + type: 'string', + desc: 'The root path of the package', + require: true + }, + typescriptDts: { + type: 'string', + desc: + 'The .d.ts file generated by the Typescript compiler as we transpile ' + + 'our sources', + require: true + }, + rollupDts: { + type: 'string', + desc: + 'A "bundled" version of our d.ts files that include all public and ' + + 'private types', + require: true + }, + untrimmedRollupDts: { + type: 'string', + desc: + ' A "bundled" version of our d.ts files that includes all public ' + + 'and private types, but also include exports marked as `@internal`. ' + + 'This file is used by compat APIs to use internal exports', + require: true + }, + publicDts: { + type: 'string', + desc: + 'The output file for the customer-facing .d.ts file that only ' + + 'includes the public APIs', + require: true + } +}).argv; + +void generateApi( + argv.package, + path.resolve(argv.packageRoot), + path.resolve(argv.typescriptDts), + path.resolve(argv.rollupDts), + path.resolve(argv.untrimmedRollupDts), + path.resolve(argv.publicDts) +); diff --git a/repo-scripts/prune-dts/package.json b/repo-scripts/prune-dts/package.json new file mode 100644 index 00000000000..c00f7fd548c --- /dev/null +++ b/repo-scripts/prune-dts/package.json @@ -0,0 +1,39 @@ +{ + "name": "firebase-repo-scripts-prune-dts", + "version": "0.1.0", + "private": true, + "engines": { + "node": "^8.13.0 || >=10.10.0" + }, + "description": "A script to prune non-exported types from a d.ts.", + "author": "Firebase (https://firebase.google.com/)", + "scripts": { + "prettier": "prettier --write '**/*.ts'", + "test": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' mocha --require ts-node/register --timeout 5000 *.test.ts" + }, + "license": "Apache-2.0", + "dependencies": { + "eslint": "7.29.0", + "eslint-plugin-unused-imports": "1.1.1", + "prettier": "2.3.1" + }, + "repository": { + "directory": "repo-scripts/prune-dts", + "type": "git", + "url": "https://github.com/firebase/firebase-js-sdk.git" + }, + "bugs": { + "url": "https://github.com/firebase/firebase-js-sdk/issues" + }, + "nyc": { + "extension": [ + ".ts" + ], + "reportDir": "./coverage/node" + }, + "devDependencies": { + "@types/eslint": "7.2.10", + "@types/prettier": "2.3.0", + "mocha": "8.4.0" + } +} diff --git a/repo-scripts/prune-dts/prune-dts.test.ts b/repo-scripts/prune-dts/prune-dts.test.ts new file mode 100644 index 00000000000..ba770e43cd9 --- /dev/null +++ b/repo-scripts/prune-dts/prune-dts.test.ts @@ -0,0 +1,99 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as os from 'os'; +import * as fs from 'fs'; +import * as path from 'path'; +import { format, resolveConfig } from 'prettier'; +import { expect } from 'chai'; +import { describe, it } from 'mocha'; +import { pruneDts } from './prune-dts'; + +const testCasesDir = path.resolve(__dirname, 'tests'); +const tmpDir = os.tmpdir(); + +const testDataFilter = /(.*).input.d.ts/; +const testCaseFilterRe = /.*/; + +async function runScript(inputFile: string): Promise { + const outputFile = path.resolve(tmpDir, 'output.d.ts'); + pruneDts(inputFile, outputFile); + return outputFile; +} + +interface TestCase { + name: string; + inputFileName: string; + outputFileName: string; +} + +function getTestCases(): TestCase[] { + if ( + !fs.existsSync(testCasesDir) || + !fs.lstatSync(testCasesDir).isDirectory() + ) { + throw new Error(`${testCasesDir} folder does not exist`); + } + + return fs + .readdirSync(testCasesDir) + .filter((fileName: string) => testDataFilter.test(fileName)) + .filter((fileName: string) => testCaseFilterRe.test(fileName)) + .map((fileName: string) => { + const testCaseName = fileName.match(testDataFilter)![1]; + + const inputFileName = `${testCaseName}.input.d.ts`; + const outputFileName = `${testCaseName}.output.d.ts`; + + const name = testCaseName.replace(/-/g, ' '); + return { name, inputFileName, outputFileName }; + }); +} + +describe('Prune DTS', () => { + for (const testCase of getTestCases()) { + it(testCase.name, async () => { + const absoluteInputFile = path.resolve( + testCasesDir, + testCase.inputFileName + ); + const absoluteOutputFile = path.resolve( + testCasesDir, + testCase.outputFileName + ); + + const tmpFile = await runScript(absoluteInputFile); + const prettierConfig = await resolveConfig(absoluteInputFile); + + const expectedDtsUnformatted = fs.readFileSync( + absoluteOutputFile, + 'utf-8' + ); + const expectedDts = format(expectedDtsUnformatted, { + filepath: absoluteOutputFile, + ...prettierConfig + }); + const actualDtsUnformatted = fs.readFileSync(tmpFile, 'utf-8'); + const actualDts = format(actualDtsUnformatted, { + filepath: tmpFile, + ...prettierConfig + }); + + expect(actualDts).to.equal(expectedDts); + }); + } +}); diff --git a/repo-scripts/prune-dts/prune-dts.ts b/repo-scripts/prune-dts/prune-dts.ts new file mode 100644 index 00000000000..cc94f0adb62 --- /dev/null +++ b/repo-scripts/prune-dts/prune-dts.ts @@ -0,0 +1,539 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as yargs from 'yargs'; +import * as ts from 'typescript'; +import * as fs from 'fs'; +import { ESLint } from 'eslint'; + +/** + * Prunes a DTS file based on three main rules: + * - Top level types are only included if they are also exported. + * - Underscore-prefixed members of class and interface types are stripped. + * - Constructors are made private or protected if marked with + * `@hideconstructor`/`@hideconstructor protected`. + * + * This function is meant to operate on DTS files generated by API extractor + * and extracts out the API that is relevant for third-party SDK consumers. + * + * @param inputLocation The file path to the .d.ts produced by API explorer. + * @param outputLocation The output location for the pruned .d.ts file. + */ +export function pruneDts(inputLocation: string, outputLocation: string): void { + const compilerOptions = {}; + const host = ts.createCompilerHost(compilerOptions); + const program = ts.createProgram([inputLocation], compilerOptions, host); + const printer: ts.Printer = ts.createPrinter(); + const sourceFile = program.getSourceFile(inputLocation)!; + + const result: ts.TransformationResult = ts.transform( + sourceFile, + [dropPrivateApiTransformer.bind(null, program, host)] + ); + const transformedSourceFile: ts.SourceFile = result.transformed[0]; + let content = printer.printFile(transformedSourceFile); + + fs.writeFileSync(outputLocation, content); +} + +export async function addBlankLines(outputLocation: string): Promise { + const eslint = new ESLint({ + fix: true, + overrideConfig: { + parserOptions: { + ecmaVersion: 2017, + sourceType: 'module', + tsconfigRootDir: __dirname, + project: ['./tsconfig.eslint.json'] + }, + env: { + es6: true + }, + plugins: ['@typescript-eslint'], + parser: '@typescript-eslint/parser', + rules: { + 'unused-imports/no-unused-imports-ts': ['off'], + // add blank lines after imports. Otherwise removeUnusedImports() will remove the comment + // of the first item after the import block + 'padding-line-between-statements': [ + 'error', + { 'blankLine': 'always', 'prev': 'import', 'next': '*' } + ] + } + } + }); + const results = await eslint.lintFiles(outputLocation); + await ESLint.outputFixes(results); +} + +export async function removeUnusedImports( + outputLocation: string +): Promise { + const eslint = new ESLint({ + fix: true, + overrideConfig: { + parserOptions: { + ecmaVersion: 2017, + sourceType: 'module', + tsconfigRootDir: __dirname, + project: ['./tsconfig.eslint.json'] + }, + env: { + es6: true + }, + plugins: ['unused-imports', '@typescript-eslint'], + parser: '@typescript-eslint/parser', + rules: { + 'unused-imports/no-unused-imports-ts': ['error'] + } + } + }); + const results = await eslint.lintFiles(outputLocation); + await ESLint.outputFixes(results); +} + +/** Determines whether the provided identifier should be hidden. */ +function hasPrivatePrefix(name: ts.Identifier): boolean { + // Identifiers that are prefixed with an underscore are not not included in + // the public API. + return !!name.escapedText?.toString().startsWith('_'); +} + +/** Returns whether type identified by `name` is exported. */ +function isExported( + typeChecker: ts.TypeChecker, + sourceFile: ts.SourceFile, + name: ts.Identifier +): boolean { + // Check is this is a public symbol (e.g. part of the DOM library) + const sourceFileNames = typeChecker + .getSymbolAtLocation(name) + ?.declarations?.map(d => d.getSourceFile().fileName); + if (sourceFileNames?.find(s => s.indexOf('typescript/lib') != -1)) { + return true; + } + + // Check is this is part of the exported symbols of the SDK module + const allExportedSymbols = typeChecker.getExportsOfModule( + typeChecker.getSymbolAtLocation(sourceFile)! + ); + return !!allExportedSymbols.find(s => s.name === name.escapedText); +} + +/** + * Replaces an existing constructor implementation if the constructor is marked + * with the JSDod tag `@hideconstructor`. The replaced constructor can either + * have `private` visibility` or `proctected`. To generate a protected + * constructor, specify `@hideconstructor proctected`. + * + * Returns either the modified constructor or the existing constructor if no + * modification was needed. + */ +function maybeHideConstructor( + node: ts.ConstructorDeclaration +): ts.ConstructorDeclaration { + const hideConstructorTag = ts + .getJSDocTags(node) + ?.find(t => t.tagName.escapedText === 'hideconstructor'); + + if (hideConstructorTag) { + const modifier = ts.createModifier( + hideConstructorTag.comment === 'protected' + ? ts.SyntaxKind.ProtectedKeyword + : ts.SyntaxKind.PrivateKeyword + ); + return ts.createConstructor( + node.decorators, + [modifier], + /*parameters=*/ [], + /* body= */ undefined + ); + } else { + return node; + } +} + +/** + * Examines `extends` and `implements` clauses and removes or replaces them if + * they refer to a non-exported type. When an export is removed, all members + * from the removed class are merged into the provided class or interface + * declaration. + * + * @example + * Input: + * class Foo { + * foo: string; + * } + * export class Bar extends Foo {} + * + * Output: + * export class Bar { + * foo: string; + * } + */ +function prunePrivateImports< + T extends ts.InterfaceDeclaration | ts.ClassDeclaration +>( + program: ts.Program, + host: ts.CompilerHost, + sourceFile: ts.SourceFile, + node: T +): T { + const typeChecker = program.getTypeChecker(); + + // The list of heritage clauses after all private symbols are removed. + const prunedHeritageClauses: ts.HeritageClause[] = []; + // Additional members that are copied from the private symbols into the public + // symbols + const additionalMembers: ts.Node[] = []; + + for (const heritageClause of node.heritageClauses || []) { + const exportedTypes: ts.ExpressionWithTypeArguments[] = []; + for (const type of heritageClause.types) { + if ( + ts.isIdentifier(type.expression) && + isExported(typeChecker, sourceFile, type.expression) + ) { + exportedTypes.push(type); + } else { + // Hide the type we are inheriting from and merge its declarations + // into the current class. + // TODO: We really only need to do this when the type that is extended + // is a class. We should skip this for interfaces. + const privateType = typeChecker.getTypeAtLocation(type); + additionalMembers.push( + ...convertPropertiesForEnclosingClass( + program, + host, + sourceFile, + privateType.getProperties(), + node + ) + ); + } + } + + if (exportedTypes.length > 0) { + prunedHeritageClauses.push( + ts.updateHeritageClause(heritageClause, exportedTypes) + ); + } + } + + if (ts.isClassDeclaration(node)) { + return ts.updateClassDeclaration( + node, + node.decorators, + node.modifiers, + node.name, + node.typeParameters, + prunedHeritageClauses, + [ + ...(node.members as ts.NodeArray), + ...(additionalMembers as ts.ClassElement[]) + ] + ) as T; + } else if (ts.isInterfaceDeclaration(node)) { + return ts.updateInterfaceDeclaration( + node, + node.decorators, + node.modifiers, + node.name, + node.typeParameters, + prunedHeritageClauses, + [ + ...(node.members as ts.NodeArray), + ...(additionalMembers as ts.TypeElement[]) + ] + ) as T; + } else { + throw new Error('Only classes or interfaces are supported'); + } +} + +/** + * Iterates the provided symbols and returns named declarations for these + * symbols if they are missing from `currentClass`. This allows us to merge + * class hierarchies for classes whose inherited types are not part of the + * public API. + * + * This method relies on a private API in TypeScript's `codefix` package. + */ +function convertPropertiesForEnclosingClass( + program: ts.Program, + host: ts.CompilerHost, + sourceFile: ts.SourceFile, + parentClassSymbols: ts.Symbol[], + currentClass: ts.ClassDeclaration | ts.InterfaceDeclaration +): ts.Node[] { + const newMembers: ts.Node[] = []; + // The `codefix` package is not public but it does exactly what we want. It's + // the same package that is used by VSCode to fill in missing members, which + // is what we are using it for in this script. `codefix` handles missing + // properties, methods and correctly deduces generics. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (ts as any).codefix.createMissingMemberNodes( + currentClass, + parentClassSymbols, + sourceFile, + { program, host }, + /* userPreferences= */ {}, + /* importAdder= */ undefined, + (missingMember: ts.ClassElement) => { + const originalSymbol = parentClassSymbols.find( + symbol => + symbol.escapedName == + (missingMember.name as ts.Identifier).escapedText + ); + const jsDocComment = originalSymbol + ? extractJSDocComment(originalSymbol, newMembers) + : undefined; + if (jsDocComment) { + newMembers.push(jsDocComment, missingMember); + } else { + newMembers.push(missingMember); + } + } + ); + return newMembers; +} + +/** Extracts the JSDoc comment from `symbol`. */ +function extractJSDocComment( + symbol: ts.Symbol, + alreadyAddedMembers: ts.Node[] +): ts.Node | null { + const overloadCount = alreadyAddedMembers.filter( + node => + ts.isClassElement(node) && + (node.name as ts.Identifier).escapedText == symbol.name + ).length; + + // Extract the comment from the overload that we are currently processing. + let targetIndex = 0; + const comments = symbol.getDocumentationComment(undefined).filter(symbol => { + // Overload comments are separated by line breaks. + if (symbol.kind == 'lineBreak') { + ++targetIndex; + return false; + } else { + return overloadCount == targetIndex; + } + }); + + if (comments.length > 0) { + const jsDocTags = ts.getJSDocTags(symbol.declarations[overloadCount]); + const maybeNewline = jsDocTags?.length > 0 ? '\n' : ''; + return ts.factory.createJSDocComment( + comments[0].text + maybeNewline, + jsDocTags + ); + } + return null; +} + +/** + * Replaces input types of public APIs that consume non-exported types, which + * allows us to exclude private types from the pruned definitions. Returns the + * the name of the exported API or undefined if no type is found. + * + * @example + * Input: + * class PrivateFoo {} + * export class PublicFoo extends PrivateFoo {} + * export function doFoo(foo: PrivateFoo); + * + * Output: + * export class PublicFoo {} + * export function doFoo(foo: PublicFoo); + */ +function extractExportedSymbol( + typeChecker: ts.TypeChecker, + sourceFile: ts.SourceFile, + typeName: ts.Node +): ts.Symbol | undefined { + if (!ts.isIdentifier(typeName)) { + return undefined; + } + + if (isExported(typeChecker, sourceFile, typeName)) { + // Don't replace the type reference if the type is already part of the + // public API. + return undefined; + } + + const localSymbolName = typeName.escapedText; + const allExportedSymbols = typeChecker.getExportsOfModule( + typeChecker.getSymbolAtLocation(sourceFile)! + ); + + // Examine all exported types and check if they extend or implement the + // provided local type. If so, we can use the exported type in lieu of the + // private type. + + // Short circuit if the local types is already part of the public types. + for (const symbol of allExportedSymbols) { + if (symbol.name === localSymbolName) { + return symbol; + } + } + + // See if there is an exported symbol that extends this private symbol. + // In this case, we can safely use the public symbol instead. + for (const symbol of allExportedSymbols) { + for (const declaration of symbol.declarations) { + if ( + ts.isClassDeclaration(declaration) || + ts.isInterfaceDeclaration(declaration) + ) { + for (const heritageClause of declaration.heritageClauses || []) { + for (const type of heritageClause.types || []) { + if (ts.isIdentifier(type.expression)) { + const subclassName = type.expression.escapedText; + if (subclassName === localSymbolName) { + // TODO: We may need to change this to return a Union type if + // more than one public type corresponds to the private type. + return symbol; + } + } + } + } + } + } + } + + // If no symbol was found that extends the private symbol, check the reverse. + // We might find an exported symbol in the inheritance chain of the local + // symbol. Note that this is not always safe as we might replace the local + // symbol with a less restrictive type. + const localSymbol = typeChecker.getSymbolAtLocation(typeName); + if (localSymbol) { + for (const declaration of localSymbol!.declarations) { + if ( + ts.isClassDeclaration(declaration) || + ts.isInterfaceDeclaration(declaration) + ) { + for (const heritageClause of declaration.heritageClauses || []) { + for (const type of heritageClause.types || []) { + if (ts.isIdentifier(type.expression)) { + if (isExported(typeChecker, sourceFile, type.expression)) { + return typeChecker.getSymbolAtLocation(type.expression); + } + } + } + } + } + } + } + + return undefined; +} + +function dropPrivateApiTransformer( + program: ts.Program, + host: ts.CompilerHost, + context: ts.TransformationContext +): ts.Transformer { + const typeChecker = program.getTypeChecker(); + + return (sourceFile: ts.SourceFile) => { + function visit(node: ts.Node): ts.Node { + if ( + ts.isInterfaceDeclaration(node) || + ts.isClassDeclaration(node) || + ts.isFunctionDeclaration(node) || + ts.isVariableStatement(node) || + ts.isTypeAliasDeclaration(node) || + ts.isModuleDeclaration(node) || + ts.isEnumDeclaration(node) + ) { + // Remove any types that are not exported. + if ( + !node.modifiers?.find(m => m.kind === ts.SyntaxKind.ExportKeyword) + ) { + return ts.createToken(ts.SyntaxKind.WhitespaceTrivia); + } + } + + if (ts.isConstructorDeclaration(node)) { + // Replace internal constructors with private constructors. + return maybeHideConstructor(node); + } else if ( + ts.isClassDeclaration(node) || + ts.isInterfaceDeclaration(node) + ) { + // Remove any imports that reference internal APIs, while retaining + // their public members. + return prunePrivateImports(program, host, sourceFile, node); + } else if ( + ts.isPropertyDeclaration(node) || + ts.isMethodDeclaration(node) || + ts.isGetAccessor(node) + ) { + // Remove any class and interface members that are prefixed with + // underscores. + if (hasPrivatePrefix(node.name as ts.Identifier)) { + return ts.createToken(ts.SyntaxKind.WhitespaceTrivia); + } + } else if (ts.isTypeReferenceNode(node)) { + // For public types that refer internal types, find a public type that + // we can refer to instead. + const publicName = extractExportedSymbol( + typeChecker, + sourceFile, + node.typeName + ); + return publicName + ? ts.updateTypeReferenceNode( + node, + ts.createIdentifier(publicName.name), + node.typeArguments + ) + : node; + } + + return node; + } + + function visitNodeAndChildren(node: T): T { + return ts.visitEachChild( + visit(node), + childNode => visitNodeAndChildren(childNode), + context + ) as T; + } + return visitNodeAndChildren(sourceFile); + }; +} + +const argv = yargs.options({ + input: { + type: 'string', + desc: 'The location of the index.ts file' + }, + output: { + type: 'string', + desc: 'The location for the index.d.ts file' + } +}).argv; + +if (argv.input && argv.output) { + console.log('Removing private exports...'); + pruneDts(argv.input, argv.output); + console.log('Removing unused imports...'); + removeUnusedImports(argv.output).then(() => console.log('Done.')); +} diff --git a/repo-scripts/prune-dts/tests/dom.input.d.ts b/repo-scripts/prune-dts/tests/dom.input.d.ts new file mode 100644 index 00000000000..80cea14308e --- /dev/null +++ b/repo-scripts/prune-dts/tests/dom.input.d.ts @@ -0,0 +1,29 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export class Foo implements PromiseLike { + then( + onfulfilled?: + | ((value: any) => PromiseLike | TResult1) + | undefined + | null, + onrejected?: + | ((reason: any) => PromiseLike | TResult2) + | undefined + | null + ): PromiseLike; +} +export function bar(foo: PromiseLike): PromiseLike; diff --git a/repo-scripts/prune-dts/tests/dom.output.d.ts b/repo-scripts/prune-dts/tests/dom.output.d.ts new file mode 100644 index 00000000000..80cea14308e --- /dev/null +++ b/repo-scripts/prune-dts/tests/dom.output.d.ts @@ -0,0 +1,29 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export class Foo implements PromiseLike { + then( + onfulfilled?: + | ((value: any) => PromiseLike | TResult1) + | undefined + | null, + onrejected?: + | ((reason: any) => PromiseLike | TResult2) + | undefined + | null + ): PromiseLike; +} +export function bar(foo: PromiseLike): PromiseLike; diff --git a/repo-scripts/prune-dts/tests/firestore.input.d.ts b/repo-scripts/prune-dts/tests/firestore.input.d.ts new file mode 100644 index 00000000000..ffd2b242f29 --- /dev/null +++ b/repo-scripts/prune-dts/tests/firestore.input.d.ts @@ -0,0 +1,5778 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { DocumentData as DocumentData_2 } from '@firebase/firestore-types'; +import { FirebaseApp } from '@firebase/app-exp'; +import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; +import { _FirebaseService } from '@firebase/app-exp'; +import { LogLevelString as LogLevel } from '@firebase/logger'; +import { Provider } from '@firebase/component'; +import { SetOptions as SetOptions_2 } from '@firebase/firestore-types'; + +/** + * Converts Firestore's internal types to the JavaScript types that we expose + * to the user. + */ +declare abstract class AbstractUserDataWriter { + convertValue( + value: Value, + serverTimestampBehavior?: ServerTimestampBehavior + ): unknown; + private convertObject; + private convertGeoPoint; + private convertArray; + private convertServerTimestamp; + private convertTimestamp; + protected convertDocumentKey( + name: string, + expectedDatabaseId: DatabaseId + ): DocumentKey; + protected abstract convertReference(name: string): unknown; + protected abstract convertBytes(bytes: ByteString): unknown; +} + +/** + * Describes a map whose keys are active target ids. We do not care about the type of the + * values. + */ +declare type ActiveTargets = SortedMap; + +/** + * Add a new document to specified `CollectionReference` with the given data, + * assigning it a document ID automatically. + * + * @param reference - A reference to the collection to add this document to. + * @param data - An Object containing the data for the new document. + * @returns A Promise resolved with a `DocumentReference` pointing to the + * newly created document after it has been written to the backend (Note that it + * won't resolve while you're offline). + */ +export declare function addDoc( + reference: CollectionReference, + data: T +): Promise>; + +declare interface ApiClientObjectMap { + [k: string]: T; +} + +/** + * Returns a special value that can be used with {@link (setDoc:1)} or {@link + * updateDoc} that tells the server to remove the given elements from any + * array value that already exists on the server. All instances of each element + * specified will be removed from the array. If the field being modified is not + * already an array it will be overwritten with an empty array. + * + * @param elements - The elements to remove from the array. + * @returns The `FieldValue` sentinel for use in a call to `setDoc()` or + * `updateDoc()` + */ +export declare function arrayRemove(...elements: unknown[]): FieldValue; + +/** + * Returns a special value that can be used with {@link setDoc} or {@link + * updateDoc} that tells the server to union the given elements with any array + * value that already exists on the server. Each specified element that doesn't + * already exist in the array will be added to the end. If the field being + * modified is not already an array it will be overwritten with an array + * containing exactly the specified elements. + * + * @param elements - The elements to union into the array. + * @returns The `FieldValue` sentinel for use in a call to `setDoc()` or + * `updateDoc()`. + */ +export declare function arrayUnion(...elements: unknown[]): FieldValue; + +declare interface AsyncQueue { + readonly isShuttingDown: boolean; + /** + * Adds a new operation to the queue without waiting for it to complete (i.e. + * we ignore the Promise result). + */ + enqueueAndForget(op: () => Promise): void; + /** + * Regardless if the queue has initialized shutdown, adds a new operation to the + * queue without waiting for it to complete (i.e. we ignore the Promise result). + */ + enqueueAndForgetEvenWhileRestricted( + op: () => Promise + ): void; + /** + * Initialize the shutdown of this queue. Once this method is called, the + * only possible way to request running an operation is through + * `enqueueEvenWhileRestricted()`. + */ + enterRestrictedMode(): void; + /** + * Adds a new operation to the queue. Returns a promise that will be resolved + * when the promise returned by the new operation is (with its value). + */ + enqueue(op: () => Promise): Promise; + /** + * Enqueue a retryable operation. + * + * A retryable operation is rescheduled with backoff if it fails with a + * IndexedDbTransactionError (the error type used by SimpleDb). All + * retryable operations are executed in order and only run if all prior + * operations were retried successfully. + */ + enqueueRetryable(op: () => Promise): void; + /** + * Schedules an operation to be queued on the AsyncQueue once the specified + * `delayMs` has elapsed. The returned DelayedOperation can be used to cancel + * or fast-forward the operation prior to its running. + */ + enqueueAfterDelay( + timerId: TimerId, + delayMs: number, + op: () => Promise + ): DelayedOperation; + /** + * Verifies there's an operation currently in-progress on the AsyncQueue. + * Unfortunately we can't verify that the running code is in the promise chain + * of that operation, so this isn't a foolproof check, but it should be enough + * to catch some bugs. + */ + verifyOperationInProgress(): void; +} + +/** + * Path represents an ordered sequence of string segments. + */ +declare abstract class BasePath> { + private segments; + private offset; + private len; + constructor(segments: string[], offset?: number, length?: number); + /** + * Abstract constructor method to construct an instance of B with the given + * parameters. + */ + protected abstract construct( + segments: string[], + offset?: number, + length?: number + ): B; + /** + * Returns a String representation. + * + * Implementing classes are required to provide deterministic implementations as + * the String representation is used to obtain canonical Query IDs. + */ + abstract toString(): string; + get length(): number; + isEqual(other: B): boolean; + child(nameOrPath: string | B): B; + /** The index of one past the last segment of the path. */ + private limit; + popFirst(size?: number): B; + popLast(): B; + firstSegment(): string; + lastSegment(): string; + get(index: number): string; + isEmpty(): boolean; + isPrefixOf(other: this): boolean; + isImmediateParentOf(potentialChild: this): boolean; + forEach(fn: (segment: string) => void): void; + toArray(): string[]; + static comparator>( + p1: BasePath, + p2: BasePath + ): number; +} + +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * BatchID is a locally assigned ID for a batch of mutations that have been + * applied. + */ +declare type BatchId = number; + +/** + * Represents a bound of a query. + * + * The bound is specified with the given components representing a position and + * whether it's just before or just after the position (relative to whatever the + * query order is). + * + * The position represents a logical index position for a query. It's a prefix + * of values for the (potentially implicit) order by clauses of a query. + * + * Bound provides a function to determine whether a document comes before or + * after a bound. This is influenced by whether the position is just before or + * just after the provided values. + */ +declare class Bound { + readonly position: Value[]; + readonly before: boolean; + constructor(position: Value[], before: boolean); +} + +/** + * Provides interfaces to save and read Firestore bundles. + */ +declare interface BundleCache { + /** + * Gets the saved `BundleMetadata` for a given `bundleId`, returns undefined + * if no bundle metadata is found under the given id. + */ + getBundleMetadata( + transaction: PersistenceTransaction, + bundleId: string + ): PersistencePromise; + /** + * Saves a `BundleMetadata` from a bundle into local storage, using its id as + * the persistent key. + */ + saveBundleMetadata( + transaction: PersistenceTransaction, + metadata: BundleMetadata_2 + ): PersistencePromise; + /** + * Gets a saved `NamedQuery` for the given query name. Returns undefined if + * no queries are found under the given name. + */ + getNamedQuery( + transaction: PersistenceTransaction, + queryName: string + ): PersistencePromise; + /** + * Saves a `NamedQuery` from a bundle, using its name as the persistent key. + */ + saveNamedQuery( + transaction: PersistenceTransaction, + query: NamedQuery_2 + ): PersistencePromise; +} + +/** Properties of a BundledQuery. */ +declare interface BundledQuery { + /** BundledQuery parent */ + parent?: string | null; + /** BundledQuery structuredQuery */ + structuredQuery?: StructuredQuery | null; + /** BundledQuery limitType */ + limitType?: LimitType_2 | null; +} + +/** + * Represents a Firestore bundle saved by the SDK in its local storage. + */ +declare interface BundleMetadata { + /** + * Id of the bundle. It is used together with `createTime` to determine if a + * bundle has been loaded by the SDK. + */ + readonly id: string; + /** Schema version of the bundle. */ + readonly version: number; + /** + * Set to the snapshot version of the bundle if created by the Server SDKs. + * Otherwise set to SnapshotVersion.MIN. + */ + readonly createTime: SnapshotVersion; +} + +/** Properties of a BundleMetadata. */ +declare interface BundleMetadata_2 { + /** BundleMetadata id */ + id?: string | null; + /** BundleMetadata createTime */ + createTime?: Timestamp_2 | null; + /** BundleMetadata version */ + version?: number | null; + /** BundleMetadata totalDocuments */ + totalDocuments?: number | null; + /** BundleMetadata totalBytes */ + totalBytes?: number | null; +} + +/** + * An immutable object representing an array of bytes. + */ +export declare class Bytes { + _byteString: ByteString; + /** @hideconstructor */ + constructor(byteString: ByteString); + /** + * Creates a new `Bytes` object from the given Base64 string, converting it to + * bytes. + * + * @param base64 - The Base64 string used to create the `Bytes` object. + */ + static fromBase64String(base64: string): Bytes; + /** + * Creates a new `Bytes` object from the given Uint8Array. + * + * @param array - The Uint8Array used to create the `Bytes` object. + */ + static fromUint8Array(array: Uint8Array): Bytes; + /** + * Returns the underlying bytes as a Base64-encoded string. + * + * @returns The Base64-encoded string created from the `Bytes` object. + */ + toBase64(): string; + /** + * Returns the underlying bytes in a new `Uint8Array`. + * + * @returns The Uint8Array created from the `Bytes` object. + */ + toUint8Array(): Uint8Array; + /** + * Returns a string representation of the `Bytes` object. + * + * @returns A string representation of the `Bytes` object. + */ + toString(): string; + /** + * Returns true if this `Bytes` object is equal to the provided one. + * + * @param other - The `Bytes` object to compare against. + * @returns true if this `Bytes` object is equal to the provided one. + */ + isEqual(other: Bytes): boolean; +} + +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Immutable class that represents a "proto" byte string. + * + * Proto byte strings can either be Base64-encoded strings or Uint8Arrays when + * sent on the wire. This class abstracts away this differentiation by holding + * the proto byte string in a common class that must be converted into a string + * before being sent as a proto. + */ +declare class ByteString { + private readonly binaryString; + static readonly EMPTY_BYTE_STRING: ByteString; + private constructor(); + static fromBase64String(base64: string): ByteString; + static fromUint8Array(array: Uint8Array): ByteString; + toBase64(): string; + toUint8Array(): Uint8Array; + approximateByteSize(): number; + compareTo(other: ByteString): number; + isEqual(other: ByteString): boolean; +} + +/** + * Constant used to indicate the LRU garbage collection should be disabled. + * Set this value as the `cacheSizeBytes` on the settings passed to the + * `Firestore` instance. + */ +export declare const CACHE_SIZE_UNLIMITED = -1; + +declare const enum ChangeType { + Added = 0, + Removed = 1, + Modified = 2, + Metadata = 3 +} + +/** + * Clears the persistent storage. This includes pending writes and cached + * documents. + * + * Must be called while the `Firestore` instance is not started (after the app is + * terminated or when the app is first initialized). On startup, this function + * must be called before other functions (other than {@link + * initializeFirestore} or {@link getFirestore})). If the `Firestore` + * instance is still running, the promise will be rejected with the error code + * of `failed-precondition`. + * + * Note: `clearIndexedDbPersistence()` is primarily intended to help write + * reliable tests that use Cloud Firestore. It uses an efficient mechanism for + * dropping existing data but does not attempt to securely overwrite or + * otherwise make cached data unrecoverable. For applications that are sensitive + * to the disclosure of cached data in between user sessions, we strongly + * recommend not enabling persistence at all. + * + * @param firestore - The `Firestore` instance to clear persistence for. + * @returns A promise that is resolved when the persistent storage is + * cleared. Otherwise, the promise is rejected with an error. + */ +export declare function clearIndexedDbPersistence( + firestore: FirebaseFirestore +): Promise; + +/** + * A randomly-generated key assigned to each Firestore instance at startup. + */ +declare type ClientId = string; + +/** + * Gets a `CollectionReference` instance that refers to the collection at + * the specified absolute path. + * + * @param firestore - A reference to the root Firestore instance. + * @param path - A slash-separated path to a collection. + * @param pathSegments - Additional path segments to apply relative to the first + * argument. + * @throws If the final path has an even number of segments and does not point + * to a collection. + * @returns The `CollectionReference` instance. + */ +export declare function collection( + firestore: FirebaseFirestore_2, + path: string, + ...pathSegments: string[] +): CollectionReference; + +/** + * Gets a `CollectionReference` instance that refers to a subcollection of + * `reference` at the the specified relative path. + * + * @param reference - A reference to a collection. + * @param path - A slash-separated path to a collection. + * @param pathSegments - Additional path segments to apply relative to the first + * argument. + * @throws If the final path has an even number of segments and does not point + * to a collection. + * @returns The `CollectionReference` instance. + */ +export declare function collection( + reference: CollectionReference, + path: string, + ...pathSegments: string[] +): CollectionReference; + +/** + * Gets a `CollectionReference` instance that refers to a subcollection of + * `reference` at the the specified relative path. + * + * @param reference - A reference to a Firestore document. + * @param path - A slash-separated path to a collection. + * @param pathSegments - Additional path segments that will be applied relative + * to the first argument. + * @throws If the final path has an even number of segments and does not point + * to a collection. + * @returns The `CollectionReference` instance. + */ +export declare function collection( + reference: DocumentReference, + path: string, + ...pathSegments: string[] +): CollectionReference; + +/** + * Creates and returns a new `Query` instance that includes all documents in the + * database that are contained in a collection or subcollection with the + * given `collectionId`. + * + * @param firestore - A reference to the root Firestore instance. + * @param collectionId - Identifies the collections to query over. Every + * collection or subcollection with this ID as the last segment of its path + * will be included. Cannot contain a slash. + * @returns The created `Query`. + */ +export declare function collectionGroup( + firestore: FirebaseFirestore_2, + collectionId: string +): Query; + +/** + * A `CollectionReference` object can be used for adding documents, getting + * document references, and querying for documents (using {@link query}). + */ +export declare class CollectionReference extends Query { + readonly firestore: FirebaseFirestore_2; + readonly _path: ResourcePath; + readonly type = 'collection'; + /** @hideconstructor */ + constructor( + firestore: FirebaseFirestore_2, + converter: FirestoreDataConverter_2 | null, + _path: ResourcePath + ); + /** The collection's identifier. */ + get id(): string; + /** + * A string representing the path of the referenced collection (relative + * to the root of the database). + */ + get path(): string; + /** + * A reference to the containing `DocumentReference` if this is a + * subcollection. If this isn't a subcollection, the reference is null. + */ + get parent(): DocumentReference | null; + /** + * Applies a custom data converter to this CollectionReference, allowing you + * to use your own custom model objects with Firestore. When you call {@link + * addDoc} with the returned `CollectionReference` instance, the provided + * converter will convert between Firestore data and your custom type `U`. + * + * @param converter - Converts objects to and from Firestore. + * @returns A `CollectionReference` that uses the provided converter. + */ + withConverter( + converter: FirestoreDataConverter_2 + ): CollectionReference; +} + +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +declare type Comparator = (key1: K, key2: K) => number; + +declare interface ComponentConfiguration { + asyncQueue: AsyncQueue; + databaseInfo: DatabaseInfo; + credentials: CredentialsProvider; + clientId: ClientId; + initialUser: User; + maxConcurrentLimboResolutions: number; +} + +declare type CompositeFilterOp = 'OPERATOR_UNSPECIFIED' | 'AND'; + +/** + * A Listener for credential change events. The listener should fetch a new + * token and may need to invalidate other state if the current user has also + * changed. + */ +declare type CredentialChangeListener = (user: User) => void; + +/** + * Provides methods for getting the uid and token for the current user and + * listening for changes. + */ +declare interface CredentialsProvider { + /** Requests a token for the current user. */ + getToken(): Promise; + /** + * Marks the last retrieved token as invalid, making the next GetToken request + * force-refresh the token. + */ + invalidateToken(): void; + /** + * Specifies a listener to be notified of credential changes + * (sign-in / sign-out, token changes). It is immediately called once with the + * initial user. + */ + setChangeListener(changeListener: CredentialChangeListener): void; + /** Removes the previously-set change listener. */ + removeChangeListener(): void; +} + +/** Settings for private credentials */ +declare type CredentialsSettings = + | FirstPartyCredentialsSettings + | ProviderCredentialsSettings; + +/** Represents the database ID a Firestore client is associated with. */ +declare class DatabaseId { + readonly projectId: string; + readonly database: string; + constructor(projectId: string, database?: string); + get isDefaultDatabase(): boolean; + isEqual(other: {}): boolean; +} + +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +declare class DatabaseInfo { + readonly databaseId: DatabaseId; + readonly persistenceKey: string; + readonly host: string; + readonly ssl: boolean; + readonly forceLongPolling: boolean; + readonly autoDetectLongPolling: boolean; + /** + * Constructs a DatabaseInfo using the provided host, databaseId and + * persistenceKey. + * + * @param databaseId - The database to use. + * @param persistenceKey - A unique identifier for this Firestore's local + * storage (used in conjunction with the databaseId). + * @param host - The Firestore backend host to connect to. + * @param ssl - Whether to use SSL when connecting. + * @param forceLongPolling - Whether to use the forceLongPolling option + * when using WebChannel as the network transport. + * @param autoDetectLongPolling - Whether to use the detectBufferingProxy + * option when using WebChannel as the network transport. + */ + constructor( + databaseId: DatabaseId, + persistenceKey: string, + host: string, + ssl: boolean, + forceLongPolling: boolean, + autoDetectLongPolling: boolean + ); +} + +/** + * Datastore and its related methods are a wrapper around the external Google + * Cloud Datastore grpc API, which provides an interface that is more convenient + * for the rest of the client SDK architecture to consume. + */ +declare abstract class Datastore { + abstract terminate(): void; +} + +/** + * Represents an operation scheduled to be run in the future on an AsyncQueue. + * + * It is created via DelayedOperation.createAndSchedule(). + * + * Supports cancellation (via cancel()) and early execution (via skipDelay()). + * + * Note: We implement `PromiseLike` instead of `Promise`, as the `Promise` type + * in newer versions of TypeScript defines `finally`, which is not available in + * IE. + */ +declare class DelayedOperation implements PromiseLike { + private readonly asyncQueue; + readonly timerId: TimerId; + readonly targetTimeMs: number; + private readonly op; + private readonly removalCallback; + private timerHandle; + private readonly deferred; + private constructor(); + /** + * Creates and returns a DelayedOperation that has been scheduled to be + * executed on the provided asyncQueue after the provided delayMs. + * + * @param asyncQueue - The queue to schedule the operation on. + * @param id - A Timer ID identifying the type of operation this is. + * @param delayMs - The delay (ms) before the operation should be scheduled. + * @param op - The operation to run. + * @param removalCallback - A callback to be called synchronously once the + * operation is executed or canceled, notifying the AsyncQueue to remove it + * from its delayedOperations list. + * PORTING NOTE: This exists to prevent making removeDelayedOperation() and + * the DelayedOperation class public. + */ + static createAndSchedule( + asyncQueue: AsyncQueue, + timerId: TimerId, + delayMs: number, + op: () => Promise, + removalCallback: (op: DelayedOperation) => void + ): DelayedOperation; + /** + * Starts the timer. This is called immediately after construction by + * createAndSchedule(). + */ + private start; + /** + * Queues the operation to run immediately (if it hasn't already been run or + * canceled). + */ + skipDelay(): void; + /** + * Cancels the operation if it hasn't already been executed or canceled. The + * promise will be rejected. + * + * As long as the operation has not yet been run, calling cancel() provides a + * guarantee that the operation will not be run. + */ + cancel(reason?: string): void; + then: ( + onfulfilled?: + | ((value: T) => TResult1 | PromiseLike) + | null + | undefined, + onrejected?: + | ((reason: any) => TResult2 | PromiseLike) + | null + | undefined + ) => Promise; + private handleDelayElapsed; + private clearTimeout; +} + +/** + * Deletes the document referred to by the specified `DocumentReference`. + * + * @param reference - A reference to the document to delete. + * @returns A Promise resolved once the document has been successfully + * deleted from the backend (note that it won't resolve while you're offline). + */ +export declare function deleteDoc( + reference: DocumentReference +): Promise; + +/** + * Returns a sentinel for use with {@link updateDoc} or + * {@link setDoc} with `{merge: true}` to mark a field for deletion. + */ +export declare function deleteField(): FieldValue; + +/** + * The direction of sorting in an order by. + */ +declare const enum Direction { + ASCENDING = 'asc', + DESCENDING = 'desc' +} + +/** + * Disables network usage for this instance. It can be re-enabled via {@link + * enableNetwork}. While the network is disabled, any snapshot listeners, + * `getDoc()` or `getDocs()` calls will return results from cache, and any write + * operations will be queued until the network is restored. + * + * @returns A promise that is resolved once the network has been disabled. + */ +export declare function disableNetwork( + firestore: FirebaseFirestore +): Promise; + +/** + * Gets a `DocumentReference` instance that refers to the document at the + * specified abosulute path. + * + * @param firestore - A reference to the root Firestore instance. + * @param path - A slash-separated path to a document. + * @param pathSegments - Additional path segments that will be applied relative + * to the first argument. + * @throws If the final path has an odd number of segments and does not point to + * a document. + * @returns The `DocumentReference` instance. + */ +export declare function doc( + firestore: FirebaseFirestore_2, + path: string, + ...pathSegments: string[] +): DocumentReference; + +/** + * Gets a `DocumentReference` instance that refers to a document within + * `reference` at the specified relative path. If no path is specified, an + * automatically-generated unique ID will be used for the returned + * `DocumentReference`. + * + * @param reference - A reference to a collection. + * @param path - A slash-separated path to a document. Has to be omitted to use + * auto-genrated IDs. + * @param pathSegments - Additional path segments that will be applied relative + * to the first argument. + * @throws If the final path has an odd number of segments and does not point to + * a document. + * @returns The `DocumentReference` instance. + */ +export declare function doc( + reference: CollectionReference, + path?: string, + ...pathSegments: string[] +): DocumentReference; + +/** + * Gets a `DocumentReference` instance that refers to a document within + * `reference` at the specified relative path. + * + * @param reference - A reference to a Firestore document. + * @param path - A slash-separated path to a document. + * @param pathSegments - Additional path segments that will be applied relative + * to the first argument. + * @throws If the final path has an odd number of segments and does not point to + * a document. + * @returns The `DocumentReference` instance. + */ +export declare function doc( + reference: DocumentReference, + path: string, + ...pathSegments: string[] +): DocumentReference; + +/** + * Represents a document in Firestore with a key, version, data and whether the + * data has local mutations applied to it. + */ +declare class Document_2 extends MaybeDocument { + private readonly objectValue; + readonly hasLocalMutations: boolean; + readonly hasCommittedMutations: boolean; + constructor( + key: DocumentKey, + version: SnapshotVersion, + objectValue: ObjectValue, + options: DocumentOptions + ); + field(path: FieldPath_2): Value | null; + data(): ObjectValue; + toProto(): { + mapValue: MapValue; + }; + isEqual(other: MaybeDocument | null | undefined): boolean; + toString(): string; + get hasPendingWrites(): boolean; +} + +/** + * A `DocumentChange` represents a change to the documents matching a query. + * It contains the document affected and the type of change that occurred. + */ +export declare interface DocumentChange { + /** The type of change ('added', 'modified', or 'removed'). */ + readonly type: DocumentChangeType; + /** The document affected by this change. */ + readonly doc: QueryDocumentSnapshot; + /** + * The index of the changed document in the result set immediately prior to + * this `DocumentChange` (i.e. supposing that all prior `DocumentChange` objects + * have been applied). Is `-1` for 'added' events. + */ + readonly oldIndex: number; + /** + * The index of the changed document in the result set immediately after + * this `DocumentChange` (i.e. supposing that all prior `DocumentChange` + * objects and the current `DocumentChange` object have been applied). + * Is -1 for 'removed' events. + */ + readonly newIndex: number; +} + +/** + * The type of a `DocumentChange` may be 'added', 'removed', or 'modified'. + */ +export declare type DocumentChangeType = 'added' | 'removed' | 'modified'; + +declare type DocumentComparator = ( + doc1: Document_2, + doc2: Document_2 +) => number; + +/** + * Document data (for use with {@link setDoc}) consists of fields mapped to + * values. + */ +export declare interface DocumentData { + [field: string]: any; +} + +/** + * Returns a special sentinel `FieldPath` to refer to the ID of a document. + * It can be used in queries to sort or filter by the document ID. + */ +export declare function documentId(): FieldPath; + +declare class DocumentKey { + readonly path: ResourcePath; + constructor(path: ResourcePath); + static fromPath(path: string): DocumentKey; + static fromName(name: string): DocumentKey; + /** Returns true if the document is in the specified collectionId. */ + hasCollectionId(collectionId: string): boolean; + isEqual(other: DocumentKey | null): boolean; + toString(): string; + static comparator(k1: DocumentKey, k2: DocumentKey): number; + static isDocumentKey(path: ResourcePath): boolean; + /** + * Creates and returns a new document key with the given segments. + * + * @param segments - The segments of the path to the document + * @returns A new instance of DocumentKey + */ + static fromSegments(segments: string[]): DocumentKey; +} + +declare type DocumentKeySet = SortedSet; + +declare type DocumentMap = SortedMap; + +declare interface DocumentOptions { + hasLocalMutations?: boolean; + hasCommittedMutations?: boolean; +} + +/** + * A `DocumentReference` refers to a document location in a Firestore database + * and can be used to write, read, or listen to the location. The document at + * the referenced location may or may not exist. + */ +export declare class DocumentReference { + readonly _converter: FirestoreDataConverter_2 | null; + readonly _key: DocumentKey; + /** The type of this Firestore reference. */ + readonly type = 'document'; + /** + * The {@link FirebaseFirestore} the document is in. + * This is useful for performing transactions, for example. + */ + readonly firestore: FirebaseFirestore_2; + /** @hideconstructor */ + constructor( + firestore: FirebaseFirestore_2, + _converter: FirestoreDataConverter_2 | null, + _key: DocumentKey + ); + get _path(): ResourcePath; + /** + * The document's identifier within its collection. + */ + get id(): string; + /** + * A string representing the path of the referenced document (relative + * to the root of the database). + */ + get path(): string; + /** + * The collection this `DocumentReference` belongs to. + */ + get parent(): CollectionReference; + /** + * Applies a custom data converter to this `DocumentReference`, allowing you + * to use your own custom model objects with Firestore. When you call {@link + * setDoc}, {@link getDoc}, etc. with the returned `DocumentReference` + * instance, the provided converter will convert between Firestore data and + * your custom type `U`. + * + * @param converter - Converts objects to and from Firestore. + * @returns A `DocumentReference` that uses the provided converter. + */ + withConverter( + converter: FirestoreDataConverter_2 + ): DocumentReference; +} + +/** + * DocumentSet is an immutable (copy-on-write) collection that holds documents + * in order specified by the provided comparator. We always add a document key + * comparator on top of what is provided to guarantee document equality based on + * the key. + */ +declare class DocumentSet { + /** + * Returns an empty copy of the existing DocumentSet, using the same + * comparator. + */ + static emptySet(oldSet: DocumentSet): DocumentSet; + private comparator; + private keyedMap; + private sortedSet; + /** The default ordering is by key if the comparator is omitted */ + constructor(comp?: DocumentComparator); + has(key: DocumentKey): boolean; + get(key: DocumentKey): Document_2 | null; + first(): Document_2 | null; + last(): Document_2 | null; + isEmpty(): boolean; + /** + * Returns the index of the provided key in the document set, or -1 if the + * document key is not present in the set; + */ + indexOf(key: DocumentKey): number; + get size(): number; + /** Iterates documents in order defined by "comparator" */ + forEach(cb: (doc: Document_2) => void): void; + /** Inserts or updates a document with the same key */ + add(doc: Document_2): DocumentSet; + /** Deletes a document with a given key */ + delete(key: DocumentKey): DocumentSet; + isEqual(other: DocumentSet | null | undefined): boolean; + toString(): string; + private copy; +} + +/** + * A `DocumentSnapshot` contains data read from a document in your Firestore + * database. The data can be extracted with `.data()` or `.get()` to + * get a specific field. + * + * For a `DocumentSnapshot` that points to a non-existing document, any data + * access will return 'undefined'. You can use the `exists()` method to + * explicitly verify a document's existence. + */ +export declare class DocumentSnapshot< + T = DocumentData +> extends DocumentSnapshot_2 { + readonly _firestore: FirebaseFirestore; + private readonly _firestoreImpl; + /** + * Metadata about the `DocumentSnapshot`, including information about its + * source and local modifications. + */ + readonly metadata: SnapshotMetadata; + /** @hideconstructor protected */ + constructor( + _firestore: FirebaseFirestore, + userDataWriter: AbstractUserDataWriter, + key: DocumentKey, + document: Document_2 | null, + metadata: SnapshotMetadata, + converter: UntypedFirestoreDataConverter | null + ); + /** + * Property of the `DocumentSnapshot` that signals whether or not the data + * exists. True if the document exists. + */ + exists(): this is QueryDocumentSnapshot; + /** + * Retrieves all fields in the document as an `Object`. Returns `undefined` if + * the document doesn't exist. + * + * By default, `FieldValue.serverTimestamp()` values that have not yet been + * set to their final value will be returned as `null`. You can override + * this by passing an options object. + * + * @param options - An options object to configure how data is retrieved from + * the snapshot (for example the desired behavior for server timestamps that + * have not yet been set to their final value). + * @returns An `Object` containing all fields in the document or `undefined` if + * the document doesn't exist. + */ + data(options?: SnapshotOptions): T | undefined; + /** + * Retrieves the field specified by `fieldPath`. Returns `undefined` if the + * document or field doesn't exist. + * + * By default, a `FieldValue.serverTimestamp()` that has not yet been set to + * its final value will be returned as `null`. You can override this by + * passing an options object. + * + * @param fieldPath - The path (for example 'foo' or 'foo.bar') to a specific + * field. + * @param options - An options object to configure how the field is retrieved + * from the snapshot (for example the desired behavior for server timestamps + * that have not yet been set to their final value). + * @returns The data at the specified field location or undefined if no such + * field exists in the document. + */ + get(fieldPath: string | FieldPath, options?: SnapshotOptions): any; +} + +/** + * A `DocumentSnapshot` contains data read from a document in your Firestore + * database. The data can be extracted with `.data()` or `.get()` to + * get a specific field. + * + * For a `DocumentSnapshot` that points to a non-existing document, any data + * access will return 'undefined'. You can use the `exists()` method to + * explicitly verify a document's existence. + */ +declare class DocumentSnapshot_2 { + _firestore: FirebaseFirestore_2; + _userDataWriter: AbstractUserDataWriter; + _key: DocumentKey; + _document: Document_2 | null; + _converter: UntypedFirestoreDataConverter | null; + /** @hideconstructor protected */ + constructor( + _firestore: FirebaseFirestore_2, + _userDataWriter: AbstractUserDataWriter, + _key: DocumentKey, + _document: Document_2 | null, + _converter: UntypedFirestoreDataConverter | null + ); + /** Property of the `DocumentSnapshot` that provides the document's ID. */ + get id(): string; + /** + * The `DocumentReference` for the document included in the `DocumentSnapshot`. + */ + get ref(): DocumentReference; + /** + * Signals whether or not the document at the snapshot's location exists. + * + * @returns true if the document exists. + */ + exists(): this is QueryDocumentSnapshot_2; + /** + * Retrieves all fields in the document as an `Object`. Returns `undefined` if + * the document doesn't exist. + * + * @returns An `Object` containing all fields in the document or `undefined` + * if the document doesn't exist. + */ + data(): T | undefined; + /** + * Retrieves the field specified by `fieldPath`. Returns `undefined` if the + * document or field doesn't exist. + * + * @param fieldPath - The path (for example 'foo' or 'foo.bar') to a specific + * field. + * @returns The data at the specified field location or undefined if no such + * field exists in the document. + */ + get(fieldPath: string | FieldPath): any; +} + +declare type DocumentVersionMap = SortedMap; + +declare interface DocumentViewChange { + type: ChangeType; + doc: Document_2; +} + +/** + * Attempts to enable persistent storage, if possible. + * + * Must be called before any other functions (other than + * {@link initializeFirestore}, {@link getFirestore} or + * {@link clearIndexedDbPersistence}. + * + * If this fails, `enableIndexedDbPersistence()` will reject the promise it + * returns. Note that even after this failure, the `Firestore` instance will + * remain usable, however offline persistence will be disabled. + * + * There are several reasons why this can fail, which can be identified by + * the `code` on the error. + * + * * failed-precondition: The app is already open in another browser tab. + * * unimplemented: The browser is incompatible with the offline + * persistence implementation. + * + * @param firestore - The `Firestore` instance to enable persistence for. + * @param persistenceSettings - Optional settings object to configure + * persistence. + * @returns A promise that represents successfully enabling persistent storage. + */ +export declare function enableIndexedDbPersistence( + firestore: FirebaseFirestore, + persistenceSettings?: PersistenceSettings +): Promise; + +/** + * Attempts to enable multi-tab persistent storage, if possible. If enabled + * across all tabs, all operations share access to local persistence, including + * shared execution of queries and latency-compensated local document updates + * across all connected instances. + * + * If this fails, `enableMultiTabIndexedDbPersistence()` will reject the promise + * it returns. Note that even after this failure, the `Firestore` instance will + * remain usable, however offline persistence will be disabled. + * + * There are several reasons why this can fail, which can be identified by + * the `code` on the error. + * + * * failed-precondition: The app is already open in another browser tab and + * multi-tab is not enabled. + * * unimplemented: The browser is incompatible with the offline + * persistence implementation. + * + * @param firestore - The `Firestore` instance to enable persistence for. + * @returns A promise that represents successfully enabling persistent + * storage. + */ +export declare function enableMultiTabIndexedDbPersistence( + firestore: FirebaseFirestore +): Promise; + +/** + * Re-enables use of the network for this Firestore instance after a prior + * call to {@link disableNetwork}. + * + * @returns A promise that is resolved once the network has been enabled. + */ +export declare function enableNetwork( + firestore: FirebaseFirestore +): Promise; + +/** + * Creates a `QueryConstraint` that modifies the result set to end at the + * provided document (inclusive). The end position is relative to the order of + * the query. The document must contain all of the fields provided in the + * orderBy of the query. + * + * @param snapshot - The snapshot of the document to end at. + * @returns A `QueryConstraint` to pass to `query()` + */ +export declare function endAt( + snapshot: DocumentSnapshot_2 +): QueryConstraint; + +/** + * Creates a `QueryConstraint` that modifies the result set to end at the + * provided fields relative to the order of the query. The order of the field + * values must match the order of the order by clauses of the query. + * + * @param fieldValues - The field values to end this query at, in order + * of the query's order by. + * @returns A `QueryConstraint` to pass to `query()` + */ +export declare function endAt(...fieldValues: unknown[]): QueryConstraint; + +/** + * Creates a `QueryConstraint` that modifies the result set to end before the + * provided document (exclusive). The end position is relative to the order of + * the query. The document must contain all of the fields provided in the + * orderBy of the query. + * + * @param snapshot - The snapshot of the document to end before. + * @returns A `QueryConstraint` to pass to `query()` + */ +export declare function endBefore( + snapshot: DocumentSnapshot_2 +): QueryConstraint; + +/** + * Creates a `QueryConstraint` that modifies the result set to end before the + * provided fields relative to the order of the query. The order of the field + * values must match the order of the order by clauses of the query. + * + * @param fieldValues - The field values to end this query before, in order + * of the query's order by. + * @returns A `QueryConstraint` to pass to `query()` + */ +export declare function endBefore(...fieldValues: unknown[]): QueryConstraint; + +declare interface Entry { + key: K; + value: V; +} + +/** + * EventManager is responsible for mapping queries to query event emitters. + * It handles "fan-out". -- Identical queries will re-use the same watch on the + * backend. + * + * PORTING NOTE: On Web, EventManager `onListen` and `onUnlisten` need to be + * assigned to SyncEngine's `listen()` and `unlisten()` API before usage. This + * allows users to tree-shake the Watch logic. + */ +declare interface EventManager { + onListen?: (query: Query_2) => Promise; + onUnlisten?: (query: Query_2) => Promise; +} + +declare type FieldFilterOp = + | 'OPERATOR_UNSPECIFIED' + | 'LESS_THAN' + | 'LESS_THAN_OR_EQUAL' + | 'GREATER_THAN' + | 'GREATER_THAN_OR_EQUAL' + | 'EQUAL' + | 'NOT_EQUAL' + | 'ARRAY_CONTAINS' + | 'IN' + | 'ARRAY_CONTAINS_ANY' + | 'NOT_IN'; + +/** + * Provides a set of fields that can be used to partially patch a document. + * FieldMask is used in conjunction with ObjectValue. + * Examples: + * foo - Overwrites foo entirely with the provided value. If foo is not + * present in the companion ObjectValue, the field is deleted. + * foo.bar - Overwrites only the field bar of the object foo. + * If foo is not an object, foo is replaced with an object + * containing foo + */ +declare class FieldMask { + readonly fields: FieldPath_2[]; + constructor(fields: FieldPath_2[]); + /** + * Verifies that `fieldPath` is included by at least one field in this field + * mask. + * + * This is an O(n) operation, where `n` is the size of the field mask. + */ + covers(fieldPath: FieldPath_2): boolean; + isEqual(other: FieldMask): boolean; +} + +/** + * A `FieldPath` refers to a field in a document. The path may consist of a + * single field name (referring to a top-level field in the document), or a + * list of field names (referring to a nested field in the document). + * + * Create a `FieldPath` by providing field names. If more than one field + * name is provided, the path will point to a nested field in a document. + */ +export declare class FieldPath { + /** Internal representation of a Firestore field path. */ + readonly _internalPath: FieldPath_2; + /** + * Creates a FieldPath from the provided field names. If more than one field + * name is provided, the path will point to a nested field in a document. + * + * @param fieldNames - A list of field names. + */ + constructor(...fieldNames: string[]); + /** + * Returns true if this `FieldPath` is equal to the provided one. + * + * @param other - The `FieldPath` to compare against. + * @returns true if this `FieldPath` is equal to the provided one. + */ + isEqual(other: FieldPath): boolean; +} + +/** A dot-separated path for navigating sub-objects within a document. */ +declare class FieldPath_2 extends BasePath { + protected construct( + segments: string[], + offset?: number, + length?: number + ): FieldPath_2; + /** + * Returns true if the string could be used as a segment in a field path + * without escaping. + */ + private static isValidIdentifier; + canonicalString(): string; + toString(): string; + /** + * Returns true if this field references the key of a document. + */ + isKeyField(): boolean; + /** + * The field designating the key of a document. + */ + static keyField(): FieldPath_2; + /** + * Parses a field string from the given server-formatted string. + * + * - Splitting the empty string is not allowed (for now at least). + * - Empty segments within the string (e.g. if there are two consecutive + * separators) are not allowed. + * + * TODO(b/37244157): we should make this more strict. Right now, it allows + * non-identifier path components, even if they aren't escaped. + */ + static fromServerFormat(path: string): FieldPath_2; + static emptyPath(): FieldPath_2; +} + +/** A field path and the TransformOperation to perform upon it. */ +declare class FieldTransform { + readonly field: FieldPath_2; + readonly transform: TransformOperation; + constructor(field: FieldPath_2, transform: TransformOperation); +} + +declare type FieldTransformSetToServerValue = + | 'SERVER_VALUE_UNSPECIFIED' + | 'REQUEST_TIME'; + +/** + * Sentinel values that can be used when writing document fields with `set()` + * or `update()`. + */ +export declare abstract class FieldValue { + _methodName: string; + /** + * @param _methodName - The public API endpoint that returns this class. + */ + constructor(_methodName: string); + abstract isEqual(other: FieldValue): boolean; + abstract _toFieldTransform(context: ParseContext): FieldTransform | null; +} + +declare abstract class Filter { + abstract matches(doc: Document_2): boolean; +} + +/** + * The Cloud Firestore service interface. + * + * Do not call this constructor directly. Instead, use {@link getFirestore}. + */ +export declare class FirebaseFirestore extends FirebaseFirestore_2 { + readonly _queue: AsyncQueue; + readonly _persistenceKey: string; + _firestoreClient: FirestoreClient | undefined; + /** @hideconstructor */ + constructor( + databaseIdOrApp: DatabaseId | FirebaseApp, + authProvider: Provider + ); + _terminate(): Promise; +} + +/** + * The Cloud Firestore service interface. + * + * Do not call this constructor directly. Instead, use {@link getFirestore}. + */ +declare class FirebaseFirestore_2 implements FirestoreService { + readonly _databaseId: DatabaseId; + readonly _persistenceKey: string; + _credentials: CredentialsProvider; + private _settings; + private _settingsFrozen; + private _terminateTask?; + private _app?; + /** @hideconstructor */ + constructor( + databaseIdOrApp: DatabaseId | FirebaseApp, + authProvider: Provider + ); + /** + * The {@link FirebaseApp} associated with this `Firestore` service + * instance. + */ + get app(): FirebaseApp; + get _initialized(): boolean; + get _terminated(): boolean; + _setSettings(settings: PrivateSettings): void; + _getSettings(): FirestoreSettings; + _freezeSettings(): FirestoreSettings; + _delete(): Promise; + toJSON(): object; + /** + * Terminates all components used by this client. Subclasses can override + * this method to clean up their own dependencies, but must also call this + * method. + * + * Only ever called once. + */ + protected _terminate(): Promise; +} + +/** + * FirestoreClient is a top-level class that constructs and owns all of the + * pieces of the client SDK architecture. It is responsible for creating the + * async queue that is shared by all of the other components in the system. + */ +declare class FirestoreClient { + private credentials; + /** + * Asynchronous queue responsible for all of our internal processing. When + * we get incoming work from the user (via public API) or the network + * (incoming GRPC messages), we should always schedule onto this queue. + * This ensures all of our work is properly serialized (e.g. we don't + * start processing a new operation while the previous one is waiting for + * an async I/O to complete). + */ + asyncQueue: AsyncQueue; + private databaseInfo; + private user; + private readonly clientId; + private credentialListener; + private readonly receivedInitialUser; + offlineComponents?: OfflineComponentProvider; + onlineComponents?: OnlineComponentProvider; + constructor( + credentials: CredentialsProvider, + /** + * Asynchronous queue responsible for all of our internal processing. When + * we get incoming work from the user (via public API) or the network + * (incoming GRPC messages), we should always schedule onto this queue. + * This ensures all of our work is properly serialized (e.g. we don't + * start processing a new operation while the previous one is waiting for + * an async I/O to complete). + */ + asyncQueue: AsyncQueue, + databaseInfo: DatabaseInfo + ); + getConfiguration(): Promise; + setCredentialChangeListener(listener: (user: User) => void): void; + /** + * Checks that the client has not been terminated. Ensures that other methods on + * this class cannot be called after the client is terminated. + */ + verifyNotTerminated(): void; + terminate(): Promise; +} + +/** + * Converter used by `withConverter()` to transform user objects of type `T` + * into Firestore data. + * + * Using the converter allows you to specify generic type arguments when + * storing and retrieving objects from Firestore. + * + * @example + * ```typescript + * class Post { + * constructor(readonly title: string, readonly author: string) {} + * + * toString(): string { + * return this.title + ', by ' + this.author; + * } + * } + * + * const postConverter = { + * toFirestore(post: Post): firebase.firestore.DocumentData { + * return {title: post.title, author: post.author}; + * }, + * fromFirestore( + * snapshot: firebase.firestore.QueryDocumentSnapshot, + * options: firebase.firestore.SnapshotOptions + * ): Post { + * const data = snapshot.data(options)!; + * return new Post(data.title, data.author); + * } + * }; + * + * const postSnap = await firebase.firestore() + * .collection('posts') + * .withConverter(postConverter) + * .doc().get(); + * const post = postSnap.data(); + * if (post !== undefined) { + * post.title; // string + * post.toString(); // Should be defined + * post.someNonExistentProperty; // TS error + * } + * ``` + */ +export declare interface FirestoreDataConverter + extends FirestoreDataConverter_2 { + /** + * Called by the Firestore SDK to convert a custom model object of type `T` + * into a plain JavaScript object (suitable for writing directly to the + * Firestore database). To use `set()` with `merge` and `mergeFields`, + * `toFirestore()` must be defined with `Partial`. + */ + toFirestore(modelObject: T): DocumentData; + /** + * Called by the Firestore SDK to convert a custom model object of type `T` + * into a plain JavaScript object (suitable for writing directly to the + * Firestore database). Used with {@link setData}, {@link WriteBatch#set} + * and {@link Transaction#set} with `merge:true` or `mergeFields`. + */ + toFirestore(modelObject: Partial, options: SetOptions): DocumentData; + /** + * Called by the Firestore SDK to convert Firestore data into an object of + * type T. You can access your data by calling: `snapshot.data(options)`. + * + * @param snapshot - A `QueryDocumentSnapshot` containing your data and metadata. + * @param options - The `SnapshotOptions` from the initial call to `data()`. + */ + fromFirestore( + snapshot: QueryDocumentSnapshot, + options?: SnapshotOptions + ): T; +} + +/** + * Converter used by `withConverter()` to transform user objects of type `T` + * into Firestore data. + * + * Using the converter allows you to specify generic type arguments when + * storing and retrieving objects from Firestore. + * + * @example + * ```typescript + * class Post { + * constructor(readonly title: string, readonly author: string) {} + * + * toString(): string { + * return this.title + ', by ' + this.author; + * } + * } + * + * const postConverter = { + * toFirestore(post: Post): firebase.firestore.DocumentData { + * return {title: post.title, author: post.author}; + * }, + * fromFirestore(snapshot: firebase.firestore.QueryDocumentSnapshot): Post { + * const data = snapshot.data(options)!; + * return new Post(data.title, data.author); + * } + * }; + * + * const postSnap = await firebase.firestore() + * .collection('posts') + * .withConverter(postConverter) + * .doc().get(); + * const post = postSnap.data(); + * if (post !== undefined) { + * post.title; // string + * post.toString(); // Should be defined + * post.someNonExistentProperty; // TS error + * } + * ``` + */ +declare interface FirestoreDataConverter_2 { + /** + * Called by the Firestore SDK to convert a custom model object of type `T` + * into a plain Javascript object (suitable for writing directly to the + * Firestore database). Used with {@link setData}, {@link WriteBatch#set} + * and {@link Transaction#set}. + */ + toFirestore(modelObject: T): DocumentData; + /** + * Called by the Firestore SDK to convert a custom model object of type `T` + * into a plain Javascript object (suitable for writing directly to the + * Firestore database). Used with {@link setData}, {@link WriteBatch#set} + * and {@link Transaction#set} with `merge:true` or `mergeFields`. + */ + toFirestore(modelObject: Partial, options: SetOptions): DocumentData; + /** + * Called by the Firestore SDK to convert Firestore data into an object of + * type T. You can access your data by calling: `snapshot.data()`. + * + * @param snapshot - A `QueryDocumentSnapshot` containing your data and + * metadata. + */ + fromFirestore(snapshot: QueryDocumentSnapshot_2): T; +} + +/** An error returned by a Firestore operation. */ +export declare class FirestoreError extends Error { + readonly code: FirestoreErrorCode; + readonly message: string; + readonly name: string; + readonly stack?: string; + /** @hideconstructor */ + constructor(code: FirestoreErrorCode, message: string); +} + +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * The set of Firestore status codes. The codes are the same at the ones + * exposed by gRPC here: + * https://github.com/grpc/grpc/blob/master/doc/statuscodes.md + * + * Possible values: + * - 'cancelled': The operation was cancelled (typically by the caller). + * - 'unknown': Unknown error or an error from a different error domain. + * - 'invalid-argument': Client specified an invalid argument. Note that this + * differs from 'failed-precondition'. 'invalid-argument' indicates + * arguments that are problematic regardless of the state of the system + * (e.g. an invalid field name). + * - 'deadline-exceeded': Deadline expired before operation could complete. + * For operations that change the state of the system, this error may be + * returned even if the operation has completed successfully. For example, + * a successful response from a server could have been delayed long enough + * for the deadline to expire. + * - 'not-found': Some requested document was not found. + * - 'already-exists': Some document that we attempted to create already + * exists. + * - 'permission-denied': The caller does not have permission to execute the + * specified operation. + * - 'resource-exhausted': Some resource has been exhausted, perhaps a + * per-user quota, or perhaps the entire file system is out of space. + * - 'failed-precondition': Operation was rejected because the system is not + * in a state required for the operation's execution. + * - 'aborted': The operation was aborted, typically due to a concurrency + * issue like transaction aborts, etc. + * - 'out-of-range': Operation was attempted past the valid range. + * - 'unimplemented': Operation is not implemented or not supported/enabled. + * - 'internal': Internal errors. Means some invariants expected by + * underlying system has been broken. If you see one of these errors, + * something is very broken. + * - 'unavailable': The service is currently unavailable. This is most likely + * a transient condition and may be corrected by retrying with a backoff. + * - 'data-loss': Unrecoverable data loss or corruption. + * - 'unauthenticated': The request does not have valid authentication + * credentials for the operation. + */ +export declare type FirestoreErrorCode = + | 'cancelled' + | 'unknown' + | 'invalid-argument' + | 'deadline-exceeded' + | 'not-found' + | 'already-exists' + | 'permission-denied' + | 'resource-exhausted' + | 'failed-precondition' + | 'aborted' + | 'out-of-range' + | 'unimplemented' + | 'internal' + | 'unavailable' + | 'data-loss' + | 'unauthenticated'; + +/** + * An interface implemented by FirebaseFirestore that provides compatibility + * with the usage in this file. + * + * This interface mainly exists to remove a cyclic dependency. + */ +declare interface FirestoreService extends _FirebaseService { + _credentials: CredentialsProvider; + _persistenceKey: string; + _databaseId: DatabaseId; + _terminated: boolean; + _freezeSettings(): FirestoreSettings; +} + +/** + * A concrete type describing all the values that can be applied via a + * user-supplied firestore.Settings object. This is a separate type so that + * defaults can be supplied and the value can be checked for equality. + */ +declare class FirestoreSettings { + /** The hostname to connect to. */ + readonly host: string; + /** Whether to use SSL when connecting. */ + readonly ssl: boolean; + readonly cacheSizeBytes: number; + readonly experimentalForceLongPolling: boolean; + readonly experimentalAutoDetectLongPolling: boolean; + readonly ignoreUndefinedProperties: boolean; + credentials?: any; + constructor(settings: PrivateSettings); + isEqual(other: FirestoreSettings): boolean; +} + +declare namespace firestoreV1ApiClientInterfaces { + interface ArrayValue { + values?: Value[]; + } + interface BatchGetDocumentsRequest { + database?: string; + documents?: string[]; + mask?: DocumentMask; + transaction?: string; + newTransaction?: TransactionOptions; + readTime?: string; + } + interface BatchGetDocumentsResponse { + found?: Document; + missing?: string; + transaction?: string; + readTime?: string; + } + interface BeginTransactionRequest { + options?: TransactionOptions; + } + interface BeginTransactionResponse { + transaction?: string; + } + interface CollectionSelector { + collectionId?: string; + allDescendants?: boolean; + } + interface CommitRequest { + database?: string; + writes?: Write[]; + transaction?: string; + } + interface CommitResponse { + writeResults?: WriteResult[]; + commitTime?: string; + } + interface CompositeFilter { + op?: CompositeFilterOp; + filters?: Filter[]; + } + interface Cursor { + values?: Value[]; + before?: boolean; + } + interface Document { + name?: string; + fields?: ApiClientObjectMap; + createTime?: Timestamp_2; + updateTime?: Timestamp_2; + } + interface DocumentChange { + document?: Document; + targetIds?: number[]; + removedTargetIds?: number[]; + } + interface DocumentDelete { + document?: string; + removedTargetIds?: number[]; + readTime?: Timestamp_2; + } + interface DocumentMask { + fieldPaths?: string[]; + } + interface DocumentRemove { + document?: string; + removedTargetIds?: number[]; + readTime?: string; + } + interface DocumentTransform { + document?: string; + fieldTransforms?: FieldTransform[]; + } + interface DocumentsTarget { + documents?: string[]; + } + interface Empty {} + interface ExistenceFilter { + targetId?: number; + count?: number; + } + interface FieldFilter { + field?: FieldReference; + op?: FieldFilterOp; + value?: Value; + } + interface FieldReference { + fieldPath?: string; + } + interface FieldTransform { + fieldPath?: string; + setToServerValue?: FieldTransformSetToServerValue; + appendMissingElements?: ArrayValue; + removeAllFromArray?: ArrayValue; + increment?: Value; + } + interface Filter { + compositeFilter?: CompositeFilter; + fieldFilter?: FieldFilter; + unaryFilter?: UnaryFilter; + } + interface Index { + name?: string; + collectionId?: string; + fields?: IndexField[]; + state?: IndexState; + } + interface IndexField { + fieldPath?: string; + mode?: IndexFieldMode; + } + interface LatLng { + latitude?: number; + longitude?: number; + } + interface ListCollectionIdsRequest { + pageSize?: number; + pageToken?: string; + } + interface ListCollectionIdsResponse { + collectionIds?: string[]; + nextPageToken?: string; + } + interface ListDocumentsResponse { + documents?: Document[]; + nextPageToken?: string; + } + interface ListIndexesResponse { + indexes?: Index[]; + nextPageToken?: string; + } + interface ListenRequest { + addTarget?: Target; + removeTarget?: number; + labels?: ApiClientObjectMap; + } + interface ListenResponse { + targetChange?: TargetChange; + documentChange?: DocumentChange; + documentDelete?: DocumentDelete; + documentRemove?: DocumentRemove; + filter?: ExistenceFilter; + } + interface MapValue { + fields?: ApiClientObjectMap; + } + interface Operation { + name?: string; + metadata?: ApiClientObjectMap; + done?: boolean; + error?: Status; + response?: ApiClientObjectMap; + } + interface Order { + field?: FieldReference; + direction?: OrderDirection; + } + interface Precondition { + exists?: boolean; + updateTime?: Timestamp_2; + } + interface Projection { + fields?: FieldReference[]; + } + interface QueryTarget { + parent?: string; + structuredQuery?: StructuredQuery; + } + interface ReadOnly { + readTime?: string; + } + interface ReadWrite { + retryTransaction?: string; + } + interface RollbackRequest { + transaction?: string; + } + interface RunQueryRequest { + parent?: string; + structuredQuery?: StructuredQuery; + transaction?: string; + newTransaction?: TransactionOptions; + readTime?: string; + } + interface RunQueryResponse { + transaction?: string; + document?: Document; + readTime?: string; + skippedResults?: number; + } + interface Status { + code?: number; + message?: string; + details?: Array>; + } + interface StructuredQuery { + select?: Projection; + from?: CollectionSelector[]; + where?: Filter; + orderBy?: Order[]; + startAt?: Cursor; + endAt?: Cursor; + offset?: number; + limit?: + | number + | { + value: number; + }; + } + interface Target { + query?: QueryTarget; + documents?: DocumentsTarget; + resumeToken?: string | Uint8Array; + readTime?: Timestamp_2; + targetId?: number; + once?: boolean; + } + interface TargetChange { + targetChangeType?: TargetChangeTargetChangeType; + targetIds?: number[]; + cause?: Status; + resumeToken?: string | Uint8Array; + readTime?: Timestamp_2; + } + interface TransactionOptions { + readOnly?: ReadOnly; + readWrite?: ReadWrite; + } + interface UnaryFilter { + op?: UnaryFilterOp; + field?: FieldReference; + } + interface Value { + nullValue?: ValueNullValue; + booleanValue?: boolean; + integerValue?: string | number; + doubleValue?: string | number; + timestampValue?: Timestamp_2; + stringValue?: string; + bytesValue?: string | Uint8Array; + referenceValue?: string; + geoPointValue?: LatLng; + arrayValue?: ArrayValue; + mapValue?: MapValue; + } + interface Write { + update?: Document; + delete?: string; + verify?: string; + transform?: DocumentTransform; + updateMask?: DocumentMask; + updateTransforms?: FieldTransform[]; + currentDocument?: Precondition; + } + interface WriteRequest { + streamId?: string; + writes?: Write[]; + streamToken?: string | Uint8Array; + labels?: ApiClientObjectMap; + } + interface WriteResponse { + streamId?: string; + streamToken?: string | Uint8Array; + writeResults?: WriteResult[]; + commitTime?: Timestamp_2; + } + interface WriteResult { + updateTime?: Timestamp_2; + transformResults?: Value[]; + } +} + +declare interface FirstPartyCredentialsSettings { + ['type']: 'gapi'; + ['client']: unknown; + ['sessionIndex']: string; +} + +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +declare type FulfilledHandler = + | ((result: T) => R | PersistencePromise) + | null; + +/** + * Interface implemented by the LRU scheduler to start(), stop() and restart + * garbage collection. + */ +declare interface GarbageCollectionScheduler { + readonly started: boolean; + start(localStore: LocalStore): void; + stop(): void; +} + +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * An immutable object representing a geographic location in Firestore. The + * location is represented as latitude/longitude pair. + * + * Latitude values are in the range of [-90, 90]. + * Longitude values are in the range of [-180, 180]. + */ +export declare class GeoPoint { + private _lat; + private _long; + /** + * Creates a new immutable `GeoPoint` object with the provided latitude and + * longitude values. + * @param latitude - The latitude as number between -90 and 90. + * @param longitude - The longitude as number between -180 and 180. + */ + constructor(latitude: number, longitude: number); + /** + * The latitude of this `GeoPoint` instance. + */ + get latitude(): number; + /** + * The longitude of this `GeoPoint` instance. + */ + get longitude(): number; + /** + * Returns true if this `GeoPoint` is equal to the provided one. + * + * @param other - The `GeoPoint` to compare against. + * @returns true if this `GeoPoint` is equal to the provided one. + */ + isEqual(other: GeoPoint): boolean; + toJSON(): { + latitude: number; + longitude: number; + }; + /** + * Actually private to JS consumers of our API, so this function is prefixed + * with an underscore. + */ + _compareTo(other: GeoPoint): number; +} + +/** + * Reads the document referred to by this `DocumentReference`. + * + * Note: `getDoc()` attempts to provide up-to-date data when possible by waiting + * for data from the server, but it may return cached data or fail if you are + * offline and the server cannot be reached. To specify this behavior, invoke + * {@link getDocFromCache} or {@link getDocFromServer}. + * + * @param reference - The reference of the document to fetch. + * @returns A Promise resolved with a `DocumentSnapshot` containing the + * current document contents. + */ +export declare function getDoc( + reference: DocumentReference +): Promise>; + +/** + * Reads the document referred to by this `DocumentReference` from cache. + * Returns an error if the document is not currently cached. + * + * @returns A Promise resolved with a `DocumentSnapshot` containing the + * current document contents. + */ +export declare function getDocFromCache( + reference: DocumentReference +): Promise>; + +/** + * Reads the document referred to by this `DocumentReference` from the server. + * Returns an error if the network is not available. + * + * @returns A Promise resolved with a `DocumentSnapshot` containing the + * current document contents. + */ +export declare function getDocFromServer( + reference: DocumentReference +): Promise>; + +/** + * Executes the query and returns the results as a `QuerySnapshot`. + * + * Note: `getDocs()` attempts to provide up-to-date data when possible by + * waiting for data from the server, but it may return cached data or fail if + * you are offline and the server cannot be reached. To specify this behavior, + * invoke {@link getDocsFromCache} or {@link getDocsFromServer}. + * + * @returns A Promise that will be resolved with the results of the query. + */ +export declare function getDocs(query: Query): Promise>; + +/** + * Executes the query and returns the results as a `QuerySnapshot` from cache. + * Returns an error if the document is not currently cached. + * + * @returns A Promise that will be resolved with the results of the query. + */ +export declare function getDocsFromCache( + query: Query +): Promise>; + +/** + * Executes the query and returns the results as a `QuerySnapshot` from the + * server. Returns an error if the network is not available. + * + * @returns A Promise that will be resolved with the results of the query. + */ +export declare function getDocsFromServer( + query: Query +): Promise>; + +/** + * Returns the existing instance of Firestore that is associated with the + * provided {@link FirebaseApp}. If no instance exists, initializes a new + * instance with default settings. + * + * @param app - The {@link FirebaseApp} instance that the returned Firestore + * instance is associated with. + * @returns The `Firestore` instance of the provided app. + */ +export declare function getFirestore(app: FirebaseApp): FirebaseFirestore; + +/** + * Returns a special value that can be used with {@link setDoc} or {@link + * updateDoc} that tells the server to increment the field's current value by + * the given value. + * + * If either the operand or the current field value uses floating point + * precision, all arithmetic follows IEEE 754 semantics. If both values are + * integers, values outside of JavaScript's safe number range + * (`Number.MIN_SAFE_INTEGER` to `Number.MAX_SAFE_INTEGER`) are also subject to + * precision loss. Furthermore, once processed by the Firestore backend, all + * integer operations are capped between -2^63 and 2^63-1. + * + * If the current field value is not of type `number`, or if the field does not + * yet exist, the transformation sets the field to the given value. + * + * @param n - The value to increment by. + * @returns The `FieldValue` sentinel for use in a call to `setDoc()` or + * `updateDoc()` + */ +export declare function increment(n: number): FieldValue; + +declare type IndexFieldMode = 'MODE_UNSPECIFIED' | 'ASCENDING' | 'DESCENDING'; + +/** + * Represents a set of indexes that are used to execute queries efficiently. + * + * Currently the only index is a [collection id] => [parent path] index, used + * to execute Collection Group queries. + */ +declare interface IndexManager { + /** + * Creates an index entry mapping the collectionId (last segment of the path) + * to the parent path (either the containing document location or the empty + * path for root-level collections). Index entries can be retrieved via + * getCollectionParents(). + * + * NOTE: Currently we don't remove index entries. If this ends up being an + * issue we can devise some sort of GC strategy. + */ + addToCollectionParentIndex( + transaction: PersistenceTransaction, + collectionPath: ResourcePath + ): PersistencePromise; + /** + * Retrieves all parent locations containing the given collectionId, as a + * list of paths (each path being either a document location or the empty + * path for a root-level collection). + */ + getCollectionParents( + transaction: PersistenceTransaction, + collectionId: string + ): PersistencePromise; +} + +declare type IndexState = 'STATE_UNSPECIFIED' | 'CREATING' | 'READY' | 'ERROR'; + +/** + * Initializes a new instance of Cloud Firestore with the provided settings. + * Can only be called before any other function, including + * {@link getFirestore}. If the custom settings are empty, this function is + * equivalent to calling {@link getFirestore}. + * + * @param app - The {@link FirebaseApp} with which the `Firestore` instance will + * be associated. + * @param settings - A settings object to configure the `Firestore` instance. + * @returns A newly initialized `Firestore` instance. + */ +export declare function initializeFirestore( + app: FirebaseApp, + settings: Settings +): FirebaseFirestore; + +/** + * Creates a `QueryConstraint` that only returns the first matching documents. + * + * @param limit - The maximum number of items to return. + * @returns The created `Query`. + */ +export declare function limit(limit: number): QueryConstraint; + +/** + * Creates a `QueryConstraint` that only returns the last matching documents. + * + * You must specify at least one `orderBy` clause for `limitToLast` queries, + * otherwise an exception will be thrown during execution. + * + * @param limit - The maximum number of items to return. + * @returns The created `Query`. + */ +export declare function limitToLast(limit: number): QueryConstraint; + +declare const enum LimitType { + First = 'F', + Last = 'L' +} + +/** LimitType enum. */ +declare type LimitType_2 = 'FIRST' | 'LAST'; + +declare type ListenSequenceNumber = number; + +declare class LLRBEmptyNode { + get key(): never; + get value(): never; + get color(): never; + get left(): never; + get right(): never; + size: number; + copy( + key: K | null, + value: V | null, + color: boolean | null, + left: LLRBNode | LLRBEmptyNode | null, + right: LLRBNode | LLRBEmptyNode | null + ): LLRBEmptyNode; + insert(key: K, value: V, comparator: Comparator): LLRBNode; + remove(key: K, comparator: Comparator): LLRBEmptyNode; + isEmpty(): boolean; + inorderTraversal(action: (k: K, v: V) => boolean): boolean; + reverseTraversal(action: (k: K, v: V) => boolean): boolean; + minKey(): K | null; + maxKey(): K | null; + isRed(): boolean; + checkMaxDepth(): boolean; + protected check(): 0; +} + +declare class LLRBNode { + key: K; + value: V; + readonly color: boolean; + readonly left: LLRBNode | LLRBEmptyNode; + readonly right: LLRBNode | LLRBEmptyNode; + readonly size: number; + static EMPTY: LLRBEmptyNode; + static RED: boolean; + static BLACK: boolean; + constructor( + key: K, + value: V, + color?: boolean, + left?: LLRBNode | LLRBEmptyNode, + right?: LLRBNode | LLRBEmptyNode + ); + copy( + key: K | null, + value: V | null, + color: boolean | null, + left: LLRBNode | LLRBEmptyNode | null, + right: LLRBNode | LLRBEmptyNode | null + ): LLRBNode; + isEmpty(): boolean; + inorderTraversal(action: (k: K, v: V) => T): T; + reverseTraversal(action: (k: K, v: V) => T): T; + private min; + minKey(): K | null; + maxKey(): K | null; + insert(key: K, value: V, comparator: Comparator): LLRBNode; + private removeMin; + remove( + key: K, + comparator: Comparator + ): LLRBNode | LLRBEmptyNode; + isRed(): boolean; + private fixUp; + private moveRedLeft; + private moveRedRight; + private rotateLeft; + private rotateRight; + private colorFlip; + checkMaxDepth(): boolean; + protected check(): number; +} + +/** + * Loads a Firestore bundle into the local cache. + * + * @param firestore - The `Firestore` instance to load bundles for for. + * @param bundleData - An object representing the bundle to be loaded. Valid objects are + * `ArrayBuffer`, `ReadableStream` or `string`. + * + * @return + * A `LoadBundleTask` object, which notifies callers with progress updates, and completion + * or error events. It can be used as a `Promise`. + */ +export declare function loadBundle( + firestore: FirebaseFirestore, + bundleData: ReadableStream | ArrayBuffer | string +): LoadBundleTask; + +/** + * Represents the task of loading a Firestore bundle. It provides progress of bundle + * loading, as well as task completion and error events. + * + * The API is compatible with `Promise`. + */ +export declare class LoadBundleTask + implements PromiseLike { + private _progressObserver; + private _taskCompletionResolver; + private _lastProgress; + /** + * Registers functions to listen to bundle loading progress events. + * @param next - Called when there is a progress update from bundle loading. Typically `next` calls occur + * each time a Firestore document is loaded from the bundle. + * @param error - Called when an error occurs during bundle loading. The task aborts after reporting the + * error, and there should be no more updates after this. + * @param complete - Called when the loading task is complete. + */ + onProgress( + next?: (progress: LoadBundleTaskProgress) => unknown, + error?: (err: Error) => unknown, + complete?: () => void + ): void; + /** + * Implements the `Promise.catch` interface. + * + * @param onRejected - Called when an error occurs during bundle loading. + */ + catch( + onRejected: (a: Error) => R | PromiseLike + ): Promise; + /** + * Implements the `Promise.then` interface. + * + * @param onFulfilled - Called on the completion of the loading task with a final `LoadBundleTaskProgress` update. + * The update will always have its `taskState` set to `"Success"`. + * @param onRejected - Called when an error occurs during bundle loading. + */ + then( + onFulfilled?: (a: LoadBundleTaskProgress) => T | PromiseLike, + onRejected?: (a: Error) => R | PromiseLike + ): Promise; + /** + * Notifies all observers that bundle loading has completed, with a provided + * `LoadBundleTaskProgress` object. + * + * @private + */ + _completeWith(progress: LoadBundleTaskProgress): void; + /** + * Notifies all observers that bundle loading has failed, with a provided + * `Error` as the reason. + * + * @private + */ + _failWith(error: FirestoreError): void; + /** + * Notifies a progress update of loading a bundle. + * @param progress - The new progress. + * + * @private + */ + _updateProgress(progress: LoadBundleTaskProgress): void; +} + +/** + * Represents a progress update or a final state from loading bundles. + */ +export declare interface LoadBundleTaskProgress { + /** How many documents have been loaded. */ + documentsLoaded: number; + /** How many documents are in the bundle being loaded. */ + totalDocuments: number; + /** How many bytes have been loaded. */ + bytesLoaded: number; + /** How many bytes are in the bundle being loaded. */ + totalBytes: number; + /** Current task state. */ + taskState: TaskState; +} + +declare interface LocalStore { + collectGarbage(garbageCollector: LruGarbageCollector): Promise; +} +export { LogLevel }; + +declare interface LruGarbageCollector { + readonly params: LruParams; + collect( + txn: PersistenceTransaction, + activeTargetIds: ActiveTargets + ): PersistencePromise; + /** Given a percentile of target to collect, returns the number of targets to collect. */ + calculateTargetCount( + txn: PersistenceTransaction, + percentile: number + ): PersistencePromise; + /** Returns the nth sequence number, counting in order from the smallest. */ + nthSequenceNumber( + txn: PersistenceTransaction, + n: number + ): PersistencePromise; + /** + * Removes documents that have a sequence number equal to or less than the + * upper bound and are not otherwise pinned. + */ + removeOrphanedDocuments( + txn: PersistenceTransaction, + upperBound: ListenSequenceNumber + ): PersistencePromise; + getCacheSize(txn: PersistenceTransaction): PersistencePromise; + /** + * Removes targets with a sequence number equal to or less than the given + * upper bound, and removes document associations with those targets. + */ + removeTargets( + txn: PersistenceTransaction, + upperBound: ListenSequenceNumber, + activeTargetIds: ActiveTargets + ): PersistencePromise; +} + +declare class LruParams { + readonly cacheSizeCollectionThreshold: number; + readonly percentileToCollect: number; + readonly maximumSequenceNumbersToCollect: number; + private static readonly DEFAULT_COLLECTION_PERCENTILE; + private static readonly DEFAULT_MAX_SEQUENCE_NUMBERS_TO_COLLECT; + static withCacheSize(cacheSize: number): LruParams; + static readonly DEFAULT: LruParams; + static readonly DISABLED: LruParams; + constructor( + cacheSizeCollectionThreshold: number, + percentileToCollect: number, + maximumSequenceNumbersToCollect: number + ); +} + +/** + * Describes the results of a garbage collection run. `didRun` will be set to + * `false` if collection was skipped (either it is disabled or the cache size + * has not hit the threshold). If collection ran, the other fields will be + * filled in with the details of the results. + */ +declare interface LruResults { + readonly didRun: boolean; + readonly sequenceNumbersCollected: number; + readonly targetsRemoved: number; + readonly documentsRemoved: number; +} + +declare type MapValue = firestoreV1ApiClientInterfaces.MapValue; + +/** + * The result of a lookup for a given path may be an existing document or a + * marker that this document does not exist at a given version. + */ +declare abstract class MaybeDocument { + readonly key: DocumentKey; + readonly version: SnapshotVersion; + constructor(key: DocumentKey, version: SnapshotVersion); + /** + * Whether this document had a local mutation applied that has not yet been + * acknowledged by Watch. + */ + abstract get hasPendingWrites(): boolean; + abstract isEqual(other: MaybeDocument | null | undefined): boolean; + abstract toString(): string; +} + +declare type MaybeDocumentMap = SortedMap; + +/** + * A mutation describes a self-contained change to a document. Mutations can + * create, replace, delete, and update subsets of documents. + * + * Mutations not only act on the value of the document but also its version. + * + * For local mutations (mutations that haven't been committed yet), we preserve + * the existing version for Set and Patch mutations. For Delete mutations, we + * reset the version to 0. + * + * Here's the expected transition table. + * + * MUTATION APPLIED TO RESULTS IN + * + * SetMutation Document(v3) Document(v3) + * SetMutation NoDocument(v3) Document(v0) + * SetMutation null Document(v0) + * PatchMutation Document(v3) Document(v3) + * PatchMutation NoDocument(v3) NoDocument(v3) + * PatchMutation null null + * DeleteMutation Document(v3) NoDocument(v0) + * DeleteMutation NoDocument(v3) NoDocument(v0) + * DeleteMutation null NoDocument(v0) + * + * For acknowledged mutations, we use the updateTime of the WriteResponse as + * the resulting version for Set and Patch mutations. As deletes have no + * explicit update time, we use the commitTime of the WriteResponse for + * Delete mutations. + * + * If a mutation is acknowledged by the backend but fails the precondition check + * locally, we return an `UnknownDocument` and rely on Watch to send us the + * updated version. + * + * Field transforms are used only with Patch and Set Mutations. We use the + * `updateTransforms` message to store transforms, rather than the `transforms`s + * messages. + * + * ## Subclassing Notes + * + * Subclasses of Mutation need to implement applyToRemoteDocument() and + * applyToLocalView() to implement the actual behavior of applying the mutation + * to some source document. + */ +declare abstract class Mutation { + abstract readonly type: MutationType; + abstract readonly key: DocumentKey; + abstract readonly precondition: Precondition; + abstract readonly fieldTransforms: FieldTransform[]; +} + +/** + * A batch of mutations that will be sent as one unit to the backend. + */ +declare class MutationBatch { + batchId: BatchId; + localWriteTime: Timestamp; + baseMutations: Mutation[]; + mutations: Mutation[]; + /** + * @param batchId - The unique ID of this mutation batch. + * @param localWriteTime - The original write time of this mutation. + * @param baseMutations - Mutations that are used to populate the base + * values when this mutation is applied locally. This can be used to locally + * overwrite values that are persisted in the remote document cache. Base + * mutations are never sent to the backend. + * @param mutations - The user-provided mutations in this mutation batch. + * User-provided mutations are applied both locally and remotely on the + * backend. + */ + constructor( + batchId: BatchId, + localWriteTime: Timestamp, + baseMutations: Mutation[], + mutations: Mutation[] + ); + /** + * Applies all the mutations in this MutationBatch to the specified document + * to create a new remote document + * + * @param docKey - The key of the document to apply mutations to. + * @param maybeDoc - The document to apply mutations to. + * @param batchResult - The result of applying the MutationBatch to the + * backend. + */ + applyToRemoteDocument( + docKey: DocumentKey, + maybeDoc: MaybeDocument | null, + batchResult: MutationBatchResult + ): MaybeDocument | null; + /** + * Computes the local view of a document given all the mutations in this + * batch. + * + * @param docKey - The key of the document to apply mutations to. + * @param maybeDoc - The document to apply mutations to. + */ + applyToLocalView( + docKey: DocumentKey, + maybeDoc: MaybeDocument | null + ): MaybeDocument | null; + /** + * Computes the local view for all provided documents given the mutations in + * this batch. + */ + applyToLocalDocumentSet(maybeDocs: MaybeDocumentMap): MaybeDocumentMap; + keys(): DocumentKeySet; + isEqual(other: MutationBatch): boolean; +} + +/** The result of applying a mutation batch to the backend. */ +declare class MutationBatchResult { + readonly batch: MutationBatch; + readonly commitVersion: SnapshotVersion; + readonly mutationResults: MutationResult[]; + /** + * A pre-computed mapping from each mutated document to the resulting + * version. + */ + readonly docVersions: DocumentVersionMap; + private constructor(); + /** + * Creates a new MutationBatchResult for the given batch and results. There + * must be one result for each mutation in the batch. This static factory + * caches a document=>version mapping (docVersions). + */ + static from( + batch: MutationBatch, + commitVersion: SnapshotVersion, + results: MutationResult[] + ): MutationBatchResult; +} + +/** A queue of mutations to apply to the remote store. */ +declare interface MutationQueue { + /** Returns true if this queue contains no mutation batches. */ + checkEmpty(transaction: PersistenceTransaction): PersistencePromise; + /** + * Creates a new mutation batch and adds it to this mutation queue. + * + * @param transaction - The transaction this operation is scoped to. + * @param localWriteTime - The original write time of this mutation. + * @param baseMutations - Mutations that are used to populate the base values + * when this mutation is applied locally. These mutations are used to locally + * overwrite values that are persisted in the remote document cache. + * @param mutations - The user-provided mutations in this mutation batch. + */ + addMutationBatch( + transaction: PersistenceTransaction, + localWriteTime: Timestamp, + baseMutations: Mutation[], + mutations: Mutation[] + ): PersistencePromise; + /** + * Loads the mutation batch with the given batchId. + */ + lookupMutationBatch( + transaction: PersistenceTransaction, + batchId: BatchId + ): PersistencePromise; + /** + * Gets the first unacknowledged mutation batch after the passed in batchId + * in the mutation queue or null if empty. + * + * @param batchId - The batch to search after, or BATCHID_UNKNOWN for the + * first mutation in the queue. + * + * @returns the next mutation or null if there wasn't one. + */ + getNextMutationBatchAfterBatchId( + transaction: PersistenceTransaction, + batchId: BatchId + ): PersistencePromise; + /** + * Gets the largest (latest) batch id in mutation queue for the current user + * that is pending server response, returns `BATCHID_UNKNOWN` if the queue is + * empty. + * + * @returns the largest batch id in the mutation queue that is not + * acknowledged. + */ + getHighestUnacknowledgedBatchId( + transaction: PersistenceTransaction + ): PersistencePromise; + /** Gets all mutation batches in the mutation queue. */ + getAllMutationBatches( + transaction: PersistenceTransaction + ): PersistencePromise; + /** + * Finds all mutation batches that could possibly affect the given + * document key. Not all mutations in a batch will necessarily affect the + * document key, so when looping through the batch you'll need to check that + * the mutation itself matches the key. + * + * Batches are guaranteed to be in sorted order. + * + * Note that because of this requirement implementations are free to return + * mutation batches that don't contain the document key at all if it's + * convenient. + */ + getAllMutationBatchesAffectingDocumentKey( + transaction: PersistenceTransaction, + documentKey: DocumentKey + ): PersistencePromise; + /** + * Finds all mutation batches that could possibly affect the given set of + * document keys. Not all mutations in a batch will necessarily affect each + * key, so when looping through the batch you'll need to check that the + * mutation itself matches the key. + * + * Batches are guaranteed to be in sorted order. + * + * Note that because of this requirement implementations are free to return + * mutation batches that don't contain any of the document keys at all if it's + * convenient. + */ + getAllMutationBatchesAffectingDocumentKeys( + transaction: PersistenceTransaction, + documentKeys: SortedMap + ): PersistencePromise; + /** + * Finds all mutation batches that could affect the results for the given + * query. Not all mutations in a batch will necessarily affect the query, so + * when looping through the batch you'll need to check that the mutation + * itself matches the query. + * + * Batches are guaranteed to be in sorted order. + * + * Note that because of this requirement implementations are free to return + * mutation batches that don't match the query at all if it's convenient. + * + * NOTE: A PatchMutation does not need to include all fields in the query + * filter criteria in order to be a match (but any fields it does contain do + * need to match). + */ + getAllMutationBatchesAffectingQuery( + transaction: PersistenceTransaction, + query: Query_2 + ): PersistencePromise; + /** + * Removes the given mutation batch from the queue. This is useful in two + * circumstances: + * + * + Removing an applied mutation from the head of the queue + * + Removing a rejected mutation from anywhere in the queue + * + * Multi-Tab Note: This operation should only be called by the primary client. + */ + removeMutationBatch( + transaction: PersistenceTransaction, + batch: MutationBatch + ): PersistencePromise; + /** + * Performs a consistency check, examining the mutation queue for any + * leaks, if possible. + */ + performConsistencyCheck( + transaction: PersistenceTransaction + ): PersistencePromise; +} + +/** The result of successfully applying a mutation to the backend. */ +declare class MutationResult { + /** + * The version at which the mutation was committed: + * + * - For most operations, this is the updateTime in the WriteResult. + * - For deletes, the commitTime of the WriteResponse (because deletes are + * not stored and have no updateTime). + * + * Note that these versions can be different: No-op writes will not change + * the updateTime even though the commitTime advances. + */ + readonly version: SnapshotVersion; + /** + * The resulting fields returned from the backend after a mutation + * containing field transforms has been committed. Contains one FieldValue + * for each FieldTransform that was in the mutation. + * + * Will be null if the mutation did not contain any field transforms. + */ + readonly transformResults: Array | null; + constructor( + /** + * The version at which the mutation was committed: + * + * - For most operations, this is the updateTime in the WriteResult. + * - For deletes, the commitTime of the WriteResponse (because deletes are + * not stored and have no updateTime). + * + * Note that these versions can be different: No-op writes will not change + * the updateTime even though the commitTime advances. + */ + version: SnapshotVersion, + /** + * The resulting fields returned from the backend after a mutation + * containing field transforms has been committed. Contains one FieldValue + * for each FieldTransform that was in the mutation. + * + * Will be null if the mutation did not contain any field transforms. + */ + transformResults: Array | null + ); +} + +declare const enum MutationType { + Set = 0, + Patch = 1, + Delete = 2, + Verify = 3 +} + +/** + * Represents a Query saved by the SDK in its local storage. + */ +declare interface NamedQuery { + /** The name of the query. */ + readonly name: string; + /** The underlying query associated with `name`. */ + readonly query: Query_2; + /** The time at which the results for this query were read. */ + readonly readTime: SnapshotVersion; +} + +/** + * Reads a Firestore `Query` from local cache, identified by the given name. + * + * The named queries are packaged into bundles on the server side (along + * with resulting documents), and loaded to local cache using `loadBundle`. Once in local + * cache, use this method to extract a `Query` by name. + */ +export declare function namedQuery( + firestore: FirebaseFirestore, + name: string +): Promise; + +/** Properties of a NamedQuery. */ +declare interface NamedQuery_2 { + /** NamedQuery name */ + name?: string | null; + /** NamedQuery bundledQuery */ + bundledQuery?: BundledQuery | null; + /** NamedQuery readTime */ + readTime?: Timestamp_2 | null; +} + +declare type NullableMaybeDocumentMap = SortedMap< + DocumentKey, + MaybeDocument | null +>; + +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * A map implementation that uses objects as keys. Objects must have an + * associated equals function and must be immutable. Entries in the map are + * stored together with the key being produced from the mapKeyFn. This map + * automatically handles collisions of keys. + */ +declare class ObjectMap { + private mapKeyFn; + private equalsFn; + /** + * The inner map for a key/value pair. Due to the possibility of collisions we + * keep a list of entries that we do a linear search through to find an actual + * match. Note that collisions should be rare, so we still expect near + * constant time lookups in practice. + */ + private inner; + constructor( + mapKeyFn: (key: KeyType) => string, + equalsFn: (l: KeyType, r: KeyType) => boolean + ); + /** Get a value for this key, or undefined if it does not exist. */ + get(key: KeyType): ValueType | undefined; + has(key: KeyType): boolean; + /** Put this key and value in the map. */ + set(key: KeyType, value: ValueType): void; + /** + * Remove this key from the map. Returns a boolean if anything was deleted. + */ + delete(key: KeyType): boolean; + forEach(fn: (key: KeyType, val: ValueType) => void): void; + isEmpty(): boolean; +} + +/** + * An ObjectValue represents a MapValue in the Firestore Proto and offers the + * ability to add and remove fields (via the ObjectValueBuilder). + */ +declare class ObjectValue { + readonly proto: { + mapValue: MapValue; + }; + constructor(proto: { mapValue: MapValue }); + static empty(): ObjectValue; + /** + * Returns the value at the given path or null. + * + * @param path - the path to search + * @returns The value at the path or if there it doesn't exist. + */ + field(path: FieldPath_2): Value | null; + isEqual(other: ObjectValue): boolean; +} + +/** + * Initializes and wires components that are needed to interface with the local + * cache. Implementations override `initialize()` to provide all components. + */ +declare interface OfflineComponentProvider { + persistence: Persistence; + sharedClientState: SharedClientState; + localStore: LocalStore; + gcScheduler: GarbageCollectionScheduler | null; + synchronizeTabs: boolean; + initialize(cfg: ComponentConfiguration): Promise; + terminate(): Promise; +} + +/** + * Initializes and wires the components that are needed to interface with the + * network. + */ +declare class OnlineComponentProvider { + protected localStore: LocalStore; + protected sharedClientState: SharedClientState; + datastore: Datastore; + eventManager: EventManager; + remoteStore: RemoteStore; + syncEngine: SyncEngine; + initialize( + offlineComponentProvider: OfflineComponentProvider, + cfg: ComponentConfiguration + ): Promise; + createEventManager(cfg: ComponentConfiguration): EventManager; + createDatastore(cfg: ComponentConfiguration): Datastore; + createRemoteStore(cfg: ComponentConfiguration): RemoteStore; + createSyncEngine( + cfg: ComponentConfiguration, + startAsPrimary: boolean + ): SyncEngine; + terminate(): Promise; +} + +/** + * Describes the online state of the Firestore client. Note that this does not + * indicate whether or not the remote store is trying to connect or not. This is + * primarily used by the View / EventManager code to change their behavior while + * offline (e.g. get() calls shouldn't wait for data from the server and + * snapshot events should set metadata.isFromCache=true). + * + * The string values should not be changed since they are persisted in + * WebStorage. + */ +declare const enum OnlineState { + /** + * The Firestore client is in an unknown online state. This means the client + * is either not actively trying to establish a connection or it is currently + * trying to establish a connection, but it has not succeeded or failed yet. + * Higher-level components should not operate in offline mode. + */ + Unknown = 'Unknown', + /** + * The client is connected and the connections are healthy. This state is + * reached after a successful connection and there has been at least one + * successful message received from the backends. + */ + Online = 'Online', + /** + * The client is either trying to establish a connection but failing, or it + * has been explicitly marked offline via a call to disableNetwork(). + * Higher-level components should operate in offline mode. + */ + Offline = 'Offline' +} + +/** + * Attaches a listener for `DocumentSnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param reference - A reference to the document to listen to. + * @param observer - A single object containing `next` and `error` callbacks. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + reference: DocumentReference, + observer: { + next?: (snapshot: DocumentSnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + } +): Unsubscribe; + +/** + * Attaches a listener for `DocumentSnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param reference - A reference to the document to listen to. + * @param options - Options controlling the listen behavior. + * @param observer - A single object containing `next` and `error` callbacks. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + reference: DocumentReference, + options: SnapshotListenOptions, + observer: { + next?: (snapshot: DocumentSnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + } +): Unsubscribe; + +/** + * Attaches a listener for `DocumentSnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param reference - A reference to the document to listen to. + * @param onNext - A callback to be called every time a new `DocumentSnapshot` + * is available. + * @param onError - A callback to be called if the listen fails or is + * cancelled. No further callbacks will occur. + * @param onCompletion - Can be provided, but will not be called since streams are + * never ending. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + reference: DocumentReference, + onNext: (snapshot: DocumentSnapshot) => void, + onError?: (error: FirestoreError) => void, + onCompletion?: () => void +): Unsubscribe; + +/** + * Attaches a listener for `DocumentSnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param reference - A reference to the document to listen to. + * @param options - Options controlling the listen behavior. + * @param onNext - A callback to be called every time a new `DocumentSnapshot` + * is available. + * @param onError - A callback to be called if the listen fails or is + * cancelled. No further callbacks will occur. + * @param onCompletion - Can be provided, but will not be called since streams are + * never ending. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + reference: DocumentReference, + options: SnapshotListenOptions, + onNext: (snapshot: DocumentSnapshot) => void, + onError?: (error: FirestoreError) => void, + onCompletion?: () => void +): Unsubscribe; + +/** + * Attaches a listener for `QuerySnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. The listener can be cancelled by + * calling the function that is returned when `onSnapshot` is called. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param query - The query to listen to. + * @param observer - A single object containing `next` and `error` callbacks. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + query: Query, + observer: { + next?: (snapshot: QuerySnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + } +): Unsubscribe; + +/** + * Attaches a listener for `QuerySnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. The listener can be cancelled by + * calling the function that is returned when `onSnapshot` is called. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param query - The query to listen to. + * @param options - Options controlling the listen behavior. + * @param observer - A single object containing `next` and `error` callbacks. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + query: Query, + options: SnapshotListenOptions, + observer: { + next?: (snapshot: QuerySnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + } +): Unsubscribe; + +/** + * Attaches a listener for `QuerySnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. The listener can be cancelled by + * calling the function that is returned when `onSnapshot` is called. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param query - The query to listen to. + * @param onNext - A callback to be called every time a new `QuerySnapshot` + * is available. + * @param onCompletion - Can be provided, but will not be called since streams are + * never ending. + * @param onError - A callback to be called if the listen fails or is + * cancelled. No further callbacks will occur. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + query: Query, + onNext: (snapshot: QuerySnapshot) => void, + onError?: (error: FirestoreError) => void, + onCompletion?: () => void +): Unsubscribe; + +/** + * Attaches a listener for `QuerySnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. The listener can be cancelled by + * calling the function that is returned when `onSnapshot` is called. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param query - The query to listen to. + * @param options - Options controlling the listen behavior. + * @param onNext - A callback to be called every time a new `QuerySnapshot` + * is available. + * @param onCompletion - Can be provided, but will not be called since streams are + * never ending. + * @param onError - A callback to be called if the listen fails or is + * cancelled. No further callbacks will occur. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + query: Query, + options: SnapshotListenOptions, + onNext: (snapshot: QuerySnapshot) => void, + onError?: (error: FirestoreError) => void, + onCompletion?: () => void +): Unsubscribe; + +/** + * Attaches a listener for a snapshots-in-sync event. The snapshots-in-sync + * event indicates that all listeners affected by a given change have fired, + * even if a single server-generated change affects multiple listeners. + * + * NOTE: The snapshots-in-sync event only indicates that listeners are in sync + * with each other, but does not relate to whether those snapshots are in sync + * with the server. Use SnapshotMetadata in the individual listeners to + * determine if a snapshot is from the cache or the server. + * + * @param firestore - The instance of Firestore for synchronizing snapshots. + * @param observer - A single object containing `next` and `error` callbacks. + * @returns An unsubscribe function that can be called to cancel the snapshot + * listener. + */ +export declare function onSnapshotsInSync( + firestore: FirebaseFirestore, + observer: { + next?: (value: void) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + } +): Unsubscribe; + +/** + * Attaches a listener for a snapshots-in-sync event. The snapshots-in-sync + * event indicates that all listeners affected by a given change have fired, + * even if a single server-generated change affects multiple listeners. + * + * NOTE: The snapshots-in-sync event only indicates that listeners are in sync + * with each other, but does not relate to whether those snapshots are in sync + * with the server. Use SnapshotMetadata in the individual listeners to + * determine if a snapshot is from the cache or the server. + * + * @param firestore - The instance of Firestore for synchronizing snapshots. + * @param onSync - A callback to be called every time all snapshot listeners are + * in sync with each other. + * @returns An unsubscribe function that can be called to cancel the snapshot + * listener. + */ +export declare function onSnapshotsInSync( + firestore: FirebaseFirestore, + onSync: () => void +): Unsubscribe; + +/** + * An ordering on a field, in some Direction. Direction defaults to ASCENDING. + */ +declare class OrderBy { + readonly field: FieldPath_2; + readonly dir: Direction; + constructor(field: FieldPath_2, dir?: Direction); +} + +/** + * Creates a `QueryConstraint` that sorts the query result by the + * specified field, optionally in descending order instead of ascending. + * + * @param fieldPath - The field to sort by. + * @param directionStr - Optional direction to sort by ('asc' or 'desc'). If + * not specified, order will be ascending. + * @returns The created `Query`. + */ +export declare function orderBy( + fieldPath: string | FieldPath, + directionStr?: OrderByDirection +): QueryConstraint; + +/** + * The direction of a {@link orderBy} clause is specified as 'desc' or 'asc' + * (descending or ascending). + */ +export declare type OrderByDirection = 'desc' | 'asc'; + +declare type OrderDirection = + | 'DIRECTION_UNSPECIFIED' + | 'ASCENDING' + | 'DESCENDING'; + +declare interface ParseContext { + readonly databaseId: DatabaseId; + readonly ignoreUndefinedProperties: boolean; +} + +/** The result of parsing document data (e.g. for a setData call). */ +declare class ParsedSetData { + readonly data: ObjectValue; + readonly fieldMask: FieldMask | null; + readonly fieldTransforms: FieldTransform[]; + constructor( + data: ObjectValue, + fieldMask: FieldMask | null, + fieldTransforms: FieldTransform[] + ); + toMutation(key: DocumentKey, precondition: Precondition): Mutation; +} + +/** The result of parsing "update" data (i.e. for an updateData call). */ +declare class ParsedUpdateData { + readonly data: ObjectValue; + readonly fieldMask: FieldMask; + readonly fieldTransforms: FieldTransform[]; + constructor( + data: ObjectValue, + fieldMask: FieldMask, + fieldTransforms: FieldTransform[] + ); + toMutation(key: DocumentKey, precondition: Precondition): Mutation; +} + +/** + * Persistence is the lowest-level shared interface to persistent storage in + * Firestore. + * + * Persistence is used to create MutationQueue and RemoteDocumentCache + * instances backed by persistence (which might be in-memory or LevelDB). + * + * Persistence also exposes an API to create and run PersistenceTransactions + * against persistence. All read / write operations must be wrapped in a + * transaction. Implementations of PersistenceTransaction / Persistence only + * need to guarantee that writes made against the transaction are not made to + * durable storage until the transaction resolves its PersistencePromise. + * Since memory-only storage components do not alter durable storage, they are + * free to ignore the transaction. + * + * This contract is enough to allow the LocalStore be be written + * independently of whether or not the stored state actually is durably + * persisted. If persistent storage is enabled, writes are grouped together to + * avoid inconsistent state that could cause crashes. + * + * Concretely, when persistent storage is enabled, the persistent versions of + * MutationQueue, RemoteDocumentCache, and others (the mutators) will + * defer their writes into a transaction. Once the local store has completed + * one logical operation, it commits the transaction. + * + * When persistent storage is disabled, the non-persistent versions of the + * mutators ignore the transaction. This short-cut is allowed because + * memory-only storage leaves no state so it cannot be inconsistent. + * + * This simplifies the implementations of the mutators and allows memory-only + * implementations to supplement the persistent ones without requiring any + * special dual-store implementation of Persistence. The cost is that the + * LocalStore needs to be slightly careful about the order of its reads and + * writes in order to avoid relying on being able to read back uncommitted + * writes. + */ +declare interface Persistence { + /** + * Whether or not this persistence instance has been started. + */ + readonly started: boolean; + readonly referenceDelegate: ReferenceDelegate; + /** Starts persistence. */ + start(): Promise; + /** + * Releases any resources held during eager shutdown. + */ + shutdown(): Promise; + /** + * Registers a listener that gets called when the database receives a + * version change event indicating that it has deleted. + * + * PORTING NOTE: This is only used for Web multi-tab. + */ + setDatabaseDeletedListener( + databaseDeletedListener: () => Promise + ): void; + /** + * Adjusts the current network state in the client's metadata, potentially + * affecting the primary lease. + * + * PORTING NOTE: This is only used for Web multi-tab. + */ + setNetworkEnabled(networkEnabled: boolean): void; + /** + * Returns a MutationQueue representing the persisted mutations for the + * given user. + * + * Note: The implementation is free to return the same instance every time + * this is called for a given user. In particular, the memory-backed + * implementation does this to emulate the persisted implementation to the + * extent possible (e.g. in the case of uid switching from + * sally=>jack=>sally, sally's mutation queue will be preserved). + */ + getMutationQueue(user: User): MutationQueue; + /** + * Returns a TargetCache representing the persisted cache of targets. + * + * Note: The implementation is free to return the same instance every time + * this is called. In particular, the memory-backed implementation does this + * to emulate the persisted implementation to the extent possible. + */ + getTargetCache(): TargetCache; + /** + * Returns a RemoteDocumentCache representing the persisted cache of remote + * documents. + * + * Note: The implementation is free to return the same instance every time + * this is called. In particular, the memory-backed implementation does this + * to emulate the persisted implementation to the extent possible. + */ + getRemoteDocumentCache(): RemoteDocumentCache; + /** + * Returns a BundleCache representing the persisted cache of loaded bundles. + * + * Note: The implementation is free to return the same instance every time + * this is called. In particular, the memory-backed implementation does this + * to emulate the persisted implementation to the extent possible. + */ + getBundleCache(): BundleCache; + /** + * Returns an IndexManager instance that manages our persisted query indexes. + * + * Note: The implementation is free to return the same instance every time + * this is called. In particular, the memory-backed implementation does this + * to emulate the persisted implementation to the extent possible. + */ + getIndexManager(): IndexManager; + /** + * Performs an operation inside a persistence transaction. Any reads or writes + * against persistence must be performed within a transaction. Writes will be + * committed atomically once the transaction completes. + * + * Persistence operations are asynchronous and therefore the provided + * transactionOperation must return a PersistencePromise. When it is resolved, + * the transaction will be committed and the Promise returned by this method + * will resolve. + * + * @param action - A description of the action performed by this transaction, + * used for logging. + * @param mode - The underlying mode of the IndexedDb transaction. Can be + * 'readonly', 'readwrite' or 'readwrite-primary'. Transactions marked + * 'readwrite-primary' can only be executed by the primary client. In this + * mode, the transactionOperation will not be run if the primary lease cannot + * be acquired and the returned promise will be rejected with a + * FAILED_PRECONDITION error. + * @param transactionOperation - The operation to run inside a transaction. + * @returns A promise that is resolved once the transaction completes. + */ + runTransaction( + action: string, + mode: PersistenceTransactionMode, + transactionOperation: ( + transaction: PersistenceTransaction + ) => PersistencePromise + ): Promise; +} + +/** + * PersistencePromise is essentially a re-implementation of Promise except + * it has a .next() method instead of .then() and .next() and .catch() callbacks + * are executed synchronously when a PersistencePromise resolves rather than + * asynchronously (Promise implementations use setImmediate() or similar). + * + * This is necessary to interoperate with IndexedDB which will automatically + * commit transactions if control is returned to the event loop without + * synchronously initiating another operation on the transaction. + * + * NOTE: .then() and .catch() only allow a single consumer, unlike normal + * Promises. + */ +declare class PersistencePromise { + private nextCallback; + private catchCallback; + private result; + private error; + private isDone; + private callbackAttached; + constructor(callback: (resolve: Resolver, reject: Rejector) => void); + catch( + fn: (error: Error) => R | PersistencePromise + ): PersistencePromise; + next( + nextFn?: FulfilledHandler, + catchFn?: RejectedHandler + ): PersistencePromise; + toPromise(): Promise; + private wrapUserFunction; + private wrapSuccess; + private wrapFailure; + static resolve(): PersistencePromise; + static resolve(result: R): PersistencePromise; + static reject(error: Error): PersistencePromise; + static waitFor(all: { + forEach: (cb: (el: PersistencePromise) => void) => void; + }): PersistencePromise; + /** + * Given an array of predicate functions that asynchronously evaluate to a + * boolean, implements a short-circuiting `or` between the results. Predicates + * will be evaluated until one of them returns `true`, then stop. The final + * result will be whether any of them returned `true`. + */ + static or( + predicates: Array<() => PersistencePromise> + ): PersistencePromise; + /** + * Given an iterable, call the given function on each element in the + * collection and wait for all of the resulting concurrent PersistencePromises + * to resolve. + */ + static forEach( + collection: { + forEach: (cb: (r: R, s: S) => void) => void; + }, + f: + | ((r: R, s: S) => PersistencePromise) + | ((r: R) => PersistencePromise) + ): PersistencePromise; + static forEach( + collection: { + forEach: (cb: (r: R) => void) => void; + }, + f: (r: R) => PersistencePromise + ): PersistencePromise; +} + +export declare interface PersistenceSettings { + forceOwnership?: boolean; +} + +/** + * A base class representing a persistence transaction, encapsulating both the + * transaction's sequence numbers as well as a list of onCommitted listeners. + * + * When you call Persistence.runTransaction(), it will create a transaction and + * pass it to your callback. You then pass it to any method that operates + * on persistence. + */ +declare abstract class PersistenceTransaction { + private readonly onCommittedListeners; + abstract readonly currentSequenceNumber: ListenSequenceNumber; + addOnCommittedListener(listener: () => void): void; + raiseOnCommittedEvent(): void; +} + +/** The different modes supported by `Persistence.runTransaction()`. */ +declare type PersistenceTransactionMode = + | 'readonly' + | 'readwrite' + | 'readwrite-primary'; + +/** + * Encodes a precondition for a mutation. This follows the model that the + * backend accepts with the special case of an explicit "empty" precondition + * (meaning no precondition). + */ +declare class Precondition { + readonly updateTime?: SnapshotVersion | undefined; + readonly exists?: boolean | undefined; + private constructor(); + /** Creates a new empty Precondition. */ + static none(): Precondition; + /** Creates a new Precondition with an exists flag. */ + static exists(exists: boolean): Precondition; + /** Creates a new Precondition based on a version a document exists at. */ + static updateTime(version: SnapshotVersion): Precondition; + /** Returns whether this Precondition is empty. */ + get isNone(): boolean; + isEqual(other: Precondition): boolean; +} + +/** Undocumented, private additional settings not exposed in our public API. */ +declare interface PrivateSettings extends Settings_2 { + credentials?: CredentialsSettings; +} + +declare interface ProviderCredentialsSettings { + ['type']: 'provider'; + ['client']: CredentialsProvider; +} + +/** + * A `Query` refers to a Query which you can read or listen to. You can also + * construct refined `Query` objects by adding filters and ordering. + */ +export declare class Query { + readonly _converter: FirestoreDataConverter_2 | null; + readonly _query: Query_2; + /** The type of this Firestore reference. */ + readonly type: 'query' | 'collection'; + /** + * The `FirebaseFirestore` for the Firestore database (useful for performing + * transactions, etc.). + */ + readonly firestore: FirebaseFirestore_2; + /** @hideconstructor protected */ + constructor( + firestore: FirebaseFirestore_2, + _converter: FirestoreDataConverter_2 | null, + _query: Query_2 + ); + /** + * Applies a custom data converter to this query, allowing you to use your own + * custom model objects with Firestore. When you call {@link getDocs} with + * the returned query, the provided converter will convert between Firestore + * data and your custom type `U`. + * + * @param converter - Converts objects to and from Firestore. + * @returns A `Query` that uses the provided converter. + */ + withConverter(converter: FirestoreDataConverter_2): Query; +} + +/** + * Creates a new immutable instance of `query` that is extended to also include + * additional query constraints. + * + * @param query - The query instance to use as a base for the new constraints. + * @param queryConstraints - The list of `QueryConstraint`s to apply. + * @throws if any of the provided query constraints cannot be combined with the + * existing or new constraints. + */ +export declare function query( + query: Query, + ...queryConstraints: QueryConstraint[] +): Query; + +/** + * The Query interface defines all external properties of a query. + * + * QueryImpl implements this interface to provide memoization for `queryOrderBy` + * and `queryToTarget`. + */ +declare interface Query_2 { + readonly path: ResourcePath; + readonly collectionGroup: string | null; + readonly explicitOrderBy: OrderBy[]; + readonly filters: Filter[]; + readonly limit: number | null; + readonly limitType: LimitType; + readonly startAt: Bound | null; + readonly endAt: Bound | null; +} + +/** + * A `QueryConstraint` is used to narrow the set of documents returned by a + * Firestore query. `QueryConstraint`s are created by invoking {@link where}, + * {@link orderBy}, {@link startAt}, {@link startAfter}, {@link + * endBefore}, {@link endAt}, {@link limit} or {@link limitToLast} and + * can then be passed to {@link query} to create a new query instance that + * also contains this `QueryConstraint`. + */ +export declare abstract class QueryConstraint { + /** The type of this query constraints */ + abstract readonly type: QueryConstraintType; + /** + * Takes the provided `Query` and returns a copy of the `Query` with this + * `QueryConstraint` applied. + */ + abstract _apply(query: Query): Query; +} + +/** Describes the different query constraints available in this SDK. */ +export declare type QueryConstraintType = + | 'where' + | 'orderBy' + | 'limit' + | 'limitToLast' + | 'startAt' + | 'startAfter' + | 'endAt' + | 'endBefore'; + +/** + * A `QueryDocumentSnapshot` contains data read from a document in your + * Firestore database as part of a query. The document is guaranteed to exist + * and its data can be extracted with `.data()` or `.get()` to get a + * specific field. + * + * A `QueryDocumentSnapshot` offers the same API surface as a + * `DocumentSnapshot`. Since query results contain only existing documents, the + * `exists` property will always be true and `data()` will never return + * 'undefined'. + */ +export declare class QueryDocumentSnapshot< + T = DocumentData +> extends DocumentSnapshot { + /** + * Retrieves all fields in the document as an `Object`. + * + * By default, `FieldValue.serverTimestamp()` values that have not yet been + * set to their final value will be returned as `null`. You can override + * this by passing an options object. + * + * @override + * @param options - An options object to configure how data is retrieved from + * the snapshot (for example the desired behavior for server timestamps that + * have not yet been set to their final value). + * @returns An `Object` containing all fields in the document. + */ + data(options?: SnapshotOptions): T; +} + +/** + * A `QueryDocumentSnapshot` contains data read from a document in your + * Firestore database as part of a query. The document is guaranteed to exist + * and its data can be extracted with `.data()` or `.get()` to get a + * specific field. + * + * A `QueryDocumentSnapshot` offers the same API surface as a + * `DocumentSnapshot`. Since query results contain only existing documents, the + * `exists` property will always be true and `data()` will never return + * 'undefined'. + */ +declare class QueryDocumentSnapshot_2< + T = DocumentData +> extends DocumentSnapshot_2 { + /** + * Retrieves all fields in the document as an `Object`. + * + * @override + * @returns An `Object` containing all fields in the document. + */ + data(): T; +} + +/** + * Returns true if the provided queries point to the same collection and apply + * the same constraints. + * + * @param left - A `Query` to compare. + * @param right - A `Query` to compare. + * @returns true if the references point to the same location in the same + * Firestore database. + */ +export declare function queryEqual(left: Query, right: Query): boolean; + +/** + * A `QuerySnapshot` contains zero or more `DocumentSnapshot` objects + * representing the results of a query. The documents can be accessed as an + * array via the `docs` property or enumerated using the `forEach` method. The + * number of documents can be determined via the `empty` and `size` + * properties. + */ +export declare class QuerySnapshot { + readonly _firestore: FirebaseFirestore; + readonly _userDataWriter: AbstractUserDataWriter; + readonly _snapshot: ViewSnapshot; + /** + * Metadata about this snapshot, concerning its source and if it has local + * modifications. + */ + readonly metadata: SnapshotMetadata; + /** + * The query on which you called `get` or `onSnapshot` in order to get this + * `QuerySnapshot`. + */ + readonly query: Query; + private _cachedChanges?; + private _cachedChangesIncludeMetadataChanges?; + /** @hideconstructor */ + constructor( + _firestore: FirebaseFirestore, + _userDataWriter: AbstractUserDataWriter, + query: Query, + _snapshot: ViewSnapshot + ); + /** An array of all the documents in the `QuerySnapshot`. */ + get docs(): Array>; + /** The number of documents in the `QuerySnapshot`. */ + get size(): number; + /** True if there are no documents in the `QuerySnapshot`. */ + get empty(): boolean; + /** + * Enumerates all of the documents in the `QuerySnapshot`. + * + * @param callback - A callback to be called with a `QueryDocumentSnapshot` for + * each document in the snapshot. + * @param thisArg - The `this` binding for the callback. + */ + forEach( + callback: (result: QueryDocumentSnapshot) => void, + thisArg?: unknown + ): void; + /** + * Returns an array of the documents changes since the last snapshot. If this + * is the first snapshot, all documents will be in the list as 'added' + * changes. + * + * @param options - `SnapshotListenOptions` that control whether metadata-only + * changes (i.e. only `DocumentSnapshot.metadata` changed) should trigger + * snapshot events. + */ + docChanges(options?: SnapshotListenOptions): Array>; +} + +/** The different states of a watch target. */ +declare type QueryTargetState = 'not-current' | 'current' | 'rejected'; + +/** + * Returns true if the provided references are equal. + * + * @param left - A reference to compare. + * @param right - A reference to compare. + * @returns true if the references point to the same location in the same + * Firestore database. + */ +export declare function refEqual( + left: DocumentReference | CollectionReference, + right: DocumentReference | CollectionReference +): boolean; + +/** + * A ReferenceDelegate instance handles all of the hooks into the document-reference lifecycle. This + * includes being added to a target, being removed from a target, being subject to mutation, and + * being mutated by the user. + * + * Different implementations may do different things with each of these events. Not every + * implementation needs to do something with every lifecycle hook. + * + * PORTING NOTE: since sequence numbers are attached to transactions in this + * client, the ReferenceDelegate does not need to deal in transactional + * semantics (onTransactionStarted/Committed()), nor does it need to track and + * generate sequence numbers (getCurrentSequenceNumber()). + */ +declare interface ReferenceDelegate { + /** Notify the delegate that the given document was added to a target. */ + addReference( + txn: PersistenceTransaction, + targetId: TargetId, + doc: DocumentKey + ): PersistencePromise; + /** Notify the delegate that the given document was removed from a target. */ + removeReference( + txn: PersistenceTransaction, + targetId: TargetId, + doc: DocumentKey + ): PersistencePromise; + /** + * Notify the delegate that a target was removed. The delegate may, but is not obligated to, + * actually delete the target and associated data. + */ + removeTarget( + txn: PersistenceTransaction, + targetData: TargetData + ): PersistencePromise; + /** + * Notify the delegate that a document may no longer be part of any views or + * have any mutations associated. + */ + markPotentiallyOrphaned( + txn: PersistenceTransaction, + doc: DocumentKey + ): PersistencePromise; + /** Notify the delegate that a limbo document was updated. */ + updateLimboDocument( + txn: PersistenceTransaction, + doc: DocumentKey + ): PersistencePromise; +} + +declare type RejectedHandler = + | ((reason: Error) => R | PersistencePromise) + | null; + +declare type Rejector = (error: Error) => void; + +/** + * Represents cached documents received from the remote backend. + * + * The cache is keyed by DocumentKey and entries in the cache are MaybeDocument + * instances, meaning we can cache both Document instances (an actual document + * with data) as well as NoDocument instances (indicating that the document is + * known to not exist). + */ +declare interface RemoteDocumentCache { + /** + * Looks up an entry in the cache. + * + * @param documentKey - The key of the entry to look up.* + * @returns The cached Document or NoDocument entry, or null if we have + * nothing cached. + */ + getEntry( + transaction: PersistenceTransaction, + documentKey: DocumentKey + ): PersistencePromise; + /** + * Looks up a set of entries in the cache. + * + * @param documentKeys - The keys of the entries to look up. + * @returns The cached Document or NoDocument entries indexed by key. If an + * entry is not cached, the corresponding key will be mapped to a null value. + */ + getEntries( + transaction: PersistenceTransaction, + documentKeys: DocumentKeySet + ): PersistencePromise; + /** + * Executes a query against the cached Document entries. + * + * Implementations may return extra documents if convenient. The results + * should be re-filtered by the consumer before presenting them to the user. + * + * Cached NoDocument entries have no bearing on query results. + * + * @param query - The query to match documents against. + * @param sinceReadTime - If not set to SnapshotVersion.min(), return only + * documents that have been read since this snapshot version (exclusive). + * @returns The set of matching documents. + */ + getDocumentsMatchingQuery( + transaction: PersistenceTransaction, + query: Query_2, + sinceReadTime: SnapshotVersion + ): PersistencePromise; + /** + * Provides access to add or update the contents of the cache. The buffer + * handles proper size accounting for the change. + * + * Multi-Tab Note: This should only be called by the primary client. + * + * @param options - Specify `trackRemovals` to create sentinel entries for + * removed documents, which allows removals to be tracked by + * `getNewDocumentChanges()`. + */ + newChangeBuffer(options?: { + trackRemovals: boolean; + }): RemoteDocumentChangeBuffer; + /** + * Get an estimate of the size of the document cache. Note that for eager + * garbage collection, we don't track sizes so this will return 0. + */ + getSize(transaction: PersistenceTransaction): PersistencePromise; +} + +/** + * Represents a document change to be applied to remote document cache. + */ +declare interface RemoteDocumentChange { + readonly maybeDocument: MaybeDocument | null; + readonly readTime: SnapshotVersion | null; +} + +/** + * An in-memory buffer of entries to be written to a RemoteDocumentCache. + * It can be used to batch up a set of changes to be written to the cache, but + * additionally supports reading entries back with the `getEntry()` method, + * falling back to the underlying RemoteDocumentCache if no entry is + * buffered. + * + * Entries added to the cache *must* be read first. This is to facilitate + * calculating the size delta of the pending changes. + * + * PORTING NOTE: This class was implemented then removed from other platforms. + * If byte-counting ends up being needed on the other platforms, consider + * porting this class as part of that implementation work. + */ +declare abstract class RemoteDocumentChangeBuffer { + protected changes: ObjectMap; + private changesApplied; + protected abstract getFromCache( + transaction: PersistenceTransaction, + documentKey: DocumentKey + ): PersistencePromise; + protected abstract getAllFromCache( + transaction: PersistenceTransaction, + documentKeys: DocumentKeySet + ): PersistencePromise; + protected abstract applyChanges( + transaction: PersistenceTransaction + ): PersistencePromise; + protected getReadTime(key: DocumentKey): SnapshotVersion; + /** + * Buffers a `RemoteDocumentCache.addEntry()` call. + * + * You can only modify documents that have already been retrieved via + * `getEntry()/getEntries()` (enforced via IndexedDbs `apply()`). + */ + addEntry(maybeDocument: MaybeDocument, readTime: SnapshotVersion): void; + /** + * Buffers a `RemoteDocumentCache.removeEntry()` call. + * + * You can only remove documents that have already been retrieved via + * `getEntry()/getEntries()` (enforced via IndexedDbs `apply()`). + */ + removeEntry(key: DocumentKey, readTime?: SnapshotVersion | null): void; + /** + * Looks up an entry in the cache. The buffered changes will first be checked, + * and if no buffered change applies, this will forward to + * `RemoteDocumentCache.getEntry()`. + * + * @param transaction - The transaction in which to perform any persistence + * operations. + * @param documentKey - The key of the entry to look up. + * @returns The cached Document or NoDocument entry, or null if we have + * nothing cached. + */ + getEntry( + transaction: PersistenceTransaction, + documentKey: DocumentKey + ): PersistencePromise; + /** + * Looks up several entries in the cache, forwarding to + * `RemoteDocumentCache.getEntry()`. + * + * @param transaction - The transaction in which to perform any persistence + * operations. + * @param documentKeys - The keys of the entries to look up. + * @returns A map of cached `Document`s or `NoDocument`s, indexed by key. If + * an entry cannot be found, the corresponding key will be mapped to a + * null value. + */ + getEntries( + transaction: PersistenceTransaction, + documentKeys: DocumentKeySet + ): PersistencePromise; + /** + * Applies buffered changes to the underlying RemoteDocumentCache, using + * the provided transaction. + */ + apply(transaction: PersistenceTransaction): PersistencePromise; + /** Helper to assert this.changes is not null */ + protected assertNotApplied(): void; +} + +/** + * An event from the RemoteStore. It is split into targetChanges (changes to the + * state or the set of documents in our watched targets) and documentUpdates + * (changes to the actual documents). + */ +declare class RemoteEvent { + /** + * The snapshot version this event brings us up to, or MIN if not set. + */ + readonly snapshotVersion: SnapshotVersion; + /** + * A map from target to changes to the target. See TargetChange. + */ + readonly targetChanges: Map; + /** + * A set of targets that is known to be inconsistent. Listens for these + * targets should be re-established without resume tokens. + */ + readonly targetMismatches: SortedSet; + /** + * A set of which documents have changed or been deleted, along with the + * doc's new values (if not deleted). + */ + readonly documentUpdates: MaybeDocumentMap; + /** + * A set of which document updates are due only to limbo resolution targets. + */ + readonly resolvedLimboDocuments: DocumentKeySet; + constructor( + /** + * The snapshot version this event brings us up to, or MIN if not set. + */ + snapshotVersion: SnapshotVersion, + /** + * A map from target to changes to the target. See TargetChange. + */ + targetChanges: Map, + /** + * A set of targets that is known to be inconsistent. Listens for these + * targets should be re-established without resume tokens. + */ + targetMismatches: SortedSet, + /** + * A set of which documents have changed or been deleted, along with the + * doc's new values (if not deleted). + */ + documentUpdates: MaybeDocumentMap, + /** + * A set of which document updates are due only to limbo resolution targets. + */ + resolvedLimboDocuments: DocumentKeySet + ); + /** + * HACK: Views require RemoteEvents in order to determine whether the view is + * CURRENT, but secondary tabs don't receive remote events. So this method is + * used to create a synthesized RemoteEvent that can be used to apply a + * CURRENT status change to a View, for queries executed in a different tab. + */ + static createSynthesizedRemoteEventForCurrentChange( + targetId: TargetId, + current: boolean + ): RemoteEvent; +} + +/** + * RemoteStore - An interface to remotely stored data, basically providing a + * wrapper around the Datastore that is more reliable for the rest of the + * system. + * + * RemoteStore is responsible for maintaining the connection to the server. + * - maintaining a list of active listens. + * - reconnecting when the connection is dropped. + * - resuming all the active listens on reconnect. + * + * RemoteStore handles all incoming events from the Datastore. + * - listening to the watch stream and repackaging the events as RemoteEvents + * - notifying SyncEngine of any changes to the active listens. + * + * RemoteStore takes writes from other components and handles them reliably. + * - pulling pending mutations from LocalStore and sending them to Datastore. + * - retrying mutations that failed because of network problems. + * - acking mutations to the SyncEngine once they are accepted or rejected. + */ +declare interface RemoteStore { + /** + * SyncEngine to notify of watch and write events. This must be set + * immediately after construction. + */ + remoteSyncer: RemoteSyncer; +} + +/** + * An interface that describes the actions the RemoteStore needs to perform on + * a cooperating synchronization engine. + */ +declare interface RemoteSyncer { + /** + * Applies one remote event to the sync engine, notifying any views of the + * changes, and releasing any pending mutation batches that would become + * visible because of the snapshot version the remote event contains. + */ + applyRemoteEvent?(remoteEvent: RemoteEvent): Promise; + /** + * Rejects the listen for the given targetID. This can be triggered by the + * backend for any active target. + * + * @param targetId - The targetID corresponds to one previously initiated by + * the user as part of TargetData passed to listen() on RemoteStore. + * @param error - A description of the condition that has forced the rejection. + * Nearly always this will be an indication that the user is no longer + * authorized to see the data matching the target. + */ + rejectListen?(targetId: TargetId, error: FirestoreError): Promise; + /** + * Applies the result of a successful write of a mutation batch to the sync + * engine, emitting snapshots in any views that the mutation applies to, and + * removing the batch from the mutation queue. + */ + applySuccessfulWrite?(result: MutationBatchResult): Promise; + /** + * Rejects the batch, removing the batch from the mutation queue, recomputing + * the local view of any documents affected by the batch and then, emitting + * snapshots with the reverted value. + */ + rejectFailedWrite?(batchId: BatchId, error: FirestoreError): Promise; + /** + * Returns the set of remote document keys for the given target ID. This list + * includes the documents that were assigned to the target when we received + * the last snapshot. + */ + getRemoteKeysForTarget?(targetId: TargetId): DocumentKeySet; + /** + * Updates all local state to match the pending mutations for the given user. + * May be called repeatedly for the same user. + */ + handleCredentialChange?(user: User): Promise; +} + +declare type Resolver = (value?: T) => void; + +/** + * A slash-separated path for navigating resources (documents and collections) + * within Firestore. + */ +declare class ResourcePath extends BasePath { + protected construct( + segments: string[], + offset?: number, + length?: number + ): ResourcePath; + canonicalString(): string; + toString(): string; + /** + * Creates a resource path from the given slash-delimited string. If multiple + * arguments are provided, all components are combined. Leading and trailing + * slashes from all components are ignored. + */ + static fromString(...pathComponents: string[]): ResourcePath; + static emptyPath(): ResourcePath; +} + +/** + * Executes the given `updateFunction` and then attempts to commit the changes + * applied within the transaction. If any document read within the transaction + * has changed, Cloud Firestore retries the `updateFunction`. If it fails to + * commit after 5 attempts, the transaction fails. + * + * The maximum number of writes allowed in a single transaction is 500. + * + * @param firestore - A reference to the Firestore database to run this + * transaction against. + * @param updateFunction - The function to execute within the transaction + * context. + * @returns If the transaction completed successfully or was explicitly aborted + * (the `updateFunction` returned a failed promise), the promise returned by the + * `updateFunction `is returned here. Otherwise, if the transaction failed, a + * rejected promise with the corresponding failure error is returned. + */ +export declare function runTransaction( + firestore: FirebaseFirestore, + updateFunction: (transaction: Transaction) => Promise +): Promise; + +/** + * Returns a sentinel used with {@link setDoc} or {@link updateDoc} to + * include a server-generated timestamp in the written data. + */ +export declare function serverTimestamp(): FieldValue; + +declare type ServerTimestampBehavior = 'estimate' | 'previous' | 'none'; + +/** + * Writes to the document referred to by this `DocumentReference`. If the + * document does not yet exist, it will be created. + * + * @param reference - A reference to the document to write. + * @param data - A map of the fields and values for the document. + * @returns A Promise resolved once the data has been successfully written + * to the backend (note that it won't resolve while you're offline). + */ +export declare function setDoc( + reference: DocumentReference, + data: T +): Promise; + +/** + * Writes to the document referred to by the specified `DocumentReference`. If + * the document does not yet exist, it will be created. If you provide `merge` + * or `mergeFields`, the provided data can be merged into an existing document. + * + * @param reference - A reference to the document to write. + * @param data - A map of the fields and values for the document. + * @param options - An object to configure the set behavior. + * @returns A Promise resolved once the data has been successfully written + * to the backend (note that it won't resolve while you're offline). + */ +export declare function setDoc( + reference: DocumentReference, + data: Partial, + options: SetOptions +): Promise; + +/** + * Sets the verbosity of Cloud Firestore logs (debug, error, or silent). + * + * @param logLevel - The verbosity you set for activity and error logging. Can + * be any of the following values: + * + *
    + *
  • `debug` for the most verbose logging level, primarily for + * debugging.
  • + *
  • `error` to log errors only.
  • + *
  • `silent` to turn off logging.
  • + *
+ */ +export declare function setLogLevel(logLevel: LogLevel): void; + +/** + * An options object that configures the behavior of {@link setDoc}, {@link + * WriteBatch#set} and {@link Transaction#set} calls. These calls can be + * configured to perform granular merges instead of overwriting the target + * documents in their entirety by providing a `SetOptions` with `merge: true`. + * + * @param merge - Changes the behavior of a `setDoc()` call to only replace the + * values specified in its data argument. Fields omitted from the `setDoc()` + * call remain untouched. + * @param mergeFields - Changes the behavior of `setDoc()` calls to only replace + * the specified field paths. Any field path that is not specified is ignored + * and remains untouched. + */ +export declare type SetOptions = + | { + readonly merge?: boolean; + } + | { + readonly mergeFields?: Array; + }; + +export declare interface Settings extends Settings_2 { + cacheSizeBytes?: number; +} + +declare interface Settings_2 { + host?: string; + ssl?: boolean; + ignoreUndefinedProperties?: boolean; + cacheSizeBytes?: number; + experimentalForceLongPolling?: boolean; + experimentalAutoDetectLongPolling?: boolean; +} + +/** + * A `SharedClientState` keeps track of the global state of the mutations + * and query targets for all active clients with the same persistence key (i.e. + * project ID and FirebaseApp name). It relays local changes to other clients + * and updates its local state as new state is observed. + * + * `SharedClientState` is primarily used for synchronization in Multi-Tab + * environments. Each tab is responsible for registering its active query + * targets and mutations. `SharedClientState` will then notify the listener + * assigned to `.syncEngine` for updates to mutations and queries that + * originated in other clients. + * + * To receive notifications, `.syncEngine` and `.onlineStateHandler` has to be + * assigned before calling `start()`. + */ +declare interface SharedClientState { + onlineStateHandler: ((onlineState: OnlineState) => void) | null; + sequenceNumberHandler: + | ((sequenceNumber: ListenSequenceNumber) => void) + | null; + /** Registers the Mutation Batch ID of a newly pending mutation. */ + addPendingMutation(batchId: BatchId): void; + /** + * Records that a pending mutation has been acknowledged or rejected. + * Called by the primary client to notify secondary clients of mutation + * results as they come back from the backend. + */ + updateMutationState( + batchId: BatchId, + state: 'acknowledged' | 'rejected', + error?: FirestoreError + ): void; + /** + * Associates a new Query Target ID with the local Firestore client. Returns + * the new query state for the query (which can be 'current' if the query is + * already associated with another tab). + * + * If the target id is already associated with local client, the method simply + * returns its `QueryTargetState`. + */ + addLocalQueryTarget(targetId: TargetId): QueryTargetState; + /** Removes the Query Target ID association from the local client. */ + removeLocalQueryTarget(targetId: TargetId): void; + /** Checks whether the target is associated with the local client. */ + isLocalQueryTarget(targetId: TargetId): boolean; + /** + * Processes an update to a query target. + * + * Called by the primary client to notify secondary clients of document + * changes or state transitions that affect the provided query target. + */ + updateQueryState( + targetId: TargetId, + state: QueryTargetState, + error?: FirestoreError + ): void; + /** + * Removes the target's metadata entry. + * + * Called by the primary client when all clients stopped listening to a query + * target. + */ + clearQueryState(targetId: TargetId): void; + /** + * Gets the active Query Targets IDs for all active clients. + * + * The implementation for this may require O(n) runtime, where 'n' is the size + * of the result set. + */ + getAllActiveQueryTargets(): SortedSet; + /** + * Checks whether the provided target ID is currently being listened to by + * any of the active clients. + * + * The implementation may require O(n*log m) runtime, where 'n' is the number + * of clients and 'm' the number of targets. + */ + isActiveQueryTarget(targetId: TargetId): boolean; + /** + * Starts the SharedClientState, reads existing client data and registers + * listeners for updates to new and existing clients. + */ + start(): Promise; + /** Shuts down the `SharedClientState` and its listeners. */ + shutdown(): void; + /** + * Changes the active user and removes all existing user-specific data. The + * user change does not call back into SyncEngine (for example, no mutations + * will be marked as removed). + */ + handleUserChange( + user: User, + removedBatchIds: BatchId[], + addedBatchIds: BatchId[] + ): void; + /** Changes the shared online state of all clients. */ + setOnlineState(onlineState: OnlineState): void; + writeSequenceNumber(sequenceNumber: ListenSequenceNumber): void; + /** + * Notifies other clients when remote documents have changed due to loading + * a bundle. + */ + notifyBundleLoaded(): void; +} + +/** + * Returns true if the provided snapshots are equal. + * + * @param left - A snapshot to compare. + * @param right - A snapshot to compare. + * @returns true if the snapshots are equal. + */ +export declare function snapshotEqual( + left: DocumentSnapshot | QuerySnapshot, + right: DocumentSnapshot | QuerySnapshot +): boolean; + +/** + * An options object that can be passed to {@link onSnapshot} and {@link + * QuerySnapshot#docChanges} to control which types of changes to include in the + * result set. + */ +export declare interface SnapshotListenOptions { + /** + * Include a change even if only the metadata of the query or of a document + * changed. Default is false. + */ + readonly includeMetadataChanges?: boolean; +} + +/** + * Metadata about a snapshot, describing the state of the snapshot. + */ +export declare class SnapshotMetadata { + /** + * True if the snapshot contains the result of local writes (for example + * `set()` or `update()` calls) that have not yet been committed to the + * backend. If your listener has opted into metadata updates (via + * `SnapshotListenOptions`) you will receive another snapshot with + * `hasPendingWrites` equal to false once the writes have been committed to + * the backend. + */ + readonly hasPendingWrites: boolean; + /** + * True if the snapshot was created from cached data rather than guaranteed + * up-to-date server data. If your listener has opted into metadata updates + * (via `SnapshotListenOptions`) you will receive another snapshot with + * `fromCache` set to false once the client has received up-to-date data from + * the backend. + */ + readonly fromCache: boolean; + /** @hideconstructor */ + constructor(hasPendingWrites: boolean, fromCache: boolean); + /** + * Returns true if this `SnapshotMetadata` is equal to the provided one. + * + * @param other - The `SnapshotMetadata` to compare against. + * @returns true if this `SnapshotMetadata` is equal to the provided one. + */ + isEqual(other: SnapshotMetadata): boolean; +} + +/** + * Options that configure how data is retrieved from a `DocumentSnapshot` (for + * example the desired behavior for server timestamps that have not yet been set + * to their final value). + */ +export declare interface SnapshotOptions { + /** + * If set, controls the return value for server timestamps that have not yet + * been set to their final value. + * + * By specifying 'estimate', pending server timestamps return an estimate + * based on the local clock. This estimate will differ from the final value + * and cause these values to change once the server result becomes available. + * + * By specifying 'previous', pending timestamps will be ignored and return + * their previous value instead. + * + * If omitted or set to 'none', `null` will be returned by default until the + * server value becomes available. + */ + readonly serverTimestamps?: 'estimate' | 'previous' | 'none'; +} + +/** + * A version of a document in Firestore. This corresponds to the version + * timestamp, such as update_time or read_time. + */ +declare class SnapshotVersion { + private timestamp; + static fromTimestamp(value: Timestamp): SnapshotVersion; + static min(): SnapshotVersion; + private constructor(); + compareTo(other: SnapshotVersion): number; + isEqual(other: SnapshotVersion): boolean; + /** Returns a number representation of the version for use in spec tests. */ + toMicroseconds(): number; + toString(): string; + toTimestamp(): Timestamp; +} + +declare class SortedMap { + comparator: Comparator; + root: LLRBNode | LLRBEmptyNode; + constructor( + comparator: Comparator, + root?: LLRBNode | LLRBEmptyNode + ); + insert(key: K, value: V): SortedMap; + remove(key: K): SortedMap; + get(key: K): V | null; + indexOf(key: K): number; + isEmpty(): boolean; + get size(): number; + minKey(): K | null; + maxKey(): K | null; + inorderTraversal(action: (k: K, v: V) => T): T; + forEach(fn: (k: K, v: V) => void): void; + toString(): string; + reverseTraversal(action: (k: K, v: V) => T): T; + getIterator(): SortedMapIterator; + getIteratorFrom(key: K): SortedMapIterator; + getReverseIterator(): SortedMapIterator; + getReverseIteratorFrom(key: K): SortedMapIterator; +} + +declare class SortedMapIterator { + private isReverse; + private nodeStack; + constructor( + node: LLRBNode | LLRBEmptyNode, + startKey: K | null, + comparator: Comparator, + isReverse: boolean + ); + getNext(): Entry; + hasNext(): boolean; + peek(): Entry | null; +} + +/** + * SortedSet is an immutable (copy-on-write) collection that holds elements + * in order specified by the provided comparator. + * + * NOTE: if provided comparator returns 0 for two elements, we consider them to + * be equal! + */ +declare class SortedSet { + private comparator; + private data; + constructor(comparator: (left: T, right: T) => number); + has(elem: T): boolean; + first(): T | null; + last(): T | null; + get size(): number; + indexOf(elem: T): number; + /** Iterates elements in order defined by "comparator" */ + forEach(cb: (elem: T) => void): void; + /** Iterates over `elem`s such that: range[0] <= elem < range[1]. */ + forEachInRange(range: [T, T], cb: (elem: T) => void): void; + /** + * Iterates over `elem`s such that: start <= elem until false is returned. + */ + forEachWhile(cb: (elem: T) => boolean, start?: T): void; + /** Finds the least element greater than or equal to `elem`. */ + firstAfterOrEqual(elem: T): T | null; + getIterator(): SortedSetIterator; + getIteratorFrom(key: T): SortedSetIterator; + /** Inserts or updates an element */ + add(elem: T): SortedSet; + /** Deletes an element */ + delete(elem: T): SortedSet; + isEmpty(): boolean; + unionWith(other: SortedSet): SortedSet; + isEqual(other: SortedSet): boolean; + toArray(): T[]; + toString(): string; + private copy; +} + +declare class SortedSetIterator { + private iter; + constructor(iter: SortedMapIterator); + getNext(): T; + hasNext(): boolean; +} + +/** + * Creates a `QueryConstraint` that modifies the result set to start after the + * provided document (exclusive). The starting position is relative to the order + * of the query. The document must contain all of the fields provided in the + * orderBy of the query. + * + * @param snapshot - The snapshot of the document to start after. + * @returns A `QueryConstraint` to pass to `query()` + */ +export declare function startAfter( + snapshot: DocumentSnapshot_2 +): QueryConstraint; + +/** + * Creates a `QueryConstraint` that modifies the result set to start after the + * provided fields relative to the order of the query. The order of the field + * values must match the order of the order by clauses of the query. + * + * @param fieldValues - The field values to start this query after, in order + * of the query's order by. + * @returns A `QueryConstraint` to pass to `query()` + */ +export declare function startAfter(...fieldValues: unknown[]): QueryConstraint; + +/** + * Creates a `QueryConstraint` that modifies the result set to start at the + * provided document (inclusive). The starting position is relative to the order + * of the query. The document must contain all of the fields provided in the + * `orderBy` of this query. + * + * @param snapshot - The snapshot of the document to start at. + * @returns A `QueryConstraint` to pass to `query()`. + */ +export declare function startAt( + snapshot: DocumentSnapshot_2 +): QueryConstraint; + +/** + * Creates a `QueryConstraint` that modifies the result set to start at the + * provided fields relative to the order of the query. The order of the field + * values must match the order of the order by clauses of the query. + * + * @param fieldValues - The field values to start this query at, in order + * of the query's order by. + * @returns A `QueryConstraint` to pass to `query()`. + */ +export declare function startAt(...fieldValues: unknown[]): QueryConstraint; + +declare type StructuredQuery = firestoreV1ApiClientInterfaces.StructuredQuery; + +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * SyncEngine is the central controller in the client SDK architecture. It is + * the glue code between the EventManager, LocalStore, and RemoteStore. Some of + * SyncEngine's responsibilities include: + * 1. Coordinating client requests and remote events between the EventManager + * and the local and remote data stores. + * 2. Managing a View object for each query, providing the unified view between + * the local and remote data stores. + * 3. Notifying the RemoteStore when the LocalStore has new mutations in its + * queue that need sending to the backend. + * + * The SyncEngine’s methods should only ever be called by methods running in the + * global async queue. + * + * PORTING NOTE: On Web, SyncEngine does not have an explicit subscribe() + * function. Instead, it directly depends on EventManager's tree-shakeable API + * (via `ensureWatchStream()`). + */ +declare interface SyncEngine { + isPrimaryClient: boolean; +} + +/** + * A Target represents the WatchTarget representation of a Query, which is used + * by the LocalStore and the RemoteStore to keep track of and to execute + * backend queries. While a Query can represent multiple Targets, each Targets + * maps to a single WatchTarget in RemoteStore and a single TargetData entry + * in persistence. + */ +declare interface Target { + readonly path: ResourcePath; + readonly collectionGroup: string | null; + readonly orderBy: OrderBy[]; + readonly filters: Filter[]; + readonly limit: number | null; + readonly startAt: Bound | null; + readonly endAt: Bound | null; +} + +/** + * Represents cached targets received from the remote backend. + * + * The cache is keyed by `Target` and entries in the cache are `TargetData` + * instances. + */ +declare interface TargetCache { + /** + * A global snapshot version representing the last consistent snapshot we + * received from the backend. This is monotonically increasing and any + * snapshots received from the backend prior to this version (e.g. for targets + * resumed with a resume_token) should be suppressed (buffered) until the + * backend has caught up to this snapshot version again. This prevents our + * cache from ever going backwards in time. + * + * This is updated whenever our we get a TargetChange with a read_time and + * empty target_ids. + */ + getLastRemoteSnapshotVersion( + transaction: PersistenceTransaction + ): PersistencePromise; + /** + * @returns The highest sequence number observed, including any that might be + * persisted on-disk. + */ + getHighestSequenceNumber( + transaction: PersistenceTransaction + ): PersistencePromise; + /** + * Call provided function with each `TargetData` that we have cached. + */ + forEachTarget( + txn: PersistenceTransaction, + f: (q: TargetData) => void + ): PersistencePromise; + /** + * Set the highest listen sequence number and optionally updates the + * snapshot version of the last consistent snapshot received from the backend + * (see getLastRemoteSnapshotVersion() for more details). + * + * @param highestListenSequenceNumber - The new maximum listen sequence number. + * @param lastRemoteSnapshotVersion - The new snapshot version. Optional. + */ + setTargetsMetadata( + transaction: PersistenceTransaction, + highestListenSequenceNumber: number, + lastRemoteSnapshotVersion?: SnapshotVersion + ): PersistencePromise; + /** + * Adds an entry in the cache. + * + * The cache key is extracted from `targetData.target`. The key must not already + * exist in the cache. + * + * @param targetData - A TargetData instance to put in the cache. + */ + addTargetData( + transaction: PersistenceTransaction, + targetData: TargetData + ): PersistencePromise; + /** + * Updates an entry in the cache. + * + * The cache key is extracted from `targetData.target`. The entry must already + * exist in the cache, and it will be replaced. + * @param targetData - The TargetData to be replaced into the cache. + */ + updateTargetData( + transaction: PersistenceTransaction, + targetData: TargetData + ): PersistencePromise; + /** + * Removes the cached entry for the given target data. It is an error to remove + * a target data that does not exist. + * + * Multi-Tab Note: This operation should only be called by the primary client. + */ + removeTargetData( + transaction: PersistenceTransaction, + targetData: TargetData + ): PersistencePromise; + /** + * The number of targets currently in the cache. + */ + getTargetCount( + transaction: PersistenceTransaction + ): PersistencePromise; + /** + * Looks up a TargetData entry by target. + * + * @param target - The query target corresponding to the entry to look up. + * @returns The cached TargetData entry, or null if the cache has no entry for + * the target. + */ + getTargetData( + transaction: PersistenceTransaction, + target: Target + ): PersistencePromise; + /** + * Adds the given document keys to cached query results of the given target + * ID. + * + * Multi-Tab Note: This operation should only be called by the primary client. + */ + addMatchingKeys( + transaction: PersistenceTransaction, + keys: DocumentKeySet, + targetId: TargetId + ): PersistencePromise; + /** + * Removes the given document keys from the cached query results of the + * given target ID. + * + * Multi-Tab Note: This operation should only be called by the primary client. + */ + removeMatchingKeys( + transaction: PersistenceTransaction, + keys: DocumentKeySet, + targetId: TargetId + ): PersistencePromise; + /** + * Removes all the keys in the query results of the given target ID. + * + * Multi-Tab Note: This operation should only be called by the primary client. + */ + removeMatchingKeysForTargetId( + transaction: PersistenceTransaction, + targetId: TargetId + ): PersistencePromise; + /** + * Returns the document keys that match the provided target ID. + */ + getMatchingKeysForTargetId( + transaction: PersistenceTransaction, + targetId: TargetId + ): PersistencePromise; + /** + * Returns a new target ID that is higher than any query in the cache. If + * there are no queries in the cache, returns the first valid target ID. + * Allocated target IDs are persisted and `allocateTargetId()` will never + * return the same ID twice. + */ + allocateTargetId( + transaction: PersistenceTransaction + ): PersistencePromise; + containsKey( + transaction: PersistenceTransaction, + key: DocumentKey + ): PersistencePromise; +} + +/** + * A TargetChange specifies the set of changes for a specific target as part of + * a RemoteEvent. These changes track which documents are added, modified or + * removed, as well as the target's resume token and whether the target is + * marked CURRENT. + * The actual changes *to* documents are not part of the TargetChange since + * documents may be part of multiple targets. + */ +declare class TargetChange { + /** + * An opaque, server-assigned token that allows watching a query to be resumed + * after disconnecting without retransmitting all the data that matches the + * query. The resume token essentially identifies a point in time from which + * the server should resume sending results. + */ + readonly resumeToken: ByteString; + /** + * The "current" (synced) status of this target. Note that "current" + * has special meaning in the RPC protocol that implies that a target is + * both up-to-date and consistent with the rest of the watch stream. + */ + readonly current: boolean; + /** + * The set of documents that were newly assigned to this target as part of + * this remote event. + */ + readonly addedDocuments: DocumentKeySet; + /** + * The set of documents that were already assigned to this target but received + * an update during this remote event. + */ + readonly modifiedDocuments: DocumentKeySet; + /** + * The set of documents that were removed from this target as part of this + * remote event. + */ + readonly removedDocuments: DocumentKeySet; + constructor( + /** + * An opaque, server-assigned token that allows watching a query to be resumed + * after disconnecting without retransmitting all the data that matches the + * query. The resume token essentially identifies a point in time from which + * the server should resume sending results. + */ + resumeToken: ByteString, + /** + * The "current" (synced) status of this target. Note that "current" + * has special meaning in the RPC protocol that implies that a target is + * both up-to-date and consistent with the rest of the watch stream. + */ + current: boolean, + /** + * The set of documents that were newly assigned to this target as part of + * this remote event. + */ + addedDocuments: DocumentKeySet, + /** + * The set of documents that were already assigned to this target but received + * an update during this remote event. + */ + modifiedDocuments: DocumentKeySet, + /** + * The set of documents that were removed from this target as part of this + * remote event. + */ + removedDocuments: DocumentKeySet + ); + /** + * This method is used to create a synthesized TargetChanges that can be used to + * apply a CURRENT status change to a View (for queries executed in a different + * tab) or for new queries (to raise snapshots with correct CURRENT status). + */ + static createSynthesizedTargetChangeForCurrentChange( + targetId: TargetId, + current: boolean + ): TargetChange; +} + +declare type TargetChangeTargetChangeType = + | 'NO_CHANGE' + | 'ADD' + | 'REMOVE' + | 'CURRENT' + | 'RESET'; + +/** + * An immutable set of metadata that the local store tracks for each target. + */ +declare class TargetData { + /** The target being listened to. */ + readonly target: Target; + /** + * The target ID to which the target corresponds; Assigned by the + * LocalStore for user listens and by the SyncEngine for limbo watches. + */ + readonly targetId: TargetId; + /** The purpose of the target. */ + readonly purpose: TargetPurpose; + /** + * The sequence number of the last transaction during which this target data + * was modified. + */ + readonly sequenceNumber: ListenSequenceNumber; + /** The latest snapshot version seen for this target. */ + readonly snapshotVersion: SnapshotVersion; + /** + * The maximum snapshot version at which the associated view + * contained no limbo documents. + */ + readonly lastLimboFreeSnapshotVersion: SnapshotVersion; + /** + * An opaque, server-assigned token that allows watching a target to be + * resumed after disconnecting without retransmitting all the data that + * matches the target. The resume token essentially identifies a point in + * time from which the server should resume sending results. + */ + readonly resumeToken: ByteString; + constructor( + /** The target being listened to. */ + target: Target, + /** + * The target ID to which the target corresponds; Assigned by the + * LocalStore for user listens and by the SyncEngine for limbo watches. + */ + targetId: TargetId, + /** The purpose of the target. */ + purpose: TargetPurpose, + /** + * The sequence number of the last transaction during which this target data + * was modified. + */ + sequenceNumber: ListenSequenceNumber, + /** The latest snapshot version seen for this target. */ + snapshotVersion?: SnapshotVersion, + /** + * The maximum snapshot version at which the associated view + * contained no limbo documents. + */ + lastLimboFreeSnapshotVersion?: SnapshotVersion, + /** + * An opaque, server-assigned token that allows watching a target to be + * resumed after disconnecting without retransmitting all the data that + * matches the target. The resume token essentially identifies a point in + * time from which the server should resume sending results. + */ + resumeToken?: ByteString + ); + /** Creates a new target data instance with an updated sequence number. */ + withSequenceNumber(sequenceNumber: number): TargetData; + /** + * Creates a new target data instance with an updated resume token and + * snapshot version. + */ + withResumeToken( + resumeToken: ByteString, + snapshotVersion: SnapshotVersion + ): TargetData; + /** + * Creates a new target data instance with an updated last limbo free + * snapshot version number. + */ + withLastLimboFreeSnapshotVersion( + lastLimboFreeSnapshotVersion: SnapshotVersion + ): TargetData; +} + +/** + * A locally-assigned ID used to refer to a target being watched via the + * Watch service. + */ +declare type TargetId = number; + +/** An enumeration of the different purposes we have for targets. */ +declare const enum TargetPurpose { + /** A regular, normal query target. */ + Listen = 0, + /** + * The query target was used to refill a query after an existence filter mismatch. + */ + ExistenceFilterMismatch = 1, + /** The query target was used to resolve a limbo document. */ + LimboResolution = 2 +} + +/** + * Represents the state of bundle loading tasks. + * + * Both 'Error' and 'Success' are sinking state: task will abort or complete and there will + * be no more updates after they are reported. + */ +export declare type TaskState = 'Error' | 'Running' | 'Success'; + +/** + * Terminates the provided Firestore instance. + * + * After calling `terminate()` only the `clearIndexedDbPersistence()` function + * may be used. Any other function will throw a `FirestoreError`. + * + * To restart after termination, create a new instance of FirebaseFirestore with + * {@link getFirestore}. + * + * Termination does not cancel any pending writes, and any promises that are + * awaiting a response from the server will not be resolved. If you have + * persistence enabled, the next time you start this instance, it will resume + * sending these writes to the server. + * + * Note: Under normal circumstances, calling `terminate()` is not required. This + * function is useful only when you want to force this instance to release all + * of its resources or in combination with `clearIndexedDbPersistence()` to + * ensure that all local state is destroyed between test runs. + * + * @returns A promise that is resolved when the instance has been successfully + * terminated. + */ +export declare function terminate(firestore: FirebaseFirestore): Promise; + +/** + * Wellknown "timer" IDs used when scheduling delayed operations on the + * AsyncQueue. These IDs can then be used from tests to check for the presence + * of operations or to run them early. + * + * The string values are used when encoding these timer IDs in JSON spec tests. + */ +declare const enum TimerId { + /** All can be used with runDelayedOperationsEarly() to run all timers. */ + All = 'all', + /** + * The following 4 timers are used in persistent_stream.ts for the listen and + * write streams. The "Idle" timer is used to close the stream due to + * inactivity. The "ConnectionBackoff" timer is used to restart a stream once + * the appropriate backoff delay has elapsed. + */ + ListenStreamIdle = 'listen_stream_idle', + ListenStreamConnectionBackoff = 'listen_stream_connection_backoff', + WriteStreamIdle = 'write_stream_idle', + WriteStreamConnectionBackoff = 'write_stream_connection_backoff', + /** + * A timer used in online_state_tracker.ts to transition from + * OnlineState.Unknown to Offline after a set timeout, rather than waiting + * indefinitely for success or failure. + */ + OnlineStateTimeout = 'online_state_timeout', + /** + * A timer used to update the client metadata in IndexedDb, which is used + * to determine the primary leaseholder. + */ + ClientMetadataRefresh = 'client_metadata_refresh', + /** A timer used to periodically attempt LRU Garbage collection */ + LruGarbageCollection = 'lru_garbage_collection', + /** + * A timer used to retry transactions. Since there can be multiple concurrent + * transactions, multiple of these may be in the queue at a given time. + */ + TransactionRetry = 'transaction_retry', + /** + * A timer used to retry operations scheduled via retryable AsyncQueue + * operations. + */ + AsyncQueueRetry = 'async_queue_retry' +} + +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * A `Timestamp` represents a point in time independent of any time zone or + * calendar, represented as seconds and fractions of seconds at nanosecond + * resolution in UTC Epoch time. + * + * It is encoded using the Proleptic Gregorian Calendar which extends the + * Gregorian calendar backwards to year one. It is encoded assuming all minutes + * are 60 seconds long, i.e. leap seconds are "smeared" so that no leap second + * table is needed for interpretation. Range is from 0001-01-01T00:00:00Z to + * 9999-12-31T23:59:59.999999999Z. + * + * For examples and further specifications, refer to the + * {@link https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto | Timestamp definition}. + */ +export declare class Timestamp { + readonly seconds: number; + readonly nanoseconds: number; + /** + * Creates a new timestamp with the current date, with millisecond precision. + * + * @returns a new timestamp representing the current date. + */ + static now(): Timestamp; + /** + * Creates a new timestamp from the given date. + * + * @param date - The date to initialize the `Timestamp` from. + * @returns A new `Timestamp` representing the same point in time as the given + * date. + */ + static fromDate(date: Date): Timestamp; + /** + * Creates a new timestamp from the given number of milliseconds. + * + * @param milliseconds - Number of milliseconds since Unix epoch + * 1970-01-01T00:00:00Z. + * @returns A new `Timestamp` representing the same point in time as the given + * number of milliseconds. + */ + static fromMillis(milliseconds: number): Timestamp; + /** + * Creates a new timestamp. + * + * @param seconds - The number of seconds of UTC time since Unix epoch + * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to + * 9999-12-31T23:59:59Z inclusive. + * @param nanoseconds - The non-negative fractions of a second at nanosecond + * resolution. Negative second values with fractions must still have + * non-negative nanoseconds values that count forward in time. Must be + * from 0 to 999,999,999 inclusive. + */ + constructor(seconds: number, nanoseconds: number); + /** + * Converts a `Timestamp` to a JavaScript `Date` object. This conversion causes + * a loss of precision since `Date` objects only support millisecond precision. + * + * @returns JavaScript `Date` object representing the same point in time as + * this `Timestamp`, with millisecond precision. + */ + toDate(): Date; + /** + * Converts a `Timestamp` to a numeric timestamp (in milliseconds since + * epoch). This operation causes a loss of precision. + * + * @returns The point in time corresponding to this timestamp, represented as + * the number of milliseconds since Unix epoch 1970-01-01T00:00:00Z. + */ + toMillis(): number; + _compareTo(other: Timestamp): number; + /** + * Returns true if this `Timestamp` is equal to the provided one. + * + * @param other - The `Timestamp` to compare against. + * @returns true if this `Timestamp` is equal to the provided one. + */ + isEqual(other: Timestamp): boolean; + toString(): string; + toJSON(): { + seconds: number; + nanoseconds: number; + }; + /** + * Converts this object to a primitive string, which allows Timestamp objects to be compared + * using the `>`, `<=`, `>=` and `>` operators. + */ + valueOf(): string; +} + +declare type Timestamp_2 = + | string + | { + seconds?: string | number; + nanos?: number; + }; + +declare interface Token { + /** Type of token. */ + type: TokenType; + /** + * The user with which the token is associated (used for persisting user + * state on disk, etc.). + */ + user: User; + /** Extra header values to be passed along with a request */ + authHeaders: { + [header: string]: string; + }; +} + +declare type TokenType = 'OAuth' | 'FirstParty'; + +/** + * A reference to a transaction. + * + * The `Transaction` object passed to a transaction's `updateFunction` provides + * the methods to read and write data within the transaction context. See + * {@link runTransaction}. + */ +export declare class Transaction extends Transaction_2 { + protected readonly _firestore: FirebaseFirestore; + /** @hideconstructor */ + constructor(_firestore: FirebaseFirestore, _transaction: Transaction_3); + /** + * Reads the document referenced by the provided {@link DocumentReference}. + * + * @param documentRef - A reference to the document to be read. + * @returns A `DocumentSnapshot` with the read data. + */ + get(documentRef: DocumentReference): Promise>; +} + +/** + * A reference to a transaction. + * + * The `Transaction` object passed to a transaction's `updateFunction` provides + * the methods to read and write data within the transaction context. See + * {@link runTransaction}. + */ +declare class Transaction_2 { + protected readonly _firestore: FirebaseFirestore_2; + private readonly _transaction; + private readonly _dataReader; + /** @hideconstructor */ + constructor(_firestore: FirebaseFirestore_2, _transaction: Transaction_3); + /** + * Reads the document referenced by the provided {@link DocumentReference}. + * + * @param documentRef - A reference to the document to be read. + * @returns A `DocumentSnapshot` with the read data. + */ + get(documentRef: DocumentReference): Promise>; + /** + * Writes to the document referred to by the provided {@link + * DocumentReference}. If the document does not exist yet, it will be created. + * + * @param documentRef - A reference to the document to be set. + * @param data - An object of the fields and values for the document. + * @returns This `Transaction` instance. Used for chaining method calls. + */ + set(documentRef: DocumentReference, data: T): this; + /** + * Writes to the document referred to by the provided {@link + * DocumentReference}. If the document does not exist yet, it will be created. + * If you provide `merge` or `mergeFields`, the provided data can be merged + * into an existing document. + * + * @param documentRef - A reference to the document to be set. + * @param data - An object of the fields and values for the document. + * @param options - An object to configure the set behavior. + * @returns This `Transaction` instance. Used for chaining method calls. + */ + set( + documentRef: DocumentReference, + data: Partial, + options: SetOptions + ): this; + /** + * Updates fields in the document referred to by the provided {@link + * DocumentReference}. The update will fail if applied to a document that does + * not exist. + * + * @param documentRef - A reference to the document to be updated. + * @param data - An object containing the fields and values with which to + * update the document. Fields can contain dots to reference nested fields + * within the document. + * @returns This `Transaction` instance. Used for chaining method calls. + */ + update(documentRef: DocumentReference, data: UpdateData): this; + /** + * Updates fields in the document referred to by the provided {@link + * DocumentReference}. The update will fail if applied to a document that does + * not exist. + * + * Nested fields can be updated by providing dot-separated field path + * strings or by providing `FieldPath` objects. + * + * @param documentRef - A reference to the document to be updated. + * @param field - The first field to update. + * @param value - The first value. + * @param moreFieldsAndValues - Additional key/value pairs. + * @returns This `Transaction` instance. Used for chaining method calls. + */ + update( + documentRef: DocumentReference, + field: string | FieldPath, + value: unknown, + ...moreFieldsAndValues: unknown[] + ): this; + /** + * Deletes the document referred to by the provided {@link DocumentReference}. + * + * @param documentRef - A reference to the document to be deleted. + * @returns This `Transaction` instance. Used for chaining method calls. + */ + delete(documentRef: DocumentReference): this; +} + +/** + * Internal transaction object responsible for accumulating the mutations to + * perform and the base versions for any documents read. + */ +declare class Transaction_3 { + private datastore; + private readVersions; + private mutations; + private committed; + /** + * A deferred usage error that occurred previously in this transaction that + * will cause the transaction to fail once it actually commits. + */ + private lastWriteError; + /** + * Set of documents that have been written in the transaction. + * + * When there's more than one write to the same key in a transaction, any + * writes after the first are handled differently. + */ + private writtenDocs; + constructor(datastore: Datastore); + lookup(keys: DocumentKey[]): Promise; + set(key: DocumentKey, data: ParsedSetData): void; + update(key: DocumentKey, data: ParsedUpdateData): void; + delete(key: DocumentKey): void; + commit(): Promise; + private recordVersion; + /** + * Returns the version of this document when it was read in this transaction, + * as a precondition, or no precondition if it was not read. + */ + private precondition; + /** + * Returns the precondition for a document if the operation is an update. + */ + private preconditionForUpdate; + private write; + private ensureCommitNotCalled; +} + +/** Used to represent a field transform on a mutation. */ +declare class TransformOperation { + private _; +} + +declare type UnaryFilterOp = + | 'OPERATOR_UNSPECIFIED' + | 'IS_NAN' + | 'IS_NULL' + | 'IS_NOT_NAN' + | 'IS_NOT_NULL'; + +export declare interface Unsubscribe { + (): void; +} + +/** + * An untyped Firestore Data Converter interface that is shared between the + * lite, firestore-exp and classic SDK. + */ +declare interface UntypedFirestoreDataConverter { + toFirestore(modelObject: T): DocumentData_2; + toFirestore(modelObject: Partial, options: SetOptions_2): DocumentData_2; + fromFirestore(snapshot: unknown, options?: unknown): T; +} + +/** + * Update data (for use with {@link updateDoc}) consists of field paths (e.g. + * 'foo' or 'foo.baz') mapped to values. Fields that contain dots reference + * nested fields within the document. + */ +export declare interface UpdateData { + [fieldPath: string]: any; +} + +/** + * Updates fields in the document referred to by the specified + * `DocumentReference`. The update will fail if applied to a document that does + * not exist. + * + * @param reference - A reference to the document to update. + * @param data - An object containing the fields and values with which to + * update the document. Fields can contain dots to reference nested fields + * within the document. + * @returns A Promise resolved once the data has been successfully written + * to the backend (note that it won't resolve while you're offline). + */ +export declare function updateDoc( + reference: DocumentReference, + data: UpdateData +): Promise; + +/** + * Updates fields in the document referred to by the specified + * `DocumentReference` The update will fail if applied to a document that does + * not exist. + * + * Nested fields can be updated by providing dot-separated field path + * strings or by providing `FieldPath` objects. + * + * @param reference - A reference to the document to update. + * @param field - The first field to update. + * @param value - The first value. + * @param moreFieldsAndValues - Additional key value pairs. + * @returns A Promise resolved once the data has been successfully written + * to the backend (note that it won't resolve while you're offline). + */ +export declare function updateDoc( + reference: DocumentReference, + field: string | FieldPath, + value: unknown, + ...moreFieldsAndValues: unknown[] +): Promise; + +/** + * Modify this instance to communicate with the Cloud Firestore emulator. + * + * Note: This must be called before this instance has been used to do any + * operations. + * + * @param firestore - The Firestore instance to configure to connect to the + * emulator. + * @param host - the emulator host (ex: localhost). + * @param port - the emulator port (ex: 9000). + */ +export declare function useFirestoreEmulator( + firestore: FirebaseFirestore_2, + host: string, + port: number +): void; + +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Simple wrapper around a nullable UID. Mostly exists to make code more + * readable. + */ +declare class User { + readonly uid: string | null; + /** A user with a null UID. */ + static readonly UNAUTHENTICATED: User; + static readonly GOOGLE_CREDENTIALS: User; + static readonly FIRST_PARTY: User; + constructor(uid: string | null); + isAuthenticated(): boolean; + /** + * Returns a key representing this user, suitable for inclusion in a + * dictionary. + */ + toKey(): string; + isEqual(otherUser: User): boolean; +} + +declare type Value = firestoreV1ApiClientInterfaces.Value; + +declare type ValueNullValue = 'NULL_VALUE'; + +declare class ViewSnapshot { + readonly query: Query_2; + readonly docs: DocumentSet; + readonly oldDocs: DocumentSet; + readonly docChanges: DocumentViewChange[]; + readonly mutatedKeys: DocumentKeySet; + readonly fromCache: boolean; + readonly syncStateChanged: boolean; + readonly excludesMetadataChanges: boolean; + constructor( + query: Query_2, + docs: DocumentSet, + oldDocs: DocumentSet, + docChanges: DocumentViewChange[], + mutatedKeys: DocumentKeySet, + fromCache: boolean, + syncStateChanged: boolean, + excludesMetadataChanges: boolean + ); + /** Returns a view snapshot as if all documents in the snapshot were added. */ + static fromInitialDocuments( + query: Query_2, + documents: DocumentSet, + mutatedKeys: DocumentKeySet, + fromCache: boolean + ): ViewSnapshot; + get hasPendingWrites(): boolean; + isEqual(other: ViewSnapshot): boolean; +} + +/** + * Waits until all currently pending writes for the active user have been + * acknowledged by the backend. + * + * The returned Promise resolves immediately if there are no outstanding writes. + * Otherwise, the Promise waits for all previously issued writes (including + * those written in a previous app session), but it does not wait for writes + * that were added after the function is called. If you want to wait for + * additional writes, call `waitForPendingWrites()` again. + * + * Any outstanding `waitForPendingWrites()` Promises are rejected during user + * changes. + * + * @returns A Promise which resolves when all currently pending writes have been + * acknowledged by the backend. + */ +export declare function waitForPendingWrites( + firestore: FirebaseFirestore +): Promise; + +/** + * Creates a `QueryConstraint` that enforces that documents must contain the + * specified field and that the value should satisfy the relation constraint + * provided. + * + * @param fieldPath - The path to compare + * @param opStr - The operation string (e.g "<", "<=", "==", "<", + * "<=", "!="). + * @param value - The value for comparison + * @returns The created `Query`. + */ +export declare function where( + fieldPath: string | FieldPath, + opStr: WhereFilterOp, + value: unknown +): QueryConstraint; + +/** + * Filter conditions in a {@link where} clause are specified using the + * strings '<', '<=', '==', '!=', '>=', '>', 'array-contains', 'in', + * 'array-contains-any', and 'not-in'. + */ +export declare type WhereFilterOp = + | '<' + | '<=' + | '==' + | '!=' + | '>=' + | '>' + | 'array-contains' + | 'in' + | 'array-contains-any' + | 'not-in'; + +/** + * A write batch, used to perform multiple writes as a single atomic unit. + * + * A `WriteBatch` object can be acquired by calling {@link writeBatch}. It + * provides methods for adding writes to the write batch. None of the writes + * will be committed (or visible locally) until {@link WriteBatch#commit} is + * called. + */ +export declare class WriteBatch { + private readonly _firestore; + private readonly _commitHandler; + private readonly _dataReader; + private _mutations; + private _committed; + /** @hideconstructor */ + constructor( + _firestore: FirebaseFirestore_2, + _commitHandler: (m: Mutation[]) => Promise + ); + /** + * Writes to the document referred to by the provided {@link + * DocumentReference}. If the document does not exist yet, it will be created. + * + * @param documentRef - A reference to the document to be set. + * @param data - An object of the fields and values for the document. + * @returns This `WriteBatch` instance. Used for chaining method calls. + */ + set(documentRef: DocumentReference, data: T): WriteBatch; + /** + * Writes to the document referred to by the provided {@link + * DocumentReference}. If the document does not exist yet, it will be created. + * If you provide `merge` or `mergeFields`, the provided data can be merged + * into an existing document. + * + * @param documentRef - A reference to the document to be set. + * @param data - An object of the fields and values for the document. + * @param options - An object to configure the set behavior. + * @returns This `WriteBatch` instance. Used for chaining method calls. + */ + set( + documentRef: DocumentReference, + data: Partial, + options: SetOptions + ): WriteBatch; + /** + * Updates fields in the document referred to by the provided {@link + * DocumentReference}. The update will fail if applied to a document that does + * not exist. + * + * @param documentRef - A reference to the document to be updated. + * @param data - An object containing the fields and values with which to + * update the document. Fields can contain dots to reference nested fields + * within the document. + * @returns This `WriteBatch` instance. Used for chaining method calls. + */ + update(documentRef: DocumentReference, data: UpdateData): WriteBatch; + /** + * Updates fields in the document referred to by this {@link + * DocumentReference}. The update will fail if applied to a document that does + * not exist. + * + * Nested fields can be update by providing dot-separated field path strings + * or by providing `FieldPath` objects. + * + * @param documentRef - A reference to the document to be updated. + * @param field - The first field to update. + * @param value - The first value. + * @param moreFieldsAndValues - Additional key value pairs. + * @returns This `WriteBatch` instance. Used for chaining method calls. + */ + update( + documentRef: DocumentReference, + field: string | FieldPath, + value: unknown, + ...moreFieldsAndValues: unknown[] + ): WriteBatch; + /** + * Deletes the document referred to by the provided {@link DocumentReference}. + * + * @param documentRef - A reference to the document to be deleted. + * @returns This `WriteBatch` instance. Used for chaining method calls. + */ + delete(documentRef: DocumentReference): WriteBatch; + /** + * Commits all of the writes in this write batch as a single atomic unit. + * + * The result of these writes will only be reflected in document reads that + * occur after the returned Promise resolves. If the client is offline, the + * write fails. If you would like to see local modifications or buffer writes + * until the client is online, use the full Firestore SDK. + * + * @returns A Promise resolved once all of the writes in the batch have been + * successfully written to the backend as an atomic unit (note that it won't + * resolve while you're offline). + */ + commit(): Promise; + private _verifyNotCommitted; +} + +/** + * Creates a write batch, used for performing multiple writes as a single + * atomic operation. The maximum number of writes allowed in a single WriteBatch + * is 500. + * + * Unlike transactions, write batches are persisted offline and therefore are + * preferable when you don't need to condition your writes on read data. + * + * @returns A `WriteBatch` that can be used to atomically execute multiple + * writes. + */ +export declare function writeBatch(firestore: FirebaseFirestore): WriteBatch; + +export {}; diff --git a/repo-scripts/prune-dts/tests/firestore.output.d.ts b/repo-scripts/prune-dts/tests/firestore.output.d.ts new file mode 100644 index 00000000000..9eb0f6a56ab --- /dev/null +++ b/repo-scripts/prune-dts/tests/firestore.output.d.ts @@ -0,0 +1,2077 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { DocumentData as DocumentData_2 } from '@firebase/firestore-types'; +import { FirebaseApp } from '@firebase/app-exp'; +import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; +import { _FirebaseService } from '@firebase/app-exp'; +import { LogLevelString as LogLevel } from '@firebase/logger'; +import { Provider } from '@firebase/component'; +import { SetOptions as SetOptions_2 } from '@firebase/firestore-types'; +/** + * Add a new document to specified `CollectionReference` with the given data, + * assigning it a document ID automatically. + * + * @param reference - A reference to the collection to add this document to. + * @param data - An Object containing the data for the new document. + * @returns A Promise resolved with a `DocumentReference` pointing to the + * newly created document after it has been written to the backend (Note that it + * won't resolve while you're offline). + */ +export declare function addDoc( + reference: CollectionReference, + data: T +): Promise>; +/** + * Returns a special value that can be used with {@link (setDoc:1)} or {@link + * updateDoc} that tells the server to remove the given elements from any + * array value that already exists on the server. All instances of each element + * specified will be removed from the array. If the field being modified is not + * already an array it will be overwritten with an empty array. + * + * @param elements - The elements to remove from the array. + * @returns The `FieldValue` sentinel for use in a call to `setDoc()` or + * `updateDoc()` + */ +export declare function arrayRemove(...elements: unknown[]): FieldValue; +/** + * Returns a special value that can be used with {@link setDoc} or {@link + * updateDoc} that tells the server to union the given elements with any array + * value that already exists on the server. Each specified element that doesn't + * already exist in the array will be added to the end. If the field being + * modified is not already an array it will be overwritten with an array + * containing exactly the specified elements. + * + * @param elements - The elements to union into the array. + * @returns The `FieldValue` sentinel for use in a call to `setDoc()` or + * `updateDoc()`. + */ +export declare function arrayUnion(...elements: unknown[]): FieldValue; +/** + * An immutable object representing an array of bytes. + */ +export declare class Bytes { + private constructor(); + /** + * Creates a new `Bytes` object from the given Base64 string, converting it to + * bytes. + * + * @param base64 - The Base64 string used to create the `Bytes` object. + */ + static fromBase64String(base64: string): Bytes; + /** + * Creates a new `Bytes` object from the given Uint8Array. + * + * @param array - The Uint8Array used to create the `Bytes` object. + */ + static fromUint8Array(array: Uint8Array): Bytes; + /** + * Returns the underlying bytes as a Base64-encoded string. + * + * @returns The Base64-encoded string created from the `Bytes` object. + */ + toBase64(): string; + /** + * Returns the underlying bytes in a new `Uint8Array`. + * + * @returns The Uint8Array created from the `Bytes` object. + */ + toUint8Array(): Uint8Array; + /** + * Returns a string representation of the `Bytes` object. + * + * @returns A string representation of the `Bytes` object. + */ + toString(): string; + /** + * Returns true if this `Bytes` object is equal to the provided one. + * + * @param other - The `Bytes` object to compare against. + * @returns true if this `Bytes` object is equal to the provided one. + */ + isEqual(other: Bytes): boolean; +} +/** + * Constant used to indicate the LRU garbage collection should be disabled. + * Set this value as the `cacheSizeBytes` on the settings passed to the + * `Firestore` instance. + */ +export declare const CACHE_SIZE_UNLIMITED = -1; +/** + * Clears the persistent storage. This includes pending writes and cached + * documents. + * + * Must be called while the `Firestore` instance is not started (after the app is + * terminated or when the app is first initialized). On startup, this function + * must be called before other functions (other than {@link + * initializeFirestore} or {@link getFirestore})). If the `Firestore` + * instance is still running, the promise will be rejected with the error code + * of `failed-precondition`. + * + * Note: `clearIndexedDbPersistence()` is primarily intended to help write + * reliable tests that use Cloud Firestore. It uses an efficient mechanism for + * dropping existing data but does not attempt to securely overwrite or + * otherwise make cached data unrecoverable. For applications that are sensitive + * to the disclosure of cached data in between user sessions, we strongly + * recommend not enabling persistence at all. + * + * @param firestore - The `Firestore` instance to clear persistence for. + * @returns A promise that is resolved when the persistent storage is + * cleared. Otherwise, the promise is rejected with an error. + */ +export declare function clearIndexedDbPersistence( + firestore: FirebaseFirestore +): Promise; +/** + * Gets a `CollectionReference` instance that refers to the collection at + * the specified absolute path. + * + * @param firestore - A reference to the root Firestore instance. + * @param path - A slash-separated path to a collection. + * @param pathSegments - Additional path segments to apply relative to the first + * argument. + * @throws If the final path has an even number of segments and does not point + * to a collection. + * @returns The `CollectionReference` instance. + */ +export declare function collection( + firestore: FirebaseFirestore, + path: string, + ...pathSegments: string[] +): CollectionReference; +/** + * Gets a `CollectionReference` instance that refers to a subcollection of + * `reference` at the the specified relative path. + * + * @param reference - A reference to a collection. + * @param path - A slash-separated path to a collection. + * @param pathSegments - Additional path segments to apply relative to the first + * argument. + * @throws If the final path has an even number of segments and does not point + * to a collection. + * @returns The `CollectionReference` instance. + */ +export declare function collection( + reference: CollectionReference, + path: string, + ...pathSegments: string[] +): CollectionReference; +/** + * Gets a `CollectionReference` instance that refers to a subcollection of + * `reference` at the the specified relative path. + * + * @param reference - A reference to a Firestore document. + * @param path - A slash-separated path to a collection. + * @param pathSegments - Additional path segments that will be applied relative + * to the first argument. + * @throws If the final path has an even number of segments and does not point + * to a collection. + * @returns The `CollectionReference` instance. + */ +export declare function collection( + reference: DocumentReference, + path: string, + ...pathSegments: string[] +): CollectionReference; +/** + * Creates and returns a new `Query` instance that includes all documents in the + * database that are contained in a collection or subcollection with the + * given `collectionId`. + * + * @param firestore - A reference to the root Firestore instance. + * @param collectionId - Identifies the collections to query over. Every + * collection or subcollection with this ID as the last segment of its path + * will be included. Cannot contain a slash. + * @returns The created `Query`. + */ +export declare function collectionGroup( + firestore: FirebaseFirestore, + collectionId: string +): Query; +/** + * A `CollectionReference` object can be used for adding documents, getting + * document references, and querying for documents (using {@link query}). + */ +export declare class CollectionReference extends Query { + readonly firestore: FirebaseFirestore; + readonly type = 'collection'; + private constructor(); + /** The collection's identifier. */ + get id(): string; + /** + * A string representing the path of the referenced collection (relative + * to the root of the database). + */ + get path(): string; + /** + * A reference to the containing `DocumentReference` if this is a + * subcollection. If this isn't a subcollection, the reference is null. + */ + get parent(): DocumentReference | null; + /** + * Applies a custom data converter to this CollectionReference, allowing you + * to use your own custom model objects with Firestore. When you call {@link + * addDoc} with the returned `CollectionReference` instance, the provided + * converter will convert between Firestore data and your custom type `U`. + * + * @param converter - Converts objects to and from Firestore. + * @returns A `CollectionReference` that uses the provided converter. + */ + withConverter( + converter: FirestoreDataConverter + ): CollectionReference; +} +/** + * Deletes the document referred to by the specified `DocumentReference`. + * + * @param reference - A reference to the document to delete. + * @returns A Promise resolved once the document has been successfully + * deleted from the backend (note that it won't resolve while you're offline). + */ +export declare function deleteDoc( + reference: DocumentReference +): Promise; +/** + * Returns a sentinel for use with {@link updateDoc} or + * {@link setDoc} with `{merge: true}` to mark a field for deletion. + */ +export declare function deleteField(): FieldValue; +/** + * Disables network usage for this instance. It can be re-enabled via {@link + * enableNetwork}. While the network is disabled, any snapshot listeners, + * `getDoc()` or `getDocs()` calls will return results from cache, and any write + * operations will be queued until the network is restored. + * + * @returns A promise that is resolved once the network has been disabled. + */ +export declare function disableNetwork( + firestore: FirebaseFirestore +): Promise; +/** + * Gets a `DocumentReference` instance that refers to the document at the + * specified abosulute path. + * + * @param firestore - A reference to the root Firestore instance. + * @param path - A slash-separated path to a document. + * @param pathSegments - Additional path segments that will be applied relative + * to the first argument. + * @throws If the final path has an odd number of segments and does not point to + * a document. + * @returns The `DocumentReference` instance. + */ +export declare function doc( + firestore: FirebaseFirestore, + path: string, + ...pathSegments: string[] +): DocumentReference; +/** + * Gets a `DocumentReference` instance that refers to a document within + * `reference` at the specified relative path. If no path is specified, an + * automatically-generated unique ID will be used for the returned + * `DocumentReference`. + * + * @param reference - A reference to a collection. + * @param path - A slash-separated path to a document. Has to be omitted to use + * auto-genrated IDs. + * @param pathSegments - Additional path segments that will be applied relative + * to the first argument. + * @throws If the final path has an odd number of segments and does not point to + * a document. + * @returns The `DocumentReference` instance. + */ +export declare function doc( + reference: CollectionReference, + path?: string, + ...pathSegments: string[] +): DocumentReference; +/** + * Gets a `DocumentReference` instance that refers to a document within + * `reference` at the specified relative path. + * + * @param reference - A reference to a Firestore document. + * @param path - A slash-separated path to a document. + * @param pathSegments - Additional path segments that will be applied relative + * to the first argument. + * @throws If the final path has an odd number of segments and does not point to + * a document. + * @returns The `DocumentReference` instance. + */ +export declare function doc( + reference: DocumentReference, + path: string, + ...pathSegments: string[] +): DocumentReference; +/** + * A `DocumentChange` represents a change to the documents matching a query. + * It contains the document affected and the type of change that occurred. + */ +export declare interface DocumentChange { + /** The type of change ('added', 'modified', or 'removed'). */ + readonly type: DocumentChangeType; + /** The document affected by this change. */ + readonly doc: QueryDocumentSnapshot; + /** + * The index of the changed document in the result set immediately prior to + * this `DocumentChange` (i.e. supposing that all prior `DocumentChange` objects + * have been applied). Is `-1` for 'added' events. + */ + readonly oldIndex: number; + /** + * The index of the changed document in the result set immediately after + * this `DocumentChange` (i.e. supposing that all prior `DocumentChange` + * objects and the current `DocumentChange` object have been applied). + * Is -1 for 'removed' events. + */ + readonly newIndex: number; +} +/** + * The type of a `DocumentChange` may be 'added', 'removed', or 'modified'. + */ +export declare type DocumentChangeType = 'added' | 'removed' | 'modified'; +/** + * Document data (for use with {@link setDoc}) consists of fields mapped to + * values. + */ +export declare interface DocumentData { + [field: string]: any; +} +/** + * Returns a special sentinel `FieldPath` to refer to the ID of a document. + * It can be used in queries to sort or filter by the document ID. + */ +export declare function documentId(): FieldPath; +/** + * A `DocumentReference` refers to a document location in a Firestore database + * and can be used to write, read, or listen to the location. The document at + * the referenced location may or may not exist. + */ +export declare class DocumentReference { + /** The type of this Firestore reference. */ + readonly type = 'document'; + /** + * The {@link FirebaseFirestore} the document is in. + * This is useful for performing transactions, for example. + */ + readonly firestore: FirebaseFirestore; + private constructor(); + /** + * The document's identifier within its collection. + */ + get id(): string; + /** + * A string representing the path of the referenced document (relative + * to the root of the database). + */ + get path(): string; + /** + * The collection this `DocumentReference` belongs to. + */ + get parent(): CollectionReference; + /** + * Applies a custom data converter to this `DocumentReference`, allowing you + * to use your own custom model objects with Firestore. When you call {@link + * setDoc}, {@link getDoc}, etc. with the returned `DocumentReference` + * instance, the provided converter will convert between Firestore data and + * your custom type `U`. + * + * @param converter - Converts objects to and from Firestore. + * @returns A `DocumentReference` that uses the provided converter. + */ + withConverter(converter: FirestoreDataConverter): DocumentReference; +} +/** + * A `DocumentSnapshot` contains data read from a document in your Firestore + * database. The data can be extracted with `.data()` or `.get()` to + * get a specific field. + * + * For a `DocumentSnapshot` that points to a non-existing document, any data + * access will return 'undefined'. You can use the `exists()` method to + * explicitly verify a document's existence. + */ +export declare class DocumentSnapshot { + /** + * Metadata about the `DocumentSnapshot`, including information about its + * source and local modifications. + */ + readonly metadata: SnapshotMetadata; + protected constructor(); + /** + * Property of the `DocumentSnapshot` that signals whether or not the data + * exists. True if the document exists. + */ + exists(): this is QueryDocumentSnapshot; + /** + * Retrieves all fields in the document as an `Object`. Returns `undefined` if + * the document doesn't exist. + * + * By default, `FieldValue.serverTimestamp()` values that have not yet been + * set to their final value will be returned as `null`. You can override + * this by passing an options object. + * + * @param options - An options object to configure how data is retrieved from + * the snapshot (for example the desired behavior for server timestamps that + * have not yet been set to their final value). + * @returns An `Object` containing all fields in the document or `undefined` if + * the document doesn't exist. + */ + data(options?: SnapshotOptions): T | undefined; + /** + * Retrieves the field specified by `fieldPath`. Returns `undefined` if the + * document or field doesn't exist. + * + * By default, a `FieldValue.serverTimestamp()` that has not yet been set to + * its final value will be returned as `null`. You can override this by + * passing an options object. + * + * @param fieldPath - The path (for example 'foo' or 'foo.bar') to a specific + * field. + * @param options - An options object to configure how the field is retrieved + * from the snapshot (for example the desired behavior for server timestamps + * that have not yet been set to their final value). + * @returns The data at the specified field location or undefined if no such + * field exists in the document. + */ + get(fieldPath: string | FieldPath, options?: SnapshotOptions): any; + /** + * Property of the `DocumentSnapshot` that provides the document's ID. + */ + get id(): string; + /** + * The `DocumentReference` for the document included in the `DocumentSnapshot`. + */ + get ref(): DocumentReference; +} +/** + * Attempts to enable persistent storage, if possible. + * + * Must be called before any other functions (other than + * {@link initializeFirestore}, {@link getFirestore} or + * {@link clearIndexedDbPersistence}. + * + * If this fails, `enableIndexedDbPersistence()` will reject the promise it + * returns. Note that even after this failure, the `Firestore` instance will + * remain usable, however offline persistence will be disabled. + * + * There are several reasons why this can fail, which can be identified by + * the `code` on the error. + * + * * failed-precondition: The app is already open in another browser tab. + * * unimplemented: The browser is incompatible with the offline + * persistence implementation. + * + * @param firestore - The `Firestore` instance to enable persistence for. + * @param persistenceSettings - Optional settings object to configure + * persistence. + * @returns A promise that represents successfully enabling persistent storage. + */ +export declare function enableIndexedDbPersistence( + firestore: FirebaseFirestore, + persistenceSettings?: PersistenceSettings +): Promise; +/** + * Attempts to enable multi-tab persistent storage, if possible. If enabled + * across all tabs, all operations share access to local persistence, including + * shared execution of queries and latency-compensated local document updates + * across all connected instances. + * + * If this fails, `enableMultiTabIndexedDbPersistence()` will reject the promise + * it returns. Note that even after this failure, the `Firestore` instance will + * remain usable, however offline persistence will be disabled. + * + * There are several reasons why this can fail, which can be identified by + * the `code` on the error. + * + * * failed-precondition: The app is already open in another browser tab and + * multi-tab is not enabled. + * * unimplemented: The browser is incompatible with the offline + * persistence implementation. + * + * @param firestore - The `Firestore` instance to enable persistence for. + * @returns A promise that represents successfully enabling persistent + * storage. + */ +export declare function enableMultiTabIndexedDbPersistence( + firestore: FirebaseFirestore +): Promise; +/** + * Re-enables use of the network for this Firestore instance after a prior + * call to {@link disableNetwork}. + * + * @returns A promise that is resolved once the network has been enabled. + */ +export declare function enableNetwork( + firestore: FirebaseFirestore +): Promise; +/** + * Creates a `QueryConstraint` that modifies the result set to end at the + * provided document (inclusive). The end position is relative to the order of + * the query. The document must contain all of the fields provided in the + * orderBy of the query. + * + * @param snapshot - The snapshot of the document to end at. + * @returns A `QueryConstraint` to pass to `query()` + */ +export declare function endAt( + snapshot: DocumentSnapshot +): QueryConstraint; +/** + * Creates a `QueryConstraint` that modifies the result set to end at the + * provided fields relative to the order of the query. The order of the field + * values must match the order of the order by clauses of the query. + * + * @param fieldValues - The field values to end this query at, in order + * of the query's order by. + * @returns A `QueryConstraint` to pass to `query()` + */ +export declare function endAt(...fieldValues: unknown[]): QueryConstraint; +/** + * Creates a `QueryConstraint` that modifies the result set to end before the + * provided document (exclusive). The end position is relative to the order of + * the query. The document must contain all of the fields provided in the + * orderBy of the query. + * + * @param snapshot - The snapshot of the document to end before. + * @returns A `QueryConstraint` to pass to `query()` + */ +export declare function endBefore( + snapshot: DocumentSnapshot +): QueryConstraint; +/** + * Creates a `QueryConstraint` that modifies the result set to end before the + * provided fields relative to the order of the query. The order of the field + * values must match the order of the order by clauses of the query. + * + * @param fieldValues - The field values to end this query before, in order + * of the query's order by. + * @returns A `QueryConstraint` to pass to `query()` + */ +export declare function endBefore(...fieldValues: unknown[]): QueryConstraint; +/** + * A `FieldPath` refers to a field in a document. The path may consist of a + * single field name (referring to a top-level field in the document), or a + * list of field names (referring to a nested field in the document). + * + * Create a `FieldPath` by providing field names. If more than one field + * name is provided, the path will point to a nested field in a document. + */ +export declare class FieldPath { + /** + * Creates a FieldPath from the provided field names. If more than one field + * name is provided, the path will point to a nested field in a document. + * + * @param fieldNames - A list of field names. + */ + constructor(...fieldNames: string[]); + /** + * Returns true if this `FieldPath` is equal to the provided one. + * + * @param other - The `FieldPath` to compare against. + * @returns true if this `FieldPath` is equal to the provided one. + */ + isEqual(other: FieldPath): boolean; +} +/** + * Sentinel values that can be used when writing document fields with `set()` + * or `update()`. + */ +export declare abstract class FieldValue { + /** + * @param _methodName - The public API endpoint that returns this class. + */ + constructor(_methodName: string); + abstract isEqual(other: FieldValue): boolean; +} +/** + * The Cloud Firestore service interface. + * + * Do not call this constructor directly. Instead, use {@link getFirestore}. + */ +export declare class FirebaseFirestore { + private constructor(); + /** + * The {@link FirebaseApp} associated with this `Firestore` service + * instance. + */ + get app(): FirebaseApp; + toJSON(): object; +} +/** + * Converter used by `withConverter()` to transform user objects of type `T` + * into Firestore data. + * + * Using the converter allows you to specify generic type arguments when + * storing and retrieving objects from Firestore. + * + * @example + * ```typescript + * class Post { + * constructor(readonly title: string, readonly author: string) {} + * + * toString(): string { + * return this.title + ', by ' + this.author; + * } + * } + * + * const postConverter = { + * toFirestore(post: Post): firebase.firestore.DocumentData { + * return {title: post.title, author: post.author}; + * }, + * fromFirestore( + * snapshot: firebase.firestore.QueryDocumentSnapshot, + * options: firebase.firestore.SnapshotOptions + * ): Post { + * const data = snapshot.data(options)!; + * return new Post(data.title, data.author); + * } + * }; + * + * const postSnap = await firebase.firestore() + * .collection('posts') + * .withConverter(postConverter) + * .doc().get(); + * const post = postSnap.data(); + * if (post !== undefined) { + * post.title; // string + * post.toString(); // Should be defined + * post.someNonExistentProperty; // TS error + * } + * ``` + */ +export declare interface FirestoreDataConverter { + /** + * Called by the Firestore SDK to convert a custom model object of type `T` + * into a plain JavaScript object (suitable for writing directly to the + * Firestore database). To use `set()` with `merge` and `mergeFields`, + * `toFirestore()` must be defined with `Partial`. + */ + toFirestore(modelObject: T): DocumentData; + /** + * Called by the Firestore SDK to convert a custom model object of type `T` + * into a plain JavaScript object (suitable for writing directly to the + * Firestore database). Used with {@link setData}, {@link WriteBatch#set} + * and {@link Transaction#set} with `merge:true` or `mergeFields`. + */ + toFirestore(modelObject: Partial, options: SetOptions): DocumentData; + /** + * Called by the Firestore SDK to convert Firestore data into an object of + * type T. You can access your data by calling: `snapshot.data(options)`. + * + * @param snapshot - A `QueryDocumentSnapshot` containing your data and metadata. + * @param options - The `SnapshotOptions` from the initial call to `data()`. + */ + fromFirestore( + snapshot: QueryDocumentSnapshot, + options?: SnapshotOptions + ): T; +} +/** An error returned by a Firestore operation. */ +export declare class FirestoreError extends Error { + readonly code: FirestoreErrorCode; + readonly message: string; + readonly name: string; + readonly stack?: string; + private constructor(); +} +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * The set of Firestore status codes. The codes are the same at the ones + * exposed by gRPC here: + * https://github.com/grpc/grpc/blob/master/doc/statuscodes.md + * + * Possible values: + * - 'cancelled': The operation was cancelled (typically by the caller). + * - 'unknown': Unknown error or an error from a different error domain. + * - 'invalid-argument': Client specified an invalid argument. Note that this + * differs from 'failed-precondition'. 'invalid-argument' indicates + * arguments that are problematic regardless of the state of the system + * (e.g. an invalid field name). + * - 'deadline-exceeded': Deadline expired before operation could complete. + * For operations that change the state of the system, this error may be + * returned even if the operation has completed successfully. For example, + * a successful response from a server could have been delayed long enough + * for the deadline to expire. + * - 'not-found': Some requested document was not found. + * - 'already-exists': Some document that we attempted to create already + * exists. + * - 'permission-denied': The caller does not have permission to execute the + * specified operation. + * - 'resource-exhausted': Some resource has been exhausted, perhaps a + * per-user quota, or perhaps the entire file system is out of space. + * - 'failed-precondition': Operation was rejected because the system is not + * in a state required for the operation's execution. + * - 'aborted': The operation was aborted, typically due to a concurrency + * issue like transaction aborts, etc. + * - 'out-of-range': Operation was attempted past the valid range. + * - 'unimplemented': Operation is not implemented or not supported/enabled. + * - 'internal': Internal errors. Means some invariants expected by + * underlying system has been broken. If you see one of these errors, + * something is very broken. + * - 'unavailable': The service is currently unavailable. This is most likely + * a transient condition and may be corrected by retrying with a backoff. + * - 'data-loss': Unrecoverable data loss or corruption. + * - 'unauthenticated': The request does not have valid authentication + * credentials for the operation. + */ +export declare type FirestoreErrorCode = + | 'cancelled' + | 'unknown' + | 'invalid-argument' + | 'deadline-exceeded' + | 'not-found' + | 'already-exists' + | 'permission-denied' + | 'resource-exhausted' + | 'failed-precondition' + | 'aborted' + | 'out-of-range' + | 'unimplemented' + | 'internal' + | 'unavailable' + | 'data-loss' + | 'unauthenticated'; +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * An immutable object representing a geographic location in Firestore. The + * location is represented as latitude/longitude pair. + * + * Latitude values are in the range of [-90, 90]. + * Longitude values are in the range of [-180, 180]. + */ +export declare class GeoPoint { + /** + * Creates a new immutable `GeoPoint` object with the provided latitude and + * longitude values. + * @param latitude - The latitude as number between -90 and 90. + * @param longitude - The longitude as number between -180 and 180. + */ + constructor(latitude: number, longitude: number); + /** + * The latitude of this `GeoPoint` instance. + */ + get latitude(): number; + /** + * The longitude of this `GeoPoint` instance. + */ + get longitude(): number; + /** + * Returns true if this `GeoPoint` is equal to the provided one. + * + * @param other - The `GeoPoint` to compare against. + * @returns true if this `GeoPoint` is equal to the provided one. + */ + isEqual(other: GeoPoint): boolean; + toJSON(): { + latitude: number; + longitude: number; + }; +} +/** + * Reads the document referred to by this `DocumentReference`. + * + * Note: `getDoc()` attempts to provide up-to-date data when possible by waiting + * for data from the server, but it may return cached data or fail if you are + * offline and the server cannot be reached. To specify this behavior, invoke + * {@link getDocFromCache} or {@link getDocFromServer}. + * + * @param reference - The reference of the document to fetch. + * @returns A Promise resolved with a `DocumentSnapshot` containing the + * current document contents. + */ +export declare function getDoc( + reference: DocumentReference +): Promise>; +/** + * Reads the document referred to by this `DocumentReference` from cache. + * Returns an error if the document is not currently cached. + * + * @returns A Promise resolved with a `DocumentSnapshot` containing the + * current document contents. + */ +export declare function getDocFromCache( + reference: DocumentReference +): Promise>; +/** + * Reads the document referred to by this `DocumentReference` from the server. + * Returns an error if the network is not available. + * + * @returns A Promise resolved with a `DocumentSnapshot` containing the + * current document contents. + */ +export declare function getDocFromServer( + reference: DocumentReference +): Promise>; +/** + * Executes the query and returns the results as a `QuerySnapshot`. + * + * Note: `getDocs()` attempts to provide up-to-date data when possible by + * waiting for data from the server, but it may return cached data or fail if + * you are offline and the server cannot be reached. To specify this behavior, + * invoke {@link getDocsFromCache} or {@link getDocsFromServer}. + * + * @returns A Promise that will be resolved with the results of the query. + */ +export declare function getDocs(query: Query): Promise>; +/** + * Executes the query and returns the results as a `QuerySnapshot` from cache. + * Returns an error if the document is not currently cached. + * + * @returns A Promise that will be resolved with the results of the query. + */ +export declare function getDocsFromCache( + query: Query +): Promise>; +/** + * Executes the query and returns the results as a `QuerySnapshot` from the + * server. Returns an error if the network is not available. + * + * @returns A Promise that will be resolved with the results of the query. + */ +export declare function getDocsFromServer( + query: Query +): Promise>; +/** + * Returns the existing instance of Firestore that is associated with the + * provided {@link FirebaseApp}. If no instance exists, initializes a new + * instance with default settings. + * + * @param app - The {@link FirebaseApp} instance that the returned Firestore + * instance is associated with. + * @returns The `Firestore` instance of the provided app. + */ +export declare function getFirestore(app: FirebaseApp): FirebaseFirestore; +/** + * Returns a special value that can be used with {@link setDoc} or {@link + * updateDoc} that tells the server to increment the field's current value by + * the given value. + * + * If either the operand or the current field value uses floating point + * precision, all arithmetic follows IEEE 754 semantics. If both values are + * integers, values outside of JavaScript's safe number range + * (`Number.MIN_SAFE_INTEGER` to `Number.MAX_SAFE_INTEGER`) are also subject to + * precision loss. Furthermore, once processed by the Firestore backend, all + * integer operations are capped between -2^63 and 2^63-1. + * + * If the current field value is not of type `number`, or if the field does not + * yet exist, the transformation sets the field to the given value. + * + * @param n - The value to increment by. + * @returns The `FieldValue` sentinel for use in a call to `setDoc()` or + * `updateDoc()` + */ +export declare function increment(n: number): FieldValue; +/** + * Initializes a new instance of Cloud Firestore with the provided settings. + * Can only be called before any other function, including + * {@link getFirestore}. If the custom settings are empty, this function is + * equivalent to calling {@link getFirestore}. + * + * @param app - The {@link FirebaseApp} with which the `Firestore` instance will + * be associated. + * @param settings - A settings object to configure the `Firestore` instance. + * @returns A newly initialized `Firestore` instance. + */ +export declare function initializeFirestore( + app: FirebaseApp, + settings: Settings +): FirebaseFirestore; +/** + * Creates a `QueryConstraint` that only returns the first matching documents. + * + * @param limit - The maximum number of items to return. + * @returns The created `Query`. + */ +export declare function limit(limit: number): QueryConstraint; +/** + * Creates a `QueryConstraint` that only returns the last matching documents. + * + * You must specify at least one `orderBy` clause for `limitToLast` queries, + * otherwise an exception will be thrown during execution. + * + * @param limit - The maximum number of items to return. + * @returns The created `Query`. + */ +export declare function limitToLast(limit: number): QueryConstraint; +/** + * Loads a Firestore bundle into the local cache. + * + * @param firestore - The `Firestore` instance to load bundles for for. + * @param bundleData - An object representing the bundle to be loaded. Valid objects are + * `ArrayBuffer`, `ReadableStream` or `string`. + * + * @return + * A `LoadBundleTask` object, which notifies callers with progress updates, and completion + * or error events. It can be used as a `Promise`. + */ +export declare function loadBundle( + firestore: FirebaseFirestore, + bundleData: ReadableStream | ArrayBuffer | string +): LoadBundleTask; +/** + * Represents the task of loading a Firestore bundle. It provides progress of bundle + * loading, as well as task completion and error events. + * + * The API is compatible with `Promise`. + */ +export declare class LoadBundleTask + implements PromiseLike { + /** + * Registers functions to listen to bundle loading progress events. + * @param next - Called when there is a progress update from bundle loading. Typically `next` calls occur + * each time a Firestore document is loaded from the bundle. + * @param error - Called when an error occurs during bundle loading. The task aborts after reporting the + * error, and there should be no more updates after this. + * @param complete - Called when the loading task is complete. + */ + onProgress( + next?: (progress: LoadBundleTaskProgress) => unknown, + error?: (err: Error) => unknown, + complete?: () => void + ): void; + /** + * Implements the `Promise.catch` interface. + * + * @param onRejected - Called when an error occurs during bundle loading. + */ + catch( + onRejected: (a: Error) => R | PromiseLike + ): Promise; + /** + * Implements the `Promise.then` interface. + * + * @param onFulfilled - Called on the completion of the loading task with a final `LoadBundleTaskProgress` update. + * The update will always have its `taskState` set to `"Success"`. + * @param onRejected - Called when an error occurs during bundle loading. + */ + then( + onFulfilled?: (a: LoadBundleTaskProgress) => T | PromiseLike, + onRejected?: (a: Error) => R | PromiseLike + ): Promise; +} +/** + * Represents a progress update or a final state from loading bundles. + */ +export declare interface LoadBundleTaskProgress { + /** How many documents have been loaded. */ + documentsLoaded: number; + /** How many documents are in the bundle being loaded. */ + totalDocuments: number; + /** How many bytes have been loaded. */ + bytesLoaded: number; + /** How many bytes are in the bundle being loaded. */ + totalBytes: number; + /** Current task state. */ + taskState: TaskState; +} +export { LogLevel }; +/** + * Reads a Firestore `Query` from local cache, identified by the given name. + * + * The named queries are packaged into bundles on the server side (along + * with resulting documents), and loaded to local cache using `loadBundle`. Once in local + * cache, use this method to extract a `Query` by name. + */ +export declare function namedQuery( + firestore: FirebaseFirestore, + name: string +): Promise; +/** + * Attaches a listener for `DocumentSnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param reference - A reference to the document to listen to. + * @param observer - A single object containing `next` and `error` callbacks. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + reference: DocumentReference, + observer: { + next?: (snapshot: DocumentSnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + } +): Unsubscribe; +/** + * Attaches a listener for `DocumentSnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param reference - A reference to the document to listen to. + * @param options - Options controlling the listen behavior. + * @param observer - A single object containing `next` and `error` callbacks. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + reference: DocumentReference, + options: SnapshotListenOptions, + observer: { + next?: (snapshot: DocumentSnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + } +): Unsubscribe; +/** + * Attaches a listener for `DocumentSnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param reference - A reference to the document to listen to. + * @param onNext - A callback to be called every time a new `DocumentSnapshot` + * is available. + * @param onError - A callback to be called if the listen fails or is + * cancelled. No further callbacks will occur. + * @param onCompletion - Can be provided, but will not be called since streams are + * never ending. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + reference: DocumentReference, + onNext: (snapshot: DocumentSnapshot) => void, + onError?: (error: FirestoreError) => void, + onCompletion?: () => void +): Unsubscribe; +/** + * Attaches a listener for `DocumentSnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param reference - A reference to the document to listen to. + * @param options - Options controlling the listen behavior. + * @param onNext - A callback to be called every time a new `DocumentSnapshot` + * is available. + * @param onError - A callback to be called if the listen fails or is + * cancelled. No further callbacks will occur. + * @param onCompletion - Can be provided, but will not be called since streams are + * never ending. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + reference: DocumentReference, + options: SnapshotListenOptions, + onNext: (snapshot: DocumentSnapshot) => void, + onError?: (error: FirestoreError) => void, + onCompletion?: () => void +): Unsubscribe; +/** + * Attaches a listener for `QuerySnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. The listener can be cancelled by + * calling the function that is returned when `onSnapshot` is called. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param query - The query to listen to. + * @param observer - A single object containing `next` and `error` callbacks. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + query: Query, + observer: { + next?: (snapshot: QuerySnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + } +): Unsubscribe; +/** + * Attaches a listener for `QuerySnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. The listener can be cancelled by + * calling the function that is returned when `onSnapshot` is called. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param query - The query to listen to. + * @param options - Options controlling the listen behavior. + * @param observer - A single object containing `next` and `error` callbacks. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + query: Query, + options: SnapshotListenOptions, + observer: { + next?: (snapshot: QuerySnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + } +): Unsubscribe; +/** + * Attaches a listener for `QuerySnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. The listener can be cancelled by + * calling the function that is returned when `onSnapshot` is called. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param query - The query to listen to. + * @param onNext - A callback to be called every time a new `QuerySnapshot` + * is available. + * @param onCompletion - Can be provided, but will not be called since streams are + * never ending. + * @param onError - A callback to be called if the listen fails or is + * cancelled. No further callbacks will occur. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + query: Query, + onNext: (snapshot: QuerySnapshot) => void, + onError?: (error: FirestoreError) => void, + onCompletion?: () => void +): Unsubscribe; +/** + * Attaches a listener for `QuerySnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. The listener can be cancelled by + * calling the function that is returned when `onSnapshot` is called. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param query - The query to listen to. + * @param options - Options controlling the listen behavior. + * @param onNext - A callback to be called every time a new `QuerySnapshot` + * is available. + * @param onCompletion - Can be provided, but will not be called since streams are + * never ending. + * @param onError - A callback to be called if the listen fails or is + * cancelled. No further callbacks will occur. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + query: Query, + options: SnapshotListenOptions, + onNext: (snapshot: QuerySnapshot) => void, + onError?: (error: FirestoreError) => void, + onCompletion?: () => void +): Unsubscribe; +/** + * Attaches a listener for a snapshots-in-sync event. The snapshots-in-sync + * event indicates that all listeners affected by a given change have fired, + * even if a single server-generated change affects multiple listeners. + * + * NOTE: The snapshots-in-sync event only indicates that listeners are in sync + * with each other, but does not relate to whether those snapshots are in sync + * with the server. Use SnapshotMetadata in the individual listeners to + * determine if a snapshot is from the cache or the server. + * + * @param firestore - The instance of Firestore for synchronizing snapshots. + * @param observer - A single object containing `next` and `error` callbacks. + * @returns An unsubscribe function that can be called to cancel the snapshot + * listener. + */ +export declare function onSnapshotsInSync( + firestore: FirebaseFirestore, + observer: { + next?: (value: void) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + } +): Unsubscribe; +/** + * Attaches a listener for a snapshots-in-sync event. The snapshots-in-sync + * event indicates that all listeners affected by a given change have fired, + * even if a single server-generated change affects multiple listeners. + * + * NOTE: The snapshots-in-sync event only indicates that listeners are in sync + * with each other, but does not relate to whether those snapshots are in sync + * with the server. Use SnapshotMetadata in the individual listeners to + * determine if a snapshot is from the cache or the server. + * + * @param firestore - The instance of Firestore for synchronizing snapshots. + * @param onSync - A callback to be called every time all snapshot listeners are + * in sync with each other. + * @returns An unsubscribe function that can be called to cancel the snapshot + * listener. + */ +export declare function onSnapshotsInSync( + firestore: FirebaseFirestore, + onSync: () => void +): Unsubscribe; +/** + * Creates a `QueryConstraint` that sorts the query result by the + * specified field, optionally in descending order instead of ascending. + * + * @param fieldPath - The field to sort by. + * @param directionStr - Optional direction to sort by ('asc' or 'desc'). If + * not specified, order will be ascending. + * @returns The created `Query`. + */ +export declare function orderBy( + fieldPath: string | FieldPath, + directionStr?: OrderByDirection +): QueryConstraint; +/** + * The direction of a {@link orderBy} clause is specified as 'desc' or 'asc' + * (descending or ascending). + */ +export declare type OrderByDirection = 'desc' | 'asc'; +export declare interface PersistenceSettings { + forceOwnership?: boolean; +} +/** + * A `Query` refers to a Query which you can read or listen to. You can also + * construct refined `Query` objects by adding filters and ordering. + */ +export declare class Query { + /** The type of this Firestore reference. */ + readonly type: 'query' | 'collection'; + /** + * The `FirebaseFirestore` for the Firestore database (useful for performing + * transactions, etc.). + */ + readonly firestore: FirebaseFirestore; + protected constructor(); + /** + * Applies a custom data converter to this query, allowing you to use your own + * custom model objects with Firestore. When you call {@link getDocs} with + * the returned query, the provided converter will convert between Firestore + * data and your custom type `U`. + * + * @param converter - Converts objects to and from Firestore. + * @returns A `Query` that uses the provided converter. + */ + withConverter(converter: FirestoreDataConverter): Query; +} +/** + * Creates a new immutable instance of `query` that is extended to also include + * additional query constraints. + * + * @param query - The query instance to use as a base for the new constraints. + * @param queryConstraints - The list of `QueryConstraint`s to apply. + * @throws if any of the provided query constraints cannot be combined with the + * existing or new constraints. + */ +export declare function query( + query: Query, + ...queryConstraints: QueryConstraint[] +): Query; +/** + * A `QueryConstraint` is used to narrow the set of documents returned by a + * Firestore query. `QueryConstraint`s are created by invoking {@link where}, + * {@link orderBy}, {@link startAt}, {@link startAfter}, {@link + * endBefore}, {@link endAt}, {@link limit} or {@link limitToLast} and + * can then be passed to {@link query} to create a new query instance that + * also contains this `QueryConstraint`. + */ +export declare abstract class QueryConstraint { + /** The type of this query constraints */ + abstract readonly type: QueryConstraintType; +} +/** Describes the different query constraints available in this SDK. */ +export declare type QueryConstraintType = + | 'where' + | 'orderBy' + | 'limit' + | 'limitToLast' + | 'startAt' + | 'startAfter' + | 'endAt' + | 'endBefore'; +/** + * A `QueryDocumentSnapshot` contains data read from a document in your + * Firestore database as part of a query. The document is guaranteed to exist + * and its data can be extracted with `.data()` or `.get()` to get a + * specific field. + * + * A `QueryDocumentSnapshot` offers the same API surface as a + * `DocumentSnapshot`. Since query results contain only existing documents, the + * `exists` property will always be true and `data()` will never return + * 'undefined'. + */ +export declare class QueryDocumentSnapshot< + T = DocumentData +> extends DocumentSnapshot { + /** + * Retrieves all fields in the document as an `Object`. + * + * By default, `FieldValue.serverTimestamp()` values that have not yet been + * set to their final value will be returned as `null`. You can override + * this by passing an options object. + * + * @override + * @param options - An options object to configure how data is retrieved from + * the snapshot (for example the desired behavior for server timestamps that + * have not yet been set to their final value). + * @returns An `Object` containing all fields in the document. + */ + data(options?: SnapshotOptions): T; +} +/** + * Returns true if the provided queries point to the same collection and apply + * the same constraints. + * + * @param left - A `Query` to compare. + * @param right - A `Query` to compare. + * @returns true if the references point to the same location in the same + * Firestore database. + */ +export declare function queryEqual(left: Query, right: Query): boolean; +/** + * A `QuerySnapshot` contains zero or more `DocumentSnapshot` objects + * representing the results of a query. The documents can be accessed as an + * array via the `docs` property or enumerated using the `forEach` method. The + * number of documents can be determined via the `empty` and `size` + * properties. + */ +export declare class QuerySnapshot { + /** + * Metadata about this snapshot, concerning its source and if it has local + * modifications. + */ + readonly metadata: SnapshotMetadata; + /** + * The query on which you called `get` or `onSnapshot` in order to get this + * `QuerySnapshot`. + */ + readonly query: Query; + private constructor(); + /** An array of all the documents in the `QuerySnapshot`. */ + get docs(): Array>; + /** The number of documents in the `QuerySnapshot`. */ + get size(): number; + /** True if there are no documents in the `QuerySnapshot`. */ + get empty(): boolean; + /** + * Enumerates all of the documents in the `QuerySnapshot`. + * + * @param callback - A callback to be called with a `QueryDocumentSnapshot` for + * each document in the snapshot. + * @param thisArg - The `this` binding for the callback. + */ + forEach( + callback: (result: QueryDocumentSnapshot) => void, + thisArg?: unknown + ): void; + /** + * Returns an array of the documents changes since the last snapshot. If this + * is the first snapshot, all documents will be in the list as 'added' + * changes. + * + * @param options - `SnapshotListenOptions` that control whether metadata-only + * changes (i.e. only `DocumentSnapshot.metadata` changed) should trigger + * snapshot events. + */ + docChanges(options?: SnapshotListenOptions): Array>; +} +/** + * Returns true if the provided references are equal. + * + * @param left - A reference to compare. + * @param right - A reference to compare. + * @returns true if the references point to the same location in the same + * Firestore database. + */ +export declare function refEqual( + left: DocumentReference | CollectionReference, + right: DocumentReference | CollectionReference +): boolean; +/** + * Executes the given `updateFunction` and then attempts to commit the changes + * applied within the transaction. If any document read within the transaction + * has changed, Cloud Firestore retries the `updateFunction`. If it fails to + * commit after 5 attempts, the transaction fails. + * + * The maximum number of writes allowed in a single transaction is 500. + * + * @param firestore - A reference to the Firestore database to run this + * transaction against. + * @param updateFunction - The function to execute within the transaction + * context. + * @returns If the transaction completed successfully or was explicitly aborted + * (the `updateFunction` returned a failed promise), the promise returned by the + * `updateFunction `is returned here. Otherwise, if the transaction failed, a + * rejected promise with the corresponding failure error is returned. + */ +export declare function runTransaction( + firestore: FirebaseFirestore, + updateFunction: (transaction: Transaction) => Promise +): Promise; +/** + * Returns a sentinel used with {@link setDoc} or {@link updateDoc} to + * include a server-generated timestamp in the written data. + */ +export declare function serverTimestamp(): FieldValue; +/** + * Writes to the document referred to by this `DocumentReference`. If the + * document does not yet exist, it will be created. + * + * @param reference - A reference to the document to write. + * @param data - A map of the fields and values for the document. + * @returns A Promise resolved once the data has been successfully written + * to the backend (note that it won't resolve while you're offline). + */ +export declare function setDoc( + reference: DocumentReference, + data: T +): Promise; +/** + * Writes to the document referred to by the specified `DocumentReference`. If + * the document does not yet exist, it will be created. If you provide `merge` + * or `mergeFields`, the provided data can be merged into an existing document. + * + * @param reference - A reference to the document to write. + * @param data - A map of the fields and values for the document. + * @param options - An object to configure the set behavior. + * @returns A Promise resolved once the data has been successfully written + * to the backend (note that it won't resolve while you're offline). + */ +export declare function setDoc( + reference: DocumentReference, + data: Partial, + options: SetOptions +): Promise; +/** + * Sets the verbosity of Cloud Firestore logs (debug, error, or silent). + * + * @param logLevel - The verbosity you set for activity and error logging. Can + * be any of the following values: + * + *
    + *
  • `debug` for the most verbose logging level, primarily for + * debugging.
  • + *
  • `error` to log errors only.
  • + *
  • `silent` to turn off logging.
  • + *
+ */ +export declare function setLogLevel(logLevel: LogLevel): void; +/** + * An options object that configures the behavior of {@link setDoc}, {@link + * WriteBatch#set} and {@link Transaction#set} calls. These calls can be + * configured to perform granular merges instead of overwriting the target + * documents in their entirety by providing a `SetOptions` with `merge: true`. + * + * @param merge - Changes the behavior of a `setDoc()` call to only replace the + * values specified in its data argument. Fields omitted from the `setDoc()` + * call remain untouched. + * @param mergeFields - Changes the behavior of `setDoc()` calls to only replace + * the specified field paths. Any field path that is not specified is ignored + * and remains untouched. + */ +export declare type SetOptions = + | { + readonly merge?: boolean; + } + | { + readonly mergeFields?: Array; + }; +export declare interface Settings { + cacheSizeBytes?: number; + host?: string; + ssl?: boolean; + ignoreUndefinedProperties?: boolean; + experimentalForceLongPolling?: boolean; + experimentalAutoDetectLongPolling?: boolean; +} +/** + * Returns true if the provided snapshots are equal. + * + * @param left - A snapshot to compare. + * @param right - A snapshot to compare. + * @returns true if the snapshots are equal. + */ +export declare function snapshotEqual( + left: DocumentSnapshot | QuerySnapshot, + right: DocumentSnapshot | QuerySnapshot +): boolean; +/** + * An options object that can be passed to {@link onSnapshot} and {@link + * QuerySnapshot#docChanges} to control which types of changes to include in the + * result set. + */ +export declare interface SnapshotListenOptions { + /** + * Include a change even if only the metadata of the query or of a document + * changed. Default is false. + */ + readonly includeMetadataChanges?: boolean; +} +/** + * Metadata about a snapshot, describing the state of the snapshot. + */ +export declare class SnapshotMetadata { + /** + * True if the snapshot contains the result of local writes (for example + * `set()` or `update()` calls) that have not yet been committed to the + * backend. If your listener has opted into metadata updates (via + * `SnapshotListenOptions`) you will receive another snapshot with + * `hasPendingWrites` equal to false once the writes have been committed to + * the backend. + */ + readonly hasPendingWrites: boolean; + /** + * True if the snapshot was created from cached data rather than guaranteed + * up-to-date server data. If your listener has opted into metadata updates + * (via `SnapshotListenOptions`) you will receive another snapshot with + * `fromCache` set to false once the client has received up-to-date data from + * the backend. + */ + readonly fromCache: boolean; + private constructor(); + /** + * Returns true if this `SnapshotMetadata` is equal to the provided one. + * + * @param other - The `SnapshotMetadata` to compare against. + * @returns true if this `SnapshotMetadata` is equal to the provided one. + */ + isEqual(other: SnapshotMetadata): boolean; +} +/** + * Options that configure how data is retrieved from a `DocumentSnapshot` (for + * example the desired behavior for server timestamps that have not yet been set + * to their final value). + */ +export declare interface SnapshotOptions { + /** + * If set, controls the return value for server timestamps that have not yet + * been set to their final value. + * + * By specifying 'estimate', pending server timestamps return an estimate + * based on the local clock. This estimate will differ from the final value + * and cause these values to change once the server result becomes available. + * + * By specifying 'previous', pending timestamps will be ignored and return + * their previous value instead. + * + * If omitted or set to 'none', `null` will be returned by default until the + * server value becomes available. + */ + readonly serverTimestamps?: 'estimate' | 'previous' | 'none'; +} +/** + * Creates a `QueryConstraint` that modifies the result set to start after the + * provided document (exclusive). The starting position is relative to the order + * of the query. The document must contain all of the fields provided in the + * orderBy of the query. + * + * @param snapshot - The snapshot of the document to start after. + * @returns A `QueryConstraint` to pass to `query()` + */ +export declare function startAfter( + snapshot: DocumentSnapshot +): QueryConstraint; +/** + * Creates a `QueryConstraint` that modifies the result set to start after the + * provided fields relative to the order of the query. The order of the field + * values must match the order of the order by clauses of the query. + * + * @param fieldValues - The field values to start this query after, in order + * of the query's order by. + * @returns A `QueryConstraint` to pass to `query()` + */ +export declare function startAfter(...fieldValues: unknown[]): QueryConstraint; +/** + * Creates a `QueryConstraint` that modifies the result set to start at the + * provided document (inclusive). The starting position is relative to the order + * of the query. The document must contain all of the fields provided in the + * `orderBy` of this query. + * + * @param snapshot - The snapshot of the document to start at. + * @returns A `QueryConstraint` to pass to `query()`. + */ +export declare function startAt( + snapshot: DocumentSnapshot +): QueryConstraint; +/** + * Creates a `QueryConstraint` that modifies the result set to start at the + * provided fields relative to the order of the query. The order of the field + * values must match the order of the order by clauses of the query. + * + * @param fieldValues - The field values to start this query at, in order + * of the query's order by. + * @returns A `QueryConstraint` to pass to `query()`. + */ +export declare function startAt(...fieldValues: unknown[]): QueryConstraint; +/** + * Represents the state of bundle loading tasks. + * + * Both 'Error' and 'Success' are sinking state: task will abort or complete and there will + * be no more updates after they are reported. + */ +export declare type TaskState = 'Error' | 'Running' | 'Success'; +/** + * Terminates the provided Firestore instance. + * + * After calling `terminate()` only the `clearIndexedDbPersistence()` function + * may be used. Any other function will throw a `FirestoreError`. + * + * To restart after termination, create a new instance of FirebaseFirestore with + * {@link getFirestore}. + * + * Termination does not cancel any pending writes, and any promises that are + * awaiting a response from the server will not be resolved. If you have + * persistence enabled, the next time you start this instance, it will resume + * sending these writes to the server. + * + * Note: Under normal circumstances, calling `terminate()` is not required. This + * function is useful only when you want to force this instance to release all + * of its resources or in combination with `clearIndexedDbPersistence()` to + * ensure that all local state is destroyed between test runs. + * + * @returns A promise that is resolved when the instance has been successfully + * terminated. + */ +export declare function terminate(firestore: FirebaseFirestore): Promise; +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * A `Timestamp` represents a point in time independent of any time zone or + * calendar, represented as seconds and fractions of seconds at nanosecond + * resolution in UTC Epoch time. + * + * It is encoded using the Proleptic Gregorian Calendar which extends the + * Gregorian calendar backwards to year one. It is encoded assuming all minutes + * are 60 seconds long, i.e. leap seconds are "smeared" so that no leap second + * table is needed for interpretation. Range is from 0001-01-01T00:00:00Z to + * 9999-12-31T23:59:59.999999999Z. + * + * For examples and further specifications, refer to the + * {@link https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto | Timestamp definition}. + */ +export declare class Timestamp { + readonly seconds: number; + readonly nanoseconds: number; + /** + * Creates a new timestamp with the current date, with millisecond precision. + * + * @returns a new timestamp representing the current date. + */ + static now(): Timestamp; + /** + * Creates a new timestamp from the given date. + * + * @param date - The date to initialize the `Timestamp` from. + * @returns A new `Timestamp` representing the same point in time as the given + * date. + */ + static fromDate(date: Date): Timestamp; + /** + * Creates a new timestamp from the given number of milliseconds. + * + * @param milliseconds - Number of milliseconds since Unix epoch + * 1970-01-01T00:00:00Z. + * @returns A new `Timestamp` representing the same point in time as the given + * number of milliseconds. + */ + static fromMillis(milliseconds: number): Timestamp; + /** + * Creates a new timestamp. + * + * @param seconds - The number of seconds of UTC time since Unix epoch + * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to + * 9999-12-31T23:59:59Z inclusive. + * @param nanoseconds - The non-negative fractions of a second at nanosecond + * resolution. Negative second values with fractions must still have + * non-negative nanoseconds values that count forward in time. Must be + * from 0 to 999,999,999 inclusive. + */ + constructor(seconds: number, nanoseconds: number); + /** + * Converts a `Timestamp` to a JavaScript `Date` object. This conversion causes + * a loss of precision since `Date` objects only support millisecond precision. + * + * @returns JavaScript `Date` object representing the same point in time as + * this `Timestamp`, with millisecond precision. + */ + toDate(): Date; + /** + * Converts a `Timestamp` to a numeric timestamp (in milliseconds since + * epoch). This operation causes a loss of precision. + * + * @returns The point in time corresponding to this timestamp, represented as + * the number of milliseconds since Unix epoch 1970-01-01T00:00:00Z. + */ + toMillis(): number; + /** + * Returns true if this `Timestamp` is equal to the provided one. + * + * @param other - The `Timestamp` to compare against. + * @returns true if this `Timestamp` is equal to the provided one. + */ + isEqual(other: Timestamp): boolean; + toString(): string; + toJSON(): { + seconds: number; + nanoseconds: number; + }; + /** + * Converts this object to a primitive string, which allows Timestamp objects to be compared + * using the `>`, `<=`, `>=` and `>` operators. + */ + valueOf(): string; +} +/** + * A reference to a transaction. + * + * The `Transaction` object passed to a transaction's `updateFunction` provides + * the methods to read and write data within the transaction context. See + * {@link runTransaction}. + */ +export declare class Transaction { + private constructor(); + /** + * Reads the document referenced by the provided {@link DocumentReference}. + * + * @param documentRef - A reference to the document to be read. + * @returns A `DocumentSnapshot` with the read data. + */ + get(documentRef: DocumentReference): Promise>; + /** + * Writes to the document referred to by the provided {@link + * DocumentReference}. If the document does not exist yet, it will be created. + * + * @param documentRef - A reference to the document to be set. + * @param data - An object of the fields and values for the document. + * @returns This `Transaction` instance. Used for chaining method calls. + */ + set(documentRef: DocumentReference, data: T): this; + /** + * Writes to the document referred to by the provided {@link + * DocumentReference}. If the document does not exist yet, it will be created. + * If you provide `merge` or `mergeFields`, the provided data can be merged + * into an existing document. + * + * @param documentRef - A reference to the document to be set. + * @param data - An object of the fields and values for the document. + * @param options - An object to configure the set behavior. + * @returns This `Transaction` instance. Used for chaining method calls. + */ + set( + documentRef: DocumentReference, + data: Partial, + options: SetOptions + ): this; + /** + * Updates fields in the document referred to by the provided {@link + * DocumentReference}. The update will fail if applied to a document that does + * not exist. + * + * @param documentRef - A reference to the document to be updated. + * @param data - An object containing the fields and values with which to +update the document. Fields can contain dots to reference nested fields +within the document. + * @returns This `Transaction` instance. Used for chaining method calls. + */ + update(documentRef: DocumentReference, data: UpdateData): this; + /** + * Updates fields in the document referred to by the provided {@link + * DocumentReference}. The update will fail if applied to a document that does + * not exist. + * + * Nested fields can be updated by providing dot-separated field path + * strings or by providing `FieldPath` objects. + * + * @param documentRef - A reference to the document to be updated. + * @param field - The first field to update. + * @param value - The first value. + * @param moreFieldsAndValues - Additional key/value pairs. + * @returns This `Transaction` instance. Used for chaining method calls. + */ + update( + documentRef: DocumentReference, + field: string | FieldPath, + value: unknown, + ...moreFieldsAndValues: unknown[] + ): this; + /** + * Deletes the document referred to by the provided {@link DocumentReference}. + * + * @param documentRef - A reference to the document to be deleted. + * @returns This `Transaction` instance. Used for chaining method calls. + */ + delete(documentRef: DocumentReference): this; +} +export declare interface Unsubscribe { + (): void; +} +/** + * Update data (for use with {@link updateDoc}) consists of field paths (e.g. + * 'foo' or 'foo.baz') mapped to values. Fields that contain dots reference + * nested fields within the document. + */ +export declare interface UpdateData { + [fieldPath: string]: any; +} +/** + * Updates fields in the document referred to by the specified + * `DocumentReference`. The update will fail if applied to a document that does + * not exist. + * + * @param reference - A reference to the document to update. + * @param data - An object containing the fields and values with which to + * update the document. Fields can contain dots to reference nested fields + * within the document. + * @returns A Promise resolved once the data has been successfully written + * to the backend (note that it won't resolve while you're offline). + */ +export declare function updateDoc( + reference: DocumentReference, + data: UpdateData +): Promise; +/** + * Updates fields in the document referred to by the specified + * `DocumentReference` The update will fail if applied to a document that does + * not exist. + * + * Nested fields can be updated by providing dot-separated field path + * strings or by providing `FieldPath` objects. + * + * @param reference - A reference to the document to update. + * @param field - The first field to update. + * @param value - The first value. + * @param moreFieldsAndValues - Additional key value pairs. + * @returns A Promise resolved once the data has been successfully written + * to the backend (note that it won't resolve while you're offline). + */ +export declare function updateDoc( + reference: DocumentReference, + field: string | FieldPath, + value: unknown, + ...moreFieldsAndValues: unknown[] +): Promise; +/** + * Modify this instance to communicate with the Cloud Firestore emulator. + * + * Note: This must be called before this instance has been used to do any + * operations. + * + * @param firestore - The Firestore instance to configure to connect to the + * emulator. + * @param host - the emulator host (ex: localhost). + * @param port - the emulator port (ex: 9000). + */ +export declare function useFirestoreEmulator( + firestore: FirebaseFirestore, + host: string, + port: number +): void; +/** + * Waits until all currently pending writes for the active user have been + * acknowledged by the backend. + * + * The returned Promise resolves immediately if there are no outstanding writes. + * Otherwise, the Promise waits for all previously issued writes (including + * those written in a previous app session), but it does not wait for writes + * that were added after the function is called. If you want to wait for + * additional writes, call `waitForPendingWrites()` again. + * + * Any outstanding `waitForPendingWrites()` Promises are rejected during user + * changes. + * + * @returns A Promise which resolves when all currently pending writes have been + * acknowledged by the backend. + */ +export declare function waitForPendingWrites( + firestore: FirebaseFirestore +): Promise; +/** + * Creates a `QueryConstraint` that enforces that documents must contain the + * specified field and that the value should satisfy the relation constraint + * provided. + * + * @param fieldPath - The path to compare + * @param opStr - The operation string (e.g "<", "<=", "==", "<", + * "<=", "!="). + * @param value - The value for comparison + * @returns The created `Query`. + */ +export declare function where( + fieldPath: string | FieldPath, + opStr: WhereFilterOp, + value: unknown +): QueryConstraint; +/** + * Filter conditions in a {@link where} clause are specified using the + * strings '<', '<=', '==', '!=', '>=', '>', 'array-contains', 'in', + * 'array-contains-any', and 'not-in'. + */ +export declare type WhereFilterOp = + | '<' + | '<=' + | '==' + | '!=' + | '>=' + | '>' + | 'array-contains' + | 'in' + | 'array-contains-any' + | 'not-in'; +/** + * A write batch, used to perform multiple writes as a single atomic unit. + * + * A `WriteBatch` object can be acquired by calling {@link writeBatch}. It + * provides methods for adding writes to the write batch. None of the writes + * will be committed (or visible locally) until {@link WriteBatch#commit} is + * called. + */ +export declare class WriteBatch { + private constructor(); + /** + * Writes to the document referred to by the provided {@link + * DocumentReference}. If the document does not exist yet, it will be created. + * + * @param documentRef - A reference to the document to be set. + * @param data - An object of the fields and values for the document. + * @returns This `WriteBatch` instance. Used for chaining method calls. + */ + set(documentRef: DocumentReference, data: T): WriteBatch; + /** + * Writes to the document referred to by the provided {@link + * DocumentReference}. If the document does not exist yet, it will be created. + * If you provide `merge` or `mergeFields`, the provided data can be merged + * into an existing document. + * + * @param documentRef - A reference to the document to be set. + * @param data - An object of the fields and values for the document. + * @param options - An object to configure the set behavior. + * @returns This `WriteBatch` instance. Used for chaining method calls. + */ + set( + documentRef: DocumentReference, + data: Partial, + options: SetOptions + ): WriteBatch; + /** + * Updates fields in the document referred to by the provided {@link + * DocumentReference}. The update will fail if applied to a document that does + * not exist. + * + * @param documentRef - A reference to the document to be updated. + * @param data - An object containing the fields and values with which to + * update the document. Fields can contain dots to reference nested fields + * within the document. + * @returns This `WriteBatch` instance. Used for chaining method calls. + */ + update(documentRef: DocumentReference, data: UpdateData): WriteBatch; + /** + * Updates fields in the document referred to by this {@link + * DocumentReference}. The update will fail if applied to a document that does + * not exist. + * + * Nested fields can be update by providing dot-separated field path strings + * or by providing `FieldPath` objects. + * + * @param documentRef - A reference to the document to be updated. + * @param field - The first field to update. + * @param value - The first value. + * @param moreFieldsAndValues - Additional key value pairs. + * @returns This `WriteBatch` instance. Used for chaining method calls. + */ + update( + documentRef: DocumentReference, + field: string | FieldPath, + value: unknown, + ...moreFieldsAndValues: unknown[] + ): WriteBatch; + /** + * Deletes the document referred to by the provided {@link DocumentReference}. + * + * @param documentRef - A reference to the document to be deleted. + * @returns This `WriteBatch` instance. Used for chaining method calls. + */ + delete(documentRef: DocumentReference): WriteBatch; + /** + * Commits all of the writes in this write batch as a single atomic unit. + * + * The result of these writes will only be reflected in document reads that + * occur after the returned Promise resolves. If the client is offline, the + * write fails. If you would like to see local modifications or buffer writes + * until the client is online, use the full Firestore SDK. + * + * @returns A Promise resolved once all of the writes in the batch have been + * successfully written to the backend as an atomic unit (note that it won't + * resolve while you're offline). + */ + commit(): Promise; +} +/** + * Creates a write batch, used for performing multiple writes as a single + * atomic operation. The maximum number of writes allowed in a single WriteBatch + * is 500. + * + * Unlike transactions, write batches are persisted offline and therefore are + * preferable when you don't need to condition your writes on read data. + * + * @returns A `WriteBatch` that can be used to atomically execute multiple + * writes. + */ +export declare function writeBatch(firestore: FirebaseFirestore): WriteBatch; +export {}; diff --git a/repo-scripts/prune-dts/tests/hide-constructor.input.d.ts b/repo-scripts/prune-dts/tests/hide-constructor.input.d.ts new file mode 100644 index 00000000000..e69af1bd2c0 --- /dev/null +++ b/repo-scripts/prune-dts/tests/hide-constructor.input.d.ts @@ -0,0 +1,22 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class A { + /** @hideconstructor */ + constructor(b: string); +} +export {}; diff --git a/repo-scripts/prune-dts/tests/hide-constructor.output.d.ts b/repo-scripts/prune-dts/tests/hide-constructor.output.d.ts new file mode 100644 index 00000000000..352ba1ffda7 --- /dev/null +++ b/repo-scripts/prune-dts/tests/hide-constructor.output.d.ts @@ -0,0 +1,20 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export class A { + private constructor(); +} +export {}; diff --git a/repo-scripts/prune-dts/tests/inherits-non-exported-members.input.d.ts b/repo-scripts/prune-dts/tests/inherits-non-exported-members.input.d.ts new file mode 100644 index 00000000000..b51730a18aa --- /dev/null +++ b/repo-scripts/prune-dts/tests/inherits-non-exported-members.input.d.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +declare class B { + b: string; +} +export class A extends B { + a: string; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/inherits-non-exported-members.output.d.ts b/repo-scripts/prune-dts/tests/inherits-non-exported-members.output.d.ts new file mode 100644 index 00000000000..59f57065189 --- /dev/null +++ b/repo-scripts/prune-dts/tests/inherits-non-exported-members.output.d.ts @@ -0,0 +1,21 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export class A { + a: string; + b: string; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/keeps-inheritance.input.d.ts b/repo-scripts/prune-dts/tests/keeps-inheritance.input.d.ts new file mode 100644 index 00000000000..5d41bf41c37 --- /dev/null +++ b/repo-scripts/prune-dts/tests/keeps-inheritance.input.d.ts @@ -0,0 +1,27 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +declare class C { + get c(): T; +} +export declare class B extends C { + get b(): T; +} +export declare class A extends B { + get a(): T; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/keeps-inheritance.output.d.ts b/repo-scripts/prune-dts/tests/keeps-inheritance.output.d.ts new file mode 100644 index 00000000000..0b9a9da6d4f --- /dev/null +++ b/repo-scripts/prune-dts/tests/keeps-inheritance.output.d.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export declare class B { + get b(): T; + get c(): T; +} +export declare class A extends B { + get a(): T; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/only-modifies-non-exported-types.input.d.ts b/repo-scripts/prune-dts/tests/only-modifies-non-exported-types.input.d.ts new file mode 100644 index 00000000000..6e854a0d717 --- /dev/null +++ b/repo-scripts/prune-dts/tests/only-modifies-non-exported-types.input.d.ts @@ -0,0 +1,25 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +declare class A { + a: string; +} +export class C extends A { + c: number; +} +export class B extends A {} +export {}; diff --git a/repo-scripts/prune-dts/tests/only-modifies-non-exported-types.output.d.ts b/repo-scripts/prune-dts/tests/only-modifies-non-exported-types.output.d.ts new file mode 100644 index 00000000000..f25cc9216b4 --- /dev/null +++ b/repo-scripts/prune-dts/tests/only-modifies-non-exported-types.output.d.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export class C { + c: number; + a: string; +} +export class B { + a: string; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/private-interface.input.d.ts b/repo-scripts/prune-dts/tests/private-interface.input.d.ts new file mode 100644 index 00000000000..8ecf30747ed --- /dev/null +++ b/repo-scripts/prune-dts/tests/private-interface.input.d.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +declare interface A { + a: string; +} +export class B implements A{ + a: string; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/private-interface.output.d.ts b/repo-scripts/prune-dts/tests/private-interface.output.d.ts new file mode 100644 index 00000000000..9d7aaf0f1e6 --- /dev/null +++ b/repo-scripts/prune-dts/tests/private-interface.output.d.ts @@ -0,0 +1,20 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export class B { + a: string; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/propagates-comments.input.d.ts b/repo-scripts/prune-dts/tests/propagates-comments.input.d.ts new file mode 100644 index 00000000000..6460b68a3e1 --- /dev/null +++ b/repo-scripts/prune-dts/tests/propagates-comments.input.d.ts @@ -0,0 +1,41 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +declare class A { + /** a */ + a: string; + /** + * b1 first line + * + * b1 second line + * + * @param b1 b1 param + * @returns b1 return + */ + b(b1: string): string; + /** + * b2 first line + * + * b2 second line + * + * @param b2 b2 param + * @returns b2 return + */ + b(b2: string): string; +} +export class B extends A {} +export {}; diff --git a/repo-scripts/prune-dts/tests/propagates-comments.output.d.ts b/repo-scripts/prune-dts/tests/propagates-comments.output.d.ts new file mode 100644 index 00000000000..1f7ba0a2cca --- /dev/null +++ b/repo-scripts/prune-dts/tests/propagates-comments.output.d.ts @@ -0,0 +1,41 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export class B { + /** + * a + */ + a: string; + /** + * b1 first line + * + * b1 second line + * + * @param b1 b1 param + * @returns b1 return + */ + b(b1: string): string; + /** + * b2 first line + * + * b2 second line + * + * @param b2 b2 param + * @returns b2 return + */ + b(b2: string): string; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/propagates-members.input.d.ts b/repo-scripts/prune-dts/tests/propagates-members.input.d.ts new file mode 100644 index 00000000000..8870cbe22e2 --- /dev/null +++ b/repo-scripts/prune-dts/tests/propagates-members.input.d.ts @@ -0,0 +1,27 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +declare class A { + a: string; +} +export class B extends A { + b: string; +} +export class C extends B { + c: string; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/propagates-members.output.d.ts b/repo-scripts/prune-dts/tests/propagates-members.output.d.ts new file mode 100644 index 00000000000..07f579f865f --- /dev/null +++ b/repo-scripts/prune-dts/tests/propagates-members.output.d.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export class B { + b: string; + a: string; +} +export class C extends B { + c: string; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/prunes-non-exported-types.input.d.ts b/repo-scripts/prune-dts/tests/prunes-non-exported-types.input.d.ts new file mode 100644 index 00000000000..54cd7c31224 --- /dev/null +++ b/repo-scripts/prune-dts/tests/prunes-non-exported-types.input.d.ts @@ -0,0 +1,22 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export function a(): void; +declare function b(): void; +export class A {} +declare class B {} +export {}; diff --git a/repo-scripts/prune-dts/tests/prunes-non-exported-types.output.d.ts b/repo-scripts/prune-dts/tests/prunes-non-exported-types.output.d.ts new file mode 100644 index 00000000000..67d55413794 --- /dev/null +++ b/repo-scripts/prune-dts/tests/prunes-non-exported-types.output.d.ts @@ -0,0 +1,19 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export function a(): void; +export class A {} +export {}; diff --git a/repo-scripts/prune-dts/tests/prunes-underscores.input.d.ts b/repo-scripts/prune-dts/tests/prunes-underscores.input.d.ts new file mode 100644 index 00000000000..a390ad2f8c5 --- /dev/null +++ b/repo-scripts/prune-dts/tests/prunes-underscores.input.d.ts @@ -0,0 +1,25 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class A { + a: string; + _b: string; + _bMethod(): string; + get _bGetter(): string; + readonly _bProperty: string; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/prunes-underscores.output.d.ts b/repo-scripts/prune-dts/tests/prunes-underscores.output.d.ts new file mode 100644 index 00000000000..c8e8d7963f0 --- /dev/null +++ b/repo-scripts/prune-dts/tests/prunes-underscores.output.d.ts @@ -0,0 +1,20 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export class A { + a: string; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/references-public-interfaces.input.d.ts b/repo-scripts/prune-dts/tests/references-public-interfaces.input.d.ts new file mode 100644 index 00000000000..1cf7bac1055 --- /dev/null +++ b/repo-scripts/prune-dts/tests/references-public-interfaces.input.d.ts @@ -0,0 +1,23 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface A { + a: T; +} +declare interface B extends A {} +export function c(a: B): void; +export {}; diff --git a/repo-scripts/prune-dts/tests/references-public-interfaces.output.d.ts b/repo-scripts/prune-dts/tests/references-public-interfaces.output.d.ts new file mode 100644 index 00000000000..901c0113fae --- /dev/null +++ b/repo-scripts/prune-dts/tests/references-public-interfaces.output.d.ts @@ -0,0 +1,21 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export interface A { + a: T; +} +export function c(a: A): void; +export {}; diff --git a/repo-scripts/prune-dts/tests/references-public-types.input.d.ts b/repo-scripts/prune-dts/tests/references-public-types.input.d.ts new file mode 100644 index 00000000000..a37b3567796 --- /dev/null +++ b/repo-scripts/prune-dts/tests/references-public-types.input.d.ts @@ -0,0 +1,28 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +declare class B {} +export class A extends B {} +export function a(b: B): B; +export class Ab { + constructor(b: B); + b: B; + readonly bProperty: B; + get bGetter(): B; + bMethod(b: B): B; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/references-public-types.output.d.ts b/repo-scripts/prune-dts/tests/references-public-types.output.d.ts new file mode 100644 index 00000000000..bdd0de28bfe --- /dev/null +++ b/repo-scripts/prune-dts/tests/references-public-types.output.d.ts @@ -0,0 +1,26 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export class A {} +export function a(b: A): A; +export class Ab { + constructor(b: A); + b: A; + readonly bProperty: A; + get bGetter(): A; + bMethod(b: A): A; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/resolves-generics-different-name.input.d.ts b/repo-scripts/prune-dts/tests/resolves-generics-different-name.input.d.ts new file mode 100644 index 00000000000..4389960f78b --- /dev/null +++ b/repo-scripts/prune-dts/tests/resolves-generics-different-name.input.d.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +declare class A { + a: T; +} +export class B extends A { + b: K; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/resolves-generics-different-name.output.d.ts b/repo-scripts/prune-dts/tests/resolves-generics-different-name.output.d.ts new file mode 100644 index 00000000000..2926cd75565 --- /dev/null +++ b/repo-scripts/prune-dts/tests/resolves-generics-different-name.output.d.ts @@ -0,0 +1,21 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export class B { + b: K; + a: K; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/resolves-generics-through-inheritence.input.d.ts b/repo-scripts/prune-dts/tests/resolves-generics-through-inheritence.input.d.ts new file mode 100644 index 00000000000..7208f2cf528 --- /dev/null +++ b/repo-scripts/prune-dts/tests/resolves-generics-through-inheritence.input.d.ts @@ -0,0 +1,25 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +declare class A { + a: T; +} +export class B extends A { + b: T; +} +export class C extends A {} +export {}; diff --git a/repo-scripts/prune-dts/tests/resolves-generics-through-inheritence.output.d.ts b/repo-scripts/prune-dts/tests/resolves-generics-through-inheritence.output.d.ts new file mode 100644 index 00000000000..3dca92f7d56 --- /dev/null +++ b/repo-scripts/prune-dts/tests/resolves-generics-through-inheritence.output.d.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export class B { + b: T; + a: string; +} +export class C { + a: T; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/resolves-generics.input.d.ts b/repo-scripts/prune-dts/tests/resolves-generics.input.d.ts new file mode 100644 index 00000000000..8c840eafffc --- /dev/null +++ b/repo-scripts/prune-dts/tests/resolves-generics.input.d.ts @@ -0,0 +1,22 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +declare class B { + b: T; +} +export class A extends B {} +export {}; diff --git a/repo-scripts/prune-dts/tests/resolves-generics.output.d.ts b/repo-scripts/prune-dts/tests/resolves-generics.output.d.ts new file mode 100644 index 00000000000..d97cf28da79 --- /dev/null +++ b/repo-scripts/prune-dts/tests/resolves-generics.output.d.ts @@ -0,0 +1,20 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export class A { + b: string; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/swaps-generics.input.d.ts b/repo-scripts/prune-dts/tests/swaps-generics.input.d.ts new file mode 100644 index 00000000000..43f9d6a004c --- /dev/null +++ b/repo-scripts/prune-dts/tests/swaps-generics.input.d.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +declare class B { + b: T; +} +export class A extends B { + a: T; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/swaps-generics.output.d.ts b/repo-scripts/prune-dts/tests/swaps-generics.output.d.ts new file mode 100644 index 00000000000..32e47ed96b9 --- /dev/null +++ b/repo-scripts/prune-dts/tests/swaps-generics.output.d.ts @@ -0,0 +1,21 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export class A { + a: T; + b: string; +} +export {}; diff --git a/repo-scripts/prune-dts/tsconfig.eslint.json b/repo-scripts/prune-dts/tsconfig.eslint.json new file mode 100644 index 00000000000..a980c6ec63b --- /dev/null +++ b/repo-scripts/prune-dts/tsconfig.eslint.json @@ -0,0 +1,4 @@ +{ + "extends": "../../config/tsconfig.base.json", + "include": ["../../packages/*/dist/**/index.d.ts"] +} diff --git a/repo-scripts/prune-dts/tsconfig.json b/repo-scripts/prune-dts/tsconfig.json new file mode 100644 index 00000000000..01d19282ef7 --- /dev/null +++ b/repo-scripts/prune-dts/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "outDir": "dist", + "importHelpers": true, + "module": "commonjs", + "moduleResolution": "node", + "resolveJsonModule": true, + "target": "es5", + "esModuleInterop": true, + "declaration": true, + "strict": true + }, + "exclude": [ + "dist/**/*" + ] +} \ No newline at end of file diff --git a/repo-scripts/size-analysis/analysis-helper.ts b/repo-scripts/size-analysis/analysis-helper.ts index cd2d041c4dc..7e766c2a33f 100644 --- a/repo-scripts/size-analysis/analysis-helper.ts +++ b/repo-scripts/size-analysis/analysis-helper.ts @@ -96,7 +96,8 @@ export async function extractDependenciesAndSize( }); const externalDepsNotResolvedBundle = await rollup.rollup({ input, - external: id => id.startsWith('@firebase') // exclude all firebase dependencies + // exclude all firebase dependencies and tslib + external: id => id.startsWith('@firebase') || id === 'tslib' }); await externalDepsNotResolvedBundle.write({ file: externalDepsNotResolvedOutput, @@ -224,7 +225,14 @@ export function extractDeclarations( } else if (ts.isVariableDeclaration(node)) { declarations.variables.push(node.name!.getText()); } else if (ts.isEnumDeclaration(node)) { - declarations.enums.push(node.name.escapedText.toString()); + // `const enum`s should not be analyzed. They do not add to bundle size and + // creating a file that imports them causes an error during the rollup step. + if ( + // Identifies if this enum had a "const" modifier attached. + !node.modifiers?.some(mod => mod.kind === ts.SyntaxKind.ConstKeyword) + ) { + declarations.enums.push(node.name.escapedText.toString()); + } } else if (ts.isVariableStatement(node)) { const variableDeclarations = node.declarationList.declarations; @@ -238,9 +246,8 @@ export function extractDeclarations( } // Binding Pattern Example: export const {a, b} = {a: 1, b: 1}; else { - const elements = variableDeclaration.name.elements as ts.NodeArray< - ts.BindingElement - >; + const elements = variableDeclaration.name + .elements as ts.NodeArray; elements.forEach((node: ts.BindingElement) => { declarations.variables.push(node.name.getText(sourceFile)); }); @@ -860,19 +867,35 @@ export async function buildJsonReport( name: moduleName, symbols: [] }; - for (const exp of publicApi.classes) { - result.symbols.push(await extractDependenciesAndSize(exp, jsFile, map)); + try { + result.symbols.push(await extractDependenciesAndSize(exp, jsFile, map)); + } catch (e) { + console.log(e); + } } + for (const exp of publicApi.functions) { - result.symbols.push(await extractDependenciesAndSize(exp, jsFile, map)); + try { + result.symbols.push(await extractDependenciesAndSize(exp, jsFile, map)); + } catch (e) { + console.log(e); + } } for (const exp of publicApi.variables) { - result.symbols.push(await extractDependenciesAndSize(exp, jsFile, map)); + try { + result.symbols.push(await extractDependenciesAndSize(exp, jsFile, map)); + } catch (e) { + console.log(e); + } } for (const exp of publicApi.enums) { - result.symbols.push(await extractDependenciesAndSize(exp, jsFile, map)); + try { + result.symbols.push(await extractDependenciesAndSize(exp, jsFile, map)); + } catch (e) { + console.log(e); + } } return result; } diff --git a/repo-scripts/size-analysis/analysis.ts b/repo-scripts/size-analysis/analysis.ts deleted file mode 100644 index 8ef8d4b0376..00000000000 --- a/repo-scripts/size-analysis/analysis.ts +++ /dev/null @@ -1,138 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { resolve, basename } from 'path'; -import { - generateReport, - generateReportForModules, - writeReportToFile, - Report, - ErrorCode, - writeReportToDirectory -} from './analysis-helper'; -import { mapWorkspaceToPackages } from '../../scripts/release/utils/workspace'; -import { projectRoot } from '../../scripts/utils'; -import * as yargs from 'yargs'; -import * as fs from 'fs'; - -/** - * Support Command Line Options - * -- inputModule (optional) : can be left unspecified which results in running analysis on all exp modules. - * can specify one to many module names seperated by space. - * eg: --inputModule "@firebase/functions-exp" "firebase/auth-exp" - * - * -- inputDtsFile (optional) : adhoc support. Specify a path to dts file. Must enable -- inputBundleFile if this flag is specified. - * - * -- inputBundleFile (optional): adhoc support. Specify a path to bundle file. Must enable -- inputDtsFile if this flag is specified. - * - * --output (required): output directory or file where reports will be generated. - * specify a directory if module(s) are analyzed - * specify a file path if ad hoc analysis is to be performed - * - */ -const argv = yargs - .options({ - inputModule: { - type: 'array', - alias: 'im', - desc: - 'The name of the module(s) to be analyzed. example: --inputModule "@firebase/functions-exp" "firebase/auth-exp"' - }, - inputDtsFile: { - type: 'string', - alias: 'if', - desc: 'support for adhoc analysis. requires a path to dts file' - }, - inputBundleFile: { - type: 'string', - alias: 'ib', - desc: 'support for adhoc analysis. requires a path to a bundle file' - }, - output: { - type: 'string', - alias: 'o', - required: true, - desc: - 'The location where report(s) will be generated, a directory path if module(s) are analyzed; a file path if ad hoc analysis is to be performed' - } - }) - .help().argv; - -/** - * Entry Point of the Tool. - * The function first checks if it's an adhoc run (by checking whether --inputDtsFile and --inputBundle are both enabled) - * The function then checks whether --inputModule flag is specified; Run analysis on all modules if not, run analysis on selected modules if enabled. - * Throw INVALID_FLAG_COMBINATION error if neither case fulfill. - */ -async function main(): Promise { - // check if it's an adhoc run - // adhoc run report can only be redirected to files - if (argv.inputDtsFile && argv.inputBundleFile && argv.output) { - const jsonReport: Report = await generateReport( - 'adhoc', - argv.inputDtsFile, - argv.inputBundleFile - ); - writeReportToFile(jsonReport, resolve(argv.output)); - } else if (!argv.inputDtsFile && !argv.inputBundleFile) { - // retrieve All Module Names - let allModulesLocation = await mapWorkspaceToPackages([ - `${projectRoot}/packages-exp/*` - ]); - allModulesLocation = allModulesLocation.filter(path => { - const json = JSON.parse( - fs.readFileSync(`${path}/package.json`, { encoding: 'utf-8' }) - ); - return ( - json.name.startsWith('@firebase') && - !json.name.includes('-compat') && - !json.name.includes('-types') - ); - }); - if (argv.inputModule) { - allModulesLocation = allModulesLocation.filter(path => { - const json = JSON.parse( - fs.readFileSync(`${path}/package.json`, { encoding: 'utf-8' }) - ); - return argv.inputModule.includes(json.name); - }); - } - let writeFiles: boolean = false; - if (argv.output) { - writeFiles = true; - } - - const reports: Report[] = await generateReportForModules( - allModulesLocation - ); - if (writeFiles) { - for (const report of reports) { - writeReportToDirectory( - report, - `${basename(report.name)}-dependencies.json`, - resolve(argv.output) - ); - } - } - } else { - throw new Error(ErrorCode.INVALID_FLAG_COMBINATION); - } -} - -main().catch(error => { - console.log(error); -}); diff --git a/repo-scripts/size-analysis/bundle-analysis.ts b/repo-scripts/size-analysis/bundle-analysis.ts new file mode 100644 index 00000000000..2e51db1c8eb --- /dev/null +++ b/repo-scripts/size-analysis/bundle-analysis.ts @@ -0,0 +1,500 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import * as tmp from 'tmp'; +import { existsSync, lstatSync, readFileSync, writeFileSync } from 'fs'; +import { spawn } from 'child-process-promise'; +import { ordinal } from '@firebase/util'; +import { bundleWithRollup } from './bundle/rollup'; +import { bundleWithWebpack } from './bundle/webpack'; +import { calculateContentSize } from './util'; +import { minify } from './bundle/minify'; +import { extractDeclarations, MemberList } from './analysis-helper'; + +interface BundleAnalysisArgs { + input: string; + bundler: 'webpack' | 'rollup' | 'both'; + mode: 'npm' | 'local'; + output: string; + debug: boolean; +} + +interface BundleAnalysisOptions { + bundleDefinitions: BundleDefinition[]; + bundler: Bundler; + mode: Mode; + output: string; + debug: boolean; +} + +interface DebugOptions { + output: string; // output folder for debug files +} + +interface BundleDefinition { + name: string; + description?: string; + dependencies: BundleDependency[]; +} + +interface BundleDependency { + packageName: string; + /** + * npm version or tag + */ + versionOrTag: string; + imports: string | SubModuleImport[]; +} + +interface SubModuleImport { + path: string; + imports: string[]; +} + +enum Bundler { + Rollup = 'rollup', + Webpack = 'webpack', + Both = 'both' +} + +enum Mode { + Npm = 'npm', + Local = 'local' +} + +enum SpecialImport { + Default = 'default import', + Sizeeffect = 'side effect import', + Namespace = 'namespace import' +} + +export async function run({ + input, + bundler, + mode, + output, + debug +}: BundleAnalysisArgs): Promise { + const options = { + bundleDefinitions: loadBundleDefinitions(input), + bundler: toBundlerEnum(bundler), + mode: toModeEnum(mode), + output, + debug + }; + + return analyze(options); +} + +function loadBundleDefinitions(path: string): BundleDefinition[] { + if (!existsSync(path)) { + throw new Error( + `${path} doesn't exist. Please provide a valid path to the bundle defintion file.` + ); + } + + if (lstatSync(path).isDirectory()) { + throw new Error( + `Expecting a file, but ${path} is a directory. Please provide a valid path to the bundle definition file.` + ); + } + + const def = parseBundleDefinition(readFileSync(path, { encoding: 'utf-8' })); + + return def; +} + +function toBundlerEnum(bundler: 'webpack' | 'rollup' | 'both'): Bundler { + switch (bundler) { + case 'rollup': + return Bundler.Rollup; + case 'webpack': + return Bundler.Webpack; + case 'both': + return Bundler.Both; + default: + throw new Error('impossible!'); + } +} + +function toModeEnum(mode: 'npm' | 'local'): Mode { + switch (mode) { + case 'npm': + return Mode.Npm; + case 'local': + return Mode.Local; + default: + throw new Error('impossible'); + } +} + +/** + * + * @param input + * @returns - an array of error messages. Empty if the bundle definition is valid + */ +function parseBundleDefinition(input: string): BundleDefinition[] { + const bundleDefinitions: BundleDefinition[] = JSON.parse(input); + + const errorMessages = []; + if (!Array.isArray(bundleDefinitions)) { + throw new Error('Bundle definition must be defined in an array'); + } + + for (let i = 0; i < bundleDefinitions.length; i++) { + const bundleDefinition = bundleDefinitions[i]; + if (!bundleDefinition.name) { + errorMessages.push( + `Missing field 'name' in the ${ordinal(i + 1)} bundle definition` + ); + } + + if (!bundleDefinition.dependencies) { + errorMessages.push( + `Missing field 'dependencies' in the ${ordinal( + i + 1 + )} bundle definition` + ); + } + + if (!Array.isArray(bundleDefinition.dependencies)) { + errorMessages.push( + `Expecting an array for field 'dependencies', but it is not an array in the ${ordinal( + i + 1 + )} bundle definition` + ); + } + + for (let j = 0; j < bundleDefinition.dependencies.length; j++) { + const dependency = bundleDefinition.dependencies[j]; + + if (!dependency.packageName) { + errorMessages.push( + `Missing field 'packageName' in the ${ordinal( + j + 1 + )} dependency of the ${ordinal(i + 1)} bundle definition` + ); + } + + if (!dependency.imports) { + errorMessages.push( + `Missing field 'imports' in the ${ordinal( + j + 1 + )} dependency of the ${ordinal(i + 1)} bundle definition` + ); + } + + if (!Array.isArray(dependency.imports)) { + errorMessages.push( + `Expecting an array for field 'imports', but it is not an array in the ${ordinal( + j + 1 + )} dependency of the ${ordinal(i + 1)} bundle definition` + ); + } + + if (!dependency.versionOrTag) { + dependency.versionOrTag = 'latest'; + } + } + } + + if (errorMessages.length > 0) { + throw new Error(errorMessages.join('\n')); + } + + return bundleDefinitions; +} + +async function analyze({ + bundleDefinitions, + bundler, + output, + mode, + debug +}: BundleAnalysisOptions): Promise { + const analyses: BundleAnalysis[] = []; + + let debugOptions: DebugOptions | undefined; + if (debug) { + const tmpDir = tmp.dirSync(); + debugOptions = { + output: tmpDir.name + }; + } + + for (const bundleDefinition of bundleDefinitions) { + analyses.push( + await analyzeBundle(bundleDefinition, bundler, mode, debugOptions) + ); + } + + writeFileSync(output, JSON.stringify(analyses, null, 2), { + encoding: 'utf-8' + }); +} + +async function analyzeBundle( + bundleDefinition: BundleDefinition, + bundler: Bundler, + mode: Mode, + debugOptions?: DebugOptions +): Promise { + const analysis: BundleAnalysis = { + name: bundleDefinition.name, + description: bundleDefinition.description ?? '', + results: [], + dependencies: bundleDefinition.dependencies + }; + + let moduleDirectory: string | undefined; + let tmpDir: tmp.DirResult | undefined; + if (mode === Mode.Npm) { + tmpDir = await setupTempProject(bundleDefinition); + moduleDirectory = `${tmpDir.name}/node_modules`; + } + + const entryFileContent = createEntryFileContent(bundleDefinition); + + switch (bundler) { + case Bundler.Rollup: + case Bundler.Webpack: + analysis.results.push( + await analyzeBundleWithBundler( + bundleDefinition.name, + entryFileContent, + bundler, + moduleDirectory, + debugOptions + ) + ); + break; + case Bundler.Both: + analysis.results.push( + await analyzeBundleWithBundler( + bundleDefinition.name, + entryFileContent, + Bundler.Rollup, + moduleDirectory, + debugOptions + ) + ); + analysis.results.push( + await analyzeBundleWithBundler( + bundleDefinition.name, + entryFileContent, + Bundler.Webpack, + moduleDirectory, + debugOptions + ) + ); + break; + default: + throw new Error('impossible!'); + } + + if (tmpDir) { + tmpDir.removeCallback(); + } + + return analysis; +} + +/** + * Create a temp project and install dependencies the bundleDefinition defines + * @returns - the path to the temp project + */ +async function setupTempProject( + bundleDefinition: BundleDefinition +): Promise { + /// set up a temporary project to install dependencies + const tmpDir = tmp.dirSync({ unsafeCleanup: true }); + console.log(tmpDir.name); + // create package.json + const pkgJson: { + name: string; + version: string; + dependencies: Record; + } = { + name: 'size-analysis', + version: '0.0.0', + dependencies: {} + }; + + for (const dep of bundleDefinition.dependencies) { + pkgJson.dependencies[dep.packageName] = dep.versionOrTag; + } + + writeFileSync( + `${tmpDir.name}/package.json`, + `${JSON.stringify(pkgJson, null, 2)}\n`, + { encoding: 'utf-8' } + ); + + // install dependencies + await spawn('npm', ['install'], { + cwd: tmpDir.name, + stdio: 'inherit' + }); + + return tmpDir; +} + +async function analyzeBundleWithBundler( + bundleName: string, + entryFileContent: string, + bundler: Exclude, + moduleDirectory?: string, + debugOptions?: DebugOptions +): Promise { + let bundledContent = ''; + + // bundle using bundlers + if (bundler === Bundler.Rollup) { + bundledContent = await bundleWithRollup(entryFileContent, moduleDirectory); + } else { + bundledContent = await bundleWithWebpack(entryFileContent, moduleDirectory); + } + + const minifiedBundle = await minify(bundledContent); + const { size, gzipSize } = calculateContentSize(minifiedBundle); + + const analysisResult: BundleAnalysisResult = { + bundler, + size, + gzipSize + }; + + if (debugOptions) { + const bundleFilePath = `${debugOptions.output}/${bundleName.replace( + / +/g, + '-' + )}.${bundler}.js`; + const minifiedBundleFilePath = `${debugOptions.output}/${bundleName.replace( + / +/g, + '-' + )}.${bundler}.minified.js`; + writeFileSync(bundleFilePath, bundledContent, { encoding: 'utf8' }); + writeFileSync(minifiedBundleFilePath, minifiedBundle, { encoding: 'utf8' }); + + analysisResult.debugInfo = { + pathToBundle: bundleFilePath, + pathToMinifiedBundle: minifiedBundleFilePath, + dependencies: extractDeclarations(bundleFilePath) + }; + } + + return analysisResult; +} + +function createEntryFileContent(bundleDefinition: BundleDefinition): string { + const contentArray = []; + // cache used symbols. Used to avoid symbol collision when multiple modules export symbols with the same name. + const symbolsCache = new Set(); + for (const dep of bundleDefinition.dependencies) { + for (const imp of dep.imports) { + if (typeof imp === 'string') { + contentArray.push( + ...createImportExport(imp, dep.packageName, symbolsCache) + ); + } else { + // submodule imports + for (const subImp of imp.imports) { + contentArray.push( + ...createImportExport( + subImp, + `${dep.packageName}/${imp.path}`, + symbolsCache + ) + ); + } + } + } + } + + return contentArray.join('\n'); +} + +function createImportExport( + symbol: string, + modulePath: string, + symbolsCache: Set +): string[] { + const contentArray = []; + + switch (symbol) { + case SpecialImport.Default: { + const nameToUse = createSymbolName('default_import', symbolsCache); + contentArray.push(`import ${nameToUse} from '${modulePath}';`); + contentArray.push(`console.log(${nameToUse})`); // prevent import from being tree shaken + break; + } + case SpecialImport.Namespace: { + const nameToUse = createSymbolName('namespace', symbolsCache); + contentArray.push(`import * as ${nameToUse} from '${modulePath}';`); + contentArray.push(`console.log(${nameToUse})`); // prevent import from being tree shaken + break; + } + case SpecialImport.Sizeeffect: + contentArray.push(`import '${modulePath}';`); + break; + default: + // named imports + const nameToUse = createSymbolName(symbol, symbolsCache); + + if (nameToUse !== symbol) { + contentArray.push( + `export {${symbol} as ${nameToUse}} from '${modulePath}';` + ); + } else { + contentArray.push(`export {${symbol}} from '${modulePath}';`); + } + } + + return contentArray; +} + +/** + * In case a symbol with the same name is already imported from another module, we need to give this symbol another name + * using "originalname as anothername" syntax, otherwise it returns the original symbol name. + */ +function createSymbolName(symbol: string, symbolsCache: Set): string { + let nameToUse = symbol; + const max = 100; + while (symbolsCache.has(nameToUse)) { + nameToUse = `${symbol}_${Math.floor(Math.random() * max)}`; + } + + symbolsCache.add(nameToUse); + return nameToUse; +} + +interface BundleAnalysis { + name: string; // the bundle name defined in the bundle definition + description: string; + dependencies: BundleDependency[]; + results: BundleAnalysisResult[]; +} + +interface BundleAnalysisResult { + bundler: 'rollup' | 'webpack'; + size: number; + gzipSize: number; + debugInfo?: { + pathToBundle?: string; + pathToMinifiedBundle?: string; + dependencies?: MemberList; + }; +} diff --git a/repo-scripts/size-analysis/bundle-definition-examples/bundle-definition-1.json b/repo-scripts/size-analysis/bundle-definition-examples/bundle-definition-1.json new file mode 100644 index 00000000000..5b0d12c7e20 --- /dev/null +++ b/repo-scripts/size-analysis/bundle-definition-examples/bundle-definition-1.json @@ -0,0 +1,44 @@ +[ + { + "name": "test", + "desecription": "", + "dependencies": [ + { + "packageName": "@firebase/app", + "versionOrTag": "exp", + "imports": [ + "initializeApp" + ] + }, + { + "packageName": "@firebase/firestore", + "versionOrTag": "exp", + "imports": [ + "getFirestore", + "doc", + "getDoc" + ] + } + ] + }, + { + "name": "test2", + "description": "default imports and side effect imports", + "dependencies": [ + { + "packageName": "@firebase/app", + "versionOrTag": "latest", + "imports": [ + "default import" + ] + }, + { + "packageName": "@firebase/firestore", + "versionOrTag": "latest", + "imports": [ + "side effect import" + ] + } + ] + } +] \ No newline at end of file diff --git a/repo-scripts/size-analysis/bundle/minify.ts b/repo-scripts/size-analysis/bundle/minify.ts new file mode 100644 index 00000000000..60597001a5e --- /dev/null +++ b/repo-scripts/size-analysis/bundle/minify.ts @@ -0,0 +1,30 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as terser from 'terser'; + +export async function minify(content: string): Promise { + const minified = await terser.minify(content, { + format: { + comments: false + }, + mangle: { toplevel: true }, + compress: false + }); + + return minified.code ?? ''; +} diff --git a/repo-scripts/size-analysis/bundle/rollup.ts b/repo-scripts/size-analysis/bundle/rollup.ts new file mode 100644 index 00000000000..272cd934d08 --- /dev/null +++ b/repo-scripts/size-analysis/bundle/rollup.ts @@ -0,0 +1,57 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as rollup from 'rollup'; +import resolve, { RollupNodeResolveOptions } from '@rollup/plugin-node-resolve'; +import commonjs from '@rollup/plugin-commonjs'; +// @ts-ignore +import virtual from '@rollup/plugin-virtual'; + +/** + * + * @param fileContent + * @param moduleDirectory - the path to the node_modules folder of the temporary project in npm mode. + * undefined in local mode + */ +export async function bundleWithRollup( + fileContent: string, + moduleDirectory?: string +): Promise { + const resolveOptions: RollupNodeResolveOptions = { + mainFields: ['esm2017', 'module', 'main'] + }; + + if (moduleDirectory) { + resolveOptions.moduleDirectories = [moduleDirectory]; + } + + const bundle = await rollup.rollup({ + input: 'entry', + plugins: [ + virtual({ + entry: fileContent + }), + resolve(resolveOptions), + commonjs() + ] + }); + + const { output } = await bundle.generate({ + format: 'es' + }); + return output[0].code; +} diff --git a/repo-scripts/size-analysis/bundle/webpack.ts b/repo-scripts/size-analysis/bundle/webpack.ts new file mode 100644 index 00000000000..93f8523c37b --- /dev/null +++ b/repo-scripts/size-analysis/bundle/webpack.ts @@ -0,0 +1,85 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import webpack from 'webpack'; +// @ts-ignore +import virtualModulesPlugin from 'webpack-virtual-modules'; +import { createFsFromVolume, IFs, Volume } from 'memfs'; +import path from 'path'; +import { projectRoot } from '../util'; + +/** + * + * @param fileContent + * @param moduleDirectory - the path to the node_modules folder of the temporary project in npm mode. + * undefined in local mode + */ +export async function bundleWithWebpack( + fileContent: string, + moduleDirectory?: string +): Promise { + const entryFileName = '/virtual_path_to_in_memory_file/index.js'; + const outputFileName = 'o.js'; + + const resolveConfig: webpack.ResolveOptions = { + mainFields: ['esm2017', 'module', 'main'] + }; + + if (moduleDirectory) { + resolveConfig.modules = [moduleDirectory]; + } else { + // local mode + resolveConfig.modules = [`${projectRoot}/node_modules`]; + } + + const compiler = webpack({ + entry: entryFileName, + output: { + filename: outputFileName + }, + resolve: resolveConfig, + plugins: [ + new virtualModulesPlugin({ + [entryFileName]: fileContent + }) + ], + mode: 'production' + }); + + // use virtual file system for output to avoid I/O + compiler.outputFileSystem = getMemoryFileSystem(); + + return new Promise((res, rej) => { + compiler.run((err, stats) => { + if (err) { + rej(err); + return; + } + + // Hack to get string output without reading the output file using an internal API from webpack + // eslint-disable-next-line @typescript-eslint/no-explicit-any + res((stats!.compilation.assets[outputFileName] as any)['_value']); + }); + }); +} + +function getMemoryFileSystem(): IFs { + const fs = createFsFromVolume(new Volume()); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (fs as any).join = path.join.bind(path); + return fs; +} diff --git a/repo-scripts/size-analysis/cli.ts b/repo-scripts/size-analysis/cli.ts new file mode 100644 index 00000000000..87987315f45 --- /dev/null +++ b/repo-scripts/size-analysis/cli.ts @@ -0,0 +1,93 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as yargs from 'yargs'; +import { run as runBundleAnalysis } from './bundle-analysis'; +import { analyzePackageSize } from './package-analysis'; + +// eslint-disable-next-line no-unused-expressions +yargs + .command( + '$0', + 'Analyze the size of individual exports from packages', + { + inputModule: { + type: 'array', + alias: 'im', + desc: + 'The name of the module(s) to be analyzed. example: --inputModule "@firebase/functions-exp" "firebase/auth-exp"' + }, + inputDtsFile: { + type: 'string', + alias: 'if', + desc: 'support for adhoc analysis. requires a path to a d.ts file' + }, + inputBundleFile: { + type: 'string', + alias: 'ib', + desc: 'support for adhoc analysis. requires a path to a bundle file' + }, + output: { + type: 'string', + alias: 'o', + required: true, + desc: + 'The location where report(s) will be generated, a directory path if module(s) are analyzed; a file path if ad hoc analysis is to be performed' + } + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + args => analyzePackageSize(args as any).catch(e => console.log(e)) + ) + .command( + 'bundle', + 'Analyze bundle size', + { + input: { + type: 'string', + alias: 'i', + required: true, + desc: 'Path to the JSON file that describes the bundles to be analyzed' + }, + mode: { + choices: ['npm', 'local'], + alias: 'm', + default: 'npm', + desc: 'Use Firebase packages from npm or the local repo' + }, + bundler: { + choices: ['rollup', 'webpack', 'both'], + alias: 'b', + default: 'rollup', + desc: 'The bundler(s) to be used' + }, + output: { + type: 'string', + alias: 'o', + default: './size-analysis-bundles.json', + desc: 'The output location' + }, + debug: { + type: 'boolean', + alias: 'd', + default: false, + desc: 'debug mode' + } + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + argv => runBundleAnalysis(argv as any) + ) + .help().argv; diff --git a/repo-scripts/size-analysis/package-analysis.ts b/repo-scripts/size-analysis/package-analysis.ts new file mode 100644 index 00000000000..b59c70d93c5 --- /dev/null +++ b/repo-scripts/size-analysis/package-analysis.ts @@ -0,0 +1,128 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { resolve, basename, dirname } from 'path'; +import { + generateReport, + generateReportForModules, + writeReportToFile, + Report, + ErrorCode, + writeReportToDirectory +} from './analysis-helper'; +import glob from 'glob'; +import * as fs from 'fs'; + +const projectRoot = dirname(resolve(__dirname, '../../package.json')); +/** + * Support Command Line Options + * -- inputModule (optional) : can be left unspecified which results in running analysis on all exp modules. + * can specify one to many module names seperated by space. + * eg: --inputModule "@firebase/functions-exp" "firebase/auth-exp" + * + * -- inputDtsFile (optional) : adhoc support. Specify a path to dts file. Must enable -- inputBundleFile if this flag is specified. + * + * -- inputBundleFile (optional): adhoc support. Specify a path to bundle file. Must enable -- inputDtsFile if this flag is specified. + * + * --output (required): output directory or file where reports will be generated. + * specify a directory if module(s) are analyzed + * specify a file path if ad hoc analysis is to be performed + * + */ +interface PackageAnalysisOptions { + inputModule: string[]; + inputDtsFile: string; + inputBundleFile: string; + output: string; +} +/** + * Entry Point of the Tool. + * The function first checks if it's an adhoc run (by checking whether --inputDtsFile and --inputBundle are both enabled) + * The function then checks whether --inputModule flag is specified; Run analysis on all modules if not, run analysis on selected modules if enabled. + * Throw INVALID_FLAG_COMBINATION error if neither case fulfill. + */ +export async function analyzePackageSize( + argv: PackageAnalysisOptions +): Promise { + // check if it's an adhoc run + // adhoc run report can only be redirected to files + if (argv.inputDtsFile && argv.inputBundleFile && argv.output) { + const jsonReport: Report = await generateReport( + 'adhoc', + argv.inputDtsFile, + argv.inputBundleFile + ); + writeReportToFile(jsonReport, resolve(argv.output)); + } else if (!argv.inputDtsFile && !argv.inputBundleFile) { + // retrieve All Module Names + // TODO: update the workspace once exp packages are officially released + let allModulesLocation = await mapWorkspaceToPackages([ + `${projectRoot}/packages-exp/*` + ]); + allModulesLocation = allModulesLocation.filter(path => { + const json = JSON.parse( + fs.readFileSync(`${path}/package.json`, { encoding: 'utf-8' }) + ); + return ( + json.name.startsWith('@firebase') && + !json.name.includes('-compat') && + !json.name.includes('-types') + ); + }); + if (argv.inputModule) { + allModulesLocation = allModulesLocation.filter(path => { + const json = JSON.parse( + fs.readFileSync(`${path}/package.json`, { encoding: 'utf-8' }) + ); + return argv.inputModule.includes(json.name); + }); + } + let writeFiles: boolean = false; + if (argv.output) { + writeFiles = true; + } + + const reports: Report[] = await generateReportForModules( + allModulesLocation + ); + if (writeFiles) { + for (const report of reports) { + writeReportToDirectory( + report, + `${basename(report.name)}-dependencies.json`, + resolve(argv.output) + ); + } + } + } else { + throw new Error(ErrorCode.INVALID_FLAG_COMBINATION); + } +} + +function mapWorkspaceToPackages(workspaces: string[]): Promise { + return Promise.all( + workspaces.map( + workspace => + new Promise(resolve => { + glob(workspace, (err, paths) => { + if (err) throw err; + resolve(paths); + }); + }) + ) + ).then(paths => paths.reduce((arr, val) => arr.concat(val), [])); +} diff --git a/repo-scripts/size-analysis/package.json b/repo-scripts/size-analysis/package.json index e52f0028497..d3faba50325 100644 --- a/repo-scripts/size-analysis/package.json +++ b/repo-scripts/size-analysis/package.json @@ -6,32 +6,42 @@ "author": "Firebase (https://firebase.google.com/)", "main": "dist/index.cjs.js", "esm2017": "dist/index.esm2017.js", - "files": ["dist"], + "files": [ + "dist" + ], "scripts": { "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", "test": "run-p lint test:node", "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:node", "pretest:node": "tsc -p test/test-inputs && rollup -c", - "test:node": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha **/*.test.ts --config ../../config/mocharc.node.js --timeout 60000" + "test:node": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha **/*.test.ts --config ../../config/mocharc.node.js --timeout 60000", + "build": "rollup -c" }, "dependencies": { - "rollup": "2.29.0", - "@rollup/plugin-commonjs": "15.1.0", + "rollup": "2.52.2", + "@rollup/plugin-commonjs": "17.1.0", "@rollup/plugin-json": "4.1.0", - "@rollup/plugin-node-resolve": "9.0.0", + "@rollup/plugin-node-resolve": "11.2.0", "rollup-plugin-replace": "2.2.0", - "rollup-plugin-typescript2": "0.27.3", + "rollup-plugin-typescript2": "0.30.0", + "@rollup/plugin-virtual": "2.0.3", + "webpack": "4.46.0", + "@types/webpack": "5.28.0", + "webpack-virtual-modules": "0.4.1", + "child-process-promise": "2.2.1", + "memfs": "3.2.0", "tmp": "0.2.1", - "typescript": "4.0.2", - "terser": "5.3.5", - "yargs": "16.0.3", - "@firebase/util": "0.3.2" + "typescript": "4.2.2", + "terser": "5.7.0", + "yargs": "16.2.0", + "@firebase/util": "1.1.0", + "gzip-size": "6.0.0" }, "license": "Apache-2.0", "devDependencies": { "@firebase/logger": "0.2.6", - "@firebase/app": "0.6.11" + "@firebase/app": "0.6.28" }, "repository": { "directory": "repo-scripts/size-analysis", diff --git a/repo-scripts/size-analysis/rollup.config.js b/repo-scripts/size-analysis/rollup.config.js index 3d39d474843..f4c4bb79ab6 100644 --- a/repo-scripts/size-analysis/rollup.config.js +++ b/repo-scripts/size-analysis/rollup.config.js @@ -24,6 +24,8 @@ const deps = Object.keys( Object.assign({}, pkg.peerDependencies, pkg.dependencies) ); +const nodeInternals = ['fs', 'path']; + export default [ { input: 'test/test-inputs/subsetExports.ts', @@ -49,5 +51,33 @@ export default [ }) ], external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + }, + { + input: 'cli.ts', + output: [ + { + file: 'dist/cli.js', + format: 'cjs', + sourcemap: false + } + ], + plugins: [ + typescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017', + module: 'es2015' + } + } + }), + json({ + preferConst: true + }) + ], + external: id => + [...deps, ...nodeInternals].some( + dep => id === dep || id.startsWith(`${dep}/`) + ) } ]; diff --git a/repo-scripts/size-analysis/test/size-analysis.test.ts b/repo-scripts/size-analysis/test/size-analysis.test.ts index 55326752cbf..574296010fb 100644 --- a/repo-scripts/size-analysis/test/size-analysis.test.ts +++ b/repo-scripts/size-analysis/test/size-analysis.test.ts @@ -454,7 +454,7 @@ describe('test writeReportToDirectory helper function', () => { fs.mkdirSync(aDir, { recursive: true }); const aFile = `a-file`; const aPathToFile = `${aDir}/${aFile}`; - fs.writeFileSync(aPathToFile, fileContent); + fs.writeFileSync(aPathToFile, JSON.stringify(fileContent)); expect(() => writeReportToDirectory(fileContent, aFile, aPathToFile) ).to.throw(ErrorCode.OUTPUT_DIRECTORY_REQUIRED); diff --git a/repo-scripts/size-analysis/test/test-inputs/far.ts b/repo-scripts/size-analysis/test/test-inputs/far.ts index 2c766f8c4fd..7688eef785c 100644 --- a/repo-scripts/size-analysis/test/test-inputs/far.ts +++ b/repo-scripts/size-analysis/test/test-inputs/far.ts @@ -71,7 +71,7 @@ export function basicUniqueFuncFar( return pickedCard; } // Otherwise just let them pick the card - else if (typeof x === 'number') { + else { return { suit: 'a', card: x % 13 }; } } diff --git a/repo-scripts/size-analysis/test/test-inputs/index.ts b/repo-scripts/size-analysis/test/test-inputs/index.ts index 6b357bd0325..3a478abf165 100644 --- a/repo-scripts/size-analysis/test/test-inputs/index.ts +++ b/repo-scripts/size-analysis/test/test-inputs/index.ts @@ -90,7 +90,7 @@ export function basicUniqueFunc( return pickedCard; } // Otherwise just let them pick the card - else if (typeof x === 'number') { + else { return { suit: 'a', card: x % 13 }; } } diff --git a/repo-scripts/size-analysis/tsconfig.json b/repo-scripts/size-analysis/tsconfig.json index b92b160eef6..01d19282ef7 100644 --- a/repo-scripts/size-analysis/tsconfig.json +++ b/repo-scripts/size-analysis/tsconfig.json @@ -7,7 +7,8 @@ "resolveJsonModule": true, "target": "es5", "esModuleInterop": true, - "declaration": true + "declaration": true, + "strict": true }, "exclude": [ "dist/**/*" diff --git a/repo-scripts/size-analysis/util.ts b/repo-scripts/size-analysis/util.ts new file mode 100644 index 00000000000..bd56e98a6e7 --- /dev/null +++ b/repo-scripts/size-analysis/util.ts @@ -0,0 +1,34 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import calculateGzipSize from 'gzip-size'; +import { dirname, resolve } from 'path'; + +interface ContentSize { + size: number; + gzipSize: number; +} + +export function calculateContentSize(content: string): ContentSize { + const size = Buffer.byteLength(content, 'utf-8'); + const gzipSize = calculateGzipSize.sync(content); + return { + size, + gzipSize + }; +} + +export const projectRoot = dirname(resolve(__dirname, '../../package.json')); diff --git a/scripts/add_changeset.ts b/scripts/add_changeset.ts new file mode 100644 index 00000000000..8167c9d6baf --- /dev/null +++ b/scripts/add_changeset.ts @@ -0,0 +1,68 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Add a patch changeset for `@firebase/app` to force its release, so that + * the SDK_VERSION is up to date with the version of the umbrella package firebase. + * + * For background, see https://github.com/firebase/firebase-js-sdk/issues/4235 + */ + +import { writeFileSync } from 'fs'; +import { projectRoot } from './utils'; +import { exec } from 'child-process-promise'; + +const CONTENT = ` +--- +'@firebase/app': patch +--- + +Update SDK_VERSION. +`; + +const FILE_PATH = `${projectRoot}/.changeset/bump-sdk-version.md`; + +async function addChangeSet() { + // check if a few firebase version is being released + try { + const { stdout } = await exec('yarn changeset status'); + // only add a changeset for @firebase/app if + // 1. we are publishing a new firebase version. and + // 2. @firebase/app is not already being published + const firebaseRelease = stdout.includes('- firebase\n'); + const firebaseAppRelease = stdout.includes('- @firebase/app\n'); + if (firebaseRelease && !firebaseAppRelease) { + console.log('Creating a patch changeset for @firebase/app'); + writeFileSync(FILE_PATH, CONTENT, { + encoding: 'utf-8' + }); + } else if (firebaseAppRelease) { + console.log( + 'Skip creating a patch changeset for @firebase/app because it is already part of the release' + ); + } else { + console.log( + 'Skip creating a patch changeset for @firebase/app because firebase is not being released' + ); + } + } catch (e) { + // log the error, the exit without creating a changeset + console.log('error:', e); + } +} + +addChangeSet(); diff --git a/scripts/check_changeset.ts b/scripts/check_changeset.ts index d906a4215f9..b4e5554509c 100644 --- a/scripts/check_changeset.ts +++ b/scripts/check_changeset.ts @@ -16,6 +16,8 @@ */ import { resolve } from 'path'; +import { existsSync } from 'fs'; +import { exec } from 'child-process-promise'; import chalk from 'chalk'; import simpleGit from 'simple-git/promise'; import fs from 'mz/fs'; @@ -23,6 +25,42 @@ import fs from 'mz/fs'; const root = resolve(__dirname, '..'); const git = simpleGit(root); +const baseRef = process.env.GITHUB_PULL_REQUEST_BASE_SHA || 'master'; +const headRef = process.env.GITHUB_PULL_REQUEST_HEAD_SHA || 'HEAD'; + +// Version bump text converted to rankable numbers. +const bumpRank: Record = { + 'patch': 0, + 'minor': 1, + 'major': 2 +}; + +/** + * Get highest bump that isn't the main firebase package, return +// numerical rank, bump text, package name. + */ +function getHighestBump(changesetPackages: Record) { + const firebasePkgJson = require(resolve( + root, + 'packages/firebase/package.json' + )); + let highestBump = bumpRank.patch; + let highestBumpText = 'patch'; + let bumpPackage = ''; + for (const pkgName of Object.keys(changesetPackages)) { + if ( + pkgName !== 'firebase' && + pkgName in firebasePkgJson.dependencies && + bumpRank[changesetPackages[pkgName]] > highestBump + ) { + highestBump = bumpRank[changesetPackages[pkgName]]; + highestBumpText = changesetPackages[pkgName]; + bumpPackage = pkgName; + } + } + return { highestBump, bumpText: highestBumpText, bumpPackage }; +} + /** * Identify modified packages. */ @@ -30,7 +68,7 @@ async function getDiffData(): Promise<{ changedPackages: Set; changesetFile: string; } | null> { - const diff = await git.diff(['--name-only', 'origin/master...HEAD']); + const diff = await git.diff(['--name-only', `${baseRef}...${headRef}`]); const changedFiles = diff.split('\n'); let changesetFile = ''; const changedPackages = new Set(); @@ -43,11 +81,15 @@ async function getDiffData(): Promise<{ // Check for changed files inside package dirs. const pkgMatch = filename.match('^(packages(-exp)?/[a-zA-Z0-9-]+)/.*'); if (pkgMatch && pkgMatch[1]) { - const changedPackage = require(resolve( - root, - pkgMatch[1], - 'package.json' - )); + // skip packages without package.json + // It could happen when we rename a package or remove a package from the repo + const pkgJsonPath = resolve(root, pkgMatch[1], 'package.json'); + + if (!existsSync(pkgJsonPath)) { + continue; + } + + const changedPackage = require(pkgJsonPath); if (changedPackage) { // Add the package itself. changedPackages.add(changedPackage.name); @@ -68,44 +110,105 @@ async function parseChangesetFile(changesetFile: string) { const fileText: string = await fs.readFile(changesetFile, 'utf8'); const fileParts = fileText.split('---\n'); const packageLines = fileParts[1].split('\n'); - const changesetPackages = packageLines + const changesetPackages: Record = {}; + packageLines .filter(line => line) - .map(line => { - const [packageName] = line.split(':'); - return packageName.replace(/['"]/g, ''); + .forEach(line => { + const [packageName, bumpType] = line.split(':'); + changesetPackages[packageName.replace(/['"]/g, '')] = bumpType.trim(); }); return changesetPackages; } async function main() { + const errors = []; try { - const diffData = await getDiffData(); - if (diffData == null) { - process.exit(); + await exec(`yarn changeset status --since ${baseRef}`); + console.log(`::set-output name=BLOCKING_FAILURE::false`); + } catch (e) { + if (e.message.match('No changesets present')) { + console.log(`::set-output name=BLOCKING_FAILURE::false`); } else { + const messageLines = e.message.replace(/🦋 error /g, '').split('\n'); + let formattedStatusError = + '- Changeset formatting error in following file:%0A'; + formattedStatusError += ' ```%0A'; + formattedStatusError += messageLines + .filter( + (line: string) => !line.match(/^ at [\w\.]+ \(.+:[0-9]+:[0-9]+\)/) + ) + .filter((line: string) => !line.includes('Command failed')) + .filter((line: string) => !line.includes('exited with error code 1')) + .map((line: string) => ` ${line}`) + .join('%0A'); + formattedStatusError += '%0A ```%0A'; + errors.push(formattedStatusError); + /** + * Sets Github Actions output for a step. Pass changeset error message to next + * step. See: + * https://github.com/actions/toolkit/blob/master/docs/commands.md#set-outputs + */ + console.log(`::set-output name=BLOCKING_FAILURE::true`); + } + } + + try { + const diffData = await getDiffData(); + if (diffData != null) { const { changedPackages, changesetFile } = diffData; const changesetPackages = await parseChangesetFile(changesetFile); + + // Check for packages where files were modified but there's no changeset. const missingPackages = [...changedPackages].filter( - changedPkg => !changesetPackages.includes(changedPkg) + changedPkg => !Object.keys(changesetPackages).includes(changedPkg) ); if (missingPackages.length > 0) { - /** - * Sets Github Actions output for a step. Pass missing package list to next - * step. See: - * https://github.com/actions/toolkit/blob/master/docs/commands.md#set-outputs - */ - console.log( - `::set-output name=MISSING_PACKAGES::${missingPackages - .map(pkg => `- ${pkg}`) - .join('%0A')}` - ); + const missingPackagesLines = [ + '- Warning: This PR modifies files in the following packages but they have not been included in the changeset file:' + ]; + for (const missingPackage of missingPackages) { + missingPackagesLines.push(` - ${missingPackage}`); + } + missingPackagesLines.push(''); + missingPackagesLines.push(' Make sure this was intentional.'); + errors.push(missingPackagesLines.join('%0A')); + } + + // Check for packages with a minor or major bump where 'firebase' hasn't been + // bumped high enough or at all. + const { highestBump, bumpText, bumpPackage } = + getHighestBump(changesetPackages); + if (highestBump > bumpRank.patch) { + if (changesetPackages['firebase'] == null) { + errors.push( + `- Package ${bumpPackage} has a ${bumpText} bump which requires an ` + + `additional line to bump the main "firebase" package to ${bumpText}.` + ); + console.log(`::set-output name=BLOCKING_FAILURE::true`); + } else if (bumpRank[changesetPackages['firebase']] < highestBump) { + errors.push( + `- Package ${bumpPackage} has a ${bumpText} bump. ` + + `Increase the bump for the main "firebase" package to ${bumpText}.` + ); + console.log(`::set-output name=BLOCKING_FAILURE::true`); + } } - process.exit(); } } catch (e) { console.error(chalk`{red ${e}}`); process.exit(1); } + + /** + * Sets Github Actions output for a step. Pass changeset error message to next + * step. See: + * https://github.com/actions/toolkit/blob/master/docs/commands.md#set-outputs + */ + if (errors.length > 0) + console.log( + `::set-output name=CHANGESET_ERROR_MESSAGE::${errors.join('%0A')}` + ); + process.exit(); } main(); diff --git a/scripts/ci-test/build_changed.ts b/scripts/ci-test/build_changed.ts index a0f8802da53..f2ce62b98b0 100644 --- a/scripts/ci-test/build_changed.ts +++ b/scripts/ci-test/build_changed.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { TestReason, getTestTasks, filterTasks } from './tasks'; +import { TestReason, getTestTasks, filterTasks, logTasks } from './tasks'; import { spawn } from 'child-process-promise'; import chalk from 'chalk'; import { resolve } from 'path'; @@ -27,14 +27,20 @@ const argv = yargs.options({ // TODO: remove once modular SDKs become official buildAppExp: { type: 'boolean', - desc: - 'whether or not build @firebase/app-exp first. It is a hack required to build Firestore' + desc: 'whether or not build @firebase/app-exp first. It is a hack required to build Firestore' + }, + buildAppCompat: { + type: 'boolean', + desc: 'whether or not build @firebase/app-compat first. It is a hack required to build Firestore' + }, + buildAll: { + type: 'boolean', + desc: 'if true, build all packages. Used in Test Auth workflow because Auth tests depends on the firebase package' } }).argv; const allTestConfigNames = Object.keys(testConfig); -const inputTestConfigName = argv._[0]; -const buildAppExp = argv.buildAppExp; +const inputTestConfigName = argv._[0].toString(); if (!inputTestConfigName) { throw Error(` @@ -54,17 +60,33 @@ if (!allTestConfigNames.includes(inputTestConfigName)) { const config = testConfig[inputTestConfigName]!; -buildForTests(config, buildAppExp); +buildForTests(config, argv); -async function buildForTests(config: TestConfig, buildAppExp = false) { +interface Options { + buildAppExp?: boolean; + buildAppCompat?: boolean; + buildAll?: boolean; +} + +async function buildForTests( + config: TestConfig, + { buildAppExp = false, buildAppCompat = false, buildAll = false }: Options +) { try { const testTasks = filterTasks(await getTestTasks(), config); - + // print tasks for info + logTasks(testTasks); if (testTasks.length === 0) { console.log(chalk`{green No test tasks. Skipping all builds }`); return; } + // build all and return + if (buildAll) { + await spawn('yarn', ['build'], { stdio: 'inherit', cwd: root }); + return; + } + // hack to build Firestore which depends on @firebase/app-exp (because of firestore exp), // but doesn't list it as a dependency in its package.json // TODO: remove once modular SDKs become official @@ -82,6 +104,40 @@ async function buildForTests(config: TestConfig, buildAppExp = false) { { stdio: 'inherit', cwd: root } ); } + // hack to build Firestore which depends on @firebase/app-compat (because of firestore exp), + // but doesn't list it as a dependency in its package.json + // TODO: remove once modular SDKs become official + if (buildAppCompat) { + await spawn( + 'npx', + [ + 'lerna', + 'run', + '--scope', + '@firebase/app-compat', + '--include-dependencies', + 'build' + ], + { stdio: 'inherit', cwd: root } + ); + } + // hack to build Storage which depends on @firebase/auth-exp (because of integration test), + // but doesn't list it as a dependency in its package.json + // TODO: remove once modular SDKs become official + if (testTasks.some(task => task.pkgName.includes('storage'))) { + await spawn( + 'npx', + [ + 'lerna', + 'run', + '--scope', + '@firebase/auth-exp', + '--include-dependencies', + 'build' + ], + { stdio: 'inherit', cwd: root } + ); + } const lernaCmd = ['lerna', 'run']; console.log(chalk`{blue Running build in:}`); diff --git a/scripts/ci-test/tasks.ts b/scripts/ci-test/tasks.ts index 6669db4309d..87bd6a7c280 100644 --- a/scripts/ci-test/tasks.ts +++ b/scripts/ci-test/tasks.ts @@ -16,6 +16,7 @@ */ import { resolve } from 'path'; +import { existsSync } from 'fs'; import { exec } from 'child-process-promise'; import chalk from 'chalk'; import simpleGit from 'simple-git/promise'; @@ -123,7 +124,15 @@ export async function getTestTasks(): Promise { // Check for changed files inside package dirs. const match = filename.match('^(packages(-exp)?/[a-zA-Z0-9-]+)/.*'); if (match && match[1]) { - const changedPkg = require(resolve(root, match[1], 'package.json')); + const pkgJsonPath = resolve(root, match[1], 'package.json'); + + // skip projects that don't have package.json + // It could happen when we rename a package or remove a package from the repo + if (!existsSync(pkgJsonPath)) { + continue; + } + + const changedPkg = require(pkgJsonPath); if (changedPkg) { const changedPkgName = changedPkg.name; const task = testTasks.find(t => t.pkgName === changedPkgName); @@ -190,3 +199,20 @@ export function filterTasks( return filteredTasks; } + +export function logTasks(testTasks: TestTask[]): void { + for (const task of testTasks) { + switch (task.reason) { + case TestReason.Changed: + console.log(chalk`{yellow ${task.pkgName} (contains modified files)}`); + break; + case TestReason.Dependent: + console.log( + chalk`{yellow ${task.pkgName} (depends on modified files)}` + ); + break; + default: + console.log(chalk`{yellow ${task.pkgName} (running all tests)}`); + } + } +} diff --git a/scripts/ci-test/testConfig.ts b/scripts/ci-test/testConfig.ts index e8636e39084..d2da843785c 100644 --- a/scripts/ci-test/testConfig.ts +++ b/scripts/ci-test/testConfig.ts @@ -37,9 +37,8 @@ export const testConfig: { 'firebase-firestore-integration-test', 'firebase-messaging-integration-test', 'firebase-namespace-integration-test', - '@firebase/testing', + 'firebase-compat-typings-test', '@firebase/rules-unit-testing', - 'rxfire', '@firebase/auth', 'firebase', 'firebase-exp' @@ -55,11 +54,7 @@ export const testConfig: { 'onlyIncludePackages': ['firebase-messaging-integration-test'] }, 'misc': { - 'onlyIncludePackages': [ - '@firebase/testing', - '@firebase/rules-unit-testing', - 'rxfire' - ] + 'onlyIncludePackages': ['@firebase/rules-unit-testing'] }, 'firebase-integration': { 'alwaysIncludePackages': ['firebase-namespace-integration-test'] diff --git a/scripts/ci-test/test_changed.ts b/scripts/ci-test/test_changed.ts index 371893047a8..d93ba52bef8 100644 --- a/scripts/ci-test/test_changed.ts +++ b/scripts/ci-test/test_changed.ts @@ -17,13 +17,13 @@ import { resolve } from 'path'; import { spawn } from 'child-process-promise'; -import { TestReason, filterTasks, getTestTasks } from './tasks'; +import { TestReason, filterTasks, getTestTasks, logTasks } from './tasks'; import chalk from 'chalk'; import { argv } from 'yargs'; import { TestConfig, testConfig } from './testConfig'; const root = resolve(__dirname, '../..'); -const inputTestConfigName = argv._[0]; +const inputTestConfigName = argv._[0].toString(); const testCommand = 'test:ci'; const allTestConfigNames = Object.keys(testConfig); @@ -51,6 +51,8 @@ async function runTests(config: TestConfig) { try { const testTasks = filterTasks(await getTestTasks(), config); + // print tasks for info + logTasks(testTasks); if (testTasks.length === 0) { chalk`{green No test tasks. Skipping all tests }`; process.exit(0); diff --git a/scripts/docgen/content-sources/js/toc.yaml b/scripts/docgen/content-sources/js/toc.yaml index 0f3538974be..1420bccfcb8 100644 --- a/scripts/docgen/content-sources/js/toc.yaml +++ b/scripts/docgen/content-sources/js/toc.yaml @@ -11,6 +11,16 @@ toc: - title: "App" path: /docs/reference/js/firebase.app.App +- title: "firebase.appcheck" + path: /docs/reference/js/firebase.appcheck + section: + - title: "AppCheck" + path: /docs/reference/js/firebase.appcheck.AppCheck + - title: "AppCheckProvider" + path: /docs/reference/js/firebase.appcheck.AppCheckProvider + - title: "AppCheckToken" + path: /docs/reference/js/firebase.appcheck.AppCheckToken + - title: "firebase.analytics" path: /docs/reference/js/firebase.analytics section: @@ -67,31 +77,31 @@ toc: - title: "IdTokenResult" path: /docs/reference/js/firebase.auth.IDTokenResult - title: "MultiFactorAssertion" - path: /docs/reference/js/firebase.auth.multifactorassertion + path: /docs/reference/js/firebase.auth.MultiFactorAssertion - title: "MultiFactorError" - path: /docs/reference/js/firebase.auth.multifactorerror + path: /docs/reference/js/firebase.auth.MultiFactorError - title: "MultiFactorInfo" - path: /docs/reference/js/firebase.auth.multifactorinfo + path: /docs/reference/js/firebase.auth.MultiFactorInfo - title: "MultiFactorResolver" - path: /docs/reference/js/firebase.auth.multifactorresolver + path: /docs/reference/js/firebase.auth.MultiFactorResolver - title: "MultiFactorSession" - path: /docs/reference/js/firebase.auth.multifactorsession + path: /docs/reference/js/firebase.auth.MultiFactorSession - title: "PhoneAuthCredential" - path: /docs/reference/js/firebase.auth.phoneauthcredential + path: /docs/reference/js/firebase.auth.PhoneAuthCredential - title: "PhoneMultiFactorAssertion" - path: /docs/reference/js/firebase.auth.phonemultifactorassertion + path: /docs/reference/js/firebase.auth.PhoneMultiFactorAssertion - title: "PhoneMultiFactorEnrollInfoOptions" - path: /docs/reference/js/firebase.auth.phonemultifactorenrollinfooptions + path: /docs/reference/js/firebase.auth.PhoneMultiFactorEnrollInfoOptions - title: "PhoneMultiFactorGenerator" - path: /docs/reference/js/firebase.auth.phonemultifactorgenerator + path: /docs/reference/js/firebase.auth.PhoneMultiFactorGenerator - title: "PhoneMultiFactorInfo" - path: /docs/reference/js/firebase.auth.phonemultifactorinfo + path: /docs/reference/js/firebase.auth.PhoneMultiFactorInfo - title: "PhoneMultiFactorSignInInfoOptions" - path: /docs/reference/js/firebase.auth.phonemultifactorsignininfooptions + path: /docs/reference/js/firebase.auth.PhoneMultiFactorSignInInfoOptions - title: "PhoneSingleFactorInfoOptions" - path: /docs/reference/js/firebase.auth.phonesinglefactorinfooptions + path: /docs/reference/js/firebase.auth.PhoneSingleFactorInfoOptions - title: "MultiFactorUser" - path: /docs/reference/js/firebase.user.multifactoruser + path: /docs/reference/js/firebase.user.MultiFactorUser - title: "OAuthCredential" path: /docs/reference/js/firebase.auth.OAuthCredential - title: "OAuthCredentialOptions" @@ -158,6 +168,10 @@ toc: path: /docs/reference/js/firebase.firestore.GeoPoint - title: "GetOptions" path: /docs/reference/js/firebase.firestore.GetOptions + - title: "LoadBundleTask" + path: /docs/reference/js/firebase.firestore.LoadBundleTask + - title: "LoadBundleTaskProgress" + path: /docs/reference/js/firebase.firestore.LoadBundleTaskProgress - title: "PersistenceSettings" path: /docs/reference/js/firebase.firestore.PersistenceSettings - title: "Query" @@ -236,6 +250,8 @@ toc: - title: "firebase.storage" path: /docs/reference/js/firebase.storage section: + - title: "FirebaseStorageError" + path: /docs/reference/js/firebase.storage.FirebaseStorageError - title: "FullMetadata" path: /docs/reference/js/firebase.storage.FullMetadata - title: "ListOptions" @@ -248,6 +264,8 @@ toc: path: /docs/reference/js/firebase.storage.SettableMetadata - title: "Storage" path: /docs/reference/js/firebase.storage.Storage + - title: "StorageObserver" + path: /docs/reference/js/firebase.storage.StorageObserver - title: "UploadMetadata" path: /docs/reference/js/firebase.storage.UploadMetadata - title: "UploadTask" diff --git a/scripts/docgen/content-sources/node/toc.yaml b/scripts/docgen/content-sources/node/toc.yaml index 27bf6feea9e..4b91f97a8a1 100644 --- a/scripts/docgen/content-sources/node/toc.yaml +++ b/scripts/docgen/content-sources/node/toc.yaml @@ -134,6 +134,10 @@ toc: path: /docs/reference/node/firebase.firestore.GeoPoint - title: "GetOptions" path: /docs/reference/node/firebase.firestore.GetOptions + - title: "LoadBundleTask" + path: /docs/reference/node/firebase.firestore.LoadBundleTask + - title: "LoadBundleTaskProgress" + path: /docs/reference/node/firebase.firestore.LoadBundleTaskProgress - title: "PersistenceSettings" path: /docs/reference/node/firebase.firestore.PersistenceSettings - title: "Query" @@ -171,4 +175,4 @@ toc: - title: "HttpsCallableResult" path: /docs/reference/node/firebase.functions.HttpsCallableResult - title: "HttpsError" - path: /docs/reference/node/firebase.functions.HttpsError \ No newline at end of file + path: /docs/reference/node/firebase.functions.HttpsError diff --git a/scripts/docgen/generate-docs.js b/scripts/docgen/generate-docs.js index 898167d3f6e..db7fbc07a15 100644 --- a/scripts/docgen/generate-docs.js +++ b/scripts/docgen/generate-docs.js @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google Inc. + * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -104,7 +104,22 @@ function fixLinks(file) { const re = new RegExp(lower, 'g'); caseFixedLinks = caseFixedLinks.replace(re, lowerToUpperLookup[lower]); } - return fs.writeFile(file, caseFixedLinks); + let badLinkCleanup = caseFixedLinks.replace( + /{@link (.+)}/g, + (all, text) => { + // It's expected to have some broken @link tags in Node docs + // since they could reference some pages only generated for JS. + // Just render as plain text. Warn if it's not a Node doc. + if (!file.includes('/node/')) { + console.log( + `Unable to generate link for "${all} in ${file}", ` + + `removing markup and rendering as plain text.` + ); + } + return text; + } + ); + return fs.writeFile(file, badLinkCleanup); }); } @@ -118,7 +133,7 @@ let tocText = ''; * @param {string} homeRaw */ function generateTempHomeMdFile(tocRaw, homeRaw) { - const { toc } = yaml.safeLoad(tocRaw); + const { toc } = yaml.load(tocRaw); let tocPageLines = [homeRaw, '# API Reference']; toc.forEach(group => { tocPageLines.push(`\n## [${group.title}](${stripPath(group.path)}.html)`); @@ -244,7 +259,7 @@ function writeGeneratedFileList(htmlFiles) { path: `${devsitePath}${filename}` }; }); - const generatedTocYAML = yaml.safeDump({ toc: fileList }); + const generatedTocYAML = yaml.dump({ toc: fileList }); return fs .writeFile(`${docPath}/_toc_autogenerated.yaml`, generatedTocYAML) .then(() => htmlFiles); diff --git a/scripts/emulator-testing/emulators/database-emulator.ts b/scripts/emulator-testing/emulators/database-emulator.ts index ddd126e7698..0d6675e9a5b 100644 --- a/scripts/emulator-testing/emulators/database-emulator.ts +++ b/scripts/emulator-testing/emulators/database-emulator.ts @@ -24,11 +24,11 @@ export class DatabaseEmulator extends Emulator { constructor(port = 8088, namespace = 'test-emulator') { super( - 'database-emulator.jar', + 'firebase-database-emulator-v4.4.1.jar', // Use locked version of emulator for test to be deterministic. // The latest version can be found from database emulator doc: // https://firebase.google.com/docs/database/security/test-rules-emulator - 'https://storage.googleapis.com/firebase-preview-drop/emulator/firebase-database-emulator-v4.4.1.jar', + 'https://storage.googleapis.com/firebase-preview-drop/emulator/firebase-database-emulator-v4.6.0.jar', port ); this.namespace = namespace; diff --git a/scripts/emulator-testing/emulators/emulator.ts b/scripts/emulator-testing/emulators/emulator.ts index e5d49433c2b..bea75d39dc4 100644 --- a/scripts/emulator-testing/emulators/emulator.ts +++ b/scripts/emulator-testing/emulators/emulator.ts @@ -19,6 +19,7 @@ import { spawn } from 'child-process-promise'; import { ChildProcess } from 'child_process'; import * as fs from 'fs'; +import * as os from 'os'; import * as path from 'path'; import * as request from 'request'; // @ts-ignore @@ -28,13 +29,25 @@ export abstract class Emulator { binaryPath: string | null = null; emulator: ChildProcess | null = null; + cacheDirectory: string; + cacheBinaryPath: string; + constructor( private binaryName: string, private binaryUrl: string, public port: number - ) {} + ) { + this.cacheDirectory = path.join(os.homedir(), `.cache/firebase-js-sdk`); + this.cacheBinaryPath = path.join(this.cacheDirectory, binaryName); + } download(): Promise { + if (fs.existsSync(this.cacheBinaryPath)) { + console.log(`Emulator found in cache: ${this.cacheBinaryPath}`); + this.binaryPath = this.cacheBinaryPath; + return Promise.resolve(); + } + return new Promise((resolve, reject) => { tmp.dir((err: Error | null, dir: string) => { if (err) reject(err); @@ -55,6 +68,10 @@ export abstract class Emulator { if (err) reject(err); console.log(`Changed emulator file permissions to 'rwxr-xr-x'.`); this.binaryPath = filepath; + + if (this.copyToCache()) { + console.log(`Cached emulator at ${this.cacheBinaryPath}`); + } resolve(); }); }) @@ -129,4 +146,23 @@ export abstract class Emulator { fs.unlinkSync(this.binaryPath); } } + + private copyToCache(): boolean { + if (!this.binaryPath) { + return false; + } + + try { + if (!fs.existsSync(this.cacheDirectory)) { + fs.mkdirSync(this.cacheDirectory, { recursive: true }); + } + fs.copyFileSync(this.binaryPath, this.cacheBinaryPath); + + return true; + } catch (e) { + console.warn(`Unable to cache ${this.binaryName}`, e); + } + + return false; + } } diff --git a/scripts/emulator-testing/emulators/firestore-emulator.ts b/scripts/emulator-testing/emulators/firestore-emulator.ts index 84cd23d2eeb..3752a61c203 100644 --- a/scripts/emulator-testing/emulators/firestore-emulator.ts +++ b/scripts/emulator-testing/emulators/firestore-emulator.ts @@ -22,7 +22,7 @@ export class FirestoreEmulator extends Emulator { constructor(port: number, projectId = 'test-emulator') { super( - 'firestore-emulator.jar', + 'cloud-firestore-emulator-v1.11.7.jar', // Use locked version of emulator for test to be deterministic. // The latest version can be found from firestore emulator doc: // https://firebase.google.com/docs/firestore/security/test-rules-emulator diff --git a/scripts/emulator-testing/firestore-test-runner.ts b/scripts/emulator-testing/firestore-test-runner.ts index 0bca94e376b..12b2b7e9107 100644 --- a/scripts/emulator-testing/firestore-test-runner.ts +++ b/scripts/emulator-testing/firestore-test-runner.ts @@ -43,13 +43,11 @@ function runTest(port: number, projectId: string, withPersistence: boolean) { if (withPersistence) { childProcesses.push( spawn('yarn', ['test:node:persistence:prod'], options), - spawn('yarn', ['test:exp:persistence:prod'], options), spawn('yarn', ['test:lite:prod'], options) ); } else { childProcesses.push( spawn('yarn', ['test:node:prod'], options), - spawn('yarn', ['test:exp:prod'], options), spawn('yarn', ['test:lite:prod'], options) ); } diff --git a/scripts/exp/create-overloads.ts b/scripts/exp/create-overloads.ts new file mode 100644 index 00000000000..c3db9829120 --- /dev/null +++ b/scripts/exp/create-overloads.ts @@ -0,0 +1,318 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as yargs from 'yargs'; +import * as ts from 'typescript'; +import * as fs from 'fs'; + +const argv = yargs + .options({ + input: { + alias: 'i', + type: 'string', + require: true + }, + output: { + alias: 'o', + default: 'out.d.ts' + }, + append: { + alias: 'a', + type: 'boolean', + default: false + }, + replace: { + alias: 'r', + type: 'array', + require: true, + describe: 'Use [match]:[replacement] format. e.g. -r Auth:AuthCompat' + }, + moduleToEnhance: { + type: 'string', + require: true + } + }) + .coerce('replace', (args: string[]) => { + return args.map(arg => { + const [match, replacement] = arg.split(':'); + return { + match, + replacement + }; + }); + }) + .help().argv; + +interface Options { + input: string; + output: string; + append: boolean; + replace: ReplaceOption[]; + moduleToEnhance: string; +} + +interface ReplaceOption { + match: string; + replacement: string; +} + +function createOverloads({ + input, + output, + append, + replace, + moduleToEnhance +}: Options) { + const compilerOptions = {}; + const host = ts.createCompilerHost(compilerOptions); + const program = ts.createProgram([input], compilerOptions, host); + const printer: ts.Printer = ts.createPrinter(); + const sourceFile = program.getSourceFile(input)!; + + const result = ts.transform(sourceFile, [ + keepPublicFunctionsTransformer.bind( + undefined, + program, + replace, + moduleToEnhance + ) + ]); + + const transformedSourceFile = result.transformed[0]; + const content = printer.printFile(transformedSourceFile); + + // if append, append to the output file + if (append) { + if (!fs.existsSync(output)) { + throw Error( + `${output} doesn't exist. Please provide path to an existing file when using the -a option` + ); + } + const stat = fs.statSync(output); + if (!stat.isFile()) { + throw Error( + `${output} is not a file. Please provide path to an existing file when using the -a option` + ); + } + + fs.appendFileSync(output, `\n${content}`); + } else { + fs.writeFileSync(output, content); + } +} + +function keepPublicFunctionsTransformer( + program: ts.Program, + replace: ReplaceOption[], + moduleNameToEnhance: string, + context: ts.TransformationContext +): ts.Transformer { + return (sourceFile: ts.SourceFile) => { + const typeChecker = program.getTypeChecker(); + const overloads: ts.Statement[] = []; + function visit(node: ts.Node): ts.Node { + if (ts.isFunctionDeclaration(node)) { + // return early if the function doesn't have any parameter of the type we are looking for + if ( + !node.parameters.find(param => { + if (param.type && ts.isTypeReferenceNode(param.type)) { + const typeName = param.type.typeName; + return replace.find(opt => typeName.getText() === opt.match); + } + return false; + }) + ) { + return ts.createToken(ts.SyntaxKind.WhitespaceTrivia); + } + + const newParameters = node.parameters.map(param => { + if (param.type && ts.isTypeReferenceNode(param.type)) { + for (const replaceOption of replace) { + if ( + param.type.typeName.getText(sourceFile) === replaceOption.match + ) { + return ts.updateParameter( + param, + param.decorators, + param.modifiers, + param.dotDotDotToken, + param.name, + param.questionToken, + ts.createTypeReferenceNode( + replaceOption.replacement, + param.type.typeArguments + ), + param.initializer + ); + } + } + } + + return param; + }); + + // remove comments + ts.setTextRange(node, { + pos: node.getStart(sourceFile), + end: node.getEnd() + }); + + overloads.push( + ts.updateFunctionDeclaration( + node, + node.decorators, + [], + node.asteriskToken, + node.name, + node.typeParameters, + newParameters, + node.type, + node.body + ) + ); + } + + // remove all nodes other than the source file itself + if (!ts.isSourceFile(node)) { + return ts.createToken(ts.SyntaxKind.WhitespaceTrivia); + } + + return node; + } + + function visitNodeAndChildren(node: T): T { + return ts.visitEachChild( + visit(node), + childNode => visitNodeAndChildren(childNode), + context + ) as T; + } + + const transformed = visitNodeAndChildren(sourceFile); + + const typesToImport: Set = new Set(); + // find types referenced in overloads. we need to import them. + for (const overload of overloads) { + findTypes(typeChecker, overload, transformed, typesToImport, [ + ...replace.map(opt => opt.replacement) + ]); + } + + // hardcode adding `import { FirebaseApp as FirebaseAppCompat } from '@firebase/app-compat'` + const appCompatImport = ts.createImportDeclaration( + undefined, + undefined, + ts.createImportClause( + undefined, + ts.createNamedImports([ + ts.createImportSpecifier( + ts.createIdentifier('FirebaseApp'), + ts.createIdentifier('FirebaseAppCompat') + ) + ]) + ), + ts.createLiteral('@firebase/app-compat') + ); + + const importStatement = ts.createImportDeclaration( + undefined, + undefined, + ts.createImportClause( + undefined, + ts.createNamedImports( + Array.from(typesToImport).map(typeName => + ts.createImportSpecifier(undefined, ts.createIdentifier(typeName)) + ) + ) + ), + ts.createLiteral(moduleNameToEnhance) + ); + const moduleToEnhance = ts.createModuleDeclaration( + undefined, + [ts.createModifier(ts.SyntaxKind.DeclareKeyword)], + ts.createStringLiteral(moduleNameToEnhance), + ts.createModuleBlock(overloads) + ); + + return ts.updateSourceFileNode(transformed, [ + appCompatImport, + importStatement, + moduleToEnhance + ]); + }; +} + +// TODO: generate the builtin types from externs, similar to packages/firestore/externs.json +const BUILTIN_TYPES = [ + 'string', + 'number', + 'boolean', + 'unknown', + 'any', + 'void', + 'null', + 'undefined', + 'never', + 'Object', + 'object', + 'Promise', + 'ReadableStream', + 'Uint8Array', + 'ArrayBuffer', + 'Partial', + 'Blob', + 'ServiceWorkerRegistration', + 'Record', + 'Error' +]; + +// find all types (except for the built-ins and primitives) referenced in the function declaration +function findTypes( + typeCheck: ts.TypeChecker, + node: ts.Node, + sourceFile: ts.SourceFile, + types: Set, + excludes: string[] = [] +): void { + const typesToIgnore = [...BUILTIN_TYPES, ...excludes]; + + function findTypesRecursively(node: ts.Node): void { + if (ts.isTypeReferenceNode(node)) { + let typeName = node.typeName.getText(sourceFile); + if (ts.isIdentifier(node.typeName)) { + typeName = node.typeName.text; + } + + // include the type if it's not in the excludes list or a builtin type + if (!typesToIgnore.includes(typeName)) { + const symbol = typeCheck.getSymbolAtLocation(node.typeName); + const declaration = symbol?.declarations[0]; + + // ignore type parameters. + if (!declaration || !ts.isTypeParameterDeclaration(declaration)) { + types.add(typeName); + } + } + } + + ts.forEachChild(node, findTypesRecursively); + } + + findTypesRecursively(node); +} + +createOverloads(argv as Options); diff --git a/scripts/exp/detect-doc-changes.ts b/scripts/exp/detect-doc-changes.ts new file mode 100644 index 00000000000..14691adf5cd --- /dev/null +++ b/scripts/exp/detect-doc-changes.ts @@ -0,0 +1,92 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import simpleGit from 'simple-git/promise'; +import { spawn } from 'child-process-promise'; +import * as tmp from 'tmp'; + +/** + * check if there is any doc changes from baseSha to headSha + */ +async function docChanged(baseSha: string, headSha: string): Promise { + const tmpDir = tmp.dirSync().name; + console.log('tmpDir is', tmpDir); + // clone repo to the tmpDir + await spawn( + 'git', + ['clone', 'https://github.com/firebase/firebase-js-sdk.git', tmpDir], + { stdio: 'inherit' } + ); + + const git = simpleGit(tmpDir); + + // generate reference docs using the baseSha + await git.checkout(baseSha); + await spawn('yarn', [], { cwd: tmpDir, stdio: 'inherit' }); + await spawn('yarn', ['docgen:exp', 'devsite'], { + cwd: tmpDir, + stdio: 'inherit' + }); + + // revert yarn.lock, so we can checkout the headSha without conflict. + await git.checkout('yarn.lock'); + // stash changes, so we can checkout the headSha without conflict. + await git.stash(); + + // generate reference docs using the headSha + await git.checkout(headSha); + await spawn('yarn', [], { cwd: tmpDir, stdio: 'inherit' }); + + // stage the docs generated by baseSha, so we can use git diff to tell if any docs are different in the headSha + await git.stash(['pop']); + await git.add('*'); + await spawn('yarn', ['docgen:exp', 'devsite'], { + cwd: tmpDir, + stdio: 'inherit' + }); + + // check if any file is changed. + const diff = await git.diff(); + // check if new files are created. + const untracked = await git.raw([ + 'ls-files', + '-o', + '--directory', + '--exclude-standard' + ]); + + return !!diff || !!untracked; +} + +// this function is supposed to run in the Github action. +async function runInCI() { + const headSha = process.env.GITHUB_PULL_REQUEST_HEAD_SHA; + const mergeBaseSha = process.env.GITHUB_PULL_REQUEST_BASE_SHA; + + if (!headSha || !mergeBaseSha) { + console.log('No merge base or head found. Is it not a PR?'); + process.exit(1); + } + + if (await docChanged(mergeBaseSha, headSha)) { + console.log(`::set-output name=DOC_CHANGED::true`); + } else { + console.log(`::set-output name=DOC_CHANGED::false`); + } +} + +runInCI(); diff --git a/scripts/exp/docgen.ts b/scripts/exp/docgen.ts index 71d4b093f54..6fa9821fddc 100644 --- a/scripts/exp/docgen.ts +++ b/scripts/exp/docgen.ts @@ -21,15 +21,56 @@ import { projectRoot } from '../utils'; import { removeExpSuffix } from './remove-exp'; import fs from 'fs'; import glob from 'glob'; +import * as yargs from 'yargs'; + +yargs + .command('$0', 'generate standard reference docs', {}, _argv => + generateDocs(/* forDevsite */ false) + ) + .command('devsite', 'generate reference docs for devsite', {}, _argv => + generateDocs(/* forDevsite */ true) + ) + .demandCommand() + .help().argv; const tmpDir = `${projectRoot}/temp`; // create *.api.json files -async function generateDocs() { +async function generateDocs(forDevsite: boolean = false) { + const outputFolder = forDevsite ? 'docs-devsite' : 'docs-exp'; + const command = forDevsite ? 'api-documenter-devsite' : 'api-documenter'; + // TODO: change yarn command once exp packages become official await spawn('yarn', ['lerna', 'run', '--scope', '@firebase/*-exp', 'build'], { stdio: 'inherit' }); + // build storage-exp + await spawn( + 'yarn', + ['lerna', 'run', '--scope', '@firebase/storage', 'build:exp'], + { + stdio: 'inherit' + } + ); + + // build database-exp + await spawn( + 'yarn', + ['lerna', 'run', '--scope', '@firebase/database', 'build:exp'], + { + stdio: 'inherit' + } + ); + + // generate public typings for firestore + await spawn( + 'yarn', + ['lerna', 'run', '--scope', '@firebase/firestore', 'prebuild'], + { + stdio: 'inherit' + } + ); + await spawn('yarn', ['api-report'], { stdio: 'inherit' }); @@ -68,10 +109,8 @@ async function generateDocs() { // Generate docs without the -exp suffix removeExpSuffix(tmpDir); await spawn( - 'npx', - ['api-documenter', 'markdown', '--input', 'temp', '--output', 'docs-exp'], + 'yarn', + [command, 'markdown', '--input', 'temp', '--output', outputFolder], { stdio: 'inherit' } ); } - -generateDocs(); diff --git a/scripts/exp/prepare-database-for-exp-release.ts b/scripts/exp/prepare-database-for-exp-release.ts new file mode 100644 index 00000000000..44db5430742 --- /dev/null +++ b/scripts/exp/prepare-database-for-exp-release.ts @@ -0,0 +1,83 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { projectRoot, readPackageJson } from '../utils'; +import { writeFile as _writeFile, readFile as _readFile } from 'fs'; +import { promisify } from 'util'; +import { resolve } from 'path'; +import { createCompatProject } from './prepare-util'; + +const writeFile = promisify(_writeFile); +const packagePath = `${projectRoot}/packages/database`; + +/** + * Transform package.json in @firebase/database so that we can use scripts/exp/release.ts to release Database exp. + * It does following things: + * 1. Update package.json to point to exp binaries + * 2. Update version to '0.0.900', the version number we choose for releasing exp packages + * (9 stands for v9, 900 to avoid conflict with official versions). + * The release script will append commit hash to it and release the package with that version. + * e.g. 0.0.900-exp.fe85035e1 + * 3. Replace peerDependencies with the exp version, so the release script can match and update them to the correct version. + */ +export async function prepare() { + // Update package.json + const packageJson = await readPackageJson(packagePath); + const expPackageJson = await readPackageJson(`${packagePath}/exp`); + packageJson.version = '0.0.900'; + + packageJson.peerDependencies = { + '@firebase/app-exp': '0.x' + }; + + packageJson.main = expPackageJson.main.replace('../', ''); + packageJson.module = expPackageJson.module.replace('../', ''); + packageJson.browser = expPackageJson.browser.replace('../', ''); + packageJson.esm5 = expPackageJson.esm5.replace('../', ''); + delete packageJson['esm2017']; + + packageJson.typings = expPackageJson.typings.replace('../', ''); + + // include files to be published + packageJson.files = [...packageJson.files, packageJson.typings]; + + // update package.json files + await writeFile( + `${packagePath}/package.json`, + `${JSON.stringify(packageJson, null, 2)}\n`, + { encoding: 'utf-8' } + ); +} + +export async function createDatabaseCompatProject() { + const DATABASE_SRC = resolve(projectRoot, 'packages/database'); + const DATABASE_COMPAT_SRC = resolve(projectRoot, 'packages/database/compat'); + const DATABASE_COMPAT_DEST = resolve( + projectRoot, + 'packages-exp/database-compat' + ); + const DATABASE_COMPAT_BINARY_SRC = resolve(DATABASE_SRC, 'dist/compat'); + const DATABASE_COMPAT_BINARY_DEST = resolve(DATABASE_COMPAT_DEST, 'dist'); + + createCompatProject({ + srcDir: DATABASE_SRC, + compatSrcDir: DATABASE_COMPAT_SRC, + compatDestDir: DATABASE_COMPAT_DEST, + compatBinarySrcDir: DATABASE_COMPAT_BINARY_SRC, + compatBinaryDestDir: DATABASE_COMPAT_BINARY_DEST + }); +} diff --git a/scripts/exp/prepare-firestore-for-exp-release.ts b/scripts/exp/prepare-firestore-for-exp-release.ts index b8be7504bc6..1df5571afcf 100644 --- a/scripts/exp/prepare-firestore-for-exp-release.ts +++ b/scripts/exp/prepare-firestore-for-exp-release.ts @@ -15,24 +15,21 @@ * limitations under the License. */ +import { resolve } from 'path'; import { projectRoot, readPackageJson } from '../utils'; -import { writeFile as _writeFile, readFile as _readFile } from 'fs'; -import { promisify } from 'util'; -import path from 'path'; +import { writeFileSync } from 'fs'; +import { createCompatProject } from './prepare-util'; -const writeFile = promisify(_writeFile); -const readFile = promisify(_readFile); const packagePath = `${projectRoot}/packages/firestore`; -// /** * Transform package.json in @firebase/firestore so that we can use scripts/exp/release.ts to release Firestore exp. * It does following things: * 1. Update package.json to point to exp binaries - * 2. Update version to '0.0.800', the version number we choose for releasing exp packages - * (8 stands for v8, 800 to avoid conflict with official versions). + * 2. Update version to '0.0.900', the version number we choose for releasing exp packages + * (9 stands for v9, 900 to avoid conflict with official versions). * The release script will append commit hash to it and release the package with that version. - * e.g. 0.0.800-exp.fe85035e1 + * e.g. 0.0.900-exp.fe85035e1 * 3. Replace peerDependencies with the exp version, so the release script can match and update them to the correct version. * 4. Replace imports with imports from exp packages in typing files. */ @@ -41,11 +38,10 @@ export async function prepare() { const packageJson = await readPackageJson(packagePath); const expPackageJson = await readPackageJson(`${packagePath}/exp`); const litePackageJson = await readPackageJson(`${packagePath}/lite`); - packageJson.version = '0.0.800'; + packageJson.version = '0.0.900'; packageJson.peerDependencies = { - '@firebase/app-exp': '0.x', - '@firebase/app-types-exp': '0.x' + '@firebase/app-exp': '0.x' }; packageJson.main = expPackageJson.main.replace('../', ''); @@ -60,8 +56,6 @@ export async function prepare() { packageJson.typings = expPackageJson.typings.replace('../', ''); - delete packageJson.scripts.prepare; - // include files to be published packageJson.files = [ ...packageJson.files, @@ -71,29 +65,31 @@ export async function prepare() { ]; // update package.json files - await writeFile( + writeFileSync( `${packagePath}/package.json`, `${JSON.stringify(packageJson, null, 2)}\n`, { encoding: 'utf-8' } ); - - const expTypingPath = `${packagePath}/${packageJson.typings}`; - const liteTypingPath = path.resolve( - `${packagePath}/lite`, - litePackageJson.typings - ); - - // remove -exp in typings files - await replaceAppTypesExpInFile(expTypingPath); - await replaceAppTypesExpInFile(liteTypingPath); } -async function replaceAppTypesExpInFile(filePath: string): Promise { - const fileContent = await readFile(filePath, { encoding: 'utf-8' }); - const newFileContent = fileContent.replace( - '@firebase/app-types-exp', - '@firebase/app-types' +export async function createFirestoreCompatProject() { + const FIRESTORE_SRC = resolve(projectRoot, 'packages/firestore'); + const FIRESTORE_COMPAT_SRC = resolve( + projectRoot, + 'packages/firestore/compat' + ); + const FIRESTORE_COMPAT_DEST = resolve( + projectRoot, + 'packages-exp/firestore-compat' ); + const FIRESTORE_COMPAT_BINARY_SRC = resolve(FIRESTORE_SRC, 'dist/compat'); + const FIRESTORE_COMPAT_BINARY_DEST = resolve(FIRESTORE_COMPAT_DEST, 'dist'); - await writeFile(filePath, newFileContent, { encoding: 'utf-8' }); + createCompatProject({ + srcDir: FIRESTORE_SRC, + compatSrcDir: FIRESTORE_COMPAT_SRC, + compatDestDir: FIRESTORE_COMPAT_DEST, + compatBinarySrcDir: FIRESTORE_COMPAT_BINARY_SRC, + compatBinaryDestDir: FIRESTORE_COMPAT_BINARY_DEST + }); } diff --git a/scripts/exp/prepare-storage-for-exp-release.ts b/scripts/exp/prepare-storage-for-exp-release.ts new file mode 100644 index 00000000000..efe5e9e1e3d --- /dev/null +++ b/scripts/exp/prepare-storage-for-exp-release.ts @@ -0,0 +1,84 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { projectRoot, readPackageJson } from '../utils'; +import { writeFile as _writeFile, readFile as _readFile } from 'fs'; +import { resolve } from 'path'; +import { promisify } from 'util'; +import { createCompatProject } from './prepare-util'; + +const writeFile = promisify(_writeFile); +const packagePath = `${projectRoot}/packages/storage`; + +/** + * Transform package.json in @firebase/storage so that we can use scripts/exp/release.ts to release storage exp. + * It does following things: + * 1. Update package.json to point to exp binaries + * 2. Update version to '0.0.900', the version number we choose for releasing exp packages + * (9 stands for v9, 900 to avoid conflict with official versions). + * The release script will append commit hash to it and release the package with that version. + * e.g. 0.0.900-exp.fe85035e1 + * 3. Replace peerDependencies with the exp version, so the release script can match and update them to the correct version. + * 4. Replace imports with imports from exp packages in typing files. + */ +export async function prepare() { + // Update package.json + const packageJson = await readPackageJson(packagePath); + const expPackageJson = await readPackageJson(`${packagePath}/exp`); + packageJson.version = '0.0.900'; + + packageJson.peerDependencies = { + '@firebase/app-exp': '0.x' + }; + + packageJson.main = expPackageJson.main.replace('./', 'exp/'); + packageJson.module = expPackageJson.module.replace('./', 'exp/'); + packageJson.browser = expPackageJson.browser.replace('./', 'exp/'); + packageJson.esm5 = expPackageJson.esm5.replace('./', 'exp/'); + delete packageJson['esm2017']; + + packageJson.typings = expPackageJson.typings.replace('./', 'exp/'); + + // include files to be published + packageJson.files = ['exp/dist']; + + // update package.json files + await writeFile( + `${packagePath}/package.json`, + `${JSON.stringify(packageJson, null, 2)}\n`, + { encoding: 'utf-8' } + ); +} + +export async function createStorageCompatProject() { + const STORAGE_SRC = resolve(projectRoot, 'packages/storage'); + const STORAGE_COMPAT_SRC = resolve(projectRoot, 'packages/storage/compat'); + const STORAGE_COMPAT_DEST = resolve( + projectRoot, + 'packages-exp/storage-compat' + ); + const STORAGE_COMPAT_BINARY_SRC = resolve(STORAGE_SRC, 'dist/compat'); + const STORAGE_COMPAT_BINARY_DEST = resolve(STORAGE_COMPAT_DEST, 'dist'); + + createCompatProject({ + srcDir: STORAGE_SRC, + compatSrcDir: STORAGE_COMPAT_SRC, + compatDestDir: STORAGE_COMPAT_DEST, + compatBinarySrcDir: STORAGE_COMPAT_BINARY_SRC, + compatBinaryDestDir: STORAGE_COMPAT_BINARY_DEST + }); +} diff --git a/scripts/exp/prepare-util.ts b/scripts/exp/prepare-util.ts new file mode 100644 index 00000000000..5e402de56e8 --- /dev/null +++ b/scripts/exp/prepare-util.ts @@ -0,0 +1,130 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { resolve } from 'path'; +import { + writeFileSync, + statSync, + readFileSync, + existsSync, + readdirSync, + mkdirSync, + copyFileSync, + rmdirSync +} from 'fs'; +import { projectRoot, readPackageJson } from '../utils'; + +export interface CompatConfig { + srcDir: string; + compatSrcDir: string; + compatDestDir: string; + compatBinarySrcDir: string; + compatBinaryDestDir: string; +} + +export async function createCompatProject(config: CompatConfig) { + const { + srcDir, + compatSrcDir, + compatDestDir, + compatBinarySrcDir, + compatBinaryDestDir + } = config; + // remove the dir if it exists + if (existsSync(compatDestDir)) { + rmdirSync(compatDestDir, { recursive: true }); + } + + copyRecursiveSync(compatSrcDir, compatDestDir); + copyRecursiveSync( + compatBinarySrcDir, + compatBinaryDestDir, + /* include d.ts files*/ true + ); + + // update root package.json + await transformFile(resolve(compatDestDir, 'package.json'), async content => { + const updatedContent = content.replace(/\.\.\/dist\/compat/g, './dist'); + const compatPkgJson = JSON.parse(updatedContent); + + const srcPkgJson = await readPackageJson(srcDir); + + compatPkgJson.dependencies = { + ...srcPkgJson.dependencies, + [srcPkgJson.name]: srcPkgJson.version + }; + + compatPkgJson.files = ['dist']; + + return `${JSON.stringify(compatPkgJson, null, 2)}\n`; + }); +} + +const FIREBASE_EXP_DIR = `${projectRoot}/packages-exp/firebase-exp`; +export async function addCompatToFirebasePkgJson(compatPkgNames: string[]) { + const compatDeps: Record = {}; + for (const pkgName of compatPkgNames) { + compatDeps[pkgName] = '0.0.900'; + } + + const firebasePkgJson = await readPackageJson(FIREBASE_EXP_DIR); + firebasePkgJson.dependencies = { + ...firebasePkgJson.dependencies, + ...compatDeps + }; + + writeFileSync( + `${FIREBASE_EXP_DIR}/package.json`, + `${JSON.stringify(firebasePkgJson, null, 2)}\n`, + { encoding: 'utf-8' } + ); +} + +export function copyRecursiveSync( + src: string, + dest: string, + includeTs: boolean = false +) { + if (!existsSync(src)) { + return; + } + + const srcStat = statSync(src); + const isDirectory = srcStat.isDirectory(); + if (isDirectory) { + mkdirSync(dest); + for (const item of readdirSync(src)) { + copyRecursiveSync(resolve(src, item), resolve(dest, item), includeTs); + } + } else { + // do not copy source file + if (src.includes('.ts') && !includeTs) { + return; + } + + copyFileSync(src, dest); + } +} + +export async function transformFile( + path: string, + transformer: (content: string) => Promise +) { + let content = readFileSync(path, 'utf8'); + + writeFileSync(path, await transformer(content), { encoding: 'utf8' }); +} diff --git a/scripts/exp/release.ts b/scripts/exp/release.ts index 431eae63712..d4778ceedc0 100644 --- a/scripts/exp/release.ts +++ b/scripts/exp/release.ts @@ -17,18 +17,33 @@ import { spawn, exec } from 'child-process-promise'; import ora from 'ora'; +import { createPromptModule } from 'inquirer'; import { projectRoot, readPackageJson } from '../utils'; import simpleGit from 'simple-git/promise'; import { mapWorkspaceToPackages } from '../release/utils/workspace'; import { inc } from 'semver'; -import { writeFile as _writeFile } from 'fs'; +import { writeFile as _writeFile, rmdirSync, existsSync } from 'fs'; +import { resolve } from 'path'; import { promisify } from 'util'; import chalk from 'chalk'; import Listr from 'listr'; -import { prepare as prepareFirestoreForRelease } from './prepare-firestore-for-exp-release'; +import { + prepare as prepareFirestoreForRelease, + createFirestoreCompatProject +} from './prepare-firestore-for-exp-release'; +import { + createStorageCompatProject, + prepare as prepareStorageForRelease +} from './prepare-storage-for-exp-release'; +import { + createDatabaseCompatProject, + prepare as prepareDatabaseForRelease +} from './prepare-database-for-exp-release'; import * as yargs from 'yargs'; +import { addCompatToFirebasePkgJson } from './prepare-util'; +const prompt = createPromptModule(); const argv = yargs .options({ dryRun: { @@ -47,69 +62,165 @@ async function publishExpPackages({ dryRun }: { dryRun: boolean }) { /** * Welcome to the firebase release CLI! */ - console.log('Welcome to the Firebase Exp Packages release CLI!'); + console.log( + `Welcome to the Firebase Exp Packages release CLI! dryRun: ${dryRun}` + ); /** - * Update fields in package.json and stuff + * Update fields in package.json and create compat packages (e.g. packages-exp/firestore-compat) */ await prepareFirestoreForRelease(); - - /** - * build packages - */ - await buildPackages(); + await prepareStorageForRelease(); + await prepareDatabaseForRelease(); // path to exp packages let packagePaths = await mapWorkspaceToPackages([ `${projectRoot}/packages-exp/*` ]); - // exclude auth packages until we are ready to release - packagePaths = packagePaths.filter(path => !path.includes('auth')); - packagePaths.push(`${projectRoot}/packages/firestore`); + packagePaths.push(`${projectRoot}/packages/storage`); + packagePaths.push(`${projectRoot}/packages/database`); + + packagePaths.push(`${projectRoot}/packages/firestore/compat`); + packagePaths.push(`${projectRoot}/packages/storage/compat`); + packagePaths.push(`${projectRoot}/packages/database/compat`); /** - * It does 2 things: - * - * 1. Bumps the patch version of firebase-exp package regardless if there is any update + * Bumps the patch version of firebase-exp package regardless if there is any update * since the last release. This simplifies the script and works fine for exp packages. * - * 2. Removes -exp in package names because we will publish them using - * the existing package names under a special release tag (e.g. firebase@exp). + * We do this before the build so the registerVersion will grab the correct + * versions from package.json for platform logging. */ - const versions = await updatePackageNamesAndVersions(packagePaths); + const versions = await getNewVersions(packagePaths); + + await updatePackageJsons(packagePaths, versions, { + // Changing the package names here will break the build process. + // It will be done after the build. + removeExpInName: false, + updateVersions: true, + makePublic: true + }); /** - * Do not publish to npm and create tags if it's a dryrun + * build packages except for the umbrella package (firebase) which will be built after firestore/storage/database compat packages are created */ - if (!dryRun) { - /** - * Release packages to NPM - */ - await publishToNpm(packagePaths); + await buildPackages(); - /** - * reset the working tree to recover package names with -exp in the package.json files, - * then bump patch version of firebase-exp (the umbrella package) only - */ - const firebaseExpVersion = new Map(); - firebaseExpVersion.set( - FIREBASE_UMBRELLA_PACKAGE_NAME, - versions.get(FIREBASE_UMBRELLA_PACKAGE_NAME) - ); - const firebaseExpPath = packagePaths.filter(p => - p.includes(FIREBASE_UMBRELLA_PACKAGE_NAME) - ); + /** + * Create compat packages for Firestore, Database and Storage + */ + await createFirestoreCompatProject(); + await createStorageCompatProject(); + await createDatabaseCompatProject(); + + /** + * Add firestore-compat, database-compat and storage-compat to the dependencies array of firebase-exp + */ + await addCompatToFirebasePkgJson([ + '@firebase/firestore-compat', + '@firebase/storage-compat', + '@firebase/database-compat' + ]); + + /** + * build firebase + */ + await buildFirebasePackage(); + + /** + * Removes -exp in package names because we will publish them using + * the existing package names under a special release tag (firebase@exp). + */ + await updatePackageJsons(packagePaths, versions, { + removeExpInName: true, + updateVersions: false, + makePublic: false + }); + + let versionCheckMessage = + '\r\nAre you sure these are the versions you want to publish?\r\n'; + for (const [pkgName, version] of versions) { + versionCheckMessage += `${pkgName} : ${version}\n`; + } + const { versionCheck } = await prompt([ + { + type: 'confirm', + name: 'versionCheck', + message: versionCheckMessage, + default: false + } + ]); + + if (!versionCheck) { + throw new Error('Version check failed'); + } + + // publish all packages under packages-exp plus firestore, storage and database + const packagesToPublish = await mapWorkspaceToPackages([ + `${projectRoot}/packages-exp/*` + ]); + + packagesToPublish.push(`${projectRoot}/packages/firestore`); + packagesToPublish.push(`${projectRoot}/packages/storage`); + packagesToPublish.push(`${projectRoot}/packages/database`); + + /** + * Release packages to NPM + */ + await publishToNpm(packagesToPublish, dryRun); + + /** + * reset the working tree to recover package names with -exp in the package.json files, + * then bump patch version of firebase-exp (the umbrella package) only + */ + const firebaseExpVersion = new Map(); + firebaseExpVersion.set( + FIREBASE_UMBRELLA_PACKAGE_NAME, + versions.get(FIREBASE_UMBRELLA_PACKAGE_NAME) + ); + const firebaseExpPath = packagePaths.filter(p => + p.includes(FIREBASE_UMBRELLA_PACKAGE_NAME) + ); + + const { resetWorkingTree } = await prompt([ + { + type: 'confirm', + name: 'resetWorkingTree', + message: 'Do you want to reset the working tree?', + default: true + } + ]); + + if (resetWorkingTree) { await resetWorkingTreeAndBumpVersions( firebaseExpPath, firebaseExpVersion ); + } else { + process.exit(0); + } + /** + * Do not push to remote if it's a dryrun + */ + if (!dryRun) { + const { shouldCommitAndPush } = await prompt([ + { + type: 'confirm', + name: 'shouldCommitAndPush', + message: + 'Do you want to commit and push the exp version update to remote?', + default: true + } + ]); /** * push to github */ - await commitAndPush(versions); + if (shouldCommitAndPush) { + await commitAndPush(versions); + } } } catch (err) { /** @@ -137,10 +248,6 @@ async function buildPackages() { 'lerna', 'run', '--scope', - // We replace `@firebase/app-exp` with `@firebase/app` during compilation, so we need to - // compile @firebase/app to make rollup happy though it's not an actual dependency. - '@firebase/app', - '--scope', '@firebase/util', '--scope', '@firebase/component', @@ -175,7 +282,16 @@ async function buildPackages() { ); // Build exp packages developed in place - // Firestore + // Firestore and firestore-compat + await spawn( + 'yarn', + ['lerna', 'run', '--scope', '@firebase/firestore', 'prebuild'], + { + cwd: projectRoot, + stdio: 'inherit' + } + ); + await spawn( 'yarn', ['lerna', 'run', '--scope', '@firebase/firestore', 'build:exp:release'], @@ -185,6 +301,43 @@ async function buildPackages() { } ); + // Storage + await spawn( + 'yarn', + ['lerna', 'run', '--scope', '@firebase/storage', 'build:exp:release'], + { + cwd: projectRoot, + stdio: 'inherit' + } + ); + + // Database + await spawn( + 'yarn', + ['lerna', 'run', '--scope', '@firebase/database', 'build:exp:release'], + { + cwd: projectRoot, + stdio: 'inherit' + } + ); + + // remove packages/installations/dist, otherwise packages that depend on packages-exp/installations-exp (e.g. Perf, FCM) + // will incorrectly reference packages/installations. + const installationsDistDirPath = resolve( + projectRoot, + 'packages/installations/dist' + ); + if (existsSync(installationsDistDirPath)) { + rmdirSync(installationsDistDirPath, { recursive: true }); + } + + spinner.stopAndPersist({ + symbol: '✅' + }); +} + +async function buildFirebasePackage() { + const spinner = ora(' Building firebase').start(); // Build firebase-exp await spawn( 'yarn', @@ -194,21 +347,20 @@ async function buildPackages() { stdio: 'inherit' } ); - spinner.stopAndPersist({ symbol: '✅' }); } -async function updatePackageNamesAndVersions(packagePaths: string[]) { +async function getNewVersions(packagePaths: string[]) { // get package name -> next version mapping const versions = new Map(); for (const path of packagePaths) { const { version, name } = await readPackageJson(path); - // increment firebase-exp's patch version + // increment firebase-exp as a v9 prerelease, e.g. 9.0.0-beta.0 -> 9.0.0-beta.1 if (name === FIREBASE_UMBRELLA_PACKAGE_NAME) { - const nextVersion = inc(version, 'patch'); + const nextVersion = inc(version, 'prerelease'); versions.set(name, nextVersion); } else { // create individual packages version @@ -218,23 +370,16 @@ async function updatePackageNamesAndVersions(packagePaths: string[]) { versions.set(name, nextVersion); } } - - await updatePackageJsons(packagePaths, versions, { - removeExpInName: true, - updateVersions: true, - makePublic: true - }); - return versions; } -async function publishToNpm(packagePaths: string[]) { +async function publishToNpm(packagePaths: string[], dryRun = false) { const taskArray = await Promise.all( packagePaths.map(async pp => { const { version, name } = await readPackageJson(pp); return { title: `📦 ${name}@${version}`, - task: () => publishPackage(pp) + task: () => publishPackage(pp, dryRun) }; }) ); @@ -248,8 +393,11 @@ async function publishToNpm(packagePaths: string[]) { return tasks.run(); } -async function publishPackage(packagePath: string) { - const args = ['publish', '--access', 'public', '--tag', 'exp']; +async function publishPackage(packagePath: string, dryRun: boolean) { + const args = ['publish', '--access', 'public', '--tag', 'beta']; + if (dryRun) { + args.push('--dry-run'); + } await spawn('npm', args, { cwd: packagePath }); } @@ -336,12 +484,10 @@ async function updatePackageJsons( } async function commitAndPush(versions: Map) { - await exec('git add */package.json yarn.lock'); + await exec('git add packages-exp/firebase-exp/package.json yarn.lock'); const firebaseExpVersion = versions.get(FIREBASE_UMBRELLA_PACKAGE_NAME); - await exec( - `git commit -m "Publish firebase@exp ${firebaseExpVersion || ''}"` - ); + await exec(`git commit -m "Publish firebase ${firebaseExpVersion || ''}"`); let { stdout: currentBranch, stderr } = await exec( `git rev-parse --abbrev-ref HEAD` diff --git a/scripts/exp/remove-exp.js b/scripts/exp/remove-exp.js index cadcd5ab51b..7bbdbf58766 100644 --- a/scripts/exp/remove-exp.js +++ b/scripts/exp/remove-exp.js @@ -21,16 +21,25 @@ */ const { argv } = require('yargs'); const path = require('path'); -const { readdirSync, statSync, readFileSync, writeFileSync } = require('fs'); +const { + readdirSync, + statSync, + readFileSync, + writeFileSync, + accessSync +} = require('fs'); // can be used in command line if (argv._[0]) { const dirOrFile = path.resolve(argv._[0]); - if (statSync(dirOrFile).isFile()) { - removeExpSuffixFromFile(dirOrFile); - } else { - removeExpSuffix(dirOrFile); - } + try { + accessSync(dirOrFile); + if (statSync(dirOrFile).isFile()) { + removeExpSuffixFromFile(dirOrFile); + } else { + removeExpSuffix(dirOrFile); + } + } catch (_err) {} } function removeExpSuffix(dir) { diff --git a/scripts/exp/ts-transform-import-path.js b/scripts/exp/ts-transform-import-path.js index dae7b2431e2..46fb7b9217d 100644 --- a/scripts/exp/ts-transform-import-path.js +++ b/scripts/exp/ts-transform-import-path.js @@ -16,6 +16,14 @@ */ import * as ts from 'typescript'; + +export function getImportPathTransformer({ pattern, template }) { + return () => ({ + before: [transformImportPath({ pattern, template })], + after: [], + afterDeclarations: [transformImportPath({ pattern, template })] + }); +} /** * remove '-exp' in import paths. * For example, `import {...} from '@firebase/app-exp'` becomes `import {...} from '@firebase/app'`. @@ -23,28 +31,38 @@ import * as ts from 'typescript'; * Used to generate the release build for exp packages. We do this because we publish them * using the existing package names under a special release tag (e.g. firebase@exp); */ - export const importPathTransformer = () => ({ - before: [transformImportPath()], + before: [ + transformImportPath({ + pattern: /^(@firebase.*)-exp(.*)$/, + template: [1, 2] + }) + ], after: [], - afterDeclarations: [transformImportPath()] + afterDeclarations: [ + transformImportPath({ + pattern: /^(@firebase.*)-exp(.*)$/, + template: [1, 2] + }) + ] }); -function transformImportPath() { +function transformImportPath({ pattern, template }) { return context => file => { - return visitNodeAndChildren(file, context); + return visitNodeAndChildren(file, context, { pattern, template }); }; } -function visitNodeAndChildren(node, context) { +function visitNodeAndChildren(node, context, { pattern, template }) { return ts.visitEachChild( - visitNode(node), - childNode => visitNodeAndChildren(childNode, context), + visitNode(node, { pattern, template }), + childNode => + visitNodeAndChildren(childNode, context, { pattern, template }), context ); } -function visitNode(node) { +function visitNode(node, { pattern, template }) { let importPath; if ( (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && @@ -56,11 +74,19 @@ function visitNode(node) { importPathWithQuotes.length - 2 ); - const pattern = /^(@firebase.*)-exp(.*)$/g; const captures = pattern.exec(importPath); - if (captures) { - const newName = `${captures[1]}${captures[2]}`; + const newNameFragments = []; + for (const fragment of template) { + if (typeof fragment === 'number') { + newNameFragments.push(captures[fragment]); + } else if (typeof fragment === 'string') { + newNameFragments.push(fragment); + } else { + throw Error(`unrecognized fragment: ${fragment}`); + } + } + const newName = newNameFragments.join(''); const newNode = ts.getMutableClone(node); newNode.moduleSpecifier = ts.createLiteral(newName); return newNode; diff --git a/scripts/exp/use_typings.js b/scripts/exp/use_typings.js new file mode 100644 index 00000000000..8842ef649bd --- /dev/null +++ b/scripts/exp/use_typings.js @@ -0,0 +1,42 @@ +/* eslint-disable @typescript-eslint/no-require-imports */ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// NOTE: the script assumes it runs at the root of the package you want to update + +const { writeFileSync } = require('fs'); +const { argv } = require('yargs'); + +const path = require('path'); +const packageJsonPath = path.resolve(process.cwd(), './package.json'); + +// point typings field to supplied path in package.json +const TYPINGS_PATH = argv._[0]; + +if (!TYPINGS_PATH) { + throw Error('Please supply a file path'); +} +const packageJson = require(packageJsonPath); +packageJson.typings = TYPINGS_PATH; + +console.log( + `Pointing the typings field in ${packageJson.name} to ${TYPINGS_PATH}` +); + +writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, { + encoding: 'utf-8' +}); diff --git a/scripts/prune-dts/tsconfig.eslint.json b/scripts/prune-dts/tsconfig.eslint.json new file mode 100644 index 00000000000..295364d2ed7 --- /dev/null +++ b/scripts/prune-dts/tsconfig.eslint.json @@ -0,0 +1,4 @@ +{ + "extends": "../../config/tsconfig.base.json", + "include": ["../../**/*.ts"] +} diff --git a/scripts/release/utils/yarn.ts b/scripts/release/utils/yarn.ts index a8ce5b488c7..e30e9e14eb5 100644 --- a/scripts/release/utils/yarn.ts +++ b/scripts/release/utils/yarn.ts @@ -32,7 +32,8 @@ export async function reinstallDeps() { export async function buildPackages() { const spinner = ora(' Building Packages').start(); await spawn('yarn', ['build:release'], { - cwd: root + cwd: root, + stdio: 'inherit' }); spinner.stopAndPersist({ symbol: '✅' diff --git a/scripts/size_report/report_binary_size.ts b/scripts/size_report/report_binary_size.ts index 9f569a2ae91..f2589630735 100644 --- a/scripts/size_report/report_binary_size.ts +++ b/scripts/size_report/report_binary_size.ts @@ -15,16 +15,21 @@ * limitations under the License. */ -import { resolve } from 'path'; import * as fs from 'fs'; -import { execSync } from 'child_process'; +import * as path from 'path'; +import * as rollup from 'rollup'; import * as terser from 'terser'; + +import { execSync } from 'child_process'; import { upload, runId, RequestBody, RequestEndpoint } from './size_report_helper'; +import { glob } from 'glob'; + +import commonjs from '@rollup/plugin-commonjs'; interface Report { sdk: string; @@ -35,10 +40,10 @@ interface BinarySizeRequestBody extends RequestBody { metric: string; results: Report[]; } -// CDN scripts + function generateReportForCDNScripts(): Report[] { const reports = []; - const firebaseRoot = resolve(__dirname, '../../packages/firebase'); + const firebaseRoot = path.resolve(__dirname, '../../packages/firebase'); const pkgJson = require(`${firebaseRoot}/package.json`); const special_files = [ @@ -58,102 +63,82 @@ function generateReportForCDNScripts(): Report[] { for (const file of files) { const { size } = fs.statSync(file); const fileName = file.split('/').slice(-1)[0]; - reports.push(makeReportObject('firebase', fileName, size)); + reports.push({ sdk: 'firebase', type: fileName, value: size }); } return reports; } -// NPM packages async function generateReportForNPMPackages(): Promise { const reports: Report[] = []; - const fields = [ - 'main', - 'module', - 'esm2017', - 'browser', - 'react-native', - 'lite', - 'lite-esm2017' - ]; - const packageInfo = JSON.parse( execSync('npx lerna ls --json --scope @firebase/*').toString() ); - - const taskPromises: Promise[] = []; - for (const pkg of packageInfo) { - // we traverse the dir in order to include binaries for submodules, e.g. @firebase/firestore/memory - // Currently we only traverse 1 level deep because we don't have any submodule deeper than that. - traverseDirs(pkg.location, collectBinarySize, 0, 1); + for (const info of packageInfo) { + const packages = await findAllPackages(info.location); + for (const pkg of packages) { + reports.push(...(await collectBinarySize(pkg))); + } } - - await Promise.all(taskPromises); - return reports; +} - function collectBinarySize(path: string) { - const packageJsonPath = `${path}/package.json`; - if (!fs.existsSync(packageJsonPath)) { - return; - } - - const promise = new Promise(async resolve => { - const packageJson = require(packageJsonPath); - - for (const field of fields) { - if (packageJson[field]) { - const filePath = `${path}/${packageJson[field]}`; - const rawCode = fs.readFileSync(filePath, 'utf-8'); - - // remove comments and whitespaces, then get size - const { code } = await terser.minify(rawCode, { - format: { - comments: false - }, - mangle: false, - compress: false - }); - - const size = Buffer.byteLength(code!, 'utf-8'); - reports.push(makeReportObject(packageJson.name, field, size)); +async function findAllPackages(root: string): Promise { + return new Promise((resolve, reject) => { + glob( + '**/package.json', + { cwd: root, ignore: '**/node_modules/**' }, + (err, files) => { + if (err) { + reject(err); + } else { + resolve(files.map(x => path.resolve(root, x))); } } - - resolve(); - }); - - taskPromises.push(promise); - } + ); + }); } -function traverseDirs( - path: string, - executor: Function, - level: number, - levelLimit: number -) { - if (level > levelLimit) { - return; - } - - executor(path); - - for (const name of fs.readdirSync(path)) { - const p = `${path}/${name}`; - - if (fs.lstatSync(p).isDirectory()) { - traverseDirs(p, executor, level + 1, levelLimit); +async function collectBinarySize(pkg: string): Promise { + const reports: Report[] = []; + const fields = [ + 'main', + 'module', + 'esm2017', + 'browser', + 'react-native', + 'lite', + 'lite-esm2017' + ]; + const json = require(pkg); + for (const field of fields) { + if (json[field]) { + const artifact = path.resolve(path.dirname(pkg), json[field]); + + // Need to create a bundle and get the size of the bundle instead of reading the size of the file directly. + // It is because some packages might be split into multiple files in order to share code between entry points. + const bundle = await rollup.rollup({ + input: artifact, + plugins: [commonjs()] + }); + + const { output } = await bundle.generate({ format: 'es' }); + const rawCode = output[0].code; + + // remove comments and whitespaces, then get size + const { code } = await terser.minify(rawCode, { + format: { + comments: false + }, + mangle: false, + compress: false + }); + + const size = Buffer.byteLength(code!, 'utf-8'); + reports.push({ sdk: json.name, type: field, value: size }); } } -} - -function makeReportObject(sdk: string, type: string, value: number): Report { - return { - sdk, - type, - value - }; + return reports; } async function generateSizeReport(): Promise { @@ -177,6 +162,11 @@ async function generateSizeReport(): Promise { }; } -generateSizeReport().then(report => { - upload(report, RequestEndpoint.BINARY_SIZE); -}); +generateSizeReport() + .then(report => { + upload(report, RequestEndpoint.BINARY_SIZE); + }) + .catch(err => { + console.error(err); + process.exit(1); + }); diff --git a/scripts/size_report/report_modular_export_binary_size.ts b/scripts/size_report/report_modular_export_binary_size.ts index 675ddae6a75..8b0e393621a 100644 --- a/scripts/size_report/report_modular_export_binary_size.ts +++ b/scripts/size_report/report_modular_export_binary_size.ts @@ -53,8 +53,13 @@ async function generateReport(): Promise { } async function main(): Promise { - const reports: ModularExportBinarySizeRequestBody = await generateReport(); - console.log(JSON.stringify(reports, null, 4)); - upload(reports, RequestEndpoint.MODULAR_EXPORT_BINARY_SIZE); + try { + const reports: ModularExportBinarySizeRequestBody = await generateReport(); + console.log(JSON.stringify(reports, null, 4)); + upload(reports, RequestEndpoint.MODULAR_EXPORT_BINARY_SIZE); + } catch (e) { + console.error(e); + process.exit(1); + } } main(); diff --git a/scripts/size_report/size_report_helper.ts b/scripts/size_report/size_report_helper.ts index 795e85f17af..beec1b604ce 100644 --- a/scripts/size_report/size_report_helper.ts +++ b/scripts/size_report/size_report_helper.ts @@ -77,7 +77,7 @@ export function upload( console.log(`Response status code: ${response.statusCode}`); response.on('data', console.log); response.on('end', () => { - if (response.statusCode !== 202) { + if (response.statusCode !== 200 && response.statusCode !== 202) { process.exit(1); } }); diff --git a/tools/gitHooks/license.js b/tools/gitHooks/license.js index 7203cf21375..ca693fbebe8 100644 --- a/tools/gitHooks/license.js +++ b/tools/gitHooks/license.js @@ -25,7 +25,7 @@ const root = resolve(__dirname, '../..'); const git = simpleGit(root); const licenseHeader = `/** * @license - * Copyright 2020 Google LLC + * Copyright 2021 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/yarn.lock b/yarn.lock index faea693ff22..a4e15f22d3b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3,395 +3,622 @@ "@apidevtools/json-schema-ref-parser@^9.0.3": - version "9.0.6" - resolved "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.6.tgz#5d9000a3ac1fd25404da886da6b266adcd99cf1c" - integrity sha512-M3YgsLjI0lZxvrpeGVk9Ap032W6TPQkH6pRAZz81Ac3WUNF79VQooAFnp8umjvVzUmD93NkogxEwbSce7qMsUg== + version "9.0.7" + resolved "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.7.tgz#64aa7f5b34e43d74ea9e408b90ddfba02050dde3" + integrity sha512-QdwOGF1+eeyFh+17v2Tz626WX0nucd1iKOm6JUTUvCZdbolblCOOQCxGrQPY0f7jEhn36PiAWqZnsC2r5vmUWg== dependencies: "@jsdevtools/ono" "^7.1.3" call-me-maybe "^1.0.1" js-yaml "^3.13.1" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" - integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== +"@babel/code-frame@7.12.11": + version "7.12.11" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" + integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== dependencies: "@babel/highlight" "^7.10.4" -"@babel/compat-data@^7.10.4", "@babel/compat-data@^7.11.0": - version "7.11.0" - resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.11.0.tgz#e9f73efe09af1355b723a7f39b11bad637d7c99c" - integrity sha512-TPSvJfv73ng0pfnEOh17bYMPQbI95+nGWc71Ss4vZdRBHTDqmM9Z8ZV4rYz8Ks7sfzc95n30k6ODIq5UGnXcYQ== - dependencies: - browserslist "^4.12.0" - invariant "^2.2.4" - semver "^5.5.0" +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13": + version "7.12.13" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658" + integrity sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g== + dependencies: + "@babel/highlight" "^7.12.13" + +"@babel/code-frame@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb" + integrity sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw== + dependencies: + "@babel/highlight" "^7.14.5" + +"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.14.5", "@babel/compat-data@^7.14.7": + version "7.14.7" + resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.7.tgz#7b047d7a3a89a67d2258dc61f604f098f1bc7e08" + integrity sha512-nS6dZaISCXJ3+518CWiBfEr//gHyMO02uDxBkXTKZDN5POruCnOZ1N4YBRZDCabwF8nZMWBpRxIicmXtBs+fvw== + +"@babel/compat-data@^7.13.15": + version "7.14.0" + resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.0.tgz#a901128bce2ad02565df95e6ecbf195cf9465919" + integrity sha512-vu9V3uMM/1o5Hl5OekMUowo3FqXLJSw+s+66nt0fSWVWTtmosdzn45JHOB3cPtZoe6CTBDzvSw0RdOY85Q37+Q== + +"@babel/core@7.14.6": + version "7.14.6" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.14.6.tgz#e0814ec1a950032ff16c13a2721de39a8416fcab" + integrity sha512-gJnOEWSqTk96qG5BoIrl5bVtc23DCycmIePPYnamY9RboYdI4nFy5vAQMSl81O5K/W0sLDWfGysnOECC+KUUCA== + dependencies: + "@babel/code-frame" "^7.14.5" + "@babel/generator" "^7.14.5" + "@babel/helper-compilation-targets" "^7.14.5" + "@babel/helper-module-transforms" "^7.14.5" + "@babel/helpers" "^7.14.6" + "@babel/parser" "^7.14.6" + "@babel/template" "^7.14.5" + "@babel/traverse" "^7.14.5" + "@babel/types" "^7.14.5" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.1.2" + semver "^6.3.0" + source-map "^0.5.0" -"@babel/core@7.11.6", "@babel/core@^7.7.5": - version "7.11.6" - resolved "https://registry.npmjs.org/@babel/core/-/core-7.11.6.tgz#3a9455dc7387ff1bac45770650bc13ba04a15651" - integrity sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg== - dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/generator" "^7.11.6" - "@babel/helper-module-transforms" "^7.11.0" - "@babel/helpers" "^7.10.4" - "@babel/parser" "^7.11.5" - "@babel/template" "^7.10.4" - "@babel/traverse" "^7.11.5" - "@babel/types" "^7.11.5" +"@babel/core@^7.7.5": + version "7.14.3" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.14.3.tgz#5395e30405f0776067fbd9cf0884f15bfb770a38" + integrity sha512-jB5AmTKOCSJIZ72sd78ECEhuPiDMKlQdDI/4QRI6lzYATx5SSogS1oQA2AoPecRCknm30gHi2l+QVvNUu3wZAg== + dependencies: + "@babel/code-frame" "^7.12.13" + "@babel/generator" "^7.14.3" + "@babel/helper-compilation-targets" "^7.13.16" + "@babel/helper-module-transforms" "^7.14.2" + "@babel/helpers" "^7.14.0" + "@babel/parser" "^7.14.3" + "@babel/template" "^7.12.13" + "@babel/traverse" "^7.14.2" + "@babel/types" "^7.14.2" convert-source-map "^1.7.0" debug "^4.1.0" - gensync "^1.0.0-beta.1" + gensync "^1.0.0-beta.2" json5 "^2.1.2" - lodash "^4.17.19" - resolve "^1.3.2" - semver "^5.4.1" + semver "^6.3.0" source-map "^0.5.0" -"@babel/generator@^7.11.5", "@babel/generator@^7.11.6", "@babel/generator@^7.4.0": - version "7.11.6" - resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz#b868900f81b163b4d464ea24545c61cbac4dc620" - integrity sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA== +"@babel/generator@^7.14.2", "@babel/generator@^7.14.3", "@babel/generator@^7.4.0": + version "7.14.3" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.14.3.tgz#0c2652d91f7bddab7cccc6ba8157e4f40dcedb91" + integrity sha512-bn0S6flG/j0xtQdz3hsjJ624h3W0r3llttBMfyHX3YrZ/KtLYr15bjA0FXkgW7FpvrDuTuElXeVjiKlYRpnOFA== dependencies: - "@babel/types" "^7.11.5" + "@babel/types" "^7.14.2" jsesc "^2.5.1" source-map "^0.5.0" -"@babel/helper-annotate-as-pure@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz#5bf0d495a3f757ac3bda48b5bf3b3ba309c72ba3" - integrity sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA== - dependencies: - "@babel/types" "^7.10.4" - -"@babel/helper-builder-binary-assignment-operator-visitor@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz#bb0b75f31bf98cbf9ff143c1ae578b87274ae1a3" - integrity sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg== - dependencies: - "@babel/helper-explode-assignable-expression" "^7.10.4" - "@babel/types" "^7.10.4" - -"@babel/helper-compilation-targets@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz#804ae8e3f04376607cc791b9d47d540276332bd2" - integrity sha512-a3rYhlsGV0UHNDvrtOXBg8/OpfV0OKTkxKPzIplS1zpx7CygDcWWxckxZeDd3gzPzC4kUT0A4nVFDK0wGMh4MQ== - dependencies: - "@babel/compat-data" "^7.10.4" - browserslist "^4.12.0" - invariant "^2.2.4" - levenary "^1.1.1" - semver "^5.5.0" - -"@babel/helper-create-class-features-plugin@^7.10.4": - version "7.10.5" - resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz#9f61446ba80e8240b0a5c85c6fdac8459d6f259d" - integrity sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A== - dependencies: - "@babel/helper-function-name" "^7.10.4" - "@babel/helper-member-expression-to-functions" "^7.10.5" - "@babel/helper-optimise-call-expression" "^7.10.4" - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-replace-supers" "^7.10.4" - "@babel/helper-split-export-declaration" "^7.10.4" - -"@babel/helper-create-regexp-features-plugin@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz#fdd60d88524659a0b6959c0579925e425714f3b8" - integrity sha512-2/hu58IEPKeoLF45DBwx3XFqsbCXmkdAay4spVr2x0jYgRxrSNp+ePwvSsy9g6YSaNDcKIQVPXk1Ov8S2edk2g== - dependencies: - "@babel/helper-annotate-as-pure" "^7.10.4" - "@babel/helper-regex" "^7.10.4" - regexpu-core "^4.7.0" - -"@babel/helper-define-map@^7.10.4": - version "7.10.5" - resolved "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz#b53c10db78a640800152692b13393147acb9bb30" - integrity sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ== - dependencies: - "@babel/helper-function-name" "^7.10.4" - "@babel/types" "^7.10.5" - lodash "^4.17.19" - -"@babel/helper-explode-assignable-expression@^7.10.4": - version "7.11.4" - resolved "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.11.4.tgz#2d8e3470252cc17aba917ede7803d4a7a276a41b" - integrity sha512-ux9hm3zR4WV1Y3xXxXkdG/0gxF9nvI0YVmKVhvK9AfMoaQkemL3sJpXw+Xbz65azo8qJiEz2XVDUpK3KYhH3ZQ== - dependencies: - "@babel/types" "^7.10.4" - -"@babel/helper-function-name@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz#d2d3b20c59ad8c47112fa7d2a94bc09d5ef82f1a" - integrity sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ== - dependencies: - "@babel/helper-get-function-arity" "^7.10.4" - "@babel/template" "^7.10.4" - "@babel/types" "^7.10.4" - -"@babel/helper-get-function-arity@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz#98c1cbea0e2332f33f9a4661b8ce1505b2c19ba2" - integrity sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A== - dependencies: - "@babel/types" "^7.10.4" - -"@babel/helper-hoist-variables@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz#d49b001d1d5a68ca5e6604dda01a6297f7c9381e" - integrity sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA== - dependencies: - "@babel/types" "^7.10.4" - -"@babel/helper-member-expression-to-functions@^7.10.4", "@babel/helper-member-expression-to-functions@^7.10.5": - version "7.11.0" - resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz#ae69c83d84ee82f4b42f96e2a09410935a8f26df" - integrity sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q== +"@babel/generator@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz#848d7b9f031caca9d0cd0af01b063f226f52d785" + integrity sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA== dependencies: - "@babel/types" "^7.11.0" - -"@babel/helper-module-imports@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz#4c5c54be04bd31670a7382797d75b9fa2e5b5620" - integrity sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw== - dependencies: - "@babel/types" "^7.10.4" - -"@babel/helper-module-transforms@^7.10.4", "@babel/helper-module-transforms@^7.10.5", "@babel/helper-module-transforms@^7.11.0": - version "7.11.0" - resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz#b16f250229e47211abdd84b34b64737c2ab2d359" - integrity sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg== - dependencies: - "@babel/helper-module-imports" "^7.10.4" - "@babel/helper-replace-supers" "^7.10.4" - "@babel/helper-simple-access" "^7.10.4" - "@babel/helper-split-export-declaration" "^7.11.0" - "@babel/template" "^7.10.4" - "@babel/types" "^7.11.0" - lodash "^4.17.19" - -"@babel/helper-optimise-call-expression@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz#50dc96413d594f995a77905905b05893cd779673" - integrity sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg== - dependencies: - "@babel/types" "^7.10.4" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375" - integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg== + "@babel/types" "^7.14.5" + jsesc "^2.5.1" + source-map "^0.5.0" -"@babel/helper-regex@^7.10.4": - version "7.10.5" - resolved "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.5.tgz#32dfbb79899073c415557053a19bd055aae50ae0" - integrity sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg== +"@babel/helper-annotate-as-pure@^7.12.13": + version "7.12.13" + resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz#0f58e86dfc4bb3b1fcd7db806570e177d439b6ab" + integrity sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw== dependencies: - lodash "^4.17.19" + "@babel/types" "^7.12.13" -"@babel/helper-remap-async-to-generator@^7.10.4": - version "7.11.4" - resolved "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.11.4.tgz#4474ea9f7438f18575e30b0cac784045b402a12d" - integrity sha512-tR5vJ/vBa9wFy3m5LLv2faapJLnDFxNWff2SAYkSE4rLUdbp7CdObYFgI7wK4T/Mj4UzpjPwzR8Pzmr5m7MHGA== +"@babel/helper-annotate-as-pure@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.14.5.tgz#7bf478ec3b71726d56a8ca5775b046fc29879e61" + integrity sha512-EivH9EgBIb+G8ij1B2jAwSH36WnGvkQSEC6CkX/6v6ZFlw5fVOHvsgGF4uiEHO2GzMvunZb6tDLQEQSdrdocrA== dependencies: - "@babel/helper-annotate-as-pure" "^7.10.4" - "@babel/helper-wrap-function" "^7.10.4" - "@babel/template" "^7.10.4" - "@babel/types" "^7.10.4" + "@babel/types" "^7.14.5" -"@babel/helper-replace-supers@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz#d585cd9388ea06e6031e4cd44b6713cbead9e6cf" - integrity sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A== +"@babel/helper-builder-binary-assignment-operator-visitor@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.14.5.tgz#b939b43f8c37765443a19ae74ad8b15978e0a191" + integrity sha512-YTA/Twn0vBXDVGJuAX6PwW7x5zQei1luDDo2Pl6q1qZ7hVNl0RZrhHCQG/ArGpR29Vl7ETiB8eJyrvpuRp300w== dependencies: - "@babel/helper-member-expression-to-functions" "^7.10.4" - "@babel/helper-optimise-call-expression" "^7.10.4" - "@babel/traverse" "^7.10.4" - "@babel/types" "^7.10.4" + "@babel/helper-explode-assignable-expression" "^7.14.5" + "@babel/types" "^7.14.5" -"@babel/helper-simple-access@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz#0f5ccda2945277a2a7a2d3a821e15395edcf3461" - integrity sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw== +"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.5.tgz#7a99c5d0967911e972fe2c3411f7d5b498498ecf" + integrity sha512-v+QtZqXEiOnpO6EYvlImB6zCD2Lel06RzOPzmkz/D/XgQiUu3C/Jb1LOqSt/AIA34TYi/Q+KlT8vTQrgdxkbLw== dependencies: - "@babel/template" "^7.10.4" - "@babel/types" "^7.10.4" + "@babel/compat-data" "^7.14.5" + "@babel/helper-validator-option" "^7.14.5" + browserslist "^4.16.6" + semver "^6.3.0" -"@babel/helper-skip-transparent-expression-wrappers@^7.11.0": - version "7.11.0" - resolved "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.11.0.tgz#eec162f112c2f58d3af0af125e3bb57665146729" - integrity sha512-0XIdiQln4Elglgjbwo9wuJpL/K7AGCY26kmEt0+pRP0TAj4jjyNq1MjoRvikrTVqKcx4Gysxt4cXvVFXP/JO2Q== +"@babel/helper-compilation-targets@^7.13.16": + version "7.13.16" + resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.16.tgz#6e91dccf15e3f43e5556dffe32d860109887563c" + integrity sha512-3gmkYIrpqsLlieFwjkGgLaSHmhnvlAYzZLlYVjlW+QwI+1zE17kGxuJGmIqDQdYp56XdmGeD+Bswx0UTyG18xA== dependencies: - "@babel/types" "^7.11.0" + "@babel/compat-data" "^7.13.15" + "@babel/helper-validator-option" "^7.12.17" + browserslist "^4.14.5" + semver "^6.3.0" -"@babel/helper-split-export-declaration@^7.10.4", "@babel/helper-split-export-declaration@^7.11.0": - version "7.11.0" - resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz#f8a491244acf6a676158ac42072911ba83ad099f" - integrity sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg== +"@babel/helper-create-class-features-plugin@^7.14.5": + version "7.14.6" + resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.6.tgz#f114469b6c06f8b5c59c6c4e74621f5085362542" + integrity sha512-Z6gsfGofTxH/+LQXqYEK45kxmcensbzmk/oi8DmaQytlQCgqNZt9XQF8iqlI/SeXWVjaMNxvYvzaYw+kh42mDg== dependencies: - "@babel/types" "^7.11.0" + "@babel/helper-annotate-as-pure" "^7.14.5" + "@babel/helper-function-name" "^7.14.5" + "@babel/helper-member-expression-to-functions" "^7.14.5" + "@babel/helper-optimise-call-expression" "^7.14.5" + "@babel/helper-replace-supers" "^7.14.5" + "@babel/helper-split-export-declaration" "^7.14.5" -"@babel/helper-validator-identifier@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2" - integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw== - -"@babel/helper-wrap-function@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz#8a6f701eab0ff39f765b5a1cfef409990e624b87" - integrity sha512-6py45WvEF0MhiLrdxtRjKjufwLL1/ob2qDJgg5JgNdojBAZSAKnAjkyOCNug6n+OBl4VW76XjvgSFTdaMcW0Ug== +"@babel/helper-create-regexp-features-plugin@^7.12.13": + version "7.14.3" + resolved "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.14.3.tgz#149aa6d78c016e318c43e2409a0ae9c136a86688" + integrity sha512-JIB2+XJrb7v3zceV2XzDhGIB902CmKGSpSl4q2C6agU9SNLG/2V1RtFRGPG1Ajh9STj3+q6zJMOC+N/pp2P9DA== dependencies: - "@babel/helper-function-name" "^7.10.4" - "@babel/template" "^7.10.4" - "@babel/traverse" "^7.10.4" - "@babel/types" "^7.10.4" + "@babel/helper-annotate-as-pure" "^7.12.13" + regexpu-core "^4.7.1" -"@babel/helpers@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz#2abeb0d721aff7c0a97376b9e1f6f65d7a475044" - integrity sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA== +"@babel/helper-create-regexp-features-plugin@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.14.5.tgz#c7d5ac5e9cf621c26057722fb7a8a4c5889358c4" + integrity sha512-TLawwqpOErY2HhWbGJ2nZT5wSkR192QpN+nBg1THfBfftrlvOh+WbhrxXCH4q4xJ9Gl16BGPR/48JA+Ryiho/A== dependencies: - "@babel/template" "^7.10.4" - "@babel/traverse" "^7.10.4" - "@babel/types" "^7.10.4" + "@babel/helper-annotate-as-pure" "^7.14.5" + regexpu-core "^4.7.1" -"@babel/highlight@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143" - integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA== +"@babel/helper-define-polyfill-provider@^0.2.2": + version "0.2.3" + resolved "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.3.tgz#0525edec5094653a282688d34d846e4c75e9c0b6" + integrity sha512-RH3QDAfRMzj7+0Nqu5oqgO5q9mFtQEVvCRsi8qCEfzLR9p2BHfn5FzhSB2oj1fF7I2+DcTORkYaQ6aTR9Cofew== dependencies: - "@babel/helper-validator-identifier" "^7.10.4" + "@babel/helper-compilation-targets" "^7.13.0" + "@babel/helper-module-imports" "^7.12.13" + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/traverse" "^7.13.0" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + semver "^6.1.2" + +"@babel/helper-explode-assignable-expression@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.14.5.tgz#8aa72e708205c7bb643e45c73b4386cdf2a1f645" + integrity sha512-Htb24gnGJdIGT4vnRKMdoXiOIlqOLmdiUYpAQ0mYfgVT/GDm8GOYhgi4GL+hMKrkiPRohO4ts34ELFsGAPQLDQ== + dependencies: + "@babel/types" "^7.14.5" + +"@babel/helper-function-name@^7.14.2": + version "7.14.2" + resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.2.tgz#397688b590760b6ef7725b5f0860c82427ebaac2" + integrity sha512-NYZlkZRydxw+YT56IlhIcS8PAhb+FEUiOzuhFTfqDyPmzAhRge6ua0dQYT/Uh0t/EDHq05/i+e5M2d4XvjgarQ== + dependencies: + "@babel/helper-get-function-arity" "^7.12.13" + "@babel/template" "^7.12.13" + "@babel/types" "^7.14.2" + +"@babel/helper-function-name@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz#89e2c474972f15d8e233b52ee8c480e2cfcd50c4" + integrity sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ== + dependencies: + "@babel/helper-get-function-arity" "^7.14.5" + "@babel/template" "^7.14.5" + "@babel/types" "^7.14.5" + +"@babel/helper-get-function-arity@^7.12.13": + version "7.12.13" + resolved "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz#bc63451d403a3b3082b97e1d8b3fe5bd4091e583" + integrity sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg== + dependencies: + "@babel/types" "^7.12.13" + +"@babel/helper-get-function-arity@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz#25fbfa579b0937eee1f3b805ece4ce398c431815" + integrity sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg== + dependencies: + "@babel/types" "^7.14.5" + +"@babel/helper-hoist-variables@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz#e0dd27c33a78e577d7c8884916a3e7ef1f7c7f8d" + integrity sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ== + dependencies: + "@babel/types" "^7.14.5" + +"@babel/helper-member-expression-to-functions@^7.13.12": + version "7.13.12" + resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz#dfe368f26d426a07299d8d6513821768216e6d72" + integrity sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw== + dependencies: + "@babel/types" "^7.13.12" + +"@babel/helper-member-expression-to-functions@^7.14.5": + version "7.14.7" + resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.7.tgz#97e56244beb94211fe277bd818e3a329c66f7970" + integrity sha512-TMUt4xKxJn6ccjcOW7c4hlwyJArizskAhoSTOCkA0uZ+KghIaci0Qg9R043kUMWI9mtQfgny+NQ5QATnZ+paaA== + dependencies: + "@babel/types" "^7.14.5" + +"@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.13.12": + version "7.13.12" + resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz#c6a369a6f3621cb25da014078684da9196b61977" + integrity sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA== + dependencies: + "@babel/types" "^7.13.12" + +"@babel/helper-module-imports@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz#6d1a44df6a38c957aa7c312da076429f11b422f3" + integrity sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ== + dependencies: + "@babel/types" "^7.14.5" + +"@babel/helper-module-transforms@^7.14.2": + version "7.14.2" + resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.2.tgz#ac1cc30ee47b945e3e0c4db12fa0c5389509dfe5" + integrity sha512-OznJUda/soKXv0XhpvzGWDnml4Qnwp16GN+D/kZIdLsWoHj05kyu8Rm5kXmMef+rVJZ0+4pSGLkeixdqNUATDA== + dependencies: + "@babel/helper-module-imports" "^7.13.12" + "@babel/helper-replace-supers" "^7.13.12" + "@babel/helper-simple-access" "^7.13.12" + "@babel/helper-split-export-declaration" "^7.12.13" + "@babel/helper-validator-identifier" "^7.14.0" + "@babel/template" "^7.12.13" + "@babel/traverse" "^7.14.2" + "@babel/types" "^7.14.2" + +"@babel/helper-module-transforms@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.5.tgz#7de42f10d789b423eb902ebd24031ca77cb1e10e" + integrity sha512-iXpX4KW8LVODuAieD7MzhNjmM6dzYY5tfRqT+R9HDXWl0jPn/djKmA+G9s/2C2T9zggw5tK1QNqZ70USfedOwA== + dependencies: + "@babel/helper-module-imports" "^7.14.5" + "@babel/helper-replace-supers" "^7.14.5" + "@babel/helper-simple-access" "^7.14.5" + "@babel/helper-split-export-declaration" "^7.14.5" + "@babel/helper-validator-identifier" "^7.14.5" + "@babel/template" "^7.14.5" + "@babel/traverse" "^7.14.5" + "@babel/types" "^7.14.5" + +"@babel/helper-optimise-call-expression@^7.12.13": + version "7.12.13" + resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz#5c02d171b4c8615b1e7163f888c1c81c30a2aaea" + integrity sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA== + dependencies: + "@babel/types" "^7.12.13" + +"@babel/helper-optimise-call-expression@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz#f27395a8619e0665b3f0364cddb41c25d71b499c" + integrity sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA== + dependencies: + "@babel/types" "^7.14.5" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.13.0" + resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz#806526ce125aed03373bc416a828321e3a6a33af" + integrity sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ== + +"@babel/helper-plugin-utils@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9" + integrity sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ== + +"@babel/helper-remap-async-to-generator@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.14.5.tgz#51439c913612958f54a987a4ffc9ee587a2045d6" + integrity sha512-rLQKdQU+HYlxBwQIj8dk4/0ENOUEhA/Z0l4hN8BexpvmSMN9oA9EagjnhnDpNsRdWCfjwa4mn/HyBXO9yhQP6A== + dependencies: + "@babel/helper-annotate-as-pure" "^7.14.5" + "@babel/helper-wrap-function" "^7.14.5" + "@babel/types" "^7.14.5" + +"@babel/helper-replace-supers@^7.13.12": + version "7.14.3" + resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.3.tgz#ca17b318b859d107f0e9b722d58cf12d94436600" + integrity sha512-Rlh8qEWZSTfdz+tgNV/N4gz1a0TMNwCUcENhMjHTHKp3LseYH5Jha0NSlyTQWMnjbYcwFt+bqAMqSLHVXkQ6UA== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.13.12" + "@babel/helper-optimise-call-expression" "^7.12.13" + "@babel/traverse" "^7.14.2" + "@babel/types" "^7.14.2" + +"@babel/helper-replace-supers@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.5.tgz#0ecc0b03c41cd567b4024ea016134c28414abb94" + integrity sha512-3i1Qe9/8x/hCHINujn+iuHy+mMRLoc77b2nI9TB0zjH1hvn9qGlXjWlggdwUcju36PkPCy/lpM7LLUdcTyH4Ow== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.14.5" + "@babel/helper-optimise-call-expression" "^7.14.5" + "@babel/traverse" "^7.14.5" + "@babel/types" "^7.14.5" + +"@babel/helper-simple-access@^7.13.12": + version "7.13.12" + resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz#dd6c538afb61819d205a012c31792a39c7a5eaf6" + integrity sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA== + dependencies: + "@babel/types" "^7.13.12" + +"@babel/helper-simple-access@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.14.5.tgz#66ea85cf53ba0b4e588ba77fc813f53abcaa41c4" + integrity sha512-nfBN9xvmCt6nrMZjfhkl7i0oTV3yxR4/FztsbOASyTvVcoYd0TRHh7eMLdlEcCqobydC0LAF3LtC92Iwxo0wyw== + dependencies: + "@babel/types" "^7.14.5" + +"@babel/helper-skip-transparent-expression-wrappers@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.14.5.tgz#96f486ac050ca9f44b009fbe5b7d394cab3a0ee4" + integrity sha512-dmqZB7mrb94PZSAOYtr+ZN5qt5owZIAgqtoTuqiFbHFtxgEcmQlRJVI+bO++fciBunXtB6MK7HrzrfcAzIz2NQ== + dependencies: + "@babel/types" "^7.14.5" + +"@babel/helper-split-export-declaration@^7.12.13": + version "7.12.13" + resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz#e9430be00baf3e88b0e13e6f9d4eaf2136372b05" + integrity sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg== + dependencies: + "@babel/types" "^7.12.13" + +"@babel/helper-split-export-declaration@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz#22b23a54ef51c2b7605d851930c1976dd0bc693a" + integrity sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA== + dependencies: + "@babel/types" "^7.14.5" + +"@babel/helper-validator-identifier@^7.14.0": + version "7.14.0" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz#d26cad8a47c65286b15df1547319a5d0bcf27288" + integrity sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A== + +"@babel/helper-validator-identifier@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz#d0f0e277c512e0c938277faa85a3968c9a44c0e8" + integrity sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg== + +"@babel/helper-validator-option@^7.12.17": + version "7.12.17" + resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz#d1fbf012e1a79b7eebbfdc6d270baaf8d9eb9831" + integrity sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw== + +"@babel/helper-validator-option@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3" + integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow== + +"@babel/helper-wrap-function@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.14.5.tgz#5919d115bf0fe328b8a5d63bcb610f51601f2bff" + integrity sha512-YEdjTCq+LNuNS1WfxsDCNpgXkJaIyqco6DAelTUjT4f2KIWC1nBcaCaSdHTBqQVLnTBexBcVcFhLSU1KnYuePQ== + dependencies: + "@babel/helper-function-name" "^7.14.5" + "@babel/template" "^7.14.5" + "@babel/traverse" "^7.14.5" + "@babel/types" "^7.14.5" + +"@babel/helpers@^7.14.0": + version "7.14.0" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.0.tgz#ea9b6be9478a13d6f961dbb5f36bf75e2f3b8f62" + integrity sha512-+ufuXprtQ1D1iZTO/K9+EBRn+qPWMJjZSw/S0KlFrxCw4tkrzv9grgpDHkY9MeQTjTY8i2sp7Jep8DfU6tN9Mg== + dependencies: + "@babel/template" "^7.12.13" + "@babel/traverse" "^7.14.0" + "@babel/types" "^7.14.0" + +"@babel/helpers@^7.14.6": + version "7.14.6" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.6.tgz#5b58306b95f1b47e2a0199434fa8658fa6c21635" + integrity sha512-yesp1ENQBiLI+iYHSJdoZKUtRpfTlL1grDIX9NRlAVppljLw/4tTyYupIB7uIYmC3stW/imAv8EqaKaS/ibmeA== + dependencies: + "@babel/template" "^7.14.5" + "@babel/traverse" "^7.14.5" + "@babel/types" "^7.14.5" + +"@babel/highlight@^7.10.4", "@babel/highlight@^7.12.13": + version "7.14.0" + resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz#3197e375711ef6bf834e67d0daec88e4f46113cf" + integrity sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg== + dependencies: + "@babel/helper-validator-identifier" "^7.14.0" chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.10.4", "@babel/parser@^7.11.5", "@babel/parser@^7.4.3": - version "7.11.5" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz#c7ff6303df71080ec7a4f5b8c003c58f1cf51037" - integrity sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q== - -"@babel/plugin-proposal-async-generator-functions@^7.10.4": - version "7.10.5" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz#3491cabf2f7c179ab820606cec27fed15e0e8558" - integrity sha512-cNMCVezQbrRGvXJwm9fu/1sJj9bHdGAgKodZdLqOQIpfoH3raqmRPBM17+lh7CzhiKRRBrGtZL9WcjxSoGYUSg== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-remap-async-to-generator" "^7.10.4" - "@babel/plugin-syntax-async-generators" "^7.8.0" - -"@babel/plugin-proposal-class-properties@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz#a33bf632da390a59c7a8c570045d1115cd778807" - integrity sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg== +"@babel/highlight@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9" + integrity sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg== dependencies: - "@babel/helper-create-class-features-plugin" "^7.10.4" - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-proposal-dynamic-import@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz#ba57a26cb98b37741e9d5bca1b8b0ddf8291f17e" - integrity sha512-up6oID1LeidOOASNXgv/CFbgBqTuKJ0cJjz6An5tWD+NVBNlp3VNSBxv2ZdU7SYl3NxJC7agAQDApZusV6uFwQ== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-dynamic-import" "^7.8.0" + "@babel/helper-validator-identifier" "^7.14.5" + chalk "^2.0.0" + js-tokens "^4.0.0" -"@babel/plugin-proposal-export-namespace-from@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.10.4.tgz#570d883b91031637b3e2958eea3c438e62c05f54" - integrity sha512-aNdf0LY6/3WXkhh0Fdb6Zk9j1NMD8ovj3F6r0+3j837Pn1S1PdNtcwJ5EG9WkVPNHPxyJDaxMaAOVq4eki0qbg== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" +"@babel/parser@^7.12.13", "@babel/parser@^7.14.2", "@babel/parser@^7.14.3", "@babel/parser@^7.4.3": + version "7.14.3" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.14.3.tgz#9b530eecb071fd0c93519df25c5ff9f14759f298" + integrity sha512-7MpZDIfI7sUC5zWo2+foJ50CSI5lcqDehZ0lVgIhSi4bFEk94fLAKlF3Q0nzSQQ+ca0lm+O6G9ztKVBeu8PMRQ== + +"@babel/parser@^7.14.5", "@babel/parser@^7.14.6", "@babel/parser@^7.14.7": + version "7.14.7" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz#6099720c8839ca865a2637e6c85852ead0bdb595" + integrity sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA== + +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.14.5.tgz#4b467302e1548ed3b1be43beae2cc9cf45e0bb7e" + integrity sha512-ZoJS2XCKPBfTmL122iP6NM9dOg+d4lc9fFk3zxc8iDjvt8Pk4+TlsHSKhIPf6X+L5ORCdBzqMZDjL/WHj7WknQ== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.14.5" + "@babel/plugin-proposal-optional-chaining" "^7.14.5" + +"@babel/plugin-proposal-async-generator-functions@^7.14.7": + version "7.14.7" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.7.tgz#784a48c3d8ed073f65adcf30b57bcbf6c8119ace" + integrity sha512-RK8Wj7lXLY3bqei69/cc25gwS5puEc3dknoFPFbqfy3XxYQBQFvu4ioWpafMBAB+L9NyptQK4nMOa5Xz16og8Q== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/helper-remap-async-to-generator" "^7.14.5" + "@babel/plugin-syntax-async-generators" "^7.8.4" + +"@babel/plugin-proposal-class-properties@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.14.5.tgz#40d1ee140c5b1e31a350f4f5eed945096559b42e" + integrity sha512-q/PLpv5Ko4dVc1LYMpCY7RVAAO4uk55qPwrIuJ5QJ8c6cVuAmhu7I/49JOppXL6gXf7ZHzpRVEUZdYoPLM04Gg== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-proposal-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.14.5.tgz#158e9e10d449c3849ef3ecde94a03d9f1841b681" + integrity sha512-KBAH5ksEnYHCegqseI5N9skTdxgJdmDoAOc0uXa+4QMYKeZD0w5IARh4FMlTNtaHhbB8v+KzMdTgxMMzsIy6Yg== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + +"@babel/plugin-proposal-dynamic-import@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.14.5.tgz#0c6617df461c0c1f8fff3b47cd59772360101d2c" + integrity sha512-ExjiNYc3HDN5PXJx+bwC50GIx/KKanX2HiggnIUAYedbARdImiCU4RhhHfdf0Kd7JNXGpsBBBCOm+bBVy3Gb0g== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + +"@babel/plugin-proposal-export-namespace-from@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.14.5.tgz#dbad244310ce6ccd083072167d8cea83a52faf76" + integrity sha512-g5POA32bXPMmSBu5Dx/iZGLGnKmKPc5AiY7qfZgurzrCYgIztDlHFbznSNCoQuv57YQLnQfaDi7dxCtLDIdXdA== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" -"@babel/plugin-proposal-json-strings@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.4.tgz#593e59c63528160233bd321b1aebe0820c2341db" - integrity sha512-fCL7QF0Jo83uy1K0P2YXrfX11tj3lkpN7l4dMv9Y9VkowkhkQDwFHFd8IiwyK5MZjE8UpbgokkgtcReH88Abaw== +"@babel/plugin-proposal-json-strings@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.14.5.tgz#38de60db362e83a3d8c944ac858ddf9f0c2239eb" + integrity sha512-NSq2fczJYKVRIsUJyNxrVUMhB27zb7N7pOFGQOhBKJrChbGcgEAqyZrmZswkPk18VMurEeJAaICbfm57vUeTbQ== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-json-strings" "^7.8.0" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/plugin-syntax-json-strings" "^7.8.3" -"@babel/plugin-proposal-logical-assignment-operators@^7.11.0": - version "7.11.0" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.11.0.tgz#9f80e482c03083c87125dee10026b58527ea20c8" - integrity sha512-/f8p4z+Auz0Uaf+i8Ekf1iM7wUNLcViFUGiPxKeXvxTSl63B875YPiVdUDdem7hREcI0E0kSpEhS8tF5RphK7Q== +"@babel/plugin-proposal-logical-assignment-operators@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.14.5.tgz#6e6229c2a99b02ab2915f82571e0cc646a40c738" + integrity sha512-YGn2AvZAo9TwyhlLvCCWxD90Xq8xJ4aSgaX3G5D/8DW94L8aaT+dS5cSP+Z06+rCJERGSr9GxMBZ601xoc2taw== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" -"@babel/plugin-proposal-nullish-coalescing-operator@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz#02a7e961fc32e6d5b2db0649e01bf80ddee7e04a" - integrity sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw== +"@babel/plugin-proposal-nullish-coalescing-operator@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.14.5.tgz#ee38589ce00e2cc59b299ec3ea406fcd3a0fdaf6" + integrity sha512-gun/SOnMqjSb98Nkaq2rTKMwervfdAoz6NphdY0vTfuzMfryj+tDGb2n6UkDKwez+Y8PZDhE3D143v6Gepp4Hg== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" -"@babel/plugin-proposal-numeric-separator@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.4.tgz#ce1590ff0a65ad12970a609d78855e9a4c1aef06" - integrity sha512-73/G7QoRoeNkLZFxsoCCvlg4ezE4eM+57PnOqgaPOozd5myfj7p0muD1mRVJvbUWbOzD+q3No2bWbaKy+DJ8DA== +"@babel/plugin-proposal-numeric-separator@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.14.5.tgz#83631bf33d9a51df184c2102a069ac0c58c05f18" + integrity sha512-yiclALKe0vyZRZE0pS6RXgjUOt87GWv6FYa5zqj15PvhOGFO69R5DusPlgK/1K5dVnCtegTiWu9UaBSrLLJJBg== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-numeric-separator" "^7.10.4" -"@babel/plugin-proposal-object-rest-spread@^7.11.0": - version "7.11.0" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.11.0.tgz#bd81f95a1f746760ea43b6c2d3d62b11790ad0af" - integrity sha512-wzch41N4yztwoRw0ak+37wxwJM2oiIiy6huGCoqkvSTA9acYWcPfn9Y4aJqmFFJ70KTJUu29f3DQ43uJ9HXzEA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.0" - "@babel/plugin-transform-parameters" "^7.10.4" - -"@babel/plugin-proposal-optional-catch-binding@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.4.tgz#31c938309d24a78a49d68fdabffaa863758554dd" - integrity sha512-LflT6nPh+GK2MnFiKDyLiqSqVHkQnVf7hdoAvyTnnKj9xB3docGRsdPuxp6qqqW19ifK3xgc9U5/FwrSaCNX5g== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" - -"@babel/plugin-proposal-optional-chaining@^7.11.0": - version "7.11.0" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.11.0.tgz#de5866d0646f6afdaab8a566382fe3a221755076" - integrity sha512-v9fZIu3Y8562RRwhm1BbMRxtqZNFmFA2EG+pT2diuU8PT3H6T/KXoZ54KgYisfOFZHV6PfvAiBIZ9Rcz+/JCxA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-skip-transparent-expression-wrappers" "^7.11.0" - "@babel/plugin-syntax-optional-chaining" "^7.8.0" - -"@babel/plugin-proposal-private-methods@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.4.tgz#b160d972b8fdba5c7d111a145fc8c421fc2a6909" - integrity sha512-wh5GJleuI8k3emgTg5KkJK6kHNsGEr0uBTDBuQUBJwckk9xs1ez79ioheEVVxMLyPscB0LfkbVHslQqIzWV6Bw== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.10.4" - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-proposal-unicode-property-regex@^7.10.4", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.4.tgz#4483cda53041ce3413b7fe2f00022665ddfaa75d" - integrity sha512-H+3fOgPnEXFL9zGYtKQe4IDOPKYlZdF1kqFDQRRb8PK4B8af1vAGK04tF5iQAAsui+mHNBQSAtd2/ndEDe9wuA== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.10.4" - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-async-generators@^7.8.0": +"@babel/plugin-proposal-object-rest-spread@^7.14.7": + version "7.14.7" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.14.7.tgz#5920a2b3df7f7901df0205974c0641b13fd9d363" + integrity sha512-082hsZz+sVabfmDWo1Oct1u1AgbKbUAyVgmX4otIc7bdsRgHBXwTwb3DpDmD4Eyyx6DNiuz5UAATT655k+kL5g== + dependencies: + "@babel/compat-data" "^7.14.7" + "@babel/helper-compilation-targets" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.14.5" + +"@babel/plugin-proposal-optional-catch-binding@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.14.5.tgz#939dd6eddeff3a67fdf7b3f044b5347262598c3c" + integrity sha512-3Oyiixm0ur7bzO5ybNcZFlmVsygSIQgdOa7cTfOYCMY+wEPAYhZAJxi3mixKFCTCKUhQXuCTtQ1MzrpL3WT8ZQ== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + +"@babel/plugin-proposal-optional-chaining@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.14.5.tgz#fa83651e60a360e3f13797eef00b8d519695b603" + integrity sha512-ycz+VOzo2UbWNI1rQXxIuMOzrDdHGrI23fRiz/Si2R4kv2XZQ1BK8ccdHwehMKBlcH/joGW/tzrUmo67gbJHlQ== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.14.5" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + +"@babel/plugin-proposal-private-methods@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.14.5.tgz#37446495996b2945f30f5be5b60d5e2aa4f5792d" + integrity sha512-838DkdUA1u+QTCplatfq4B7+1lnDa/+QMI89x5WZHBcnNv+47N8QEj2k9I2MUU9xIv8XJ4XvPCviM/Dj7Uwt9g== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-proposal-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.14.5.tgz#9f65a4d0493a940b4c01f8aa9d3f1894a587f636" + integrity sha512-62EyfyA3WA0mZiF2e2IV9mc9Ghwxcg8YTu8BS4Wss4Y3PY725OmS9M0qLORbJwLqFtGh+jiE4wAmocK2CTUK2Q== + dependencies: + "@babel/helper-annotate-as-pure" "^7.14.5" + "@babel/helper-create-class-features-plugin" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + +"@babel/plugin-proposal-unicode-property-regex@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.14.5.tgz#0f95ee0e757a5d647f378daa0eca7e93faa8bbe8" + integrity sha512-6axIeOU5LnY471KenAB9vI8I5j7NQ2d652hIYwVyRfgaZT5UpiqFKCuVXCDMSrU+3VFafnu2c5m3lrWIlr6A5Q== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-proposal-unicode-property-regex@^7.4.4": + version "7.12.13" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.13.tgz#bebde51339be829c17aaaaced18641deb62b39ba" + integrity sha512-XyJmZidNfofEkqFV5VC/bLabGmO5QzenPO/YOfGuEbgU+2sSwMmio3YLb4WtBgcmmdwZHyVyv8on77IUjQ5Gvg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.12.13" + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" resolved "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-class-properties@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz#6644e6a0baa55a61f9e3231f6c9eeb6ee46c124c" - integrity sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA== +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-dynamic-import@^7.8.0": +"@babel/plugin-syntax-dynamic-import@^7.8.3": version "7.8.3" resolved "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== @@ -405,7 +632,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-syntax-json-strings@^7.8.0": +"@babel/plugin-syntax-json-strings@^7.8.3": version "7.8.3" resolved "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== @@ -419,7 +646,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0": +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": version "7.8.3" resolved "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== @@ -433,360 +660,377 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-syntax-object-rest-spread@^7.8.0": +"@babel/plugin-syntax-object-rest-spread@^7.8.3": version "7.8.3" resolved "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-optional-catch-binding@^7.8.0": +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": version "7.8.3" resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-optional-chaining@^7.8.0": +"@babel/plugin-syntax-optional-chaining@^7.8.3": version "7.8.3" resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-top-level-await@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.4.tgz#4bbeb8917b54fcf768364e0a81f560e33a3ef57d" - integrity sha512-ni1brg4lXEmWyafKr0ccFWkJG0CeMt4WV1oyeBW6EFObF4oOHclbkj5cARxAPQyAQ2UTuplJyK4nfkXIMMFvsQ== +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-arrow-functions@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.4.tgz#e22960d77e697c74f41c501d44d73dbf8a6a64cd" - integrity sha512-9J/oD1jV0ZCBcgnoFWFq1vJd4msoKb/TCpGNFyyLt0zABdcvgK3aYikZ8HjzB14c26bc7E3Q1yugpwGy2aTPNA== +"@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-async-to-generator@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.4.tgz#41a5017e49eb6f3cda9392a51eef29405b245a37" - integrity sha512-F6nREOan7J5UXTLsDsZG3DXmZSVofr2tGNwfdrVwkDWHfQckbQXnXSPfD7iO+c/2HGqycwyLST3DnZ16n+cBJQ== +"@babel/plugin-transform-arrow-functions@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.14.5.tgz#f7187d9588a768dd080bf4c9ffe117ea62f7862a" + integrity sha512-KOnO0l4+tD5IfOdi4x8C1XmEIRWUjNRV8wc6K2vz/3e8yAOoZZvsRXRRIF/yo/MAOFb4QjtAw9xSxMXbSMRy8A== dependencies: - "@babel/helper-module-imports" "^7.10.4" - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-remap-async-to-generator" "^7.10.4" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-block-scoped-functions@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.4.tgz#1afa595744f75e43a91af73b0d998ecfe4ebc2e8" - integrity sha512-WzXDarQXYYfjaV1szJvN3AD7rZgZzC1JtjJZ8dMHUyiK8mxPRahynp14zzNjU3VkPqPsO38CzxiWO1c9ARZ8JA== +"@babel/plugin-transform-async-to-generator@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.14.5.tgz#72c789084d8f2094acb945633943ef8443d39e67" + integrity sha512-szkbzQ0mNk0rpu76fzDdqSyPu0MuvpXgC+6rz5rpMb5OIRxdmHfQxrktL8CYolL2d8luMCZTR0DpIMIdL27IjA== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-module-imports" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/helper-remap-async-to-generator" "^7.14.5" -"@babel/plugin-transform-block-scoping@^7.10.4": - version "7.11.1" - resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.11.1.tgz#5b7efe98852bef8d652c0b28144cd93a9e4b5215" - integrity sha512-00dYeDE0EVEHuuM+26+0w/SCL0BH2Qy7LwHuI4Hi4MH5gkC8/AqMN5uWFJIsoXZrAphiMm1iXzBw6L2T+eA0ew== +"@babel/plugin-transform-block-scoped-functions@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.14.5.tgz#e48641d999d4bc157a67ef336aeb54bc44fd3ad4" + integrity sha512-dtqWqdWZ5NqBX3KzsVCWfQI3A53Ft5pWFCT2eCVUftWZgjc5DpDponbIF1+c+7cSGk2wN0YK7HGL/ezfRbpKBQ== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-classes@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.4.tgz#405136af2b3e218bc4a1926228bc917ab1a0adc7" - integrity sha512-2oZ9qLjt161dn1ZE0Ms66xBncQH4In8Sqw1YWgBUZuGVJJS5c0OFZXL6dP2MRHrkU/eKhWg8CzFJhRQl50rQxA== +"@babel/plugin-transform-block-scoping@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.14.5.tgz#8cc63e61e50f42e078e6f09be775a75f23ef9939" + integrity sha512-LBYm4ZocNgoCqyxMLoOnwpsmQ18HWTQvql64t3GvMUzLQrNoV1BDG0lNftC8QKYERkZgCCT/7J5xWGObGAyHDw== dependencies: - "@babel/helper-annotate-as-pure" "^7.10.4" - "@babel/helper-define-map" "^7.10.4" - "@babel/helper-function-name" "^7.10.4" - "@babel/helper-optimise-call-expression" "^7.10.4" - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-replace-supers" "^7.10.4" - "@babel/helper-split-export-declaration" "^7.10.4" - globals "^11.1.0" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-computed-properties@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.4.tgz#9ded83a816e82ded28d52d4b4ecbdd810cdfc0eb" - integrity sha512-JFwVDXcP/hM/TbyzGq3l/XWGut7p46Z3QvqFMXTfk6/09m7xZHJUN9xHfsv7vqqD4YnfI5ueYdSJtXqqBLyjBw== +"@babel/plugin-transform-classes@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.14.5.tgz#0e98e82097b38550b03b483f9b51a78de0acb2cf" + integrity sha512-J4VxKAMykM06K/64z9rwiL6xnBHgB1+FVspqvlgCdwD1KUbQNfszeKVVOMh59w3sztHYIZDgnhOC4WbdEfHFDA== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-annotate-as-pure" "^7.14.5" + "@babel/helper-function-name" "^7.14.5" + "@babel/helper-optimise-call-expression" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/helper-replace-supers" "^7.14.5" + "@babel/helper-split-export-declaration" "^7.14.5" + globals "^11.1.0" -"@babel/plugin-transform-destructuring@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.4.tgz#70ddd2b3d1bea83d01509e9bb25ddb3a74fc85e5" - integrity sha512-+WmfvyfsyF603iPa6825mq6Qrb7uLjTOsa3XOFzlYcYDHSS4QmpOWOL0NNBY5qMbvrcf3tq0Cw+v4lxswOBpgA== +"@babel/plugin-transform-computed-properties@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.14.5.tgz#1b9d78987420d11223d41195461cc43b974b204f" + integrity sha512-pWM+E4283UxaVzLb8UBXv4EIxMovU4zxT1OPnpHJcmnvyY9QbPPTKZfEj31EUvG3/EQRbYAGaYEUZ4yWOBC2xg== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-dotall-regex@^7.10.4", "@babel/plugin-transform-dotall-regex@^7.4.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.4.tgz#469c2062105c1eb6a040eaf4fac4b488078395ee" - integrity sha512-ZEAVvUTCMlMFAbASYSVQoxIbHm2OkG2MseW6bV2JjIygOjdVv8tuxrCTzj1+Rynh7ODb8GivUy7dzEXzEhuPaA== +"@babel/plugin-transform-destructuring@^7.14.7": + version "7.14.7" + resolved "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.14.7.tgz#0ad58ed37e23e22084d109f185260835e5557576" + integrity sha512-0mDE99nK+kVh3xlc5vKwB6wnP9ecuSj+zQCa/n0voENtP/zymdT4HH6QEb65wjjcbqr1Jb/7z9Qp7TF5FtwYGw== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.10.4" - "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-duplicate-keys@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.4.tgz#697e50c9fee14380fe843d1f306b295617431e47" - integrity sha512-GL0/fJnmgMclHiBTTWXNlYjYsA7rDrtsazHG6mglaGSTh0KsrW04qml+Bbz9FL0LcJIRwBWL5ZqlNHKTkU3xAA== +"@babel/plugin-transform-dotall-regex@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.14.5.tgz#2f6bf76e46bdf8043b4e7e16cf24532629ba0c7a" + integrity sha512-loGlnBdj02MDsFaHhAIJzh7euK89lBrGIdM9EAtHFo6xKygCUGuuWe07o1oZVk287amtW1n0808sQM99aZt3gw== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-create-regexp-features-plugin" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-exponentiation-operator@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.4.tgz#5ae338c57f8cf4001bdb35607ae66b92d665af2e" - integrity sha512-S5HgLVgkBcRdyQAHbKj+7KyuWx8C6t5oETmUuwz1pt3WTWJhsUV0WIIXuVvfXMxl/QQyHKlSCNNtaIamG8fysw== +"@babel/plugin-transform-dotall-regex@^7.4.4": + version "7.12.13" + resolved "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.13.tgz#3f1601cc29905bfcb67f53910f197aeafebb25ad" + integrity sha512-foDrozE65ZFdUC2OfgeOCrEPTxdB3yjqxpXh8CH+ipd9CHd4s/iq81kcUpyH8ACGNEPdFqbtzfgzbT/ZGlbDeQ== dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.10.4" - "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-create-regexp-features-plugin" "^7.12.13" + "@babel/helper-plugin-utils" "^7.12.13" -"@babel/plugin-transform-for-of@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.4.tgz#c08892e8819d3a5db29031b115af511dbbfebae9" - integrity sha512-ItdQfAzu9AlEqmusA/65TqJ79eRcgGmpPPFvBnGILXZH975G0LNjP1yjHvGgfuCxqrPPueXOPe+FsvxmxKiHHQ== +"@babel/plugin-transform-duplicate-keys@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.14.5.tgz#365a4844881bdf1501e3a9f0270e7f0f91177954" + integrity sha512-iJjbI53huKbPDAsJ8EmVmvCKeeq21bAze4fu9GBQtSLqfvzj2oRuHVx4ZkDwEhg1htQ+5OBZh/Ab0XDf5iBZ7A== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-function-name@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.4.tgz#6a467880e0fc9638514ba369111811ddbe2644b7" - integrity sha512-OcDCq2y5+E0dVD5MagT5X+yTRbcvFjDI2ZVAottGH6tzqjx/LKpgkUepu3hp/u4tZBzxxpNGwLsAvGBvQ2mJzg== +"@babel/plugin-transform-exponentiation-operator@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.14.5.tgz#5154b8dd6a3dfe6d90923d61724bd3deeb90b493" + integrity sha512-jFazJhMBc9D27o9jDnIE5ZErI0R0m7PbKXVq77FFvqFbzvTMuv8jaAwLZ5PviOLSFttqKIW0/wxNSDbjLk0tYA== dependencies: - "@babel/helper-function-name" "^7.10.4" - "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-literals@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.4.tgz#9f42ba0841100a135f22712d0e391c462f571f3c" - integrity sha512-Xd/dFSTEVuUWnyZiMu76/InZxLTYilOSr1UlHV+p115Z/Le2Fi1KXkJUYz0b42DfndostYlPub3m8ZTQlMaiqQ== +"@babel/plugin-transform-for-of@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.14.5.tgz#dae384613de8f77c196a8869cbf602a44f7fc0eb" + integrity sha512-CfmqxSUZzBl0rSjpoQSFoR9UEj3HzbGuGNL21/iFTmjb5gFggJp3ph0xR1YBhexmLoKRHzgxuFvty2xdSt6gTA== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-member-expression-literals@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.4.tgz#b1ec44fcf195afcb8db2c62cd8e551c881baf8b7" - integrity sha512-0bFOvPyAoTBhtcJLr9VcwZqKmSjFml1iVxvPL0ReomGU53CX53HsM4h2SzckNdkQcHox1bpAqzxBI1Y09LlBSw== +"@babel/plugin-transform-function-name@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.14.5.tgz#e81c65ecb900746d7f31802f6bed1f52d915d6f2" + integrity sha512-vbO6kv0fIzZ1GpmGQuvbwwm+O4Cbm2NrPzwlup9+/3fdkuzo1YqOZcXw26+YUJB84Ja7j9yURWposEHLYwxUfQ== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-function-name" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-modules-amd@^7.10.4": - version "7.10.5" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.5.tgz#1b9cddaf05d9e88b3aad339cb3e445c4f020a9b1" - integrity sha512-elm5uruNio7CTLFItVC/rIzKLfQ17+fX7EVz5W0TMgIHFo1zY0Ozzx+lgwhL4plzl8OzVn6Qasx5DeEFyoNiRw== +"@babel/plugin-transform-literals@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.14.5.tgz#41d06c7ff5d4d09e3cf4587bd3ecf3930c730f78" + integrity sha512-ql33+epql2F49bi8aHXxvLURHkxJbSmMKl9J5yHqg4PLtdE6Uc48CH1GS6TQvZ86eoB/ApZXwm7jlA+B3kra7A== dependencies: - "@babel/helper-module-transforms" "^7.10.5" - "@babel/helper-plugin-utils" "^7.10.4" - babel-plugin-dynamic-import-node "^2.3.3" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-modules-commonjs@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.4.tgz#66667c3eeda1ebf7896d41f1f16b17105a2fbca0" - integrity sha512-Xj7Uq5o80HDLlW64rVfDBhao6OX89HKUmb+9vWYaLXBZOma4gA6tw4Ni1O5qVDoZWUV0fxMYA0aYzOawz0l+1w== +"@babel/plugin-transform-member-expression-literals@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.14.5.tgz#b39cd5212a2bf235a617d320ec2b48bcc091b8a7" + integrity sha512-WkNXxH1VXVTKarWFqmso83xl+2V3Eo28YY5utIkbsmXoItO8Q3aZxN4BTS2k0hz9dGUloHK26mJMyQEYfkn/+Q== dependencies: - "@babel/helper-module-transforms" "^7.10.4" - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-simple-access" "^7.10.4" - babel-plugin-dynamic-import-node "^2.3.3" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-modules-systemjs@^7.10.4": - version "7.10.5" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.5.tgz#6270099c854066681bae9e05f87e1b9cadbe8c85" - integrity sha512-f4RLO/OL14/FP1AEbcsWMzpbUz6tssRaeQg11RH1BP/XnPpRoVwgeYViMFacnkaw4k4wjRSjn3ip1Uw9TaXuMw== +"@babel/plugin-transform-modules-amd@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.14.5.tgz#4fd9ce7e3411cb8b83848480b7041d83004858f7" + integrity sha512-3lpOU8Vxmp3roC4vzFpSdEpGUWSMsHFreTWOMMLzel2gNGfHE5UWIh/LN6ghHs2xurUp4jRFYMUIZhuFbody1g== dependencies: - "@babel/helper-hoist-variables" "^7.10.4" - "@babel/helper-module-transforms" "^7.10.5" - "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-module-transforms" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-umd@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz#9a8481fe81b824654b3a0b65da3df89f3d21839e" - integrity sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA== - dependencies: - "@babel/helper-module-transforms" "^7.10.4" - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-named-capturing-groups-regex@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz#78b4d978810b6f3bcf03f9e318f2fc0ed41aecb6" - integrity sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.10.4" - -"@babel/plugin-transform-new-target@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz#9097d753cb7b024cb7381a3b2e52e9513a9c6888" - integrity sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-object-super@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz#d7146c4d139433e7a6526f888c667e314a093894" - integrity sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-replace-supers" "^7.10.4" - -"@babel/plugin-transform-parameters@^7.10.4": - version "7.10.5" - resolved "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.5.tgz#59d339d58d0b1950435f4043e74e2510005e2c4a" - integrity sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw== - dependencies: - "@babel/helper-get-function-arity" "^7.10.4" - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-property-literals@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz#f6fe54b6590352298785b83edd815d214c42e3c0" - integrity sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g== +"@babel/plugin-transform-modules-commonjs@7.14.5", "@babel/plugin-transform-modules-commonjs@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.14.5.tgz#7aaee0ea98283de94da98b28f8c35701429dad97" + integrity sha512-en8GfBtgnydoao2PS+87mKyw62k02k7kJ9ltbKe0fXTHrQmG6QZZflYuGI1VVG7sVpx4E1n7KBpNlPb8m78J+A== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-transform-regenerator@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz#2015e59d839074e76838de2159db421966fd8b63" - integrity sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw== - dependencies: - regenerator-transform "^0.14.2" + "@babel/helper-module-transforms" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/helper-simple-access" "^7.14.5" + babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-reserved-words@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz#8f2682bcdcef9ed327e1b0861585d7013f8a54dd" - integrity sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ== +"@babel/plugin-transform-modules-systemjs@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.14.5.tgz#c75342ef8b30dcde4295d3401aae24e65638ed29" + integrity sha512-mNMQdvBEE5DcMQaL5LbzXFMANrQjd2W7FPzg34Y4yEz7dBgdaC+9B84dSO+/1Wba98zoDbInctCDo4JGxz1VYA== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-hoist-variables" "^7.14.5" + "@babel/helper-module-transforms" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/helper-validator-identifier" "^7.14.5" + babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-shorthand-properties@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz#9fd25ec5cdd555bb7f473e5e6ee1c971eede4dd6" - integrity sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q== +"@babel/plugin-transform-modules-umd@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.14.5.tgz#fb662dfee697cce274a7cda525190a79096aa6e0" + integrity sha512-RfPGoagSngC06LsGUYyM9QWSXZ8MysEjDJTAea1lqRjNECE3y0qIJF/qbvJxc4oA4s99HumIMdXOrd+TdKaAAA== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-module-transforms" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-spread@^7.11.0": - version "7.11.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.11.0.tgz#fa84d300f5e4f57752fe41a6d1b3c554f13f17cc" - integrity sha512-UwQYGOqIdQJe4aWNyS7noqAnN2VbaczPLiEtln+zPowRNlD+79w3oi2TWfYe0eZgd+gjZCbsydN7lzWysDt+gw== +"@babel/plugin-transform-named-capturing-groups-regex@^7.14.7": + version "7.14.7" + resolved "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.14.7.tgz#60c06892acf9df231e256c24464bfecb0908fd4e" + integrity sha512-DTNOTaS7TkW97xsDMrp7nycUVh6sn/eq22VaxWfEdzuEbRsiaOU0pqU7DlyUGHVsbQbSghvjKRpEl+nUCKGQSg== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-skip-transparent-expression-wrappers" "^7.11.0" + "@babel/helper-create-regexp-features-plugin" "^7.14.5" -"@babel/plugin-transform-sticky-regex@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz#8f3889ee8657581130a29d9cc91d7c73b7c4a28d" - integrity sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ== +"@babel/plugin-transform-new-target@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.14.5.tgz#31bdae8b925dc84076ebfcd2a9940143aed7dbf8" + integrity sha512-Nx054zovz6IIRWEB49RDRuXGI4Gy0GMgqG0cII9L3MxqgXz/+rgII+RU58qpo4g7tNEx1jG7rRVH4ihZoP4esQ== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/helper-regex" "^7.10.4" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-template-literals@^7.10.4": - version "7.10.5" - resolved "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.5.tgz#78bc5d626a6642db3312d9d0f001f5e7639fde8c" - integrity sha512-V/lnPGIb+KT12OQikDvgSuesRX14ck5FfJXt6+tXhdkJ+Vsd0lDCVtF6jcB4rNClYFzaB2jusZ+lNISDk2mMMw== +"@babel/plugin-transform-object-super@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.14.5.tgz#d0b5faeac9e98597a161a9cf78c527ed934cdc45" + integrity sha512-MKfOBWzK0pZIrav9z/hkRqIk/2bTv9qvxHzPQc12RcVkMOzpIKnFCNYJip00ssKWYkd8Sf5g0Wr7pqJ+cmtuFg== dependencies: - "@babel/helper-annotate-as-pure" "^7.10.4" - "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/helper-replace-supers" "^7.14.5" -"@babel/plugin-transform-typeof-symbol@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz#9509f1a7eec31c4edbffe137c16cc33ff0bc5bfc" - integrity sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA== +"@babel/plugin-transform-parameters@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.14.5.tgz#49662e86a1f3ddccac6363a7dfb1ff0a158afeb3" + integrity sha512-Tl7LWdr6HUxTmzQtzuU14SqbgrSKmaR77M0OKyq4njZLQTPfOvzblNKyNkGwOfEFCEx7KeYHQHDI0P3F02IVkA== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-unicode-escapes@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.4.tgz#feae523391c7651ddac115dae0a9d06857892007" - integrity sha512-y5XJ9waMti2J+e7ij20e+aH+fho7Wb7W8rNuu72aKRwCHFqQdhkdU2lo3uZ9tQuboEJcUFayXdARhcxLQ3+6Fg== +"@babel/plugin-transform-property-literals@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.14.5.tgz#0ddbaa1f83db3606f1cdf4846fa1dfb473458b34" + integrity sha512-r1uilDthkgXW8Z1vJz2dKYLV1tuw2xsbrp3MrZmD99Wh9vsfKoob+JTgri5VUb/JqyKRXotlOtwgu4stIYCmnw== dependencies: - "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-unicode-regex@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz#e56d71f9282fac6db09c82742055576d5e6d80a8" - integrity sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A== +"@babel/plugin-transform-regenerator@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.14.5.tgz#9676fd5707ed28f522727c5b3c0aa8544440b04f" + integrity sha512-NVIY1W3ITDP5xQl50NgTKlZ0GrotKtLna08/uGY6ErQt6VEQZXla86x/CTddm5gZdcr+5GSsvMeTmWA5Ii6pkg== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.10.4" - "@babel/helper-plugin-utils" "^7.10.4" + regenerator-transform "^0.14.2" -"@babel/preset-env@7.11.5": - version "7.11.5" - resolved "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.11.5.tgz#18cb4b9379e3e92ffea92c07471a99a2914e4272" - integrity sha512-kXqmW1jVcnB2cdueV+fyBM8estd5mlNfaQi6lwLgRwCby4edpavgbFhiBNjmWA3JpB/yZGSISa7Srf+TwxDQoA== - dependencies: - "@babel/compat-data" "^7.11.0" - "@babel/helper-compilation-targets" "^7.10.4" - "@babel/helper-module-imports" "^7.10.4" - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-proposal-async-generator-functions" "^7.10.4" - "@babel/plugin-proposal-class-properties" "^7.10.4" - "@babel/plugin-proposal-dynamic-import" "^7.10.4" - "@babel/plugin-proposal-export-namespace-from" "^7.10.4" - "@babel/plugin-proposal-json-strings" "^7.10.4" - "@babel/plugin-proposal-logical-assignment-operators" "^7.11.0" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.10.4" - "@babel/plugin-proposal-numeric-separator" "^7.10.4" - "@babel/plugin-proposal-object-rest-spread" "^7.11.0" - "@babel/plugin-proposal-optional-catch-binding" "^7.10.4" - "@babel/plugin-proposal-optional-chaining" "^7.11.0" - "@babel/plugin-proposal-private-methods" "^7.10.4" - "@babel/plugin-proposal-unicode-property-regex" "^7.10.4" - "@babel/plugin-syntax-async-generators" "^7.8.0" - "@babel/plugin-syntax-class-properties" "^7.10.4" - "@babel/plugin-syntax-dynamic-import" "^7.8.0" +"@babel/plugin-transform-reserved-words@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.14.5.tgz#c44589b661cfdbef8d4300dcc7469dffa92f8304" + integrity sha512-cv4F2rv1nD4qdexOGsRQXJrOcyb5CrgjUH9PKrrtyhSDBNWGxd0UIitjyJiWagS+EbUGjG++22mGH1Pub8D6Vg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-transform-shorthand-properties@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.14.5.tgz#97f13855f1409338d8cadcbaca670ad79e091a58" + integrity sha512-xLucks6T1VmGsTB+GWK5Pl9Jl5+nRXD1uoFdA5TSO6xtiNjtXTjKkmPdFXVLGlK5A2/or/wQMKfmQ2Y0XJfn5g== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-transform-spread@^7.14.6": + version "7.14.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.14.6.tgz#6bd40e57fe7de94aa904851963b5616652f73144" + integrity sha512-Zr0x0YroFJku7n7+/HH3A2eIrGMjbmAIbJSVv0IZ+t3U2WUQUA64S/oeied2e+MaGSjmt4alzBCsK9E8gh+fag== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.14.5" + +"@babel/plugin-transform-sticky-regex@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.14.5.tgz#5b617542675e8b7761294381f3c28c633f40aeb9" + integrity sha512-Z7F7GyvEMzIIbwnziAZmnSNpdijdr4dWt+FJNBnBLz5mwDFkqIXU9wmBcWWad3QeJF5hMTkRe4dAq2sUZiG+8A== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-transform-template-literals@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.14.5.tgz#a5f2bc233937d8453885dc736bdd8d9ffabf3d93" + integrity sha512-22btZeURqiepOfuy/VkFr+zStqlujWaarpMErvay7goJS6BWwdd6BY9zQyDLDa4x2S3VugxFb162IZ4m/S/+Gg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-transform-typeof-symbol@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.14.5.tgz#39af2739e989a2bd291bf6b53f16981423d457d4" + integrity sha512-lXzLD30ffCWseTbMQzrvDWqljvZlHkXU+CnseMhkMNqU1sASnCsz3tSzAaH3vCUXb9PHeUb90ZT1BdFTm1xxJw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-transform-unicode-escapes@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.14.5.tgz#9d4bd2a681e3c5d7acf4f57fa9e51175d91d0c6b" + integrity sha512-crTo4jATEOjxj7bt9lbYXcBAM3LZaUrbP2uUdxb6WIorLmjNKSpHfIybgY4B8SRpbf8tEVIWH3Vtm7ayCrKocA== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-transform-unicode-regex@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.14.5.tgz#4cd09b6c8425dd81255c7ceb3fb1836e7414382e" + integrity sha512-UygduJpC5kHeCiRw/xDVzC+wj8VaYSoKl5JNVmbP7MadpNinAm3SvZCxZ42H37KZBKztz46YC73i9yV34d0Tzw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/preset-env@7.14.7": + version "7.14.7" + resolved "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.14.7.tgz#5c70b22d4c2d893b03d8c886a5c17422502b932a" + integrity sha512-itOGqCKLsSUl0Y+1nSfhbuuOlTs0MJk2Iv7iSH+XT/mR8U1zRLO7NjWlYXB47yhK4J/7j+HYty/EhFZDYKa/VA== + dependencies: + "@babel/compat-data" "^7.14.7" + "@babel/helper-compilation-targets" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/helper-validator-option" "^7.14.5" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.14.5" + "@babel/plugin-proposal-async-generator-functions" "^7.14.7" + "@babel/plugin-proposal-class-properties" "^7.14.5" + "@babel/plugin-proposal-class-static-block" "^7.14.5" + "@babel/plugin-proposal-dynamic-import" "^7.14.5" + "@babel/plugin-proposal-export-namespace-from" "^7.14.5" + "@babel/plugin-proposal-json-strings" "^7.14.5" + "@babel/plugin-proposal-logical-assignment-operators" "^7.14.5" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.14.5" + "@babel/plugin-proposal-numeric-separator" "^7.14.5" + "@babel/plugin-proposal-object-rest-spread" "^7.14.7" + "@babel/plugin-proposal-optional-catch-binding" "^7.14.5" + "@babel/plugin-proposal-optional-chaining" "^7.14.5" + "@babel/plugin-proposal-private-methods" "^7.14.5" + "@babel/plugin-proposal-private-property-in-object" "^7.14.5" + "@babel/plugin-proposal-unicode-property-regex" "^7.14.5" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - "@babel/plugin-syntax-json-strings" "^7.8.0" + "@babel/plugin-syntax-json-strings" "^7.8.3" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" "@babel/plugin-syntax-numeric-separator" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.0" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" - "@babel/plugin-syntax-optional-chaining" "^7.8.0" - "@babel/plugin-syntax-top-level-await" "^7.10.4" - "@babel/plugin-transform-arrow-functions" "^7.10.4" - "@babel/plugin-transform-async-to-generator" "^7.10.4" - "@babel/plugin-transform-block-scoped-functions" "^7.10.4" - "@babel/plugin-transform-block-scoping" "^7.10.4" - "@babel/plugin-transform-classes" "^7.10.4" - "@babel/plugin-transform-computed-properties" "^7.10.4" - "@babel/plugin-transform-destructuring" "^7.10.4" - "@babel/plugin-transform-dotall-regex" "^7.10.4" - "@babel/plugin-transform-duplicate-keys" "^7.10.4" - "@babel/plugin-transform-exponentiation-operator" "^7.10.4" - "@babel/plugin-transform-for-of" "^7.10.4" - "@babel/plugin-transform-function-name" "^7.10.4" - "@babel/plugin-transform-literals" "^7.10.4" - "@babel/plugin-transform-member-expression-literals" "^7.10.4" - "@babel/plugin-transform-modules-amd" "^7.10.4" - "@babel/plugin-transform-modules-commonjs" "^7.10.4" - "@babel/plugin-transform-modules-systemjs" "^7.10.4" - "@babel/plugin-transform-modules-umd" "^7.10.4" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.10.4" - "@babel/plugin-transform-new-target" "^7.10.4" - "@babel/plugin-transform-object-super" "^7.10.4" - "@babel/plugin-transform-parameters" "^7.10.4" - "@babel/plugin-transform-property-literals" "^7.10.4" - "@babel/plugin-transform-regenerator" "^7.10.4" - "@babel/plugin-transform-reserved-words" "^7.10.4" - "@babel/plugin-transform-shorthand-properties" "^7.10.4" - "@babel/plugin-transform-spread" "^7.11.0" - "@babel/plugin-transform-sticky-regex" "^7.10.4" - "@babel/plugin-transform-template-literals" "^7.10.4" - "@babel/plugin-transform-typeof-symbol" "^7.10.4" - "@babel/plugin-transform-unicode-escapes" "^7.10.4" - "@babel/plugin-transform-unicode-regex" "^7.10.4" - "@babel/preset-modules" "^0.1.3" - "@babel/types" "^7.11.5" - browserslist "^4.12.0" - core-js-compat "^3.6.2" - invariant "^2.2.2" - levenary "^1.1.1" - semver "^5.5.0" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + "@babel/plugin-transform-arrow-functions" "^7.14.5" + "@babel/plugin-transform-async-to-generator" "^7.14.5" + "@babel/plugin-transform-block-scoped-functions" "^7.14.5" + "@babel/plugin-transform-block-scoping" "^7.14.5" + "@babel/plugin-transform-classes" "^7.14.5" + "@babel/plugin-transform-computed-properties" "^7.14.5" + "@babel/plugin-transform-destructuring" "^7.14.7" + "@babel/plugin-transform-dotall-regex" "^7.14.5" + "@babel/plugin-transform-duplicate-keys" "^7.14.5" + "@babel/plugin-transform-exponentiation-operator" "^7.14.5" + "@babel/plugin-transform-for-of" "^7.14.5" + "@babel/plugin-transform-function-name" "^7.14.5" + "@babel/plugin-transform-literals" "^7.14.5" + "@babel/plugin-transform-member-expression-literals" "^7.14.5" + "@babel/plugin-transform-modules-amd" "^7.14.5" + "@babel/plugin-transform-modules-commonjs" "^7.14.5" + "@babel/plugin-transform-modules-systemjs" "^7.14.5" + "@babel/plugin-transform-modules-umd" "^7.14.5" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.14.7" + "@babel/plugin-transform-new-target" "^7.14.5" + "@babel/plugin-transform-object-super" "^7.14.5" + "@babel/plugin-transform-parameters" "^7.14.5" + "@babel/plugin-transform-property-literals" "^7.14.5" + "@babel/plugin-transform-regenerator" "^7.14.5" + "@babel/plugin-transform-reserved-words" "^7.14.5" + "@babel/plugin-transform-shorthand-properties" "^7.14.5" + "@babel/plugin-transform-spread" "^7.14.6" + "@babel/plugin-transform-sticky-regex" "^7.14.5" + "@babel/plugin-transform-template-literals" "^7.14.5" + "@babel/plugin-transform-typeof-symbol" "^7.14.5" + "@babel/plugin-transform-unicode-escapes" "^7.14.5" + "@babel/plugin-transform-unicode-regex" "^7.14.5" + "@babel/preset-modules" "^0.1.4" + "@babel/types" "^7.14.5" + babel-plugin-polyfill-corejs2 "^0.2.2" + babel-plugin-polyfill-corejs3 "^0.2.2" + babel-plugin-polyfill-regenerator "^0.2.2" + core-js-compat "^3.15.0" + semver "^6.3.0" -"@babel/preset-modules@^0.1.3": +"@babel/preset-modules@^0.1.4": version "0.1.4" resolved "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz#362f2b68c662842970fdb5e254ffc8fc1c2e415e" integrity sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg== @@ -797,103 +1041,134 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" -"@babel/runtime@^7.10.4", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4": - version "7.11.2" - resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736" - integrity sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw== +"@babel/runtime@^7.10.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4": + version "7.14.0" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.0.tgz#46794bc20b612c5f75e62dd071e24dfd95f1cbe6" + integrity sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA== dependencies: regenerator-runtime "^0.13.4" -"@babel/template@^7.10.4", "@babel/template@^7.4.0": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" - integrity sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA== - dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/parser" "^7.10.4" - "@babel/types" "^7.10.4" +"@babel/template@^7.12.13", "@babel/template@^7.4.0": + version "7.12.13" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz#530265be8a2589dbb37523844c5bcb55947fb327" + integrity sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA== + dependencies: + "@babel/code-frame" "^7.12.13" + "@babel/parser" "^7.12.13" + "@babel/types" "^7.12.13" + +"@babel/template@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz#a9bc9d8b33354ff6e55a9c60d1109200a68974f4" + integrity sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g== + dependencies: + "@babel/code-frame" "^7.14.5" + "@babel/parser" "^7.14.5" + "@babel/types" "^7.14.5" + +"@babel/traverse@^7.13.0", "@babel/traverse@^7.14.0", "@babel/traverse@^7.14.2", "@babel/traverse@^7.4.3": + version "7.14.2" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.2.tgz#9201a8d912723a831c2679c7ebbf2fe1416d765b" + integrity sha512-TsdRgvBFHMyHOOzcP9S6QU0QQtjxlRpEYOy3mcCO5RgmC305ki42aSAmfZEMSSYBla2oZ9BMqYlncBaKmD/7iA== + dependencies: + "@babel/code-frame" "^7.12.13" + "@babel/generator" "^7.14.2" + "@babel/helper-function-name" "^7.14.2" + "@babel/helper-split-export-declaration" "^7.12.13" + "@babel/parser" "^7.14.2" + "@babel/types" "^7.14.2" + debug "^4.1.0" + globals "^11.1.0" -"@babel/traverse@^7.10.4", "@babel/traverse@^7.11.5", "@babel/traverse@^7.4.3": - version "7.11.5" - resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz#be777b93b518eb6d76ee2e1ea1d143daa11e61c3" - integrity sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ== - dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/generator" "^7.11.5" - "@babel/helper-function-name" "^7.10.4" - "@babel/helper-split-export-declaration" "^7.11.0" - "@babel/parser" "^7.11.5" - "@babel/types" "^7.11.5" +"@babel/traverse@^7.14.5": + version "7.14.7" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.7.tgz#64007c9774cfdc3abd23b0780bc18a3ce3631753" + integrity sha512-9vDr5NzHu27wgwejuKL7kIOm4bwEtaPQ4Z6cpCmjSuaRqpH/7xc4qcGEscwMqlkwgcXl6MvqoAjZkQ24uSdIZQ== + dependencies: + "@babel/code-frame" "^7.14.5" + "@babel/generator" "^7.14.5" + "@babel/helper-function-name" "^7.14.5" + "@babel/helper-hoist-variables" "^7.14.5" + "@babel/helper-split-export-declaration" "^7.14.5" + "@babel/parser" "^7.14.7" + "@babel/types" "^7.14.5" debug "^4.1.0" globals "^11.1.0" - lodash "^4.17.19" -"@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.11.5", "@babel/types@^7.4.0", "@babel/types@^7.4.4": - version "7.11.5" - resolved "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz#d9de577d01252d77c6800cee039ee64faf75662d" - integrity sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q== +"@babel/types@^7.0.0", "@babel/types@^7.12.13", "@babel/types@^7.13.12", "@babel/types@^7.14.0", "@babel/types@^7.14.2", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4": + version "7.14.2" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.14.2.tgz#4208ae003107ef8a057ea8333e56eb64d2f6a2c3" + integrity sha512-SdjAG/3DikRHpUOjxZgnkbR11xUlyDMUFJdvnIgZEE16mqmY0BINMmc4//JMJglEmn6i7sq6p+mGrFWyZ98EEw== dependencies: - "@babel/helper-validator-identifier" "^7.10.4" - lodash "^4.17.19" + "@babel/helper-validator-identifier" "^7.14.0" to-fast-properties "^2.0.0" -"@changesets/apply-release-plan@^4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-4.0.0.tgz#e78efb56a4e459a8dab814ba43045f2ace0f27c9" - integrity sha512-MrcUd8wIlQ4S/PznzqJVsmnEpUGfPEkCGF54iqt8G05GEqi/zuxpoTfebcScpj5zeiDyxFIcA9RbeZ3pvJJxoA== +"@babel/types@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz#3bb997ba829a2104cedb20689c4a5b8121d383ff" + integrity sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg== + dependencies: + "@babel/helper-validator-identifier" "^7.14.5" + to-fast-properties "^2.0.0" + +"@changesets/apply-release-plan@^5.0.0": + version "5.0.0" + resolved "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-5.0.0.tgz#11bf168acecbf4cfa2b0e6425160bac5ceeec1c3" + integrity sha512-SE+5nPNSKUyUociPnAvnjYSVF+diciEhX9ZHSqKWMlydswCDjiaq9gz67qwWCmwgEgEOz0TS7VrQBoOlzbitvA== dependencies: - "@babel/runtime" "^7.4.4" - "@changesets/config" "^1.2.0" + "@babel/runtime" "^7.10.4" + "@changesets/config" "^1.6.0" "@changesets/get-version-range-type" "^0.3.2" - "@changesets/git" "^1.0.5" - "@changesets/types" "^3.1.0" + "@changesets/git" "^1.1.1" + "@changesets/types" "^4.0.0" "@manypkg/get-packages" "^1.0.1" + detect-indent "^6.0.0" fs-extra "^7.0.1" lodash.startcase "^4.4.0" outdent "^0.5.0" - prettier "^1.18.2" + prettier "^1.19.1" resolve-from "^5.0.0" semver "^5.4.1" -"@changesets/assemble-release-plan@^4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-4.0.0.tgz#60c2392c0e2c99f24778ab3a5c8e8c80ddaaaa59" - integrity sha512-3Kv21FNvysTQvZs3fHr6aZeDibhZHtgI1++fMZplzVtwNVmpjow3zv9lcZmJP26LthbpVH3I8+nqlU7M43lfWA== +"@changesets/assemble-release-plan@^5.0.0": + version "5.0.0" + resolved "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-5.0.0.tgz#3e57405e5c375e2d933f62e74d1874915e60cd61" + integrity sha512-LElDXTCBUkPSmdXlCisoUWw2paX48snatBmw/hKnGiSvnyZqdTIylLojAGQWG0/vOO9v3s/DvJ4hdagIquxJjg== dependencies: "@babel/runtime" "^7.10.4" "@changesets/errors" "^0.1.4" - "@changesets/get-dependents-graph" "^1.1.3" - "@changesets/types" "^3.1.0" + "@changesets/get-dependents-graph" "^1.2.1" + "@changesets/types" "^4.0.0" "@manypkg/get-packages" "^1.0.1" semver "^5.4.1" -"@changesets/changelog-github@0.2.7": - version "0.2.7" - resolved "https://registry.npmjs.org/@changesets/changelog-github/-/changelog-github-0.2.7.tgz#11ad3e89276cf867fabeb09fd36774a67347ac89" - integrity sha512-bWK8IADPYsiSiyORNpVLbmdo7J7A4LktmzUowtIivslP03UW5f/YBDzGub7N0+BVdVmZrqvyWzAIKmpGVkEICg== +"@changesets/changelog-github@0.4.0": + version "0.4.0" + resolved "https://registry.npmjs.org/@changesets/changelog-github/-/changelog-github-0.4.0.tgz#ec80b18b9a5645191b5323fbddce4e4a012302d4" + integrity sha512-4GphTAdHJfECuuQg4l0eFGYyUh26mfyXpAi2FImrQvaN+boqTM+9EAAJe2b8Y3OKRfoET+ihgo+LARhW2xJoiw== dependencies: - "@changesets/get-github-info" "^0.4.4" - "@changesets/types" "^3.0.0" + "@changesets/get-github-info" "^0.5.0" + "@changesets/types" "^4.0.0" dotenv "^8.1.0" -"@changesets/cli@2.11.0": - version "2.11.0" - resolved "https://registry.npmjs.org/@changesets/cli/-/cli-2.11.0.tgz#048449289da95f24d96b187f3148c2955065d609" - integrity sha512-imsXWxl+QpLt6PYHSiuZS7xPdKrGs2c8saSG4ENzeZBgnTBMiJdxDloOT6Bmv15ICggHZj9mnaUBNOMPbvnlbA== +"@changesets/cli@2.16.0": + version "2.16.0" + resolved "https://registry.npmjs.org/@changesets/cli/-/cli-2.16.0.tgz#9f794005d0503efba5e348b929821a1732fd0f0d" + integrity sha512-VFkXSyyk/WRjjUoBI7g7cDy09qBjPbBQOloPMEshTzMo/NY9muWHl2yB/FSSkV/6PxGimPtJ7aEJPYfk8HCfXw== dependencies: "@babel/runtime" "^7.10.4" - "@changesets/apply-release-plan" "^4.0.0" - "@changesets/assemble-release-plan" "^4.0.0" - "@changesets/config" "^1.4.0" + "@changesets/apply-release-plan" "^5.0.0" + "@changesets/assemble-release-plan" "^5.0.0" + "@changesets/config" "^1.6.0" "@changesets/errors" "^0.1.4" - "@changesets/get-dependents-graph" "^1.1.3" - "@changesets/get-release-plan" "^2.0.1" - "@changesets/git" "^1.0.6" + "@changesets/get-dependents-graph" "^1.2.1" + "@changesets/get-release-plan" "^3.0.0" + "@changesets/git" "^1.1.1" "@changesets/logger" "^0.0.5" - "@changesets/pre" "^1.0.4" - "@changesets/read" "^0.4.6" - "@changesets/types" "^3.2.0" - "@changesets/write" "^0.1.3" + "@changesets/pre" "^1.0.6" + "@changesets/read" "^0.4.7" + "@changesets/types" "^4.0.0" + "@changesets/write" "^0.1.4" "@manypkg/get-packages" "^1.0.1" "@types/semver" "^6.0.0" boxen "^1.3.0" @@ -903,36 +1178,24 @@ fs-extra "^7.0.1" human-id "^1.0.2" is-ci "^2.0.0" - meow "^5.0.0" + meow "^6.0.0" outdent "^0.5.0" p-limit "^2.2.0" preferred-pm "^3.0.0" semver "^5.4.1" spawndamnit "^2.0.0" term-size "^2.1.0" - tty-table "^2.7.0" - -"@changesets/config@^1.2.0": - version "1.3.0" - resolved "https://registry.npmjs.org/@changesets/config/-/config-1.3.0.tgz#82fcbf572b00ba16636be9ea45167983f1fc203b" - integrity sha512-IeAHmN5kI7OywBUNJXsk/v4vcXDDscwgTe/K5D3FSng5QTvzbgiMAe5K1iwBxBvuT4u/33n89kxSJdg4TTTFfA== - dependencies: - "@changesets/errors" "^0.1.4" - "@changesets/get-dependents-graph" "^1.1.3" - "@changesets/logger" "^0.0.5" - "@changesets/types" "^3.1.0" - "@manypkg/get-packages" "^1.0.1" - fs-extra "^7.0.1" + tty-table "^2.8.10" -"@changesets/config@^1.4.0": - version "1.4.0" - resolved "https://registry.npmjs.org/@changesets/config/-/config-1.4.0.tgz#c157a4121f198b749f2bbc2e9015b6e976ece7d6" - integrity sha512-eoTOcJ6py7jBDY8rUXwEGxR5UtvUX+p//0NhkVpPGcnvIeITHq+DOIsuWyGzWcb+1FaYkof3CCr32/komZTu4Q== +"@changesets/config@^1.6.0": + version "1.6.0" + resolved "https://registry.npmjs.org/@changesets/config/-/config-1.6.0.tgz#2cd9426b9d4212534d2b31c51de43280b76d3df4" + integrity sha512-vMY/OpMFSDC2crDKb5Nq2kMX9hozcXL4dY5Rr+a1JQ044Rz+jqjJPpdTP2yQ+j7qmeGcUTvwjJoEMeekYwfqhg== dependencies: "@changesets/errors" "^0.1.4" - "@changesets/get-dependents-graph" "^1.1.3" + "@changesets/get-dependents-graph" "^1.2.1" "@changesets/logger" "^0.0.5" - "@changesets/types" "^3.2.0" + "@changesets/types" "^4.0.0" "@manypkg/get-packages" "^1.0.1" fs-extra "^7.0.1" micromatch "^4.0.2" @@ -944,36 +1207,36 @@ dependencies: extendable-error "^0.1.5" -"@changesets/get-dependents-graph@^1.1.3": - version "1.1.3" - resolved "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-1.1.3.tgz#da959c43ce98f3a990a6b8d9c1f894bcc1b629c7" - integrity sha512-cTbySXwSv9yWp4Pp5R/b5Qv23wJgFaFCqUbsI3IJ2pyPl0vMaODAZS8NI1nNK2XSxGIg1tw+dWNSR4PlrKBSVQ== +"@changesets/get-dependents-graph@^1.2.1": + version "1.2.1" + resolved "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-1.2.1.tgz#462908693dc3a354622e43f85a764b74b5bb53af" + integrity sha512-vJOibo9SkqhVbgfq5AHIlQ7tzkYQIXh3tPAnlNLy2bPZsU+SByd74GaxHYWt1zOBlncU25WKrIM6J7XBB+GVUg== dependencies: - "@changesets/types" "^3.0.0" + "@changesets/types" "^4.0.0" "@manypkg/get-packages" "^1.0.1" chalk "^2.1.0" fs-extra "^7.0.1" semver "^5.4.1" -"@changesets/get-github-info@0.4.4", "@changesets/get-github-info@^0.4.4": - version "0.4.4" - resolved "https://registry.npmjs.org/@changesets/get-github-info/-/get-github-info-0.4.4.tgz#a2911e93aa0809506cd8806f365180cf8833eb6f" - integrity sha512-WoVdRBQXLvJVg3jsMc0+aB8WAFzSGOG45HSo2ep46ixq5lBjM2D51MbgtSYHRGFGvYuAUu6kAfnVmoZYCZFsKQ== +"@changesets/get-github-info@0.5.0", "@changesets/get-github-info@^0.5.0": + version "0.5.0" + resolved "https://registry.npmjs.org/@changesets/get-github-info/-/get-github-info-0.5.0.tgz#b91ceb2d82edef78ae1598ea9fc335a012250295" + integrity sha512-vm5VgHwrxkMkUjFyn3UVNKLbDp9YMHd3vMf1IyJoa/7B+6VpqmtAaXyDS0zBLfN5bhzVCHrRnj4GcZXXcqrFTw== dependencies: dataloader "^1.4.0" node-fetch "^2.5.0" -"@changesets/get-release-plan@^2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@changesets/get-release-plan/-/get-release-plan-2.0.1.tgz#b95d8f1a3cc719ff4b42b9b9aae72458d8787c13" - integrity sha512-+x5N9/Iaka+c0Kq7+3JsboMNyffKYlWPmdm+VeafDcMwJFhBDkxm84qaCJ93ydmnzQOTig6gYVqw0k8BbHExyQ== +"@changesets/get-release-plan@^3.0.0": + version "3.0.0" + resolved "https://registry.npmjs.org/@changesets/get-release-plan/-/get-release-plan-3.0.0.tgz#55efc01db2e24bd7a88e703956eb2f6c4a79054f" + integrity sha512-7VLiqpcWZyjwIXYgkubBC/9cdwqUJEhLMRT9/Y9+ctHqrpsXmJg15QQPTOh3HT9yGN5fJPL1WwuZkc1HXUhK0g== dependencies: "@babel/runtime" "^7.10.4" - "@changesets/assemble-release-plan" "^4.0.0" - "@changesets/config" "^1.2.0" - "@changesets/pre" "^1.0.4" - "@changesets/read" "^0.4.6" - "@changesets/types" "^3.1.0" + "@changesets/assemble-release-plan" "^5.0.0" + "@changesets/config" "^1.6.0" + "@changesets/pre" "^1.0.6" + "@changesets/read" "^0.4.7" + "@changesets/types" "^4.0.0" "@manypkg/get-packages" "^1.0.1" "@changesets/get-version-range-type@^0.3.2": @@ -981,14 +1244,14 @@ resolved "https://registry.npmjs.org/@changesets/get-version-range-type/-/get-version-range-type-0.3.2.tgz#8131a99035edd11aa7a44c341cbb05e668618c67" integrity sha512-SVqwYs5pULYjYT4op21F2pVbcrca4qA/bAA3FmFXKMN7Y+HcO8sbZUTx3TAy2VXulP2FACd1aC7f2nTuqSPbqg== -"@changesets/git@^1.0.5", "@changesets/git@^1.0.6": - version "1.0.6" - resolved "https://registry.npmjs.org/@changesets/git/-/git-1.0.6.tgz#057e627e5d3fcb74bf6c18d7284e130ba5a7632e" - integrity sha512-e0M06XuME3W5lGhz+CO0vLc60u+hLk/pYjOx/6GXEWuQrwtGgeycFIfRgRt8qTs664o1oKtVHBbd7ItpoWgFfA== +"@changesets/git@^1.1.1": + version "1.1.1" + resolved "https://registry.npmjs.org/@changesets/git/-/git-1.1.1.tgz#f444d3ff3604acb6949560656c9ef330485a5fa3" + integrity sha512-Z12TcKwgU33YE3r76cyU+X81RchOXljDZ5s3G2u0Zd+ODyrwlDb91IO55+6R0Ha6ouPz8ioont0gA70c1RFngg== dependencies: "@babel/runtime" "^7.10.4" "@changesets/errors" "^0.1.4" - "@changesets/types" "^3.1.1" + "@changesets/types" "^4.0.0" "@manypkg/get-packages" "^1.0.1" is-subdir "^1.1.1" spawndamnit "^2.0.0" @@ -1000,59 +1263,67 @@ dependencies: chalk "^2.1.0" -"@changesets/parse@^0.3.6": - version "0.3.7" - resolved "https://registry.npmjs.org/@changesets/parse/-/parse-0.3.7.tgz#1368136e2b83d5cff11b4d383a3032723530db99" - integrity sha512-8yqKulslq/7V2VRBsJqPgjnZMoehYqhJm5lEOXJPZ2rcuSdyj8+p/2vq2vRDBJT2m0rP+C9G8DujsGYQIFZezw== +"@changesets/parse@^0.3.8": + version "0.3.8" + resolved "https://registry.npmjs.org/@changesets/parse/-/parse-0.3.8.tgz#0bb244eccb35cb301168f85684bb03389c59341d" + integrity sha512-0S7Dc7XbMOKamBtd48vVuWL2aFZyaglw6lJsXNddn9forFf8oMKMmdyJ/HQPyeEChDDOhDF1/ya7m/zpt4Dk4w== dependencies: - "@changesets/types" "^3.0.0" + "@changesets/types" "^4.0.0" js-yaml "^3.13.1" -"@changesets/pre@^1.0.4": - version "1.0.5" - resolved "https://registry.npmjs.org/@changesets/pre/-/pre-1.0.5.tgz#91e5e3b31b4a85ce37de72f6511a786f62f29b51" - integrity sha512-p43aAQY3aijhDnBLCriPao5YArlRjD4mSHRJq9PsBhljVLWqQQXcn6seSd77d+bD1tATLhB8tQ2eYoxMtMydXQ== +"@changesets/pre@^1.0.6": + version "1.0.6" + resolved "https://registry.npmjs.org/@changesets/pre/-/pre-1.0.6.tgz#45700cf18274b35b2296000befe7fe4df8ff046f" + integrity sha512-ZwFFQLjhTmA4hj8+Cf9pm6nD9Tp+AiBz1wJLaGum4Ae1fPXMwDnJfHknFUTytqZBlC0gHkiGSj6QkUuetWvckg== dependencies: - "@babel/runtime" "^7.4.4" + "@babel/runtime" "^7.10.4" "@changesets/errors" "^0.1.4" - "@changesets/types" "^3.0.0" + "@changesets/types" "^4.0.0" "@manypkg/get-packages" "^1.0.1" fs-extra "^7.0.1" -"@changesets/read@^0.4.6": - version "0.4.6" - resolved "https://registry.npmjs.org/@changesets/read/-/read-0.4.6.tgz#1c03e709a870a070fc95490ffa696297d23458f7" - integrity sha512-rOd8dsF/Lgyy2SYlDalb3Ts/meDI2AcKPXYhSXIW3k6+ZLlj6Pt+nmgV5Ut8euyH7loibklNTDemfvMffF4xig== +"@changesets/read@^0.4.7": + version "0.4.7" + resolved "https://registry.npmjs.org/@changesets/read/-/read-0.4.7.tgz#5a32ae7092330fba31eaec4c83321bb936605766" + integrity sha512-E70QrYQpSCMF0nC0dlPU7i6A9zht+8zkQczrKMbOUwDVrfidcvgojxfuJSQbzptYSb9OKYh8GOLd+bsq9+uO9Q== dependencies: - "@babel/runtime" "^7.4.4" - "@changesets/git" "^1.0.5" + "@babel/runtime" "^7.10.4" + "@changesets/git" "^1.1.1" "@changesets/logger" "^0.0.5" - "@changesets/parse" "^0.3.6" - "@changesets/types" "^3.0.0" + "@changesets/parse" "^0.3.8" + "@changesets/types" "^4.0.0" chalk "^2.1.0" fs-extra "^7.0.1" p-filter "^2.1.0" -"@changesets/types@3.2.0", "@changesets/types@^3.2.0": - version "3.2.0" - resolved "https://registry.npmjs.org/@changesets/types/-/types-3.2.0.tgz#d8306d7219c3b19b6d860ddeb9d7374e2dd6b035" - integrity sha512-rAmPtOyXpisEEE25CchKNUAf2ApyAeuZ/h78YDoqKZaCk5tUD0lgYZGPIRV9WTPoqNjJULIym37ogc6pkax5jg== +"@changesets/types@3.3.0": + version "3.3.0" + resolved "https://registry.npmjs.org/@changesets/types/-/types-3.3.0.tgz#04cd8184b2d2da760667bd89bf9b930938dbd96e" + integrity sha512-rJamRo+OD/MQekImfIk07JZwYSB18iU6fYL8xOg0gfAiTh1a1+OlR1fPIxm55I7RsWw812is2YcPPwXdIewrhA== -"@changesets/types@^3.0.0", "@changesets/types@^3.1.0", "@changesets/types@^3.1.1": - version "3.1.1" - resolved "https://registry.npmjs.org/@changesets/types/-/types-3.1.1.tgz#447481380c42044a8788e46c0dbdf592b338b62f" - integrity sha512-XWGEGWXhM92zvBWiQt2sOwhjTt8eCQbrsRbqkv4WYwW3Zsl4qPpvhHsNt845S42dJXrxgjWvId+jxFQocCayNQ== +"@changesets/types@^4.0.0": + version "4.0.0" + resolved "https://registry.npmjs.org/@changesets/types/-/types-4.0.0.tgz#635f804546b0a96ecc0ca3f26403a6782a3dc938" + integrity sha512-whLmPx2wgJRoOtxVZop+DJ71z1gTSkij7osiHgN+pe//FiE6bb4ffvBBb0rACs2cUPfAkWxgSPzqkECgKS1jvQ== -"@changesets/write@^0.1.3": - version "0.1.3" - resolved "https://registry.npmjs.org/@changesets/write/-/write-0.1.3.tgz#00ae575af50274773d7493e77fb96838a08ad8ad" - integrity sha512-q79rbwlVmTNKP9O6XxcMDj81CEOn/kQHbTFdRleW0tFUv98S1EyEAE9vLPPzO6WnQipHnaozxB1zMhHy0aQn8Q== +"@changesets/write@^0.1.4": + version "0.1.4" + resolved "https://registry.npmjs.org/@changesets/write/-/write-0.1.4.tgz#5828ecc70c48d0e8696c5f13fe06b730cdfde6f2" + integrity sha512-uco+vS3mo2JqflLciIU707har+6AEFOeP8pgu3vVC1M2WcKukQgR1KylHFqZJxKQWahf8mQnuUSbgR4yJQuhmA== dependencies: - "@babel/runtime" "^7.4.4" - "@changesets/types" "^3.0.0" + "@babel/runtime" "^7.10.4" + "@changesets/types" "^4.0.0" fs-extra "^7.0.1" human-id "^1.0.2" - prettier "^1.18.2" + prettier "^1.19.1" + +"@cnakazawa/watch@^1.0.3": + version "1.0.4" + resolved "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" + integrity sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ== + dependencies: + exec-sh "^0.3.2" + minimist "^1.2.0" "@dabh/diagnostics@^2.0.2": version "2.0.2" @@ -1063,19 +1334,18 @@ enabled "2.0.x" kuler "^2.0.0" -"@eslint/eslintrc@^0.1.3": - version "0.1.3" - resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.3.tgz#7d1a2b2358552cc04834c0979bd4275362e37085" - integrity sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA== +"@eslint/eslintrc@^0.4.2": + version "0.4.2" + resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.2.tgz#f63d0ef06f5c0c57d76c4ab5f63d3835c51b0179" + integrity sha512-8nmGq/4ycLpIwzvhI4tNDmQztZ8sp+hI7cyG8i1nQDhkAbRzHpXPidRAHlNvCZQpJTKw5ItIpMw9RSToGF00mg== dependencies: ajv "^6.12.4" debug "^4.1.1" espree "^7.3.0" - globals "^12.1.0" + globals "^13.9.0" ignore "^4.0.6" import-fresh "^3.2.1" js-yaml "^3.13.1" - lodash "^4.17.19" minimatch "^3.0.4" strip-json-comments "^3.1.1" @@ -1153,10 +1423,10 @@ unique-filename "^1.1.1" which "^1.3.1" -"@google-cloud/common@^3.3.0": - version "3.4.0" - resolved "https://registry.npmjs.org/@google-cloud/common/-/common-3.4.0.tgz#8951d0dc94c9dfd8af2b49ed125984dc71f1de6b" - integrity sha512-bVMQlK4aZEeopo2oJwDUJiBhPVjRRQHfFCCv9JowmKS3L//PBHNDJzC/LxJixGZEU3fh3YXkUwm67JZ5TBCCNQ== +"@google-cloud/common@^3.6.0": + version "3.6.0" + resolved "https://registry.npmjs.org/@google-cloud/common/-/common-3.6.0.tgz#c2f6da5f79279a4a9ac7c71fc02d582beab98e8b" + integrity sha512-aHIFTqJZmeTNO9md8XxV+ywuvXF3xBm5WNmgWeeCK+XN5X+kGW0WEX94wGwj+/MdOnrVf4dL2RvSIt9J5yJG6Q== dependencies: "@google-cloud/projectify" "^2.0.0" "@google-cloud/promisify" "^2.0.0" @@ -1164,26 +1434,29 @@ duplexify "^4.1.1" ent "^2.2.0" extend "^3.0.2" - google-auth-library "^6.0.0" + google-auth-library "^7.0.2" retry-request "^4.1.1" teeny-request "^7.0.0" -"@google-cloud/firestore@4.4.0", "@google-cloud/firestore@^4.0.0": - version "4.4.0" - resolved "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.4.0.tgz#6cdbd462f32a8f94e138c57ef81195156c79e680" - integrity sha512-nixsumd4C7eL+hHEgyihspzhBBNe3agsvNFRX0xfqO3uR/6ro4CUj9XdcCvdnSSd3yTyqKfdBSRK2fEj1jIbYg== +"@google-cloud/firestore@4.12.3": + version "4.12.3" + resolved "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.12.3.tgz#eef62aceec5b1193385cfe3a2f39b628db353484" + integrity sha512-FTty3+paAj73KEfTJEpDxG9apLp9K3DySTeeewLLdljusRjZFgJ3jIiqi7tAKJjVsKOiXY4NRk4/0rpEQhHitQ== dependencies: fast-deep-equal "^3.1.1" functional-red-black-tree "^1.0.1" - google-gax "^2.2.0" + google-gax "^2.12.0" + protobufjs "^6.8.6" -"@google-cloud/paginator@^2.0.0": - version "2.0.3" - resolved "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-2.0.3.tgz#c7987ad05d1c3ebcef554381be80e9e8da4e4882" - integrity sha512-kp/pkb2p/p0d8/SKUu4mOq8+HGwF8NPzHWkj+VKrIPQPyMRw8deZtrO/OcSiy9C/7bpfU5Txah5ltUNfPkgEXg== +"@google-cloud/firestore@^4.5.0": + version "4.11.1" + resolved "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.11.1.tgz#03e12e5721383165efc09e208143378f3ea681d6" + integrity sha512-iNsCGYwKBxYZS+TpkUAJLGkGko2QtWaf11JDNx6kvqOVN0359qSnZlF1SWFTvm26ZsKyX6uR4oAiFmmjfXTlCg== dependencies: - arrify "^2.0.0" - extend "^3.0.2" + fast-deep-equal "^3.1.1" + functional-red-black-tree "^1.0.1" + google-gax "^2.12.0" + protobufjs "^6.8.6" "@google-cloud/paginator@^3.0.0": version "3.0.5" @@ -1193,68 +1466,59 @@ arrify "^2.0.0" extend "^3.0.2" -"@google-cloud/precise-date@^1.0.0": - version "1.0.3" - resolved "https://registry.npmjs.org/@google-cloud/precise-date/-/precise-date-1.0.3.tgz#39c600ed52213f4158692a72c90d13b2162a93d2" - integrity sha512-wWnDGh9y3cJHLuVEY8t6un78vizzMWsS7oIWKeFtPj+Ndy+dXvHW0HTx29ZUhen+tswSlQYlwFubvuRP5kKdzQ== - -"@google-cloud/projectify@^1.0.0": - version "1.0.4" - resolved "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-1.0.4.tgz#28daabebba6579ed998edcadf1a8f3be17f3b5f0" - integrity sha512-ZdzQUN02eRsmTKfBj9FDL0KNDIFNjBn/d6tHQmA/+FImH5DO6ZV8E7FzxMgAUiVAUq41RFAkb25p1oHOZ8psfg== +"@google-cloud/precise-date@^2.0.0": + version "2.0.3" + resolved "https://registry.npmjs.org/@google-cloud/precise-date/-/precise-date-2.0.3.tgz#14f6f28ce35dabf3882e7aeab1c9d51bd473faed" + integrity sha512-+SDJ3ZvGkF7hzo6BGa8ZqeK3F6Z4+S+KviC9oOK+XCs3tfMyJCh/4j93XIWINgMMDIh9BgEvlw4306VxlXIlYA== "@google-cloud/projectify@^2.0.0": version "2.0.1" resolved "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.0.1.tgz#13350ee609346435c795bbfe133a08dfeab78d65" integrity sha512-ZDG38U/Yy6Zr21LaR3BTiiLtpJl6RkPS/JwoRT453G+6Q1DhlV0waNf8Lfu+YVYGIIxgKnLayJRfYlFJfiI8iQ== -"@google-cloud/promisify@^1.0.0": - version "1.0.4" - resolved "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-1.0.4.tgz#ce86ffa94f9cfafa2e68f7b3e4a7fad194189723" - integrity sha512-VccZDcOql77obTnFh0TbNED/6ZbbmHDf8UMNnzO1d5g9V0Htfm4k5cllY8P1tJsRKC3zWYGRLaViiupcgVjBoQ== - "@google-cloud/promisify@^2.0.0": version "2.0.3" resolved "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.3.tgz#f934b5cdc939e3c7039ff62b9caaf59a9d89e3a8" integrity sha512-d4VSA86eL/AFTe5xtyZX+ePUjE8dIFu2T8zmdeNBSa5/kNgXPCx/o/wbFNHAGLJdGnk1vddRuMESD9HbOC8irw== -"@google-cloud/pubsub@^1.7.0": - version "1.7.3" - resolved "https://registry.npmjs.org/@google-cloud/pubsub/-/pubsub-1.7.3.tgz#0fa51d67eb4db979a66b05738d81c3cef992b5bf" - integrity sha512-v+KdeaOS17WtHnsDf2bPGxKDT9HIRPYo3n+WsAEmvAzDHnh8q65mFcuYoQxuy2iRhmN/1ql2a0UU2tAAL7XZ8Q== +"@google-cloud/pubsub@^2.7.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@google-cloud/pubsub/-/pubsub-2.12.0.tgz#3375a504ff7612b06301c0fcc35d4d62c533f51f" + integrity sha512-SF+y7sKX9Jo2ZoVqzwEuSReUyb6O4mwxzxP1B+dP3j0ArUk0XXSC/tFLQhYM7lC2ViLxGWoIbEZbRIw8SrIQnw== dependencies: - "@google-cloud/paginator" "^2.0.0" - "@google-cloud/precise-date" "^1.0.0" - "@google-cloud/projectify" "^1.0.0" - "@google-cloud/promisify" "^1.0.0" + "@google-cloud/paginator" "^3.0.0" + "@google-cloud/precise-date" "^2.0.0" + "@google-cloud/projectify" "^2.0.0" + "@google-cloud/promisify" "^2.0.0" + "@opentelemetry/api" "^0.18.1" + "@opentelemetry/semantic-conventions" "^0.18.2" "@types/duplexify" "^3.6.0" "@types/long" "^4.0.0" arrify "^2.0.0" - async-each "^1.0.1" extend "^3.0.2" - google-auth-library "^5.5.0" - google-gax "^1.14.2" + google-auth-library "^7.0.0" + google-gax "^2.12.0" is-stream-ended "^0.1.4" lodash.snakecase "^4.1.1" p-defer "^3.0.0" - protobufjs "^6.8.1" "@google-cloud/storage@^5.3.0": - version "5.3.0" - resolved "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.3.0.tgz#cf86683911cce68829e46de544abb41947d29da2" - integrity sha512-3t5UF3SZ14Bw2kcBHubCai6EIugU2GnQOstYWVSFuoO8IJ94RAaIOPq/dtexvQbUTpBTAGpd5smVR9WPL1mJVw== + version "5.8.5" + resolved "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.8.5.tgz#2cf1e2e0ef8ca552abc4450301fef3fea4900ef6" + integrity sha512-i0gB9CRwQeOBYP7xuvn14M40LhHCwMjceBjxE4CTvsqL519sVY5yVKxLiAedHWGwUZHJNRa7Q2CmNfkdRwVNPg== dependencies: - "@google-cloud/common" "^3.3.0" + "@google-cloud/common" "^3.6.0" "@google-cloud/paginator" "^3.0.0" "@google-cloud/promisify" "^2.0.0" arrify "^2.0.0" + async-retry "^1.3.1" compressible "^2.0.12" - concat-stream "^2.0.0" - date-and-time "^0.14.0" - duplexify "^3.5.0" + date-and-time "^1.0.0" + duplexify "^4.0.0" extend "^3.0.2" - gaxios "^3.0.0" - gcs-resumable-upload "^3.1.0" + gaxios "^4.0.0" + gcs-resumable-upload "^3.1.4" + get-stream "^6.0.0" hash-stream-validation "^0.2.2" mime "^2.2.0" mime-types "^2.0.8" @@ -1265,61 +1529,36 @@ stream-events "^1.0.1" xdg-basedir "^4.0.0" -"@grpc/grpc-js@^0.6.12": - version "0.6.18" - resolved "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-0.6.18.tgz#ba3b3dfef869533161d192a385412a4abd0db127" - integrity sha512-uAzv/tM8qpbf1vpx1xPMfcUMzbfdqJtdCYAqY/LsLeQQlnTb4vApylojr+wlCyr7bZeg3AFfHvtihnNOQQt/nA== - dependencies: - semver "^6.2.0" - -"@grpc/grpc-js@^1.0.0", "@grpc/grpc-js@~1.1.1": - version "1.1.7" - resolved "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.1.7.tgz#d3d71c6da95397e2d63895ccc4a05e7572f7b7e6" - integrity sha512-EuxMstI0u778dp0nk6Fe3gHXYPeV6FYsWOe0/QFwxv1NQ6bc5Wl/0Yxa4xl9uBlKElL6AIxuASmSfu7KEJhqiw== - dependencies: - "@grpc/proto-loader" "^0.6.0-pre14" - "@types/node" "^12.12.47" - google-auth-library "^6.0.0" - semver "^6.2.0" - -"@grpc/grpc-js@~1.0.3": - version "1.0.5" - resolved "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.0.5.tgz#09948c0810e62828fdd61455b2eb13d7879888b0" - integrity sha512-Hm+xOiqAhcpT9RYM8lc15dbQD7aQurM7ZU8ulmulepiPlN7iwBXXwP3vSBUimoFoApRqz7pSIisXU8pZaCB4og== - dependencies: - semver "^6.2.0" - -"@grpc/proto-loader@^0.5.0", "@grpc/proto-loader@^0.5.1": - version "0.5.5" - resolved "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.5.tgz#6725e7a1827bdf8e92e29fbf4e9ef0203c0906a9" - integrity sha512-WwN9jVNdHRQoOBo9FDH7qU+mgfjPc8GygPYms3M+y3fbQLfnCe/Kv/E01t7JRgnrsOHH8euvSbed3mIalXhwqQ== +"@grpc/grpc-js@^1.3.2", "@grpc/grpc-js@~1.3.0": + version "1.3.2" + resolved "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.3.2.tgz#eae97e6daf5abd49a7818aadeca0744dfb1ebca1" + integrity sha512-UXepkOKCATJrhHGsxt+CGfpZy9zUn1q9mop5kfcXq1fBkTePxVNPOdnISlCbJFlCtld+pSLGyZCzr9/zVprFKA== dependencies: - lodash.camelcase "^4.3.0" - protobufjs "^6.8.6" + "@types/node" ">=12.12.47" -"@grpc/proto-loader@^0.6.0-pre14": - version "0.6.0-pre9" - resolved "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.0-pre9.tgz#0c6fe42f6c5ef9ce1b3cef7be64d5b09d6fe4d6d" - integrity sha512-oM+LjpEjNzW5pNJjt4/hq1HYayNeQT+eGrOPABJnYHv7TyNPDNzkQ76rDYZF86X5swJOa4EujEMzQ9iiTdPgww== +"@grpc/proto-loader@^0.6.0", "@grpc/proto-loader@^0.6.1": + version "0.6.2" + resolved "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.2.tgz#412575f3ff5ef0a9b79d4ea12c08cba5601041cb" + integrity sha512-q2Qle60Ht2OQBCp9S5hv1JbI4uBBq6/mqSevFNK3ZEgRDBCAkWqZPUhD/K9gXOHrHKluliHiVq2L9sw1mVyAIg== dependencies: "@types/long" "^4.0.1" lodash.camelcase "^4.3.0" long "^4.0.0" - protobufjs "^6.9.0" - yargs "^15.3.1" + protobufjs "^6.10.0" + yargs "^16.1.1" -"@gulp-sourcemaps/identity-map@1.X": - version "1.0.2" - resolved "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-1.0.2.tgz#1e6fe5d8027b1f285dc0d31762f566bccd73d5a9" - integrity sha512-ciiioYMLdo16ShmfHBXJBOFm3xPC4AuwO4xeRpFeHz7WK9PYsWCmigagG2XyzZpubK4a3qNKoUBDhbzHfa50LQ== +"@gulp-sourcemaps/identity-map@^2.0.1": + version "2.0.1" + resolved "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-2.0.1.tgz#a6e8b1abec8f790ec6be2b8c500e6e68037c0019" + integrity sha512-Tb+nSISZku+eQ4X1lAkevcQa+jknn/OVUgZ3XCxEKIsLsqYuPoJwJOPQeaOk75X3WPftb29GWY1eqE7GLsXb1Q== dependencies: - acorn "^5.0.3" - css "^2.2.1" - normalize-path "^2.1.1" + acorn "^6.4.1" + normalize-path "^3.0.0" + postcss "^7.0.16" source-map "^0.6.0" - through2 "^2.0.3" + through2 "^3.0.1" -"@gulp-sourcemaps/map-sources@1.X": +"@gulp-sourcemaps/map-sources@^1.0.0": version "1.0.0" resolved "https://registry.npmjs.org/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz#890ae7c5d8c877f6d384860215ace9d7ec945bda" integrity sha1-iQrnxdjId/bThIYCFazp1+yUW9o= @@ -1339,9 +1578,42 @@ resolve-from "^5.0.0" "@istanbuljs/schema@^0.1.2": - version "0.1.2" - resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" - integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== + version "0.1.3" + resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^26.6.2": + version "26.6.2" + resolved "https://registry.npmjs.org/@jest/console/-/console-26.6.2.tgz#4e04bc464014358b03ab4937805ee36a0aeb98f2" + integrity sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g== + dependencies: + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^26.6.2" + jest-util "^26.6.2" + slash "^3.0.0" + +"@jest/test-result@^26.5.2": + version "26.6.2" + resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.2.tgz#55da58b62df134576cc95476efa5f7949e3f5f18" + integrity sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ== + dependencies: + "@jest/console" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/types@^26.6.2": + version "26.6.2" + resolved "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" + integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^15.0.0" + chalk "^4.0.0" "@jsdevtools/ono@^7.1.3": version "7.1.3" @@ -2066,48 +2338,10 @@ globby "^11.0.0" read-yaml-file "^1.1.0" -"@microsoft/api-documenter@7.9.10": - version "7.9.10" - resolved "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.9.10.tgz#405d28a48803b6aeb5a284b0375b5d468e1b2f27" - integrity sha512-Zabla5EelV1HY7+f8S+DyGEZ3IsYzr0PJgoGuZNfp8MX4HQUEM9jRQpuxcnFbdFJWmAHAbQu7gN3yT6GrxgoMg== - dependencies: - "@microsoft/api-extractor-model" "7.10.3" - "@microsoft/tsdoc" "0.12.19" - "@rushstack/node-core-library" "3.34.3" - "@rushstack/ts-command-line" "4.7.3" - colors "~1.2.1" - js-yaml "~3.13.1" - resolve "~1.17.0" - -"@microsoft/api-extractor-model@7.10.3": - version "7.10.3" - resolved "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.10.3.tgz#f687f324e940bd71e3e73b5b262a54594b0ea61c" - integrity sha512-etP4NbZpj+zPCuO3YYigIYXkXq5zYhfE3vo/hrCj1OOd/159HDbSHnEQrNWRVy5TR79RAzHvkYAwtLYKeYP8Ag== - dependencies: - "@microsoft/tsdoc" "0.12.19" - "@rushstack/node-core-library" "3.34.3" - -"@microsoft/api-extractor@7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.10.4.tgz#9055fab0a702d2d74eba05ff369c50012cf41176" - integrity sha512-vod9Y8IHhBtB3hcKiOe4OLi/hNeWtgRh/mxGrye5SeHaJpu5urAAA9CxLxBT8fDe+pyr1ipzlaiM/eMm2vXKgw== - dependencies: - "@microsoft/api-extractor-model" "7.10.3" - "@microsoft/tsdoc" "0.12.19" - "@rushstack/node-core-library" "3.34.3" - "@rushstack/rig-package" "0.2.4" - "@rushstack/ts-command-line" "4.7.3" - colors "~1.2.1" - lodash "~4.17.15" - resolve "~1.17.0" - semver "~7.3.0" - source-map "~0.6.1" - typescript "~3.9.7" - -"@microsoft/tsdoc@0.12.19": - version "0.12.19" - resolved "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.12.19.tgz#2173ccb92469aaf62031fa9499d21b16d07f9b57" - integrity sha512-IpgPxHrNxZiMNUSXqR1l/gePKPkfAmIKoDRP9hp7OwjU29ZR8WCJsOJ8iBKgw0Qk+pFwR+8Y1cy8ImLY6e9m4A== +"@microsoft/tsdoc@0.12.24": + version "0.12.24" + resolved "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.12.24.tgz#30728e34ebc90351dd3aff4e18d038eed2c3e098" + integrity sha512-Mfmij13RUTmHEMi9vRUhMXD7rnGR2VvxeNYtaGtaJ4redwwjT4UXYJ+nzmVJF7hhd4pn/Fx5sncDKxMVFJSWPg== "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" @@ -2117,18 +2351,18 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" -"@nodelib/fs.scandir@2.1.3": - version "2.1.3" - resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" - integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw== +"@nodelib/fs.scandir@2.1.4": + version "2.1.4" + resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69" + integrity sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA== dependencies: - "@nodelib/fs.stat" "2.0.3" + "@nodelib/fs.stat" "2.0.4" run-parallel "^1.1.9" -"@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2": - version "2.0.3" - resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3" - integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA== +"@nodelib/fs.stat@2.0.4", "@nodelib/fs.stat@^2.0.2": + version "2.0.4" + resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz#a3f2dd61bab43b8db8fa108a121cfffe4c676655" + integrity sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q== "@nodelib/fs.stat@^1.1.2": version "1.1.3" @@ -2136,29 +2370,42 @@ integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== "@nodelib/fs.walk@^1.2.3": - version "1.2.4" - resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976" - integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ== + version "1.2.6" + resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz#cce9396b30aa5afe9e3756608f5831adcb53d063" + integrity sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow== dependencies: - "@nodelib/fs.scandir" "2.1.3" + "@nodelib/fs.scandir" "2.1.4" fastq "^1.6.0" +"@npmcli/move-file@^1.0.1": + version "1.1.2" + resolved "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" + integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== + dependencies: + mkdirp "^1.0.4" + rimraf "^3.0.2" + "@octokit/auth-token@^2.4.0": - version "2.4.2" - resolved "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.2.tgz#10d0ae979b100fa6b72fa0e8e63e27e6d0dbff8a" - integrity sha512-jE/lE/IKIz2v1+/P0u4fJqv0kYwXOTujKemJMFr6FeopsxlIK3+wKDCJGnysg81XID5TgZQbIfuJ5J0lnTiuyQ== + version "2.4.5" + resolved "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.5.tgz#568ccfb8cb46f36441fac094ce34f7a875b197f3" + integrity sha512-BpGYsPgJt05M7/L/5FoE1PiAbdxXFZkX/3kDYcsvd1v6UhlnE5e96dTDr0ezX/EFwciQxf3cNV0loipsURU+WA== dependencies: - "@octokit/types" "^5.0.0" + "@octokit/types" "^6.0.3" "@octokit/endpoint@^6.0.1": - version "6.0.6" - resolved "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.6.tgz#4f09f2b468976b444742a1d5069f6fa45826d999" - integrity sha512-7Cc8olaCoL/mtquB7j/HTbPM+sY6Ebr4k2X2y4JoXpVKQ7r5xB4iGQE0IoO58wIPsUk4AzoT65AMEpymSbWTgQ== + version "6.0.11" + resolved "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.11.tgz#082adc2aebca6dcefa1fb383f5efb3ed081949d1" + integrity sha512-fUIPpx+pZyoLW4GCs3yMnlj2LfoXTWDUVPTC4V3MUEKZm48W+XYpeWSZCv+vYF1ZABUm2CqnDVf1sFtIYrj7KQ== dependencies: - "@octokit/types" "^5.0.0" + "@octokit/types" "^6.0.3" is-plain-object "^5.0.0" universal-user-agent "^6.0.0" +"@octokit/openapi-types@^7.0.0": + version "7.0.0" + resolved "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-7.0.0.tgz#0f6992db9854af15eca77d71ab0ec7fad2f20411" + integrity sha512-gV/8DJhAL/04zjTI95a7FhQwS6jlEE0W/7xeYAzuArD0KVAVWDLP2f3vi98hs3HLTczxXdRK/mF0tRoQPpolEw== + "@octokit/plugin-enterprise-rest@^6.0.1": version "6.0.1" resolved "https://registry.npmjs.org/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-6.0.1.tgz#e07896739618dab8da7d4077c658003775f95437" @@ -2172,9 +2419,9 @@ "@octokit/types" "^2.0.1" "@octokit/plugin-request-log@^1.0.0": - version "1.0.0" - resolved "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.0.tgz#eef87a431300f6148c39a7f75f8cfeb218b2547e" - integrity sha512-ywoxP68aOT3zHCLgWZgwUJatiENeHE7xJzYjfz8WI0goynp96wETBF+d95b8g/uL4QmS6owPVlaxiz3wyMAzcw== + version "1.0.3" + resolved "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.3.tgz#70a62be213e1edc04bb8897ee48c311482f9700d" + integrity sha512-4RFU4li238jMJAzLgAwkBAw+4Loile5haQMQr+uhFq27BmyJXcXSKvoQKqh0agsZEiUlW6iSv3FAgvmGkur7OQ== "@octokit/plugin-rest-endpoint-methods@2.4.0": version "2.4.0" @@ -2194,26 +2441,24 @@ once "^1.4.0" "@octokit/request-error@^2.0.0": - version "2.0.2" - resolved "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.2.tgz#0e76b83f5d8fdda1db99027ea5f617c2e6ba9ed0" - integrity sha512-2BrmnvVSV1MXQvEkrb9zwzP0wXFNbPJij922kYBTLIlIafukrGOb+ABBT2+c6wZiuyWDH1K1zmjGQ0toN/wMWw== + version "2.0.5" + resolved "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.5.tgz#72cc91edc870281ad583a42619256b380c600143" + integrity sha512-T/2wcCFyM7SkXzNoyVNWjyVlUwBvW3igM3Btr/eKYiPmucXTtkxt2RBsf6gn3LTzaLSLTQtNmvg+dGsOxQrjZg== dependencies: - "@octokit/types" "^5.0.1" + "@octokit/types" "^6.0.3" deprecation "^2.0.0" once "^1.4.0" "@octokit/request@^5.2.0": - version "5.4.9" - resolved "https://registry.npmjs.org/@octokit/request/-/request-5.4.9.tgz#0a46f11b82351b3416d3157261ad9b1558c43365" - integrity sha512-CzwVvRyimIM1h2n9pLVYfTDmX9m+KHSgCpqPsY8F1NdEK8IaWqXhSBXsdjOBFZSpEcxNEeg4p0UO9cQ8EnOCLA== + version "5.4.15" + resolved "https://registry.npmjs.org/@octokit/request/-/request-5.4.15.tgz#829da413dc7dd3aa5e2cdbb1c7d0ebe1f146a128" + integrity sha512-6UnZfZzLwNhdLRreOtTkT9n57ZwulCve8q3IT/Z477vThu6snfdkBuhxnChpOKNGxcQ71ow561Qoa6uqLdPtag== dependencies: "@octokit/endpoint" "^6.0.1" "@octokit/request-error" "^2.0.0" - "@octokit/types" "^5.0.0" - deprecation "^2.0.0" + "@octokit/types" "^6.7.1" is-plain-object "^5.0.0" node-fetch "^2.6.1" - once "^1.4.0" universal-user-agent "^6.0.0" "@octokit/rest@^16.28.4": @@ -2245,12 +2490,27 @@ dependencies: "@types/node" ">= 8" -"@octokit/types@^5.0.0", "@octokit/types@^5.0.1": - version "5.5.0" - resolved "https://registry.npmjs.org/@octokit/types/-/types-5.5.0.tgz#e5f06e8db21246ca102aa28444cdb13ae17a139b" - integrity sha512-UZ1pErDue6bZNjYOotCNveTXArOMZQFG6hKJfOnGnulVCMcVVi7YIIuuR4WfBhjo7zgpmzn/BkPDnUXtNx+PcQ== +"@octokit/types@^6.0.3", "@octokit/types@^6.7.1": + version "6.14.2" + resolved "https://registry.npmjs.org/@octokit/types/-/types-6.14.2.tgz#64c9457f38fb8522bdbba3c8cc814590a2d61bf5" + integrity sha512-wiQtW9ZSy4OvgQ09iQOdyXYNN60GqjCL/UdMsepDr1Gr0QzpW6irIKbH3REuAHXAhxkEk9/F2a3Gcs1P6kW5jA== dependencies: - "@types/node" ">= 8" + "@octokit/openapi-types" "^7.0.0" + +"@opentelemetry/api@^0.18.1": + version "0.18.1" + resolved "https://registry.npmjs.org/@opentelemetry/api/-/api-0.18.1.tgz#fb499e07afa1f55acffc47b2469eb218dcdee2a2" + integrity sha512-pKNxHe3AJ5T2N5G3AlT9gx6FyF5K2FS9ZNc+FipC+f1CpVF/EY+JHTJ749dnM2kWIgZTbDJFiGMuc0FYjNSCOg== + +"@opentelemetry/semantic-conventions@^0.18.2": + version "0.18.2" + resolved "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-0.18.2.tgz#a0f15d2ef752567713c1f59f69c6742edb03030c" + integrity sha512-+0P+PrP9qSFVaayNdek4P1OAGE+PEl2SsufuHDRmUpOY25Wzjo7Atyar56Trjc32jkNy4lID6ZFT6BahsR9P9A== + +"@panva/asn1.js@^1.0.0": + version "1.0.0" + resolved "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz#dd55ae7b8129e02049f009408b97c61ccf9032f6" + integrity sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw== "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" @@ -2305,17 +2565,17 @@ resolved "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= -"@rollup/plugin-alias@3.1.1": - version "3.1.1" - resolved "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-3.1.1.tgz#bb96cf37fefeb0a953a6566c284855c7d1cd290c" - integrity sha512-hNcQY4bpBUIvxekd26DBPgF7BT4mKVNDF5tBG4Zi+3IgwLxGYRY0itHs9D0oLVwXM5pvJDWJlBQro+au8WaUWw== +"@rollup/plugin-alias@3.1.2": + version "3.1.2" + resolved "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-3.1.2.tgz#c585b05be4a7782d269c69d13def56f44e417772" + integrity sha512-wzDnQ6v7CcoRzS0qVwFPrFdYA4Qlr+ookA217Y2Z3DPZE1R8jrFNM3jvGgOf6o6DMjbnQIn5lCIJgHPe1Bt3uw== dependencies: slash "^3.0.0" -"@rollup/plugin-commonjs@15.1.0": - version "15.1.0" - resolved "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-15.1.0.tgz#1e7d076c4f1b2abf7e65248570e555defc37c238" - integrity sha512-xCQqz4z/o0h2syQ7d9LskIMvBSH4PX5PjYdpSSvgS+pQik3WahkQVNWg3D8XJeYjZoVWnIUQYDghuEMRGrmQYQ== +"@rollup/plugin-commonjs@17.1.0": + version "17.1.0" + resolved "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-17.1.0.tgz#757ec88737dffa8aa913eb392fade2e45aef2a2d" + integrity sha512-PoMdXCw0ZyvjpCMT5aV4nkL0QywxP29sODQsSGeDpr/oI49Qq9tRtAsb/LbYbDzFlOydVEqHmmZWFtXJEAX9ew== dependencies: "@rollup/pluginutils" "^3.1.0" commondir "^1.0.1" @@ -2332,28 +2592,33 @@ dependencies: "@rollup/pluginutils" "^3.0.8" -"@rollup/plugin-node-resolve@9.0.0": - version "9.0.0" - resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-9.0.0.tgz#39bd0034ce9126b39c1699695f440b4b7d2b62e6" - integrity sha512-gPz+utFHLRrd41WMP13Jq5mqqzHL3OXrfj3/MkSyB6UBIcuNt9j60GCbarzMzdf1VHFpOxfQh/ez7wyadLMqkg== +"@rollup/plugin-node-resolve@11.2.0": + version "11.2.0" + resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.0.tgz#a5ab88c35bb7622d115f44984dee305112b6f714" + integrity sha512-qHjNIKYt5pCcn+5RUBQxK8krhRvf1HnyVgUCcFFcweDS7fhkOLZeYh0mhHK6Ery8/bb9tvN/ubPzmfF0qjDCTA== dependencies: "@rollup/pluginutils" "^3.1.0" "@types/resolve" "1.17.1" builtin-modules "^3.1.0" deepmerge "^4.2.2" is-module "^1.0.0" - resolve "^1.17.0" + resolve "^1.19.0" -"@rollup/plugin-strip@2.0.0": - version "2.0.0" - resolved "https://registry.npmjs.org/@rollup/plugin-strip/-/plugin-strip-2.0.0.tgz#46014ca2bc30bfff4ac289c52d7174671418173d" - integrity sha512-1xtE2FDk7Wwr2dEPugkQp3M2FGtSVYeRQaJnHJIkHzcXpBlxT/z+jJoVUvm8LGXso+bLWgO65WrmQ6bC6e/x+A== +"@rollup/plugin-strip@2.0.1": + version "2.0.1" + resolved "https://registry.npmjs.org/@rollup/plugin-strip/-/plugin-strip-2.0.1.tgz#276e7789a33ae0b10bc8522a20f187d8a6b6b550" + integrity sha512-+JJInHt/90Ta/ofCH+YHrI6nyDKe9jVzwBkmnakjDUMD+2QUTPHy60jep+kaMm4ARKWavtfmPbQp7e+xxAHU7g== dependencies: - "@rollup/pluginutils" "^3.0.4" - estree-walker "^1.0.1" - magic-string "^0.25.5" + "@rollup/pluginutils" "^3.1.0" + estree-walker "^2.0.1" + magic-string "^0.25.7" + +"@rollup/plugin-virtual@2.0.3": + version "2.0.3" + resolved "https://registry.npmjs.org/@rollup/plugin-virtual/-/plugin-virtual-2.0.3.tgz#0afc88d75c1e1378ab290b8e9898d4edb5be0d74" + integrity sha512-pw6ziJcyjZtntQ//bkad9qXaBx665SgEL8C8KI5wO8G5iU5MPxvdWrQyVaAvjojGm9tJoS8M9Z/EEepbqieYmw== -"@rollup/pluginutils@^3.0.4", "@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.0.9", "@rollup/pluginutils@^3.1.0": +"@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.0.9", "@rollup/pluginutils@^3.1.0": version "3.1.0" resolved "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== @@ -2362,10 +2627,18 @@ estree-walker "^1.0.1" picomatch "^2.2.2" -"@rushstack/node-core-library@3.34.3": - version "3.34.3" - resolved "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.34.3.tgz#a59a1e452dcc79bd4e5f0840b4e9603551668f85" - integrity sha512-WNXHEk5/uoZsbrKzGpYUzDDymJvZarRkByab4uS1fbEcTSDFSVB9e0rREzCkU9yDAQlRutbFwiTXLu3LVR5F6w== +"@rollup/pluginutils@^4.1.0": + version "4.1.0" + resolved "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.1.0.tgz#0dcc61c780e39257554feb7f77207dceca13c838" + integrity sha512-TrBhfJkFxA+ER+ew2U2/fHbebhLT/l/2pRk0hfj9KusXUuRXd2v0R58AfaZK9VXDQ4TogOSEmICVrQAA3zFnHQ== + dependencies: + estree-walker "^2.0.1" + picomatch "^2.2.2" + +"@rushstack/node-core-library@3.36.0": + version "3.36.0" + resolved "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.36.0.tgz#95dace39d763c8695d6607c421f95c6ac65b0ed4" + integrity sha512-bID2vzXpg8zweXdXgQkKToEdZwVrVCN9vE9viTRk58gqzYaTlz4fMId6V3ZfpXN6H0d319uGi2KDlm+lUEeqCg== dependencies: "@types/node" "10.17.13" colors "~1.2.1" @@ -2377,19 +2650,34 @@ timsort "~0.3.0" z-schema "~3.18.3" -"@rushstack/rig-package@0.2.4": - version "0.2.4" - resolved "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.2.4.tgz#259feb6637bedbfdc130d3a188ce85370e048743" - integrity sha512-/UXb6N0m0l+5kU4mLmRxyw3we+/w1fesgvfg96xllty5LyQxKDvkscmjlvCU/Yx55WO1tVxN4/7YlNEB2DcHyA== +"@rushstack/node-core-library@3.39.0": + version "3.39.0" + resolved "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.39.0.tgz#38928946d15ae89b773386cf97433d0d1ec83b93" + integrity sha512-kgu3+7/zOBkZU0+NdJb1rcHcpk3/oTjn5c8cg5nUTn+JDjEw58yG83SoeJEcRNNdl11dGX0lKG2PxPsjCokZOQ== + dependencies: + "@types/node" "10.17.13" + 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/rig-package@0.2.9": + version "0.2.9" + resolved "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.2.9.tgz#57ef94e7f7703b18e275b603d3f59a1a16580716" + integrity sha512-4tqsZ/m+BjeNAGeAJYzPF53CT96TsAYeZ3Pq3T4tb1pGGM3d3TWfkmALZdKNhpRlAeShKUrb/o/f/0sAuK/1VQ== dependencies: "@types/node" "10.17.13" resolve "~1.17.0" strip-json-comments "~3.1.1" -"@rushstack/ts-command-line@4.7.3": - version "4.7.3" - resolved "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.7.3.tgz#fa72c637d70aa29c201f8f016f7db626f2d23a2c" - integrity sha512-8FNrUSbMgKLgRVcsg1STsIC2xAdyes7qJtVwg36hSnBAMZgCCIM+Z36nnxyrnYTS/6qwiXv7fwVaUxXH+SyiAQ== +"@rushstack/ts-command-line@4.7.8": + version "4.7.8" + resolved "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.7.8.tgz#3aa77cf544c571be3206fc2bcba20c7a096ed254" + integrity sha512-8ghIWhkph7NnLCMDJtthpsb7TMOsVGXVDvmxjE/CeklTqjbbUFBjGXizJfpbEkRQTELuZQ2+vGn7sGwIWKN2uA== dependencies: "@types/argparse" "1.0.38" argparse "~1.0.9" @@ -2409,9 +2697,9 @@ integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== "@sinonjs/commons@^1", "@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.8.1": - version "1.8.1" - resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz#e7df00f98a203324f6dc7cc606cad9d4a8ab2217" - integrity sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw== + version "1.8.3" + resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" + integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== dependencies: type-detect "4.0.8" @@ -2422,6 +2710,13 @@ dependencies: "@sinonjs/commons" "^1.7.0" +"@sinonjs/fake-timers@^7.0.4": + version "7.0.5" + resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.0.5.tgz#558a7f8145a01366c44b3dcbdd7172c05c461564" + integrity sha512-fUt6b15bjV/VW93UP5opNXJxdwZSbK1EdiwnhN7XrQrcpaOhMJpZ/CjwFpM3THpxwA+YviBUJKSuEqKlCK5alw== + dependencies: + "@sinonjs/commons" "^1.7.0" + "@sinonjs/formatio@^5.0.1": version "5.0.1" resolved "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-5.0.1.tgz#f13e713cb3313b1ab965901b01b0828ea6b77089" @@ -2430,19 +2725,10 @@ "@sinonjs/commons" "^1" "@sinonjs/samsam" "^5.0.2" -"@sinonjs/samsam@^5.0.2": - version "5.1.0" - resolved "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.1.0.tgz#3afe719232b541bb6cf3411a4c399a188de21ec0" - integrity sha512-42nyaQOVunX5Pm6GRJobmzbS7iLI+fhERITnETXzzwDZh+TtDr/Au3yAvXVjFmZ4wEUaE4Y3NFZfKv0bV0cbtg== - dependencies: - "@sinonjs/commons" "^1.6.0" - lodash.get "^4.4.2" - type-detect "^4.0.8" - -"@sinonjs/samsam@^5.2.0": - version "5.2.0" - resolved "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.2.0.tgz#fcff83ab86f83b5498f4a967869c079408d9b5eb" - integrity sha512-CaIcyX5cDsjcW/ab7HposFWzV1kC++4HNsfnEdFJa7cP1QIuILAKV+BgfeqRXhcnSAc76r/Rh/O5C+300BwUIw== +"@sinonjs/samsam@^5.0.2", "@sinonjs/samsam@^5.3.0": + version "5.3.1" + resolved "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.3.1.tgz#375a45fe6ed4e92fca2fb920e007c48232a6507f" + integrity sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg== dependencies: "@sinonjs/commons" "^1.6.0" lodash.get "^4.4.2" @@ -2470,11 +2756,25 @@ resolved "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== +"@types/archiver@^5.1.0": + version "5.1.0" + resolved "https://registry.npmjs.org/@types/archiver/-/archiver-5.1.0.tgz#869f4ce4028e49cf9a0243cf914415f4cc3d1f3d" + integrity sha512-baFOhanb/hxmcOd1Uey2TfFg43kTSmM6py1Eo7Rjbv/ivcl7PXLhY0QgXGf50Hx/eskGCFqPfhs/7IZLb15C5g== + dependencies: + "@types/glob" "*" + "@types/argparse@1.0.38": version "1.0.38" resolved "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz#a81fd8606d481f873a3800c6ebae4f1d768a56a9" integrity sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA== +"@types/babel__traverse@^7.0.4": + version "7.11.1" + resolved "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.11.1.tgz#654f6c4f67568e24c23b367e947098c6206fa639" + integrity sha512-Vs0hm0vPahPMYi9tDjtP66llufgO3ST16WXaSTtDGEl9cewAl3AibmxWw6TINOqHPT9z0uABKAYjT9jNSg4npw== + dependencies: + "@babel/types" "^7.3.0" + "@types/body-parser@*": version "1.19.0" resolved "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f" @@ -2496,14 +2796,14 @@ "@types/chai" "*" "@types/chai@*": - version "4.2.12" - resolved "https://registry.npmjs.org/@types/chai/-/chai-4.2.12.tgz#6160ae454cd89dae05adc3bb97997f488b608201" - integrity sha512-aN5IAC8QNtSUdQzxu7lGBgYAOuU1tmRU4c9dIq5OKGf/SBVjXo+ffM2wEjudAWbgpOhy60nLoAGH1xm8fpCKFQ== + version "4.2.18" + resolved "https://registry.npmjs.org/@types/chai/-/chai-4.2.18.tgz#0c8e298dbff8205e2266606c1ea5fbdba29b46e4" + integrity sha512-rS27+EkB/RE1Iz3u0XtVL5q36MGDWbgYe7zWiodyKNUnthxY0rukK5V36eiUCtCisB7NN8zKYH6DO2M37qxFEQ== -"@types/chai@4.2.13": - version "4.2.13" - resolved "https://registry.npmjs.org/@types/chai/-/chai-4.2.13.tgz#8a3801f6655179d1803d81e94a2e4aaf317abd16" - integrity sha512-o3SGYRlOpvLFpwJA6Sl1UPOwKFEvE4FxTEB/c9XHI2whdnd4kmPVkNLL8gY4vWGBxWWDumzLbKsAhEH5SKn37Q== +"@types/chai@4.2.14": + version "4.2.14" + resolved "https://registry.npmjs.org/@types/chai/-/chai-4.2.14.tgz#44d2dd0b5de6185089375d976b4ec5caf6861193" + integrity sha512-G+ITQPXkwTrslfG5L/BksmbLUA0M1iybEsmCWPqzSxsRRhJZimBKJkoMi8fr/CPygPTj4zO5pJH7I2/cm9M7SQ== "@types/child-process-promise@2.2.1": version "2.2.1" @@ -2517,15 +2817,10 @@ resolved "https://registry.npmjs.org/@types/clone/-/clone-2.1.0.tgz#cb888a3fe5319275b566ae3a9bc606e310c533d4" integrity sha512-d/aS/lPOnUSruPhgNtT8jW39fHRVTLQy9sodysP1kkG8EdAtdZu1vt8NJaYA8w/6Z9j8izkAsx1A/yJhcYR1CA== -"@types/color-name@^1.1.1": - version "1.1.1" - resolved "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" - integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== - "@types/connect@*": - version "3.4.33" - resolved "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz#31610c901eca573b8713c3330abc6e6b9f588546" - integrity sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A== + version "3.4.34" + resolved "https://registry.npmjs.org/@types/connect/-/connect-3.4.34.tgz#170a40223a6d666006d93ca128af2beb1d9b1901" + integrity sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ== dependencies: "@types/node" "*" @@ -2536,25 +2831,80 @@ dependencies: "@types/node" "*" -"@types/estree@*": - version "0.0.45" - resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.45.tgz#e9387572998e5ecdac221950dab3e8c3b16af884" - integrity sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g== +"@types/eslint-scope@^3.7.0": + version "3.7.0" + resolved "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.0.tgz#4792816e31119ebd506902a482caec4951fabd86" + integrity sha512-O/ql2+rrCUe2W2rs7wMR+GqPRcgB6UiqN5RhrR5xruFlY7l9YLMn0ZkDzjoHLeiFkR8MCQZVudUuuvQ2BLC9Qw== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*", "@types/eslint@7.2.10": + version "7.2.10" + resolved "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.10.tgz#4b7a9368d46c0f8cd5408c23288a59aa2394d917" + integrity sha512-kUEPnMKrqbtpCq/KTaGFFKAcz6Ethm2EjCoKIDaCmfRBWLbFuTcOJfTlorwbnboXBzahqWLgUp1BQeKHiJzPUQ== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*", "@types/estree@^0.0.47": + version "0.0.47" + resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.47.tgz#d7a51db20f0650efec24cd04994f523d93172ed4" + integrity sha512-c5ciR06jK8u9BstrmJyO97m+klJrrhCf9u3rLu3DEAJBirxRqSCvDQoYKmxuYwQI5SZChAWu+tq9oVlGRuzPAg== "@types/estree@0.0.39": version "0.0.39" resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== +"@types/expect@^1.20.4": + version "1.20.4" + resolved "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz#8288e51737bf7e3ab5d7c77bfa695883745264e5" + integrity sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg== + +"@types/express-jwt@0.0.42": + version "0.0.42" + resolved "https://registry.npmjs.org/@types/express-jwt/-/express-jwt-0.0.42.tgz#4f04e1fadf9d18725950dc041808a4a4adf7f5ae" + integrity sha512-WszgUddvM1t5dPpJ3LhWNH8kfNN8GPIBrAGxgIYXVCEGx6Bx4A036aAuf/r5WH9DIEdlmp7gHOYvSM6U87B0ag== + dependencies: + "@types/express" "*" + "@types/express-unless" "*" + "@types/express-serve-static-core@*": - version "4.17.13" - resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.13.tgz#d9af025e925fc8b089be37423b8d1eac781be084" - integrity sha512-RgDi5a4nuzam073lRGKTUIaL3eF2+H7LJvJ8eUnCI0wA6SNjXc44DCmWNiTLs/AZ7QlsFWZiw/gTG3nSQGL0fA== + version "4.17.19" + resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.19.tgz#00acfc1632e729acac4f1530e9e16f6dd1508a1d" + integrity sha512-DJOSHzX7pCiSElWaGR8kCprwibCB/3yW6vcT8VG3P0SJjnv19gnWG/AZMfM60Xj/YJIp/YCaDHyvzsFVeniARA== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express-serve-static-core@^4.17.18": + version "4.17.21" + resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.21.tgz#a427278e106bca77b83ad85221eae709a3414d42" + integrity sha512-gwCiEZqW6f7EoR8TTEfalyEhb1zA5jQJnRngr97+3pzMaO1RKoI1w2bw07TK72renMUVWcWS5mLI6rk1NqN0nA== dependencies: "@types/node" "*" "@types/qs" "*" "@types/range-parser" "*" +"@types/express-unless@*": + version "0.5.1" + resolved "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.5.1.tgz#4f440b905e42bbf53382b8207bc337dc5ff9fd1f" + integrity sha512-5fuvg7C69lemNgl0+v+CUxDYWVPSfXHhJPst4yTLcqi4zKJpORCxnDrnnilk3k0DTq/WrAUdvXFs01+vUqUZHw== + dependencies: + "@types/express" "*" + +"@types/express@*": + version "4.17.12" + resolved "https://registry.npmjs.org/@types/express/-/express-4.17.12.tgz#4bc1bf3cd0cfe6d3f6f2853648b40db7d54de350" + integrity sha512-pTYas6FrP15B1Oa0bkN5tQMNqOcVXa9j4FTFtO8DWI9kppKib+6NJtfTOOLcwxuuYvcX2+dVG6et1SxW/Kc17Q== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.18" + "@types/qs" "*" + "@types/serve-static" "*" + "@types/express@4.17.3": version "4.17.3" resolved "https://registry.npmjs.org/@types/express/-/express-4.17.3.tgz#38e4458ce2067873b09a73908df488870c303bd9" @@ -2571,7 +2921,7 @@ dependencies: "@types/node" "*" -"@types/glob@^7.1.1": +"@types/glob@*", "@types/glob@^7.1.1": version "7.1.3" resolved "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w== @@ -2579,6 +2929,13 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/graceful-fs@^4.1.2": + version "4.1.5" + resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" + integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== + dependencies: + "@types/node" "*" + "@types/inquirer@7.3.1": version "7.3.1" resolved "https://registry.npmjs.org/@types/inquirer/-/inquirer-7.3.1.tgz#1f231224e7df11ccfaf4cf9acbcc3b935fea292d" @@ -2587,10 +2944,34 @@ "@types/through" "*" rxjs "^6.4.0" -"@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6": - version "7.0.6" - resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" - integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw== +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": + version "2.0.3" + resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" + integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw== + +"@types/istanbul-lib-report@*": + version "3.0.0" + resolved "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" + integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.0" + resolved "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz#508b13aa344fa4976234e75dddcc34925737d821" + integrity sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/js-yaml@4.0.0": + version "4.0.0" + resolved "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.0.tgz#d1a11688112091f2c711674df3a65ea2f47b5dfb" + integrity sha512-4vlpCM5KPCL5CfGmTbpjwVKbISRYhduEJvvUWsH5EB7QInhEj94XPZ3ts/9FPiLZFqYO0xoW4ZL8z2AabTGgJA== + +"@types/json-schema@*", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6", "@types/json-schema@^7.0.7": + version "7.0.7" + resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" + integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== "@types/json-stable-stringify@1.0.32": version "1.0.32" @@ -2615,30 +2996,35 @@ resolved "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w== -"@types/mime@*": - version "2.0.3" - resolved "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz#c893b73721db73699943bfc3653b1deb7faa4a3a" - integrity sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q== +"@types/mime@^1": + version "1.3.2" + resolved "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" + integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== + +"@types/minimatch@*", "@types/minimatch@^3.0.3": + version "3.0.4" + resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz#f0ec25dbf2f0e4b18647313ac031134ca5b24b21" + integrity sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA== -"@types/minimatch@*", "@types/minimatch@3.0.3", "@types/minimatch@^3.0.3": +"@types/minimatch@3.0.3": version "3.0.3" resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== "@types/minimist@^1.2.0": - version "1.2.0" - resolved "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz#69a23a3ad29caf0097f06eda59b361ee2f0639f6" - integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY= + version "1.2.1" + resolved "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.1.tgz#283f669ff76d7b8260df8ab7a4262cc83d988256" + integrity sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg== "@types/mocha@7.0.2": version "7.0.2" resolved "https://registry.npmjs.org/@types/mocha/-/mocha-7.0.2.tgz#b17f16cf933597e10d6d78eae3251e692ce8b0ce" integrity sha512-ZvO2tAcjmMi8V/5Z3JsyofMe3hasRcaw88cto5etSVMwVQfeivGAlEYmaQgceUSVYFofVjT+ioHsATjdWcFt1w== -"@types/mz@2.7.1": - version "2.7.1" - resolved "https://registry.npmjs.org/@types/mz/-/mz-2.7.1.tgz#1ac1d69b039c8b3cbe603972b5c12d3167a84f58" - integrity sha512-H86h7KmRDVs9UeSiQvtUeVhS+WYpJSYSsZrRvNYpGWGiytEqxwEtvgRnINESQtCgnojIH2wS2WgaMTJP0firBw== +"@types/mz@2.7.3": + version "2.7.3" + resolved "https://registry.npmjs.org/@types/mz/-/mz-2.7.3.tgz#e42a21e73f5f9340fe4a176981fafb1eb8cc6c12" + integrity sha512-Zp1NUJ4Alh3gaun0a5rkF3DL7b2j1WB6rPPI5h+CJ98sQnxe9qwskClvupz/4bqChGR3L/BRhTjlaOwR+uiZJg== dependencies: "@types/node" "*" @@ -2650,35 +3036,30 @@ "@types/node" "*" form-data "^3.0.0" -"@types/node@*", "@types/node@>= 8": - version "14.11.2" - resolved "https://registry.npmjs.org/@types/node/-/node-14.11.2.tgz#2de1ed6670439387da1c9f549a2ade2b0a799256" - integrity sha512-jiE3QIxJ8JLNcb1Ps6rDbysDhN4xa8DJJvuC9prr6w+1tIh+QAbYyNF3tyiZNLDBIuBCf4KEcV2UvQm/V60xfA== +"@types/node@*", "@types/node@>= 8", "@types/node@>=12.12.47", "@types/node@>=13.7.0": + version "15.3.0" + resolved "https://registry.npmjs.org/@types/node/-/node-15.3.0.tgz#d6fed7d6bc6854306da3dea1af9f874b00783e26" + integrity sha512-8/bnjSZD86ZfpBsDlCIkNXIvm+h6wi9g7IqL+kmFkQ+Wvu3JrasgLElfiPgoo8V8vVfnEi0QVS12gbl94h9YsQ== "@types/node@10.17.13": version "10.17.13" resolved "https://registry.npmjs.org/@types/node/-/node-10.17.13.tgz#ccebcdb990bd6139cd16e84c39dc2fb1023ca90c" integrity sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg== -"@types/node@12.12.67": - version "12.12.67" - resolved "https://registry.npmjs.org/@types/node/-/node-12.12.67.tgz#4f86badb292e822e3b13730a1f9713ed2377f789" - integrity sha512-R48tgL2izApf+9rYNH+3RBMbRpPeW3N8f0I9HMhggeq4UXwBDqumJ14SDs4ctTMhG11pIOduZ4z3QWGOiMc9Vg== +"@types/node@12.20.15": + version "12.20.15" + resolved "https://registry.npmjs.org/@types/node/-/node-12.20.15.tgz#10ee6a6a3f971966fddfa3f6e89ef7a73ec622df" + integrity sha512-F6S4Chv4JicJmyrwlDkxUdGNSplsQdGwp1A0AJloEVDirWdZOAiRHhovDlsFkKUrquUXhz1imJhXHsf59auyAg== -"@types/node@^10.10.0": - version "10.17.35" - resolved "https://registry.npmjs.org/@types/node/-/node-10.17.35.tgz#58058f29b870e6ae57b20e4f6e928f02b7129f56" - integrity sha512-gXx7jAWpMddu0f7a+L+txMplp3FnHl53OhQIF9puXKq3hDGY/GjH+MF04oWnV/adPSCrbtHumDCFwzq2VhltWA== +"@types/node@^12.7.1": + version "12.20.13" + resolved "https://registry.npmjs.org/@types/node/-/node-12.20.13.tgz#e743bae112bd779ac9650f907197dd2caa7f0364" + integrity sha512-1x8W5OpxPq+T85OUsHRP6BqXeosKmeXRtjoF39STcdf/UWLqUsoehstZKOi0CunhVqHG17AyZgpj20eRVooK6A== -"@types/node@^12.12.47", "@types/node@^12.7.1": - version "12.12.62" - resolved "https://registry.npmjs.org/@types/node/-/node-12.12.62.tgz#733923d73669188d35950253dd18a21570085d2b" - integrity sha512-qAfo81CsD7yQIM9mVyh6B/U47li5g7cfpVQEDMfQeF8pSZVwzbhwU3crc0qG4DmpsebpJPR49AKOExQyJ05Cpg== - -"@types/node@^13.7.0": - version "13.13.21" - resolved "https://registry.npmjs.org/@types/node/-/node-13.13.21.tgz#e48d3c2e266253405cf404c8654d1bcf0d333e5c" - integrity sha512-tlFWakSzBITITJSxHV4hg4KvrhR/7h3xbJdSFbYJBVzKubrASbnnIFuSgolUh7qKGo/ZeJPKUfbZ0WS6Jp14DQ== +"@types/node@^14.14.41": + version "14.17.3" + resolved "https://registry.npmjs.org/@types/node/-/node-14.17.3.tgz#6d327abaa4be34a74e421ed6409a0ae2f47f4c3d" + integrity sha512-e6ZowgGJmTuXa3GyaPbTGxX17tnThl2aSSizrFthQ7m9uLGZBXiGhgE55cjRZTF5kjZvYn9EOPOMljdjwbflxw== "@types/normalize-package-data@^2.4.0": version "2.4.0" @@ -2690,15 +3071,25 @@ resolved "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== +"@types/prettier@2.3.0": + version "2.3.0" + resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.3.0.tgz#2e8332cc7363f887d32ec5496b207d26ba8052bb" + integrity sha512-hkc1DATxFLQo4VxPDpMH1gCkPpBbpOoJ/4nhuXw4n63/0R6bCpQECj4+K226UJ4JO/eJQz+1mC2I7JsWanAdQw== + +"@types/prettier@^2.0.0": + version "2.2.3" + resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.2.3.tgz#ef65165aea2924c9359205bf748865b8881753c0" + integrity sha512-PijRCG/K3s3w1We6ynUKdxEc5AcuuH3NBmMDP8uvKVp6X43UY7NQlTzczakXP3DJR0F4dfNQIGjU2cUeRYs2AA== + "@types/q@^0.0.32": version "0.0.32" resolved "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz#bd284e57c84f1325da702babfc82a5328190c0c5" integrity sha1-vShOV8hPEyXacCur/IKlMoGQwMU= "@types/qs@*": - version "6.9.5" - resolved "https://registry.npmjs.org/@types/qs/-/qs-6.9.5.tgz#434711bdd49eb5ee69d90c1d67c354a9a8ecb18b" - integrity sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ== + version "6.9.6" + resolved "https://registry.npmjs.org/@types/qs/-/qs-6.9.6.tgz#df9c3c8b31a247ec315e6996566be3171df4b3b1" + integrity sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA== "@types/range-parser@*": version "1.2.3" @@ -2722,6 +3113,11 @@ dependencies: "@types/node" "*" +"@types/resolve@1.20.0": + version "1.20.0" + resolved "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.0.tgz#11325a379b6f63b858fed49552fd4178495ee087" + integrity sha512-SFT3jdUNlLkjxUWwH/0QjLiEsV38hjdDX8oMcX9jZAD8KWNzRLdg6INZE7UMz9O86b2BOHzA3dR8nF+DbonX2Q== + "@types/selenium-webdriver@^3.0.0": version "3.0.17" resolved "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.17.tgz#50bea0c3c2acc31c959c5b1e747798b3b3d06d4b" @@ -2733,12 +3129,12 @@ integrity sha512-RxAwYt4rGwK5GyoRwuP0jT6ZHAVTdz2EqgsHmX0PYNjGsko+OeT4WFXXTs/lM3teJUJodM+SNtAL5/pXIJ61IQ== "@types/serve-static@*": - version "1.13.5" - resolved "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.5.tgz#3d25d941a18415d3ab092def846e135a08bbcf53" - integrity sha512-6M64P58N+OXjU432WoLLBQxbA0LRGBCRm7aAGQJ+SMC1IMl0dgRVi9EFfoDcS2a7Xogygk/eGN94CfwU9UF7UQ== + version "1.13.9" + resolved "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.9.tgz#aacf28a85a05ee29a11fb7c3ead935ac56f33e4e" + integrity sha512-ZFqF6qa48XsPdjXV5Gsz0Zqmux2PerNd3a/ktL45mHpa19cuMi/cL8tcxdAx497yRh+QtYPuofjT9oWw9P7nkA== dependencies: - "@types/express-serve-static-core" "*" - "@types/mime" "*" + "@types/mime" "^1" + "@types/node" "*" "@types/sinon-chai@3.2.5": version "3.2.5" @@ -2749,16 +3145,16 @@ "@types/sinon" "*" "@types/sinon@*": - version "9.0.7" - resolved "https://registry.npmjs.org/@types/sinon/-/sinon-9.0.7.tgz#c277e19cf9eb0c71106e785650f1e5c299262302" - integrity sha512-uyFiy2gp4P/FK9pmU3WIbT5ZzH54hCswwRkQFhxX7xl8jzhW3g+xOkVqk5YP4cIO//Few8UDAX0MtzFpqBEqwA== + version "10.0.0" + resolved "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.0.tgz#eecc3847af03d45ffe53d55aaaaf6ecb28b5e584" + integrity sha512-jDZ55oCKxqlDmoTBBbBBEx+N8ZraUVhggMZ9T5t+6/Dh8/4NiOjSUfpLrPiEwxQDlAe3wpAkoXhWvE6LibtsMQ== dependencies: - "@types/sinonjs__fake-timers" "*" + "@sinonjs/fake-timers" "^7.0.4" -"@types/sinon@9.0.8": - version "9.0.8" - resolved "https://registry.npmjs.org/@types/sinon/-/sinon-9.0.8.tgz#1ed0038d356784f75b086104ef83bfd4130bb81b" - integrity sha512-IVnI820FZFMGI+u1R+2VdRaD/82YIQTdqLYC9DLPszZuynAJDtCvCtCs3bmyL66s7FqRM3+LPX7DhHnVTaagDw== +"@types/sinon@9.0.10": + version "9.0.10" + resolved "https://registry.npmjs.org/@types/sinon/-/sinon-9.0.10.tgz#7fb9bcb6794262482859cab66d59132fca18fcf7" + integrity sha512-/faDC0erR06wMdybwI/uR8wEKV/E83T0k4sepIpB7gXuy2gzx2xiOjmztq6a2Y6rIGJ04D+6UU0VBmWy+4HEMA== dependencies: "@types/sinonjs__fake-timers" "*" @@ -2767,6 +3163,11 @@ resolved "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.2.tgz#3a84cf5ec3249439015e14049bd3161419bf9eae" integrity sha512-dIPoZ3g5gcx9zZEszaxLSVTvMReD3xxyyDnQUjA6IYDG9Ba2AV0otMPs+77sG9ojB4Qr2N2Vk5RnKeuA0X/0bg== +"@types/stack-utils@^2.0.0": + version "2.0.0" + resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff" + integrity sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw== + "@types/through@*": version "0.0.30" resolved "https://registry.npmjs.org/@types/through/-/through-0.0.30.tgz#e0e42ce77e897bd6aead6f6ea62aeb135b8a3895" @@ -2784,15 +3185,39 @@ resolved "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.0.tgz#fef1904e4668b6e5ecee60c52cc6a078ffa6697d" integrity sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A== +"@types/vinyl@^2.0.4": + version "2.0.4" + resolved "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.4.tgz#9a7a8071c8d14d3a95d41ebe7135babe4ad5995a" + integrity sha512-2o6a2ixaVI2EbwBPg1QYLGQoHK56p/8X/sGfKbFC8N6sY9lfjsMf/GprtkQkSya0D4uRiutRZ2BWj7k3JvLsAQ== + dependencies: + "@types/expect" "^1.20.4" + "@types/node" "*" + +"@types/webpack@5.28.0": + version "5.28.0" + resolved "https://registry.npmjs.org/@types/webpack/-/webpack-5.28.0.tgz#78dde06212f038d77e54116cfe69e88ae9ed2c03" + integrity sha512-8cP0CzcxUiFuA9xGJkfeVpqmWTk9nx6CWwamRGCj95ph1SmlRRk9KlCZ6avhCbZd4L68LvYT6l1kpdEnQXrF8w== + dependencies: + "@types/node" "*" + tapable "^2.2.0" + webpack "^5" + "@types/yargs-parser@*": - version "15.0.0" - resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d" - integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw== + version "20.2.0" + resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.0.tgz#dd3e6699ba3237f0348cd085e4698780204842f9" + integrity sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA== + +"@types/yargs@16.0.0": + version "16.0.0" + resolved "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.0.tgz#0e033b23452da5d61b6c44747612cb80ac528751" + integrity sha512-2nN6AGeMwe8+O6nO9ytQfbMQOJy65oi1yK2y/9oReR08DaXSGtMsrLyCM1ooKqfICpCx4oITaR4LkOmdzz41Ww== + dependencies: + "@types/yargs-parser" "*" -"@types/yargs@15.0.8": - version "15.0.8" - resolved "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.8.tgz#7644904cad7427eb704331ea9bf1ee5499b82e23" - integrity sha512-b0BYzFUzBpOhPjpl1wtAHU994jBeKF4TKVlT7ssFv44T617XNcPdRoG4AzHLVshLzlrF7i3lTelH7UbuNYV58Q== +"@types/yargs@^15.0.0": + version "15.0.13" + resolved "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz#34f7fec8b389d7f3c1fd08026a5763e072d3c6dc" + integrity sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ== dependencies: "@types/yargs-parser" "*" @@ -2803,83 +3228,95 @@ dependencies: "@types/node" "*" -"@typescript-eslint/eslint-plugin-tslint@4.4.1": - version "4.4.1" - resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin-tslint/-/eslint-plugin-tslint-4.4.1.tgz#b2fe38ab8e07f6c9de228dd99a1014672aa20513" - integrity sha512-5IVPQjhS2NzWjRpP18SuPnip5ep7zChPZLkPJr1onHg0TADDBolg35hCk9/55FedjBin8LIjzTbyMn24XXr+QQ== +"@typescript-eslint/eslint-plugin-tslint@4.28.0": + version "4.28.0" + resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin-tslint/-/eslint-plugin-tslint-4.28.0.tgz#f555425ee8b7dc3de1f5941df51c2c6edf19e891" + integrity sha512-3Djf20rQvQD8CT+ACMRC1VoAQngJJeWhEcVqy2lbvoWls39CarCMJ03IANI+iUsQY/nkl7qMQKDbjFpZ16GcdA== dependencies: - "@typescript-eslint/experimental-utils" "4.4.1" - lodash "^4.17.15" + "@typescript-eslint/experimental-utils" "4.28.0" + lodash "^4.17.21" -"@typescript-eslint/eslint-plugin@4.4.1": - version "4.4.1" - resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.4.1.tgz#b8acea0373bd2a388ac47df44652f00bf8b368f5" - integrity sha512-O+8Utz8pb4OmcA+Nfi5THQnQpHSD2sDUNw9AxNHpuYOo326HZTtG8gsfT+EAYuVrFNaLyNb2QnUNkmTRDskuRA== +"@typescript-eslint/eslint-plugin@4.28.0": + version "4.28.0" + resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.0.tgz#1a66f03b264844387beb7dc85e1f1d403bd1803f" + integrity sha512-KcF6p3zWhf1f8xO84tuBailV5cN92vhS+VT7UJsPzGBm9VnQqfI9AsiMUFUCYHTYPg1uCCo+HyiDnpDuvkAMfQ== dependencies: - "@typescript-eslint/experimental-utils" "4.4.1" - "@typescript-eslint/scope-manager" "4.4.1" - debug "^4.1.1" + "@typescript-eslint/experimental-utils" "4.28.0" + "@typescript-eslint/scope-manager" "4.28.0" + debug "^4.3.1" functional-red-black-tree "^1.0.1" - regexpp "^3.0.0" - semver "^7.3.2" - tsutils "^3.17.1" - -"@typescript-eslint/experimental-utils@4.4.1": - version "4.4.1" - resolved "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.4.1.tgz#40613b9757fa0170de3e0043254dbb077cafac0c" - integrity sha512-Nt4EVlb1mqExW9cWhpV6pd1a3DkUbX9DeyYsdoeziKOpIJ04S2KMVDO+SEidsXRH/XHDpbzXykKcMTLdTXH6cQ== - dependencies: - "@types/json-schema" "^7.0.3" - "@typescript-eslint/scope-manager" "4.4.1" - "@typescript-eslint/types" "4.4.1" - "@typescript-eslint/typescript-estree" "4.4.1" - eslint-scope "^5.0.0" - eslint-utils "^2.0.0" - -"@typescript-eslint/parser@4.4.1": - version "4.4.1" - resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.4.1.tgz#25fde9c080611f303f2f33cedb145d2c59915b80" - integrity sha512-S0fuX5lDku28Au9REYUsV+hdJpW/rNW0gWlc4SXzF/kdrRaAVX9YCxKpziH7djeWT/HFAjLZcnY7NJD8xTeUEg== - dependencies: - "@typescript-eslint/scope-manager" "4.4.1" - "@typescript-eslint/types" "4.4.1" - "@typescript-eslint/typescript-estree" "4.4.1" - debug "^4.1.1" + regexpp "^3.1.0" + semver "^7.3.5" + tsutils "^3.21.0" + +"@typescript-eslint/experimental-utils@4.28.0": + version "4.28.0" + resolved "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.0.tgz#13167ed991320684bdc23588135ae62115b30ee0" + integrity sha512-9XD9s7mt3QWMk82GoyUpc/Ji03vz4T5AYlHF9DcoFNfJ/y3UAclRsfGiE2gLfXtyC+JRA3trR7cR296TEb1oiQ== + dependencies: + "@types/json-schema" "^7.0.7" + "@typescript-eslint/scope-manager" "4.28.0" + "@typescript-eslint/types" "4.28.0" + "@typescript-eslint/typescript-estree" "4.28.0" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + +"@typescript-eslint/parser@4.28.0": + version "4.28.0" + resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.0.tgz#2404c16751a28616ef3abab77c8e51d680a12caa" + integrity sha512-7x4D22oPY8fDaOCvkuXtYYTQ6mTMmkivwEzS+7iml9F9VkHGbbZ3x4fHRwxAb5KeuSkLqfnYjs46tGx2Nour4A== + dependencies: + "@typescript-eslint/scope-manager" "4.28.0" + "@typescript-eslint/types" "4.28.0" + "@typescript-eslint/typescript-estree" "4.28.0" + debug "^4.3.1" + +"@typescript-eslint/scope-manager@4.28.0": + version "4.28.0" + resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.0.tgz#6a3009d2ab64a30fc8a1e257a1a320067f36a0ce" + integrity sha512-eCALCeScs5P/EYjwo6se9bdjtrh8ByWjtHzOkC4Tia6QQWtQr3PHovxh3TdYTuFcurkYI4rmFsRFpucADIkseg== + dependencies: + "@typescript-eslint/types" "4.28.0" + "@typescript-eslint/visitor-keys" "4.28.0" + +"@typescript-eslint/types@4.28.0": + version "4.28.0" + resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.0.tgz#a33504e1ce7ac51fc39035f5fe6f15079d4dafb0" + integrity sha512-p16xMNKKoiJCVZY5PW/AfILw2xe1LfruTcfAKBj3a+wgNYP5I9ZEKNDOItoRt53p4EiPV6iRSICy8EPanG9ZVA== + +"@typescript-eslint/typescript-estree@4.28.0": + version "4.28.0" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.0.tgz#e66d4e5aa2ede66fec8af434898fe61af10c71cf" + integrity sha512-m19UQTRtxMzKAm8QxfKpvh6OwQSXaW1CdZPoCaQuLwAq7VZMNuhJmZR4g5281s2ECt658sldnJfdpSZZaxUGMQ== + dependencies: + "@typescript-eslint/types" "4.28.0" + "@typescript-eslint/visitor-keys" "4.28.0" + debug "^4.3.1" + globby "^11.0.3" + is-glob "^4.0.1" + semver "^7.3.5" + tsutils "^3.21.0" -"@typescript-eslint/scope-manager@4.4.1": - version "4.4.1" - resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.4.1.tgz#d19447e60db2ce9c425898d62fa03b2cce8ea3f9" - integrity sha512-2oD/ZqD4Gj41UdFeWZxegH3cVEEH/Z6Bhr/XvwTtGv66737XkR4C9IqEkebCuqArqBJQSj4AgNHHiN1okzD/wQ== +"@typescript-eslint/visitor-keys@4.28.0": + version "4.28.0" + resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.0.tgz#255c67c966ec294104169a6939d96f91c8a89434" + integrity sha512-PjJyTWwrlrvM5jazxYF5ZPs/nl0kHDZMVbuIcbpawVXaDPelp3+S9zpOz5RmVUfS/fD5l5+ZXNKnWhNYjPzCvw== dependencies: - "@typescript-eslint/types" "4.4.1" - "@typescript-eslint/visitor-keys" "4.4.1" - -"@typescript-eslint/types@4.4.1": - version "4.4.1" - resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.4.1.tgz#c507b35cf523bc7ba00aae5f75ee9b810cdabbc1" - integrity sha512-KNDfH2bCyax5db+KKIZT4rfA8rEk5N0EJ8P0T5AJjo5xrV26UAzaiqoJCxeaibqc0c/IvZxp7v2g3difn2Pn3w== + "@typescript-eslint/types" "4.28.0" + eslint-visitor-keys "^2.0.0" -"@typescript-eslint/typescript-estree@4.4.1": - version "4.4.1" - resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.4.1.tgz#598f6de488106c2587d47ca2462c60f6e2797cb8" - integrity sha512-wP/V7ScKzgSdtcY1a0pZYBoCxrCstLrgRQ2O9MmCUZDtmgxCO/TCqOTGRVwpP4/2hVfqMz/Vw1ZYrG8cVxvN3g== - dependencies: - "@typescript-eslint/types" "4.4.1" - "@typescript-eslint/visitor-keys" "4.4.1" - debug "^4.1.1" - globby "^11.0.1" - is-glob "^4.0.1" - lodash "^4.17.15" - semver "^7.3.2" - tsutils "^3.17.1" +"@ungap/promise-all-settled@1.1.2": + version "1.1.2" + resolved "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" + integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== -"@typescript-eslint/visitor-keys@4.4.1": - version "4.4.1" - resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.4.1.tgz#1769dc7a9e2d7d2cfd3318b77ed8249187aed5c3" - integrity sha512-H2JMWhLaJNeaylSnMSQFEhT/S/FsJbebQALmoJxMPMxLtlVAMy2uJP/Z543n9IizhjRayLSqoInehCeNW9rWcw== +"@webassemblyjs/ast@1.11.0": + version "1.11.0" + resolved "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.0.tgz#a5aa679efdc9e51707a4207139da57920555961f" + integrity sha512-kX2W49LWsbthrmIRMbQZuQDhGtjyqXfEmmHyEi4XWnSZtPmxY0+3anPIzsnRb45VH/J55zlOfWvZuY47aJZTJg== dependencies: - "@typescript-eslint/types" "4.4.1" - eslint-visitor-keys "^2.0.0" + "@webassemblyjs/helper-numbers" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" "@webassemblyjs/ast@1.9.0": version "1.9.0" @@ -2890,16 +3327,31 @@ "@webassemblyjs/helper-wasm-bytecode" "1.9.0" "@webassemblyjs/wast-parser" "1.9.0" +"@webassemblyjs/floating-point-hex-parser@1.11.0": + version "1.11.0" + resolved "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.0.tgz#34d62052f453cd43101d72eab4966a022587947c" + integrity sha512-Q/aVYs/VnPDVYvsCBL/gSgwmfjeCb4LW8+TMrO3cSzJImgv8lxxEPM2JA5jMrivE7LSz3V+PFqtMbls3m1exDA== + "@webassemblyjs/floating-point-hex-parser@1.9.0": version "1.9.0" resolved "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz#3c3d3b271bddfc84deb00f71344438311d52ffb4" integrity sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA== +"@webassemblyjs/helper-api-error@1.11.0": + version "1.11.0" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.0.tgz#aaea8fb3b923f4aaa9b512ff541b013ffb68d2d4" + integrity sha512-baT/va95eXiXb2QflSx95QGT5ClzWpGaa8L7JnJbgzoYeaA27FCvuBXU758l+KXWRndEmUXjP0Q5fibhavIn8w== + "@webassemblyjs/helper-api-error@1.9.0": version "1.9.0" resolved "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz#203f676e333b96c9da2eeab3ccef33c45928b6a2" integrity sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw== +"@webassemblyjs/helper-buffer@1.11.0": + version "1.11.0" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.0.tgz#d026c25d175e388a7dbda9694e91e743cbe9b642" + integrity sha512-u9HPBEl4DS+vA8qLQdEQ6N/eJQ7gT7aNvMIo8AAWvAl/xMrcOSiI2M0MAnMCy3jIFke7bEee/JwdX1nUpCtdyA== + "@webassemblyjs/helper-buffer@1.9.0": version "1.9.0" resolved "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz#a1442d269c5feb23fcbc9ef759dac3547f29de00" @@ -2924,11 +3376,35 @@ dependencies: "@webassemblyjs/ast" "1.9.0" +"@webassemblyjs/helper-numbers@1.11.0": + version "1.11.0" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.0.tgz#7ab04172d54e312cc6ea4286d7d9fa27c88cd4f9" + integrity sha512-DhRQKelIj01s5IgdsOJMKLppI+4zpmcMQ3XboFPLwCpSNH6Hqo1ritgHgD0nqHeSYqofA6aBN/NmXuGjM1jEfQ== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.11.0" + "@webassemblyjs/helper-api-error" "1.11.0" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/helper-wasm-bytecode@1.11.0": + version "1.11.0" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.0.tgz#85fdcda4129902fe86f81abf7e7236953ec5a4e1" + integrity sha512-MbmhvxXExm542tWREgSFnOVo07fDpsBJg3sIl6fSp9xuu75eGz5lz31q7wTLffwL3Za7XNRCMZy210+tnsUSEA== + "@webassemblyjs/helper-wasm-bytecode@1.9.0": version "1.9.0" resolved "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz#4fed8beac9b8c14f8c58b70d124d549dd1fe5790" integrity sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw== +"@webassemblyjs/helper-wasm-section@1.11.0": + version "1.11.0" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.0.tgz#9ce2cc89300262509c801b4af113d1ca25c1a75b" + integrity sha512-3Eb88hcbfY/FCukrg6i3EH8H2UsD7x8Vy47iVJrP967A9JGqgBVL9aH71SETPx1JrGsOUVLo0c7vMCN22ytJew== + dependencies: + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-buffer" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + "@webassemblyjs/wasm-gen" "1.11.0" + "@webassemblyjs/helper-wasm-section@1.9.0": version "1.9.0" resolved "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz#5a4138d5a6292ba18b04c5ae49717e4167965346" @@ -2939,6 +3415,13 @@ "@webassemblyjs/helper-wasm-bytecode" "1.9.0" "@webassemblyjs/wasm-gen" "1.9.0" +"@webassemblyjs/ieee754@1.11.0": + version "1.11.0" + resolved "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.0.tgz#46975d583f9828f5d094ac210e219441c4e6f5cf" + integrity sha512-KXzOqpcYQwAfeQ6WbF6HXo+0udBNmw0iXDmEK5sFlmQdmND+tr773Ti8/5T/M6Tl/413ArSJErATd8In3B+WBA== + dependencies: + "@xtuc/ieee754" "^1.2.0" + "@webassemblyjs/ieee754@1.9.0": version "1.9.0" resolved "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz#15c7a0fbaae83fb26143bbacf6d6df1702ad39e4" @@ -2946,6 +3429,13 @@ dependencies: "@xtuc/ieee754" "^1.2.0" +"@webassemblyjs/leb128@1.11.0": + version "1.11.0" + resolved "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.0.tgz#f7353de1df38aa201cba9fb88b43f41f75ff403b" + integrity sha512-aqbsHa1mSQAbeeNcl38un6qVY++hh8OpCOzxhixSYgbRfNWcxJNJQwe2rezK9XEcssJbbWIkblaJRwGMS9zp+g== + dependencies: + "@xtuc/long" "4.2.2" + "@webassemblyjs/leb128@1.9.0": version "1.9.0" resolved "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz#f19ca0b76a6dc55623a09cffa769e838fa1e1c95" @@ -2953,11 +3443,30 @@ dependencies: "@xtuc/long" "4.2.2" +"@webassemblyjs/utf8@1.11.0": + version "1.11.0" + resolved "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.0.tgz#86e48f959cf49e0e5091f069a709b862f5a2cadf" + integrity sha512-A/lclGxH6SpSLSyFowMzO/+aDEPU4hvEiooCMXQPcQFPPJaYcPQNKGOCLUySJsYJ4trbpr+Fs08n4jelkVTGVw== + "@webassemblyjs/utf8@1.9.0": version "1.9.0" resolved "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz#04d33b636f78e6a6813227e82402f7637b6229ab" integrity sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w== +"@webassemblyjs/wasm-edit@1.11.0": + version "1.11.0" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.0.tgz#ee4a5c9f677046a210542ae63897094c2027cb78" + integrity sha512-JHQ0damXy0G6J9ucyKVXO2j08JVJ2ntkdJlq1UTiUrIgfGMmA7Ik5VdC/L8hBK46kVJgujkBIoMtT8yVr+yVOQ== + dependencies: + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-buffer" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + "@webassemblyjs/helper-wasm-section" "1.11.0" + "@webassemblyjs/wasm-gen" "1.11.0" + "@webassemblyjs/wasm-opt" "1.11.0" + "@webassemblyjs/wasm-parser" "1.11.0" + "@webassemblyjs/wast-printer" "1.11.0" + "@webassemblyjs/wasm-edit@1.9.0": version "1.9.0" resolved "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz#3fe6d79d3f0f922183aa86002c42dd256cfee9cf" @@ -2972,6 +3481,17 @@ "@webassemblyjs/wasm-parser" "1.9.0" "@webassemblyjs/wast-printer" "1.9.0" +"@webassemblyjs/wasm-gen@1.11.0": + version "1.11.0" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.0.tgz#3cdb35e70082d42a35166988dda64f24ceb97abe" + integrity sha512-BEUv1aj0WptCZ9kIS30th5ILASUnAPEvE3tVMTrItnZRT9tXCLW2LEXT8ezLw59rqPP9klh9LPmpU+WmRQmCPQ== + dependencies: + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + "@webassemblyjs/ieee754" "1.11.0" + "@webassemblyjs/leb128" "1.11.0" + "@webassemblyjs/utf8" "1.11.0" + "@webassemblyjs/wasm-gen@1.9.0": version "1.9.0" resolved "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz#50bc70ec68ded8e2763b01a1418bf43491a7a49c" @@ -2983,6 +3503,16 @@ "@webassemblyjs/leb128" "1.9.0" "@webassemblyjs/utf8" "1.9.0" +"@webassemblyjs/wasm-opt@1.11.0": + version "1.11.0" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.0.tgz#1638ae188137f4bb031f568a413cd24d32f92978" + integrity sha512-tHUSP5F4ywyh3hZ0+fDQuWxKx3mJiPeFufg+9gwTpYp324mPCQgnuVKwzLTZVqj0duRDovnPaZqDwoyhIO8kYg== + dependencies: + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-buffer" "1.11.0" + "@webassemblyjs/wasm-gen" "1.11.0" + "@webassemblyjs/wasm-parser" "1.11.0" + "@webassemblyjs/wasm-opt@1.9.0": version "1.9.0" resolved "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz#2211181e5b31326443cc8112eb9f0b9028721a61" @@ -2993,6 +3523,18 @@ "@webassemblyjs/wasm-gen" "1.9.0" "@webassemblyjs/wasm-parser" "1.9.0" +"@webassemblyjs/wasm-parser@1.11.0": + version "1.11.0" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.0.tgz#3e680b8830d5b13d1ec86cc42f38f3d4a7700754" + integrity sha512-6L285Sgu9gphrcpDXINvm0M9BskznnzJTE7gYkjDbxET28shDqp27wpruyx3C2S/dvEwiigBwLA1cz7lNUi0kw== + dependencies: + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-api-error" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + "@webassemblyjs/ieee754" "1.11.0" + "@webassemblyjs/leb128" "1.11.0" + "@webassemblyjs/utf8" "1.11.0" + "@webassemblyjs/wasm-parser@1.9.0": version "1.9.0" resolved "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz#9d48e44826df4a6598294aa6c87469d642fff65e" @@ -3017,6 +3559,14 @@ "@webassemblyjs/helper-fsm" "1.9.0" "@xtuc/long" "4.2.2" +"@webassemblyjs/wast-printer@1.11.0": + version "1.11.0" + resolved "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.0.tgz#680d1f6a5365d6d401974a8e949e05474e1fab7e" + integrity sha512-Fg5OX46pRdTgB7rKIUojkh9vXaVN6sGYCnEiJN1GYkb0RPwShZXp6KTDqmoMdQPKhcroOXh3fEzmkWmCYaKYhQ== + dependencies: + "@webassemblyjs/ast" "1.11.0" + "@xtuc/long" "4.2.2" + "@webassemblyjs/wast-printer@1.9.0": version "1.9.0" resolved "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz#4935d54c85fef637b00ce9f52377451d00d47899" @@ -3078,37 +3628,42 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: mime-types "~2.1.24" negotiator "0.6.2" -acorn-jsx@^5.2.0: +acorn-jsx@^5.3.1: version "5.3.1" resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== -acorn-walk@^8.0.0: - version "8.0.0" - resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.0.0.tgz#56ae4c0f434a45fff4a125e7ea95fa9c98f67a16" - integrity sha512-oZRad/3SMOI/pxbbmqyurIx7jHw1wZDcR9G44L8pUVFEomX/0dH89SrM1KaDXuv1NpzAXz6Op/Xu/Qd5XXzdEA== - -acorn@5.X, acorn@^5.0.3: - version "5.7.4" - resolved "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" - integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg== +acorn-walk@^8.0.2: + version "8.1.0" + resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.1.0.tgz#d3c6a9faf00987a5e2b9bdb506c2aa76cd707f83" + integrity sha512-mjmzmv12YIG/G8JQdQuz2MUDShEJ6teYpT5bmWA4q7iwoGen8xtt3twF3OvzIUl+Q06aWIjvnwQUKvQ6TtMRjg== acorn@^6.4.1: - version "6.4.1" - resolved "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" - integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== + version "6.4.2" + resolved "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" + integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== acorn@^7.4.0: - version "7.4.0" - resolved "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz#e1ad486e6c54501634c6c397c5c121daa383607c" - integrity sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w== + version "7.4.1" + resolved "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +acorn@^8.1.0: + version "8.4.0" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.4.0.tgz#af53266e698d7cffa416714b503066a82221be60" + integrity sha512-ULr0LDaEqQrMFGyQ3bhJkLsbtrQ8QibAseGZeaSUiT/6zb9IvIkomWHJIvgvwad+hinRAgsI51JcWk2yvwyL+w== -acorn@^8.0.1: - version "8.0.2" - resolved "https://registry.npmjs.org/acorn/-/acorn-8.0.2.tgz#f7503a253311d4af42332bc188d5713edb2e030a" - integrity sha512-t0Dw7AOyeKs4nez4dhzkBDHB28ICo1pxk3UFsLfsCHOkLW+CwbAZJPMa0vBbq0Mqsslhb7n/7H4qB5txaVQ4ew== +acorn@^8.2.1: + version "8.2.4" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.2.4.tgz#caba24b08185c3b56e3168e97d15ed17f4d31fd0" + integrity sha512-Ibt84YwBDDA890eDiDCEqcbwvHlBvzzDkU2cGBBDDI1QWT12jTiXIOn2CIw5KK4i6N5Z2HUxwYjzriDyqaqqZg== -adm-zip@0.4.16, adm-zip@^0.4.9, adm-zip@~0.4.3: +adm-zip@0.5.3: + version "0.5.3" + resolved "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.3.tgz#f5ac6b2507f4418e2691c0feb6f130b4b90d7643" + integrity sha512-zsoTXEwRNCxBzRHLENFLuecCcwzzXiEhWo1r3GP68iwi8Q/hW2RrqgeY1nfJ/AhNQNWnZq/4v0TbfMsUkI+TYw== + +adm-zip@^0.4.9, adm-zip@~0.4.3: version "0.4.16" resolved "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz#cf4c508fdffab02c269cbc7f471a875f05570365" integrity sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg== @@ -3125,10 +3680,10 @@ agent-base@4, agent-base@^4.3.0: dependencies: es6-promisify "^5.0.0" -agent-base@6: - version "6.0.1" - resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz#808007e4e5867decb0ab6ab2f928fbdb5a596db4" - integrity sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg== +agent-base@6, agent-base@^6.0.0: + version "6.0.2" + resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== dependencies: debug "4" @@ -3146,6 +3701,15 @@ agentkeepalive@^3.4.1: dependencies: humanize-ms "^1.2.1" +agentkeepalive@^4.1.3: + version "4.1.4" + resolved "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.1.4.tgz#d928028a4862cb11718e55227872e842a44c945b" + integrity sha512-+V/rGa3EuU74H6wR04plBb7Ks10FbtUQgRj/FQOG7uUIEuaINI+AiqJR1k6t3SVNs7o7ZjIdus6706qqzVq8jQ== + dependencies: + debug "^4.1.0" + depd "^1.1.2" + humanize-ms "^1.2.1" + aggregate-error@^3.0.0: version "3.1.0" resolved "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" @@ -3174,24 +3738,24 @@ ajv@^5.0.0: fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.3.0" -ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.3, ajv@^6.12.4: - version "6.12.5" - resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.5.tgz#19b0e8bae8f476e5ba666300387775fb1a00a4da" - integrity sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag== +ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: + version "6.12.6" + resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== dependencies: fast-deep-equal "^3.1.1" fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^6.12.5: - version "6.12.6" - resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== +ajv@^8.0.1: + version "8.4.0" + resolved "https://registry.npmjs.org/ajv/-/ajv-8.4.0.tgz#48984fdb2ce225cab15795f0772a8d85669075e4" + integrity sha512-7QD2l6+KBSLwf+7MuYocbWvRPdOu63/trReTLu2KFwkgctnub1auoF+Y1WYcm09CTM7quuscrzqmASaLHC/K4Q== dependencies: fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" uri-js "^4.2.2" ansi-align@^2.0.0: @@ -3213,6 +3777,11 @@ ansi-colors@3.2.3: resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813" integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw== +ansi-colors@4.1.1, ansi-colors@^4.1.1: + version "4.1.1" + resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + ansi-colors@^1.0.1: version "1.1.0" resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz#6374b4dd5d4718ff3ce27a671a3b1cad077132a9" @@ -3225,22 +3794,17 @@ ansi-colors@^3.0.0: resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== -ansi-colors@^4.1.1: - version "4.1.1" - resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" - integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== - ansi-escapes@^3.0.0, ansi-escapes@^3.1.0, ansi-escapes@^3.2.0: version "3.2.0" resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== ansi-escapes@^4.2.1: - version "4.3.1" - resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" - integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA== + version "4.3.2" + resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== dependencies: - type-fest "^0.11.0" + type-fest "^0.21.3" ansi-gray@^0.1.1: version "0.1.1" @@ -3282,11 +3846,10 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1: color-convert "^1.9.0" ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.2.1" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" - integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== + version "4.3.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== dependencies: - "@types/color-name" "^1.1.1" color-convert "^2.0.1" ansi-wrap@0.1.0, ansi-wrap@^0.1.0: @@ -3317,14 +3880,52 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" -anymatch@~3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" - integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== +anymatch@^3.0.3, anymatch@~3.1.1: + version "3.1.2" + resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== dependencies: normalize-path "^3.0.0" picomatch "^2.0.4" +api-documenter-me@0.1.1: + version "0.1.1" + resolved "https://registry.npmjs.org/api-documenter-me/-/api-documenter-me-0.1.1.tgz#961abb299c3737b01f2dd7aa10b0caf1c9110538" + integrity sha512-h6CjdRZUcv6lK3VfnX8CDF+2CfA5DBqg3EfR+HOEZp4AggQfKZ4D5vegvdt5P2n+b95GRounfU5u3TJ2PAIlLQ== + dependencies: + "@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 "~3.13.1" + resolve "~1.17.0" + +api-extractor-me@0.1.2: + version "0.1.2" + resolved "https://registry.npmjs.org/api-extractor-me/-/api-extractor-me-0.1.2.tgz#ca0771a459c5982676565bae58fc955fbada8bbb" + integrity sha512-wgZeRMhIG1HRsvfjDRSHoamMPa2OHw+smMJOyU1WMXhsq4MvhVpM4sVFfWwzFmyKoh6tzxi26A6GGL/idGXsnw== + dependencies: + "@microsoft/tsdoc" "0.12.24" + "@rushstack/node-core-library" "3.36.0" + "@rushstack/rig-package" "0.2.9" + "@rushstack/ts-command-line" "4.7.8" + api-extractor-model-me "0.1.1" + colors "~1.2.1" + lodash "~4.17.15" + resolve "~1.17.0" + semver "~7.3.0" + source-map "~0.6.1" + typescript "~4.1.3" + +api-extractor-model-me@0.1.1: + version "0.1.1" + resolved "https://registry.npmjs.org/api-extractor-model-me/-/api-extractor-model-me-0.1.1.tgz#e656e9d31e50976dd2a8d1d6e351bac4a6149932" + integrity sha512-Ez801ZMADfkseOWNRFquvyQYDm3D9McpxfkKMWL6JFCGcpub0miJ+TFNphIR1nSZbrsxz3kIeOovNMY4VlL6Bw== + dependencies: + "@microsoft/tsdoc" "0.12.24" + "@rushstack/node-core-library" "3.36.0" + append-buffer@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz#d8220cf466081525efea50614f3de6514dfa58f1" @@ -3372,18 +3973,18 @@ archiver-utils@^2.1.0: normalize-path "^3.0.0" readable-stream "^2.0.0" -archiver@^3.0.0: - version "3.1.1" - resolved "https://registry.npmjs.org/archiver/-/archiver-3.1.1.tgz#9db7819d4daf60aec10fe86b16cb9258ced66ea0" - integrity sha512-5Hxxcig7gw5Jod/8Gq0OneVgLYET+oNHcxgWItq4TbhOzRLKNAFUb9edAftiMKXvXfCB0vbGrJdZDNq0dWMsxg== +archiver@^5.0.0: + version "5.3.0" + resolved "https://registry.npmjs.org/archiver/-/archiver-5.3.0.tgz#dd3e097624481741df626267564f7dd8640a45ba" + integrity sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg== dependencies: archiver-utils "^2.1.0" - async "^2.6.3" + async "^3.2.0" buffer-crc32 "^0.2.1" - glob "^7.1.4" - readable-stream "^3.4.0" - tar-stream "^2.1.0" - zip-stream "^2.1.2" + readable-stream "^3.6.0" + readdir-glob "^1.0.0" + tar-stream "^2.2.0" + zip-stream "^4.1.0" archy@^1.0.0: version "1.0.0" @@ -3410,6 +4011,11 @@ argparse@^1.0.7, argparse@~1.0.9: dependencies: sprintf-js "~1.0.2" +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + argsarray@^0.0.1: version "0.0.1" resolved "https://registry.npmjs.org/argsarray/-/argsarray-0.0.1.tgz#6e7207b4ecdb39b0af88303fa5ae22bda8df61cb" @@ -3459,11 +4065,6 @@ array-each@^1.0.0, array-each@^1.0.1: resolved "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" integrity sha1-p5SvDAWrF1KEbudTofIRoFugxE8= -array-filter@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz#baf79e62e6ef4c2a4c0b831232daffec251f9d83" - integrity sha1-uveeYubvTCpMC4MSMtr/7CUfnYM= - array-find-index@^1.0.1, array-find-index@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" @@ -3484,13 +4085,15 @@ array-ify@^1.0.0: resolved "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" integrity sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4= -array-includes@^3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz#cdd67e6852bdf9c1215460786732255ed2459348" - integrity sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ== +array-includes@^3.1.3: + version "3.1.3" + resolved "https://registry.npmjs.org/array-includes/-/array-includes-3.1.3.tgz#c7f619b382ad2afaf5326cddfdc0afc61af7690a" + integrity sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.0" + es-abstract "^1.18.0-next.2" + get-intrinsic "^1.1.1" is-string "^1.0.5" array-initial@^1.0.0: @@ -3544,13 +4147,25 @@ array-unique@^0.3.2: resolved "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= -array.prototype.flat@^1.2.3: - version "1.2.3" - resolved "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz#0de82b426b0318dbfdb940089e38b043d37f6c7b" - integrity sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ== +array.prototype.filter@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.0.tgz#24d63e38983cdc6bf023a3c574b2f2a3f384c301" + integrity sha512-TfO1gz+tLm+Bswq0FBOXPqAchtCr2Rn48T8dLJoRFl8NoEosjZmzptmuo1X8aZBzZcqsR1W8U761tjACJtngTQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.18.0" + es-array-method-boxes-properly "^1.0.0" + is-string "^1.0.5" + +array.prototype.flat@^1.2.4: + version "1.2.4" + resolved "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz#6ef638b43312bd401b4c6199fdec7e2dc9e9a123" + integrity sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg== dependencies: + call-bind "^1.0.0" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" + es-abstract "^1.18.0-next.1" arraybuffer.slice@~0.0.7: version "0.0.7" @@ -3636,10 +4251,17 @@ assign-symbols@^1.0.0: resolved "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= -astral-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" - integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== +ast-types@^0.13.2: + version "0.13.4" + resolved "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz#ee0d77b343263965ecc3fb62da16e7222b2b6782" + integrity sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w== + dependencies: + tslib "^2.0.1" + +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== async-done@^1.2.0, async-done@^1.2.2: version "1.3.2" @@ -3656,10 +4278,12 @@ async-each@^1.0.1: resolved "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== -async-limiter@~1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" - integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== +async-retry@^1.3.1: + version "1.3.1" + resolved "https://registry.npmjs.org/async-retry/-/async-retry-1.3.1.tgz#139f31f8ddce50c0870b0ba558a6079684aaed55" + integrity sha512-aiieFW/7h3hY0Bq5d+ktDBejxuwR78vRu9hDUdR8rNhSaQ29VzPL4AoIRG7D/c7tdenwOcKvgPM6tIxB3cB6HA== + dependencies: + retry "0.12.0" async-settle@^1.0.0: version "1.0.0" @@ -3668,19 +4292,19 @@ async-settle@^1.0.0: dependencies: async-done "^1.2.2" -async@^1.3.0, async@^1.5.2: +async@^1.3.0: version "1.5.2" resolved "https://registry.npmjs.org/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= -async@^2.0.0, async@^2.1.2, async@^2.6.2, async@^2.6.3: +async@^2.1.2, async@^2.6.2: version "2.6.3" resolved "https://registry.npmjs.org/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== dependencies: lodash "^4.17.14" -async@^3.0.1, async@^3.1.0: +async@^3.0.1, async@^3.1.0, async@^3.2.0: version "3.2.0" resolved "https://registry.npmjs.org/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720" integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw== @@ -3700,12 +4324,12 @@ atob@^2.1.2: resolved "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== -available-typed-arrays@^1.0.0, available-typed-arrays@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz#6b098ca9d8039079ee3f77f7b783c4480ba513f5" - integrity sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ== +available-typed-arrays@^1.0.2: + version "1.0.3" + resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.3.tgz#fb7d02445bfedefad79fad1fe47931163a227198" + integrity sha512-CuPhFULixV/d89POo1UG4GqGbR7dmrefY2ZdmsYakeR4gOSJXoF7tfeaiqMHGOMrlTiJoeEs87fpLsBYmE2BMw== dependencies: - array-filter "^1.0.0" + array.prototype.filter "^1.0.0" aws-sign2@~0.7.0: version "0.7.0" @@ -3713,16 +4337,16 @@ aws-sign2@~0.7.0: integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= aws4@^1.8.0: - version "1.10.1" - resolved "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428" - integrity sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA== + version "1.11.0" + resolved "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" + integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== -axios@^0.19.2: - version "0.19.2" - resolved "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" - integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== +axios@^0.21.1: + version "0.21.1" + resolved "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" + integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== dependencies: - follow-redirects "1.5.10" + follow-redirects "^1.10.0" babel-code-frame@^6.26.0: version "6.26.0" @@ -3747,15 +4371,14 @@ babel-generator@^6.18.0: source-map "^0.5.7" trim-right "^1.0.1" -babel-loader@8.1.0: - version "8.1.0" - resolved "https://registry.npmjs.org/babel-loader/-/babel-loader-8.1.0.tgz#c611d5112bd5209abe8b9fa84c3e4da25275f1c3" - integrity sha512-7q7nC1tYOrqvUrN3LQK4GwSk/TQorZSOlO9C+RZDZpODgyN4ZlCqE5q9cDsyWOliN+aU9B4JX01xK9eJXowJLw== +babel-loader@8.2.2: + version "8.2.2" + resolved "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.2.tgz#9363ce84c10c9a40e6c753748e1441b60c8a0b81" + integrity sha512-JvTd0/D889PQBtUXJ2PXaKU/pjZDMtHA9V2ecm+eNRmmBCMR09a+fmpGTNwnJtFmFl5Ei7Vy47LjBb+L0wQ99g== dependencies: - find-cache-dir "^2.1.0" + find-cache-dir "^3.3.1" loader-utils "^1.4.0" - mkdirp "^0.5.3" - pify "^4.0.1" + make-dir "^3.1.0" schema-utils "^2.6.5" babel-messages@^6.23.0: @@ -3772,6 +4395,30 @@ babel-plugin-dynamic-import-node@^2.3.3: dependencies: object.assign "^4.1.0" +babel-plugin-polyfill-corejs2@^0.2.2: + version "0.2.2" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.2.tgz#e9124785e6fd94f94b618a7954e5693053bf5327" + integrity sha512-kISrENsJ0z5dNPq5eRvcctITNHYXWOA4DUZRFYCz3jYCcvTb/A546LIddmoGNMVYg2U38OyFeNosQwI9ENTqIQ== + dependencies: + "@babel/compat-data" "^7.13.11" + "@babel/helper-define-polyfill-provider" "^0.2.2" + semver "^6.1.1" + +babel-plugin-polyfill-corejs3@^0.2.2: + version "0.2.3" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.3.tgz#72add68cf08a8bf139ba6e6dfc0b1d504098e57b" + integrity sha512-rCOFzEIJpJEAU14XCcV/erIf/wZQMmMT5l5vXOpL5uoznyOGfDIjPj6FVytMvtzaKSTSVKouOCTPJ5OMUZH30g== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.2.2" + core-js-compat "^3.14.0" + +babel-plugin-polyfill-regenerator@^0.2.2: + version "0.2.2" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.2.tgz#b310c8d642acada348c1fa3b3e6ce0e851bee077" + integrity sha512-Goy5ghsc21HgPDFtzRkSirpZVW35meGoTmTOb2bxqdl60ghub4xOidgNTHaZfQ2FaxQsKmwvXtOAkcIS4SMBWg== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.2.2" + babel-runtime@^6.22.0, babel-runtime@^6.26.0: version "6.26.0" resolved "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" @@ -3849,29 +4496,24 @@ backo2@1.0.2: integrity sha1-MasayLEpNjRj41s+u2n038+6eUc= balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + version "1.0.2" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base64-arraybuffer-es6@^0.6.0: - version "0.6.0" - resolved "https://registry.npmjs.org/base64-arraybuffer-es6/-/base64-arraybuffer-es6-0.6.0.tgz#036f79f57588dca0018de7792ddf149299382007" - integrity sha512-57nLqKj4ShsDwFJWJsM4sZx6u60WbCge35rWRSevUwqxDtRwwxiKAO800zD2upPv4CfdWjQp//wSLar35nDKvA== +base64-arraybuffer-es6@^0.7.0: + version "0.7.0" + resolved "https://registry.npmjs.org/base64-arraybuffer-es6/-/base64-arraybuffer-es6-0.7.0.tgz#dbe1e6c87b1bf1ca2875904461a7de40f21abc86" + integrity sha512-ESyU/U1CFZDJUdr+neHRhNozeCv72Y7Vm0m1DCbjX3KBjT6eYocvAJlSk6+8+HkVwXlT1FNxhGW6q3UKAlCvvw== base64-arraybuffer@0.1.4: version "0.1.4" resolved "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz#9818c79e059b1355f97e0428a017c838e90ba812" integrity sha1-mBjHngWbE1X5fgQooBfIOOkLqBI= -base64-arraybuffer@0.1.5: - version "0.1.5" - resolved "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" - integrity sha1-c5JncZI7Whl0etZmqlzUv5xunOg= - -base64-js@^1.0.2, base64-js@^1.2.3, base64-js@^1.3.0: - version "1.3.1" - resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" - integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== +base64-js@^1.0.2, base64-js@^1.3.0, base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== base64id@2.0.0: version "2.0.0" @@ -3916,16 +4558,9 @@ bcrypt-pbkdf@^1.0.0: tweetnacl "^0.14.3" before-after-hook@^2.0.0: - version "2.1.0" - resolved "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.1.0.tgz#b6c03487f44e24200dd30ca5e6a1979c5d2fb635" - integrity sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A== - -better-assert@~1.0.0: - version "1.0.2" - resolved "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522" - integrity sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI= - dependencies: - callsite "1.0.0" + version "2.2.1" + resolved "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.1.tgz#73540563558687586b52ed217dad6a802ab1549c" + integrity sha512-/6FKxSTWoJdbsLDF8tdIjaRiFXiE6UHsEHE3OPI/cwPURCVi1ukP0gmLn7XWEiFk5TcwQjjY5PWsU+j+tgXgmw== better-path-resolve@1.0.0: version "1.0.0" @@ -3955,9 +4590,9 @@ binary-extensions@^1.0.0: integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== binary-extensions@^2.0.0: - version "2.1.0" - resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9" - integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ== + version "2.2.0" + resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== binary@~0.3.0: version "0.3.0" @@ -3967,7 +4602,7 @@ binary@~0.3.0: buffers "~0.1.1" chainsaw "~0.1.0" -binaryextensions@2: +binaryextensions@^2.2.0: version "2.3.0" resolved "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.3.0.tgz#1d269cbf7e6243ea886aa41453c3651ccbe13c22" integrity sha512-nAihlQsYGyc5Bwq6+EsubvANYGExeJKHDO3RjnvwU042fawQTQfM3Kxn7IHUXQOz4bzfwsGYYHGSvXyW4zOGLg== @@ -3979,10 +4614,10 @@ bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" -bl@^4.0.3: - version "4.0.3" - resolved "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz#12d6287adc29080e22a705e5764b2a9522cdc489" - integrity sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg== +bl@^4.0.3, bl@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== dependencies: buffer "^5.5.0" inherits "^2.0.4" @@ -4015,15 +4650,15 @@ bluebird@~3.4.1: resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3" integrity sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM= -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.4.0: - version "4.11.9" - resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828" - integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw== +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: + version "4.12.0" + resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== -bn.js@^5.1.1: - version "5.1.3" - resolved "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz#beca005408f642ebebea80b042b4d18d2ac0ee6b" - integrity sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ== +bn.js@^5.0.0, bn.js@^5.1.1: + version "5.2.0" + resolved "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002" + integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== body-parser@1.19.0, body-parser@^1.18.3, body-parser@^1.19.0: version "1.19.0" @@ -4068,6 +4703,20 @@ boxen@^4.2.0: type-fest "^0.8.1" widest-line "^3.1.0" +boxen@^5.0.0: + version "5.0.1" + resolved "https://registry.npmjs.org/boxen/-/boxen-5.0.1.tgz#657528bdd3f59a772b8279b831f27ec2c744664b" + integrity sha512-49VBlw+PrWEF51aCmy7QIteYPIFZxSpvqBdP/2itCPPlJ49kj9zg/XPRFrdkne2W+CfwXUls8exMvu1RysZpKA== + dependencies: + ansi-align "^3.0.0" + camelcase "^6.2.0" + chalk "^4.1.0" + cli-boxes "^2.2.1" + string-width "^4.2.0" + type-fest "^0.20.2" + widest-line "^3.1.0" + wrap-ansi "^7.0.0" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -4106,7 +4755,7 @@ breakword@^1.0.5: dependencies: wcwidth "^1.0.1" -brorand@^1.0.1: +brorand@^1.0.1, brorand@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= @@ -4155,11 +4804,11 @@ browserify-des@^1.0.0: safe-buffer "^5.1.2" browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" - integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ= + version "4.1.0" + resolved "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d" + integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog== dependencies: - bn.js "^4.1.0" + bn.js "^5.0.0" randombytes "^2.0.1" browserify-sign@^4.0.0: @@ -4184,23 +4833,31 @@ browserify-zlib@^0.2.0: dependencies: pako "~1.0.5" -browserslist@^4.12.0, browserslist@^4.8.5: - version "4.14.5" - resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.14.5.tgz#1c751461a102ddc60e40993639b709be7f2c4015" - integrity sha512-Z+vsCZIvCBvqLoYkBFTwEYH3v5MCQbsAjp50ERycpOjnPmolg1Gjy4+KaWWpm8QOJt9GHkhdqAl14NpCX73CWA== +browserslist@^4.14.5, browserslist@^4.16.6: + version "4.16.6" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2" + integrity sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ== dependencies: - caniuse-lite "^1.0.30001135" - electron-to-chromium "^1.3.571" - escalade "^3.1.0" - node-releases "^1.1.61" + caniuse-lite "^1.0.30001219" + colorette "^1.2.2" + electron-to-chromium "^1.3.723" + escalade "^3.1.1" + node-releases "^1.1.71" browserstack@^1.5.1: - version "1.6.0" - resolved "https://registry.npmjs.org/browserstack/-/browserstack-1.6.0.tgz#5a56ab90987605d9c138d7a8b88128370297f9bf" - integrity sha512-HJDJ0TSlmkwnt9RZ+v5gFpa1XZTBYTj0ywvLwJ3241J7vMw2jAsGNVhKHtmCOyg+VxeLZyaibO9UL71AsUeDIw== + version "1.6.1" + resolved "https://registry.npmjs.org/browserstack/-/browserstack-1.6.1.tgz#e051f9733ec3b507659f395c7a4765a1b1e358b3" + integrity sha512-GxtFjpIaKdbAyzHfFDKixKO8IBT7wR3NjbzrGc78nNs/Ciys9wU3/nBtsqsWv5nDSrdI5tz0peKuzCPuNXNUiw== dependencies: https-proxy-agent "^2.2.1" +bser@2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + btoa-lite@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337" @@ -4245,13 +4902,13 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" -buffer@^5.1.0, buffer@^5.4.3, buffer@^5.5.0: - version "5.6.0" - resolved "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" - integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== +buffer@^5.4.3, buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" + base64-js "^1.3.1" + ieee754 "^1.1.13" buffers@~0.1.1: version "0.1.1" @@ -4264,9 +4921,9 @@ builtin-modules@^1.1.1: integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= builtin-modules@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484" - integrity sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw== + version "3.2.0" + resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887" + integrity sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA== builtin-status-codes@^3.0.0: version "3.0.0" @@ -4319,6 +4976,29 @@ cacache@^12.0.0, cacache@^12.0.2, cacache@^12.0.3: unique-filename "^1.1.1" y18n "^4.0.0" +cacache@^15.0.5: + version "15.1.0" + resolved "https://registry.npmjs.org/cacache/-/cacache-15.1.0.tgz#164c2f857ee606e4cc793c63018fefd0ea5eba7b" + integrity sha512-mfx0C+mCfWjD1PnwQ9yaOrwG1ou9FkKnx0SvzUHWdFt7r7GaRtzT+9M8HAvLu62zIHtnpQ/1m93nWNDCckJGXQ== + dependencies: + "@npmcli/move-file" "^1.0.1" + chownr "^2.0.0" + fs-minipass "^2.0.0" + glob "^7.1.4" + infer-owner "^1.0.4" + lru-cache "^6.0.0" + minipass "^3.1.1" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.2" + mkdirp "^1.0.3" + p-map "^4.0.0" + promise-inflight "^1.0.1" + rimraf "^3.0.2" + ssri "^8.0.1" + tar "^6.0.2" + unique-filename "^1.1.1" + cache-base@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" @@ -4357,6 +5037,14 @@ caching-transform@^4.0.0: package-hash "^4.0.0" write-file-atomic "^3.0.0" +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + call-me-maybe@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" @@ -4376,11 +5064,6 @@ caller-path@^2.0.0: dependencies: caller-callsite "^2.0.0" -callsite@1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" - integrity sha1-KAOY5dZkvXQDi28JBRU+borxvCA= - callsites@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" @@ -4437,10 +5120,22 @@ camelcase@^5.0.0, camelcase@^5.3.1: resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -caniuse-lite@^1.0.30001135: - version "1.0.30001141" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001141.tgz#214a196d81aa938b268fb0cb6d8fab23fdf14378" - integrity sha512-EHfInJHoQTmlMdVZrEc5gmwPc0zyN/hVufmGHPbVNQwlk7tJfCmQ2ysRZMY2MeleBivALUTyyxXnQjK18XrVpA== +camelcase@^6.0.0, camelcase@^6.2.0: + version "6.2.0" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" + integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== + +caniuse-lite@^1.0.30001219: + version "1.0.30001228" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001228.tgz#bfdc5942cd3326fa51ee0b42fbef4da9d492a7fa" + integrity sha512-QQmLOGJ3DEgokHbMSA8cj2a+geXqmnpyOFT0lhQV6P3/YOJvGDEwoedcwxEQ30gJIwIIunHIicunJ2rzK5gB2A== + +capture-exit@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" + integrity sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g== + dependencies: + rsvp "^4.8.4" capture-stack-trace@^1.0.0: version "1.0.1" @@ -4467,16 +5162,16 @@ chai-as-promised@7.1.1: dependencies: check-error "^1.0.2" -chai@4.2.0: - version "4.2.0" - resolved "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz#760aa72cf20e3795e84b12877ce0e83737aa29e5" - integrity sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw== +chai@4.3.4: + version "4.3.4" + resolved "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz#b55e655b31e1eac7099be4c08c21964fce2e6c49" + integrity sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA== dependencies: assertion-error "^1.1.0" check-error "^1.0.2" deep-eql "^3.0.1" get-func-name "^2.0.0" - pathval "^1.1.0" + pathval "^1.1.1" type-detect "^4.0.5" chainsaw@~0.1.0: @@ -4486,7 +5181,7 @@ chainsaw@~0.1.0: dependencies: traverse ">=0.3.0 <0.4" -chalk@2.x, "chalk@^1.1.3 || 2.x", chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.4.1, chalk@^2.4.2: +chalk@2.x, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -4495,7 +5190,7 @@ chalk@2.x, "chalk@^1.1.3 || 2.x", chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chal escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@4.1.0, chalk@^4.0.0, chalk@^4.1.0: +chalk@4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== @@ -4522,10 +5217,13 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -char-spinner@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/char-spinner/-/char-spinner-1.0.1.tgz#e6ea67bd247e107112983b7ab0479ed362800081" - integrity sha1-5upnvSR+EHESmDt6sEee02KAAIE= +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1: + version "4.1.1" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" + integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" chardet@^0.7.0: version "0.7.0" @@ -4561,6 +5259,21 @@ chokidar@3.3.0: optionalDependencies: fsevents "~2.1.1" +chokidar@3.5.1, chokidar@^3.0.2, chokidar@^3.4.1, chokidar@^3.4.2: + version "3.5.1" + resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" + integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.5.0" + optionalDependencies: + fsevents "~2.3.1" + chokidar@^2.0.0, chokidar@^2.1.8: version "2.1.8" resolved "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" @@ -4580,21 +5293,6 @@ chokidar@^2.0.0, chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" -chokidar@^3.0.2, chokidar@^3.4.1, chokidar@^3.4.2: - version "3.4.2" - resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz#38dc8e658dec3809741eb3ef7bb0a47fe424232d" - integrity sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A== - dependencies: - anymatch "~3.1.1" - braces "~3.0.2" - glob-parent "~5.1.0" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.4.0" - optionalDependencies: - fsevents "~2.1.2" - chownr@^1.1.1, chownr@^1.1.2: version "1.1.4" resolved "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" @@ -4606,23 +5304,21 @@ chownr@^2.0.0: integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== chrome-trace-event@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" - integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ== - dependencies: - tslib "^1.9.0" + version "1.0.3" + resolved "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" + integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== -chromedriver@86.0.0: - version "86.0.0" - resolved "https://registry.npmjs.org/chromedriver/-/chromedriver-86.0.0.tgz#4b9504d5bbbcd4c6bd6d6fd1dd8247ab8cdeca67" - integrity sha512-byLJWhAfuYOmzRYPDf4asJgGDbI4gJGHa+i8dnQZGuv+6WW1nW1Fg+8zbBMOfLvGn7sKL41kVdmCEVpQHn9oyg== +chromedriver@91.0.0: + version "91.0.0" + resolved "https://registry.npmjs.org/chromedriver/-/chromedriver-91.0.0.tgz#b8c07d715c1bde2ed5817757e923bd65565c8f94" + integrity sha512-0eQGLDWvfVd1apkqQpt4452bCATrsj50whhVzMqPiazNSfCXXwfYWRonYxx3DVFCG3+RwSCLvsk8/vpuojCyJA== dependencies: "@testim/chrome-version" "^1.0.7" - axios "^0.19.2" - del "^5.1.0" + axios "^0.21.1" + del "^6.0.0" extract-zip "^2.0.1" https-proxy-agent "^5.0.0" - mkdirp "^1.0.4" + proxy-from-env "^1.1.0" tcp-port-used "^1.0.1" ci-info@^2.0.0: @@ -4665,7 +5361,7 @@ cli-boxes@^1.0.0: resolved "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" integrity sha1-T6kXw+WclKAEzWH47lCdplFocUM= -cli-boxes@^2.2.0: +cli-boxes@^2.2.0, cli-boxes@^2.2.1: version "2.2.1" resolved "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== @@ -4696,15 +5392,15 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" -cli-spinners@^2.0.0, cli-spinners@^2.4.0: - version "2.4.0" - resolved "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.4.0.tgz#c6256db216b878cfba4720e719cec7cf72685d7f" - integrity sha512-sJAofoarcm76ZGpuooaO0eDy8saEy+YoZBLjC4h8srt4jeBnkYeOgqxgsJQTpyt2LjI5PTfLJHSL+41Yu4fEJA== +cli-spinners@^2.0.0, cli-spinners@^2.5.0: + version "2.6.0" + resolved "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.0.tgz#36c7dc98fb6a9a76bd6238ec3f77e2425627e939" + integrity sha512-t+4/y50K/+4xcCRosKkA7W4gTr1MySvLV0q+PxmG7FJ5g+66ChKurYjxBCjHggHH3HA5Hh9cy+lcUGWDqVH+4Q== cli-table@^0.3.1: - version "0.3.1" - resolved "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23" - integrity sha1-9TsFJmqLGguTSz0IIebi3FkUriM= + version "0.3.6" + resolved "https://registry.npmjs.org/cli-table/-/cli-table-0.3.6.tgz#e9d6aa859c7fe636981fd3787378c2a20bce92fc" + integrity sha512-ZkNZbnZjKERTY5NwC2SeMeLeifSPq/pubeRoTpdr3WchLlnZg6hEgvHkK5zL7KNFdd9PmHN8lxrENUwI3cE8vQ== dependencies: colors "1.0.3" @@ -4753,10 +5449,10 @@ cliui@^6.0.0: strip-ansi "^6.0.0" wrap-ansi "^6.2.0" -cliui@^7.0.0: - version "7.0.1" - resolved "https://registry.npmjs.org/cliui/-/cliui-7.0.1.tgz#a4cb67aad45cd83d8d05128fc9f4d8fbb887e6b3" - integrity sha512-rcvHOWyGyid6I1WjT/3NatKj2kDt9OdSHSXpyLXaMWFbKpGACNW8pRhhdPUq9MWUOdwn8Rz9AVETjF4105rZZQ== +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== dependencies: string-width "^4.2.0" strip-ansi "^6.0.0" @@ -4817,6 +5513,11 @@ code-point-at@^1.0.0: resolved "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= +collect-v8-coverage@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" + integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== + collection-map@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz#aea0f06f8d26c780c2b75494385544b2255af18c" @@ -4859,9 +5560,9 @@ color-name@^1.0.0, color-name@~1.1.4: integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== color-string@^1.5.2: - version "1.5.3" - resolved "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc" - integrity sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw== + version "1.5.5" + resolved "https://registry.npmjs.org/color-string/-/color-string-1.5.5.tgz#65474a8f0e7439625f3d27a6a19d89fc45223014" + integrity sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg== dependencies: color-name "^1.0.0" simple-swizzle "^0.2.2" @@ -4879,12 +5580,17 @@ color@3.0.x: color-convert "^1.9.1" color-string "^1.5.2" +colorette@^1.1.0, colorette@^1.2.2: + version "1.2.2" + resolved "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" + integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== + colors@1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs= -colors@^1.1.2, colors@^1.2.1, colors@^1.4.0: +colors@^1.1.2, colors@^1.2.1, colors@^1.4.0, colors@~1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== @@ -4987,15 +5693,15 @@ component-inherit@0.0.3: resolved "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" integrity sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM= -compress-commons@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/compress-commons/-/compress-commons-2.1.1.tgz#9410d9a534cf8435e3fbbb7c6ce48de2dc2f0610" - integrity sha512-eVw6n7CnEMFzc3duyFVrQEuY1BlHR3rYsSztyG32ibGMW722i3C6IizEGMFmfMU+A+fALvBIwxN3czffTcdA+Q== +compress-commons@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.0.tgz#25ec7a4528852ccd1d441a7d4353cd0ece11371b" + integrity sha512-ofaaLqfraD1YRTkrRKPCrGJ1pFeDG/MVCkVVV2FNGeWquSlqw5wOrwOfPQ1xF2u+blpeWASie5EubHz+vsNIgA== dependencies: buffer-crc32 "^0.2.13" - crc32-stream "^3.0.1" + crc32-stream "^4.0.1" normalize-path "^3.0.0" - readable-stream "^2.3.6" + readable-stream "^3.6.0" compressible@^2.0.12, compressible@~2.0.16: version "2.0.18" @@ -5069,13 +5775,6 @@ configstore@^5.0.0, configstore@^5.0.1: write-file-atomic "^3.0.0" xdg-basedir "^4.0.0" -connect-query@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/connect-query/-/connect-query-1.0.0.tgz#de44f577209da2404d1fc04692d1a4118e582119" - integrity sha1-3kT1dyCdokBNH8BGktGkEY5YIRk= - dependencies: - qs "~6.4.0" - connect@^3.6.2, connect@^3.7.0: version "3.7.0" resolved "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz#5d49348910caa5e07a01800b030d0c35f20484f8" @@ -5101,11 +5800,6 @@ constants-browserify@^1.0.0: resolved "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= -contains-path@^0.1.0: - version "0.1.0" - resolved "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" - integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= - content-disposition@0.5.3: version "0.5.3" resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" @@ -5119,9 +5813,9 @@ content-type@^1.0.4, content-type@~1.0.4: integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== conventional-changelog-angular@^5.0.3: - version "5.0.11" - resolved "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.11.tgz#99a3ca16e4a5305e0c2c2fae3ef74fd7631fc3fb" - integrity sha512-nSLypht/1yEflhuTogC03i7DX7sOrXGsRn14g131Potqi6cbGbGEE9PSDEHKldabB6N76HiSyw9Ph+kLmC04Qw== + version "5.0.12" + resolved "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.12.tgz#c979b8b921cbfe26402eb3da5bbfda02d865a2b9" + integrity sha512-5GLsbnkR/7A89RyHLvvoExbiGbd9xKdKqDTrArnPbOqBqG/2wIosu0fHwpeIRI8Tl94MhVNBXcLJZl92ZQ5USw== dependencies: compare-func "^2.0.0" q "^1.5.1" @@ -5151,40 +5845,40 @@ conventional-changelog-preset-loader@^2.1.1: integrity sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g== conventional-changelog-writer@^4.0.6: - version "4.0.17" - resolved "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.17.tgz#4753aaa138bf5aa59c0b274cb5937efcd2722e21" - integrity sha512-IKQuK3bib/n032KWaSb8YlBFds+aLmzENtnKtxJy3+HqDq5kohu3g/UdNbIHeJWygfnEbZjnCKFxAW0y7ArZAw== + version "4.1.0" + resolved "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.1.0.tgz#1ca7880b75aa28695ad33312a1f2366f4b12659f" + integrity sha512-WwKcUp7WyXYGQmkLsX4QmU42AZ1lqlvRW9mqoyiQzdD+rJWbTepdWoKJuwXTS+yq79XKnQNa93/roViPQrAQgw== dependencies: compare-func "^2.0.0" - conventional-commits-filter "^2.0.6" + conventional-commits-filter "^2.0.7" dateformat "^3.0.0" handlebars "^4.7.6" json-stringify-safe "^5.0.1" lodash "^4.17.15" - meow "^7.0.0" + meow "^8.0.0" semver "^6.0.0" split "^1.0.0" - through2 "^3.0.0" + through2 "^4.0.0" -conventional-commits-filter@^2.0.2, conventional-commits-filter@^2.0.6: - version "2.0.6" - resolved "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.6.tgz#0935e1240c5ca7698329affee1b6a46d33324c4c" - integrity sha512-4g+sw8+KA50/Qwzfr0hL5k5NWxqtrOVw4DDk3/h6L85a9Gz0/Eqp3oP+CWCNfesBvZZZEFHF7OTEbRe+yYSyKw== +conventional-commits-filter@^2.0.2, conventional-commits-filter@^2.0.7: + version "2.0.7" + resolved "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz#f8d9b4f182fce00c9af7139da49365b136c8a0b3" + integrity sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA== dependencies: lodash.ismatch "^4.4.0" modify-values "^1.0.0" conventional-commits-parser@^3.0.3: - version "3.1.0" - resolved "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.1.0.tgz#10140673d5e7ef5572633791456c5d03b69e8be4" - integrity sha512-RSo5S0WIwXZiRxUGTPuYFbqvrR4vpJ1BDdTlthFgvHt5kEdnd1+pdvwWphWn57/oIl4V72NMmOocFqqJ8mFFhA== + version "3.2.1" + resolved "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.2.1.tgz#ba44f0b3b6588da2ee9fd8da508ebff50d116ce2" + integrity sha512-OG9kQtmMZBJD/32NEw5IhN5+HnBqVjy03eC+I71I0oQRFA5rOgA4OtPOYG7mz1GkCfCNxn3gKIX8EiHJYuf1cA== dependencies: JSONStream "^1.0.4" is-text-path "^1.0.1" lodash "^4.17.15" - meow "^7.0.0" - split2 "^2.0.0" - through2 "^3.0.0" + meow "^8.0.0" + split2 "^3.0.0" + through2 "^4.0.0" trim-off-newlines "^1.0.0" conventional-recommended-bump@^5.0.0: @@ -5201,7 +5895,7 @@ conventional-recommended-bump@^5.0.0: meow "^4.0.0" q "^1.5.1" -convert-source-map@1.X, convert-source-map@^1.5.0, convert-source-map@^1.7.0: +convert-source-map@^1.0.0, convert-source-map@^1.5.0, convert-source-map@^1.7.0: version "1.7.0" resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== @@ -5218,16 +5912,16 @@ cookie-signature@1.0.6: resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= -cookie@0.3.1: - version "0.3.1" - resolved "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" - integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= - cookie@0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== +cookie@~0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" + integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== + copy-concurrently@^1.0.0: version "1.0.5" resolved "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" @@ -5246,30 +5940,30 @@ copy-descriptor@^0.1.0: integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= copy-props@^2.0.1: - version "2.0.4" - resolved "https://registry.npmjs.org/copy-props/-/copy-props-2.0.4.tgz#93bb1cadfafd31da5bb8a9d4b41f471ec3a72dfe" - integrity sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A== + version "2.0.5" + resolved "https://registry.npmjs.org/copy-props/-/copy-props-2.0.5.tgz#03cf9ae328d4ebb36f8f1d804448a6af9ee3f2d2" + integrity sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw== dependencies: - each-props "^1.3.0" - is-plain-object "^2.0.1" + each-props "^1.3.2" + is-plain-object "^5.0.0" -core-js-compat@^3.6.2: - version "3.6.5" - resolved "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz#2a51d9a4e25dfd6e690251aa81f99e3c05481f1c" - integrity sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng== +core-js-compat@^3.14.0, core-js-compat@^3.15.0: + version "3.15.1" + resolved "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.15.1.tgz#1afe233716d37ee021956ef097594071b2b585a7" + integrity sha512-xGhzYMX6y7oEGQGAJmP2TmtBLvR4nZmRGEcFa3ubHOq5YEp51gGN9AovVa0AoujGZIq+Wm6dISiYyGNfdflYww== dependencies: - browserslist "^4.8.5" + browserslist "^4.16.6" semver "7.0.0" -core-js@3.6.5: - version "3.6.5" - resolved "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a" - integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA== +core-js@3.15.1: + version "3.15.1" + resolved "https://registry.npmjs.org/core-js/-/core-js-3.15.1.tgz#6c08ab88abdf56545045ccf5fd81f47f407e7f1a" + integrity sha512-h8VbZYnc9pDzueiS2610IULDkpFFPunHwIpl8yRwFahAEEdSpHlTy3h3z3rKq5h11CaUdBEeRViu9AYvbxiMeg== core-js@^2.4.0: - version "2.6.11" - resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" - integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== + version "2.6.12" + resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" + integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" @@ -5321,20 +6015,21 @@ coveralls@3.1.0: minimist "^1.2.5" request "^2.88.2" -crc32-stream@^3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/crc32-stream/-/crc32-stream-3.0.1.tgz#cae6eeed003b0e44d739d279de5ae63b171b4e85" - integrity sha512-mctvpXlbzsvK+6z8kJwSJ5crm7yBwrQMTybJzMw1O4lLGJqjlDCXY2Zw7KheiA6XBEcBmfLx1D88mjRGVJtY9w== +crc-32@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz#cb2db6e29b88508e32d9dd0ec1693e7b41a18208" + integrity sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA== dependencies: - crc "^3.4.4" - readable-stream "^3.4.0" + exit-on-epipe "~1.0.1" + printj "~1.1.0" -crc@^3.4.4: - version "3.8.0" - resolved "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6" - integrity sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ== +crc32-stream@^4.0.1: + version "4.0.2" + resolved "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.2.tgz#c922ad22b38395abe9d3870f02fa8134ed709007" + integrity sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w== dependencies: - buffer "^5.1.0" + crc-32 "^1.2.0" + readable-stream "^3.4.0" create-ecdh@^4.0.0: version "4.0.4" @@ -5374,6 +6069,11 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: safe-buffer "^5.0.1" sha.js "^2.4.8" +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + cross-env@^5.1.3: version "5.2.1" resolved "https://registry.npmjs.org/cross-env/-/cross-env-5.2.1.tgz#b2c76c1ca7add66dc874d11798466094f551b34d" @@ -5440,25 +6140,24 @@ crypto-random-string@^2.0.0: resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== -css@2.X, css@^2.2.1: - version "2.2.4" - resolved "https://registry.npmjs.org/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929" - integrity sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw== +css@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/css/-/css-3.0.0.tgz#4447a4d58fdd03367c516ca9f64ae365cee4aa5d" + integrity sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ== dependencies: - inherits "^2.0.3" + inherits "^2.0.4" source-map "^0.6.1" - source-map-resolve "^0.5.2" - urix "^0.1.0" + source-map-resolve "^0.6.0" -csv-generate@^3.2.4: - version "3.2.4" - resolved "https://registry.npmjs.org/csv-generate/-/csv-generate-3.2.4.tgz#440dab9177339ee0676c9e5c16f50e2b3463c019" - integrity sha512-qNM9eqlxd53TWJeGtY1IQPj90b563Zx49eZs8e0uMyEvPgvNVmX1uZDtdzAcflB3PniuH9creAzcFOdyJ9YGvA== +csv-generate@^3.4.0: + version "3.4.0" + resolved "https://registry.npmjs.org/csv-generate/-/csv-generate-3.4.0.tgz#360ed73ef8ec7119515a47c3bd5970ac4b988f00" + integrity sha512-D6yi7c6lL70cpTx3TQIVWKrfxuLiKa0pBizu0zi7fSRXlhmE7u674gk9k1IjCEnxKq2t6xzbXnxcOmSdBbE8vQ== -csv-parse@^4.8.8: - version "4.12.0" - resolved "https://registry.npmjs.org/csv-parse/-/csv-parse-4.12.0.tgz#fd42d6291bbaadd51d3009f6cadbb3e53b4ce026" - integrity sha512-wPQl3H79vWLPI8cgKFcQXl0NBgYYEqVnT1i6/So7OjMpsI540oD7p93r3w6fDSyPvwkTepG05F69/7AViX2lXg== +csv-parse@^4.15.3: + version "4.15.4" + resolved "https://registry.npmjs.org/csv-parse/-/csv-parse-4.15.4.tgz#ad1ec62aaf71a642982dfcb81f1848184d691db5" + integrity sha512-OdBbFc0yZhOm17lSxqkirrHlFFVpKRT0wp4DAGoJelsP3LbGzV9LNr7XmM/lrr0uGkCtaqac9UhP8PDHXOAbMg== csv-streamify@^3.0.4: version "3.0.4" @@ -5467,20 +6166,20 @@ csv-streamify@^3.0.4: dependencies: through2 "2.0.1" -csv-stringify@^5.3.6: - version "5.5.1" - resolved "https://registry.npmjs.org/csv-stringify/-/csv-stringify-5.5.1.tgz#f42cdd379b0f7f142933a11f674b1a91ebd0fcd0" - integrity sha512-HM0/86Ks8OwFbaYLd495tqTs1NhscZL52dC4ieKYumy8+nawQYC0xZ63w1NqLf0M148T2YLYqowoImc1giPn0g== +csv-stringify@^5.6.2: + version "5.6.2" + resolved "https://registry.npmjs.org/csv-stringify/-/csv-stringify-5.6.2.tgz#e653783e2189c4c797fbb12abf7f4943c787caa9" + integrity sha512-n3rIVbX6ylm1YsX2NEug9IaPV8xRnT+9/NNZbrA/bcHgOSSeqtWla6XnI/xmyu57wIw+ASCAoX1oM6EZtqJV0A== csv@^5.3.1: - version "5.3.2" - resolved "https://registry.npmjs.org/csv/-/csv-5.3.2.tgz#50b344e25dfbb8c62684a1bcec18c22468b2161e" - integrity sha512-odDyucr9OgJTdGM2wrMbJXbOkJx3nnUX3Pt8SFOwlAMOpsUQlz1dywvLMXJWX/4Ib0rjfOsaawuuwfI5ucqBGQ== + version "5.5.0" + resolved "https://registry.npmjs.org/csv/-/csv-5.5.0.tgz#8ef89e9ac22559064aedf3cbbb912ed4c2aaf9ac" + integrity sha512-32tcuxdb4HW3zbk8NBcVQb8/7xuJB5sv+q4BuQ6++E/K6JvHvWoCHcGzB5Au95vVikNH4ztE0XNC/Bws950cfA== dependencies: - csv-generate "^3.2.4" - csv-parse "^4.8.8" - csv-stringify "^5.3.6" - stream-transform "^2.0.1" + csv-generate "^3.4.0" + csv-parse "^4.15.3" + csv-stringify "^5.6.2" + stream-transform "^2.1.0" currently-unhandled@^0.4.1: version "0.4.1" @@ -5521,15 +6220,20 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +data-uri-to-buffer@3: + version "3.0.1" + resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636" + integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og== + dataloader@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/dataloader/-/dataloader-1.4.0.tgz#bca11d867f5d3f1b9ed9f737bd15970c65dff5c8" integrity sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw== -date-and-time@^0.14.0: - version "0.14.1" - resolved "https://registry.npmjs.org/date-and-time/-/date-and-time-0.14.1.tgz#969634697b78956fb66b8be6fb0f39fbd631f2f6" - integrity sha512-M4RggEH5OF2ZuCOxgOU67R6Z9ohjKbxGvAQz48vj53wLmL0bAgumkBvycR32f30pK+Og9pIR+RFDyChbaE4oLA== +date-and-time@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/date-and-time/-/date-and-time-1.0.0.tgz#0062394bdf6f44e961f0db00511cb19cdf3cc0a5" + integrity sha512-477D7ypIiqlXBkxhU7YtG9wWZJEQ+RUpujt2quTfgf4+E8g5fNUkB0QIL0bVyP5/TKBg8y55Hfa1R/c4bt3dEw== date-fns@^1.27.2: version "1.30.1" @@ -5551,7 +6255,7 @@ dateformat@^3.0.0: resolved "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== -debug-fabulous@1.X: +debug-fabulous@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-1.1.0.tgz#af8a08632465224ef4174a9f06308c3c2a1ebc8e" integrity sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg== @@ -5567,33 +6271,33 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: dependencies: ms "2.0.0" -debug@3.1.0, debug@=3.1.0, debug@~3.1.0: +debug@3.1.0, debug@~3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== dependencies: ms "2.0.0" -debug@3.2.6, debug@3.X, debug@^3.1.0, debug@^3.1.1, debug@^3.2.6: +debug@3.2.6: version "3.2.6" resolved "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== dependencies: ms "^2.1.1" -debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: - version "4.2.0" - resolved "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1" - integrity sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg== +debug@3.X, debug@^3.1.0, debug@^3.1.1, debug@^3.2.6, debug@^3.2.7: + version "3.2.7" + resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== dependencies: - ms "2.1.2" + ms "^2.1.1" -debug@4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz#373687bffa678b38b1cd91f861b63850035ddc87" - integrity sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg== +debug@4, debug@4.3.1, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: + version "4.3.1" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== dependencies: - ms "^2.1.1" + ms "2.1.2" debug@~4.1.0: version "4.1.1" @@ -5620,6 +6324,11 @@ decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0: resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= +decamelize@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" + integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== + decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" @@ -5654,7 +6363,7 @@ deep-freeze@0.0.1: resolved "https://registry.npmjs.org/deep-freeze/-/deep-freeze-0.0.1.tgz#3a0b0005de18672819dfd38cd31f91179c893e84" integrity sha1-OgsABd4YZygZ39OM0x+RF5yJPoQ= -deep-is@^0.1.3: +deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.3" resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= @@ -5731,7 +6440,16 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" -del@6.0.0: +degenerator@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/degenerator/-/degenerator-2.2.0.tgz#49e98c11fa0293c5b26edfbb52f15729afcdb254" + integrity sha512-aiQcQowF01RxFI4ZLFMpzyotbQonhNpBao6dkI8JPk5a+hmSjR5ErHp2CQySmQe8os3VBqLCIh87nDBgZXvsmg== + dependencies: + ast-types "^0.13.2" + escodegen "^1.8.1" + esprima "^4.0.0" + +del@6.0.0, del@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/del/-/del-6.0.0.tgz#0b40d0332cea743f1614f818be4feb717714c952" integrity sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ== @@ -5758,20 +6476,6 @@ del@^2.2.0: pinkie-promise "^2.0.0" rimraf "^2.2.8" -del@^5.1.0: - version "5.1.0" - resolved "https://registry.npmjs.org/del/-/del-5.1.0.tgz#d9487c94e367410e6eff2925ee58c0c84a75b3a7" - integrity sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA== - dependencies: - globby "^10.0.1" - graceful-fs "^4.2.2" - is-glob "^4.0.1" - is-path-cwd "^2.2.0" - is-path-inside "^3.0.1" - p-map "^3.0.0" - rimraf "^3.0.0" - slash "^3.0.0" - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -5782,7 +6486,7 @@ delegates@^1.0.0: resolved "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= -depd@~1.1.2: +depd@^1.1.2, depd@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= @@ -5792,10 +6496,10 @@ depd@~2.0.0: resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== -dependency-graph@0.9.0: - version "0.9.0" - resolved "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.9.0.tgz#11aed7e203bc8b00f48356d92db27b265c445318" - integrity sha512-9YLIBURXj4DJMFALxXw9K3Y3rwb5Fk0X5/8ipCzaN84+gKxoHK43tVKRNakCQbiEx07E8Uwhuq21BpUagFhZ8w== +dependency-graph@0.11.0: + version "0.11.0" + resolved "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz#ac0ce7ed68a54da22165a85e97a01d53f5eb2e27" + integrity sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg== deprecation@^2.0.0, deprecation@^2.3.1: version "2.3.1" @@ -5832,12 +6536,17 @@ detect-indent@^5.0.0: resolved "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" integrity sha1-OHHMCmoALow+Wzz38zYmRnXwa50= +detect-indent@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/detect-indent/-/detect-indent-6.0.0.tgz#0abd0f549f69fc6659a254fe96786186b6f528fd" + integrity sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA== + detect-libc@^1.0.2: version "1.0.3" resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= -detect-newline@2.X: +detect-newline@^2.0.0: version "2.1.0" resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I= @@ -5862,11 +6571,21 @@ dicer@^0.3.0: dependencies: streamsearch "0.1.2" +diff-sequences@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" + integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q== + diff@3.5.0: version "3.5.0" resolved "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== +diff@5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" + integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== + diff@^4.0.1, diff@^4.0.2: version "4.0.2" resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" @@ -5900,13 +6619,12 @@ dmg@^0.1.0: resolved "https://registry.npmjs.org/dmg/-/dmg-0.1.0.tgz#b38ea2107f6f0b070442bbf799bfc4f2aedaa5f8" integrity sha1-s46iEH9vCwcEQrv3mb/E8q7apfg= -doctrine@1.5.0: - version "1.5.0" - resolved "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" - integrity sha1-N53Ocw9hZvds76TmcHoVmwLFpvo= +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== dependencies: esutils "^2.0.2" - isarray "^1.0.0" doctrine@^3.0.0: version "3.0.0" @@ -5936,9 +6654,9 @@ domain-browser@^1.1.1: integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== domain-browser@^4.16.0: - version "4.18.0" - resolved "https://registry.npmjs.org/domain-browser/-/domain-browser-4.18.0.tgz#b050d93a417be6920372a1b0e8b81cafe5a19919" - integrity sha512-zUa9XMz3NmuabwyUGQcYhm2yiG/3MjUUF4/DEFXUl0X035GyCorUFxX2CbHXFzxPWFg6xNgml7JZng6ORxqszg== + version "4.19.0" + resolved "https://registry.npmjs.org/domain-browser/-/domain-browser-4.19.0.tgz#1093e17c0a17dbd521182fe90d49ac1370054af1" + integrity sha512-fRA+BaAWOR/yr/t7T9E9GJztHPeFjj8U35ajyAjCDtAAnTn1Rc1f6W6VGPJrO1tkQv9zWu+JRof7z6oQtiYVFQ== dot-prop@^4.2.0: version "4.2.1" @@ -5960,9 +6678,9 @@ dotenv@^6.1.0: integrity sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w== dotenv@^8.1.0: - version "8.2.0" - resolved "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" - integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== + version "8.6.0" + resolved "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b" + integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g== duplexer2@^0.1.4, duplexer2@~0.1.4: version "0.1.4" @@ -5976,12 +6694,12 @@ duplexer3@^0.1.4: resolved "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= -duplexer@^0.1.1: +duplexer@^0.1.1, duplexer@^0.1.2: version "0.1.2" resolved "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== -duplexify@^3.4.2, duplexify@^3.5.0, duplexify@^3.6.0: +duplexify@^3.4.2, duplexify@^3.6.0: version "3.7.1" resolved "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== @@ -6001,7 +6719,7 @@ duplexify@^4.0.0, duplexify@^4.1.1: readable-stream "^3.1.1" stream-shift "^1.0.0" -each-props@^1.3.0: +each-props@^1.3.2: version "1.3.2" resolved "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz#ea45a414d16dd5cfa419b1a81720d5ca06892333" integrity sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA== @@ -6034,20 +6752,15 @@ ecstatic@^3.3.2: minimist "^1.1.0" url-join "^2.0.5" -editions@^1.3.3: - version "1.3.4" - resolved "https://registry.npmjs.org/editions/-/editions-1.3.4.tgz#3662cb592347c3168eb8e498a0ff73271d67f50b" - integrity sha512-gzao+mxnYDzIysXKMQi/+M1mjy/rjestjg6OPoYTtI+3Izp23oiGZitsl9lPDPiTGXbcSIk1iJWhliSaglxnUg== - ee-first@1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -electron-to-chromium@^1.3.571: - version "1.3.576" - resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.576.tgz#2e70234484e03d7c7e90310d7d79fd3775379c34" - integrity sha512-uSEI0XZ//5ic+0NdOqlxp0liCD44ck20OAGyLMSymIWTEAtHKVJi6JM18acOnRgUgX7Q65QqnI+sNncNvIy8ew== +electron-to-chromium@^1.3.723: + version "1.3.732" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.732.tgz#2a07a8d61f74f2084b6f6bf2a908605a7a0b2d8d" + integrity sha512-qKD5Pbq+QMk4nea4lMuncUMhpEiQwaJyCW7MrvissnRcBDENhVfDmAqQYRQ3X525oTzhar9Zh1cK0L2d1UKYcw== elegant-spinner@^1.0.1: version "1.0.1" @@ -6055,17 +6768,17 @@ elegant-spinner@^1.0.1: integrity sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4= elliptic@^6.5.3: - version "6.5.3" - resolved "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6" - integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw== + version "6.5.4" + resolved "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== dependencies: - bn.js "^4.4.0" - brorand "^1.0.1" + bn.js "^4.11.9" + brorand "^1.1.0" hash.js "^1.0.0" - hmac-drbg "^1.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" emoji-regex@^7.0.1: version "7.0.3" @@ -6092,7 +6805,7 @@ encodeurl@~1.0.2: resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= -encoding@^0.1.11: +encoding@^0.1.11, encoding@^0.1.12: version "0.1.13" resolved "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== @@ -6106,10 +6819,10 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: dependencies: once "^1.4.0" -engine.io-client@~3.4.0: - version "3.4.4" - resolved "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.4.4.tgz#77d8003f502b0782dd792b073a4d2cf7ca5ab967" - integrity sha512-iU4CRr38Fecj8HoZEnFtm2EiKGbYZcPn3cHxqNGl/tmdWRf60KhK+9vE0JeSjgnlS/0oynEfLgKbT9ALpim0sQ== +engine.io-client@~3.5.0: + version "3.5.2" + resolved "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.5.2.tgz#0ef473621294004e9ceebe73cef0af9e36f2f5fa" + integrity sha512-QEqIp+gJ/kMHeUun7f5Vv3bteRHppHH/FMBQX/esFj/fuYfjyUKWGMo3VCvIP/V8bE9KcjHmRZrhIz2Z9oNsDA== dependencies: component-emitter "~1.3.0" component-inherit "0.0.3" @@ -6119,8 +6832,8 @@ engine.io-client@~3.4.0: indexof "0.0.1" parseqs "0.0.6" parseuri "0.0.6" - ws "~6.1.0" - xmlhttprequest-ssl "~1.5.4" + ws "~7.4.2" + xmlhttprequest-ssl "~1.6.2" yeast "0.1.2" engine.io-parser@~2.2.0: @@ -6134,27 +6847,35 @@ engine.io-parser@~2.2.0: blob "0.0.5" has-binary2 "~1.0.2" -engine.io@~3.4.0: - version "3.4.2" - resolved "https://registry.npmjs.org/engine.io/-/engine.io-3.4.2.tgz#8fc84ee00388e3e228645e0a7d3dfaeed5bd122c" - integrity sha512-b4Q85dFkGw+TqgytGPrGgACRUhsdKc9S9ErRAXpPGy/CXKs4tYoHDkvIRdsseAF7NjfVwjRFIn6KTnbw7LwJZg== +engine.io@~3.5.0: + version "3.5.0" + resolved "https://registry.npmjs.org/engine.io/-/engine.io-3.5.0.tgz#9d6b985c8a39b1fe87cd91eb014de0552259821b" + integrity sha512-21HlvPUKaitDGE4GXNtQ7PLP0Sz4aWLddMPw2VTyFz1FVZqu/kZsJUO8WNpKuE/OCL7nkfRaOui2ZCJloGznGA== dependencies: accepts "~1.3.4" base64id "2.0.0" - cookie "0.3.1" + cookie "~0.4.1" debug "~4.1.0" engine.io-parser "~2.2.0" - ws "^7.1.2" + ws "~7.4.2" -enhanced-resolve@^4.0.0, enhanced-resolve@^4.3.0: - version "4.3.0" - resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz#3b806f3bfafc1ec7de69551ef93cca46c1704126" - integrity sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ== +enhanced-resolve@^4.0.0, enhanced-resolve@^4.5.0: + version "4.5.0" + resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz#2f3cfd84dbe3b487f18f2db2ef1e064a571ca5ec" + integrity sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg== dependencies: graceful-fs "^4.1.2" memory-fs "^0.5.0" tapable "^1.0.0" +enhanced-resolve@^5.8.0: + version "5.8.2" + resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.2.tgz#15ddc779345cbb73e97c611cd00c01c1e7bf4d8b" + integrity sha512-F27oB3WuHDzvR2DOGNTaYy0D5o0cnrv8TeI482VM4kYgQd/FT9lUQwuNsJ0oOHtBUq7eiW5ytqzp7nBFknL+GA== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + enquirer@^2.3.0, enquirer@^2.3.5: version "2.3.6" resolved "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" @@ -6168,24 +6889,29 @@ ent@^2.2.0, ent@~2.2.0: integrity sha1-6WQhkyWiHQX0RGai9obtbOX13R0= env-paths@^2.2.0: - version "2.2.0" - resolved "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43" - integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA== + version "2.2.1" + resolved "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" + integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== envinfo@^7.3.1: - version "7.7.3" - resolved "https://registry.npmjs.org/envinfo/-/envinfo-7.7.3.tgz#4b2d8622e3e7366afb8091b23ed95569ea0208cc" - integrity sha512-46+j5QxbPWza0PB1i15nZx0xQ4I/EfQxg9J8Had3b408SV63nEtor2e+oiY63amTo9KTuh2a3XLObNwduxYwwA== + version "7.8.1" + resolved "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475" + integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw== err-code@^1.0.0: version "1.1.2" resolved "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz#06e0116d3028f6aef4806849eb0ea6a748ae6960" integrity sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA= +err-code@^2.0.2: + version "2.0.3" + resolved "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" + integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== + errno@^0.1.3, errno@~0.1.7: - version "0.1.7" - resolved "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" - integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg== + version "0.1.8" + resolved "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" + integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A== dependencies: prr "~1.0.1" @@ -6196,40 +6922,59 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.4, es-abstract@^1.17.5: - version "1.17.7" - resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c" - integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g== +es-abstract@^1.18.0, es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2: + version "1.18.0" + resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz#ab80b359eecb7ede4c298000390bc5ac3ec7b5a4" + integrity sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw== dependencies: + call-bind "^1.0.2" es-to-primitive "^1.2.1" function-bind "^1.1.1" + get-intrinsic "^1.1.1" has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.2.2" - is-regex "^1.1.1" - object-inspect "^1.8.0" + has-symbols "^1.0.2" + is-callable "^1.2.3" + is-negative-zero "^2.0.1" + is-regex "^1.1.2" + is-string "^1.0.5" + object-inspect "^1.9.0" object-keys "^1.1.1" - object.assign "^4.1.1" - string.prototype.trimend "^1.0.1" - string.prototype.trimstart "^1.0.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.4" + string.prototype.trimstart "^1.0.4" + unbox-primitive "^1.0.0" -es-abstract@^1.18.0-next.0, es-abstract@^1.18.0-next.1: - version "1.18.0-next.1" - resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68" - integrity sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA== +es-abstract@^1.18.2: + version "1.18.3" + resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.3.tgz#25c4c3380a27aa203c44b2b685bba94da31b63e0" + integrity sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw== dependencies: + call-bind "^1.0.2" es-to-primitive "^1.2.1" function-bind "^1.1.1" + get-intrinsic "^1.1.1" has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.2.2" - is-negative-zero "^2.0.0" - is-regex "^1.1.1" - object-inspect "^1.8.0" + has-symbols "^1.0.2" + is-callable "^1.2.3" + is-negative-zero "^2.0.1" + is-regex "^1.1.3" + is-string "^1.0.6" + object-inspect "^1.10.3" object-keys "^1.1.1" - object.assign "^4.1.1" - string.prototype.trimend "^1.0.1" - string.prototype.trimstart "^1.0.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.4" + string.prototype.trimstart "^1.0.4" + unbox-primitive "^1.0.1" + +es-array-method-boxes-properly@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" + integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA== + +es-module-lexer@^0.4.0: + version "0.4.1" + resolved "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.4.1.tgz#dda8c6a14d8f340a24e34331e0fab0cb50438e0e" + integrity sha512-ooYciCUtfw6/d2w56UVeqHPcoCFAiJdz5XOkYpv/Txl1HMUozpXjz/2RIQgqwKdXNDPSF1W7mJCFse3G+HDyAA== es-to-primitive@^1.2.1: version "1.2.1" @@ -6240,7 +6985,7 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -es5-ext@^0.10.35, es5-ext@^0.10.45, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: +es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: version "0.10.53" resolved "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== @@ -6288,7 +7033,7 @@ es6-symbol@^3.1.1, es6-symbol@~3.1.3: d "^1.0.1" ext "^1.1.2" -es6-weak-map@^2.0.1, es6-weak-map@^2.0.2: +es6-weak-map@^2.0.1, es6-weak-map@^2.0.3: version "2.0.3" resolved "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz#b6da1f16cc2cc0d9be43e6bdbfc5e7dfcdf31d53" integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA== @@ -6298,10 +7043,10 @@ es6-weak-map@^2.0.1, es6-weak-map@^2.0.2: es6-iterator "^2.0.3" es6-symbol "^3.1.1" -escalade@^3.0.2, escalade@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.0.tgz#e8e2d7c7a8b76f6ee64c2181d6b8151441602d4e" - integrity sha512-mAk+hPSO8fLDkhV7V0dXazH5pDc6MrjBTPyD3VeKzxnVFjH1MIxbCdqGZB9O8+EwWakZs3ZCbDS4IpRt79V1ig== +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== escape-goat@^2.0.0: version "2.1.1" @@ -6318,6 +7063,28 @@ escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1 resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= +escape-string-regexp@4.0.0, escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escodegen@^1.8.1: + version "1.14.3" + resolved "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" + integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== + dependencies: + esprima "^4.0.1" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + eslint-import-resolver-node@^0.3.4: version "0.3.4" resolved "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717" @@ -6326,33 +7093,47 @@ eslint-import-resolver-node@^0.3.4: debug "^2.6.9" resolve "^1.13.1" -eslint-module-utils@^2.6.0: - version "2.6.0" - resolved "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz#579ebd094f56af7797d19c9866c9c9486629bfa6" - integrity sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA== +eslint-module-utils@^2.6.1: + version "2.6.1" + resolved "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.1.tgz#b51be1e473dd0de1c5ea638e22429c2490ea8233" + integrity sha512-ZXI9B8cxAJIH4nfkhTwcRTEAnrVfobYqwjWy/QMCZ8rHkZHFjf9yO4BzpiF9kCSfNlMG54eKigISHpX0+AaT4A== dependencies: - debug "^2.6.9" + debug "^3.2.7" pkg-dir "^2.0.0" -eslint-plugin-import@2.22.1: - version "2.22.1" - resolved "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz#0896c7e6a0cf44109a2d97b95903c2bb689d7702" - integrity sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw== +eslint-plugin-import@2.23.4: + version "2.23.4" + resolved "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.23.4.tgz#8dceb1ed6b73e46e50ec9a5bb2411b645e7d3d97" + integrity sha512-6/wP8zZRsnQFiR3iaPFgh5ImVRM1WN5NUWfTIRqwOdeiGJlBcSk82o1FEVq8yXmy4lkIzTo7YhHCIxlU/2HyEQ== dependencies: - array-includes "^3.1.1" - array.prototype.flat "^1.2.3" - contains-path "^0.1.0" + array-includes "^3.1.3" + array.prototype.flat "^1.2.4" debug "^2.6.9" - doctrine "1.5.0" + doctrine "^2.1.0" eslint-import-resolver-node "^0.3.4" - eslint-module-utils "^2.6.0" + eslint-module-utils "^2.6.1" + find-up "^2.0.0" has "^1.0.3" + is-core-module "^2.4.0" minimatch "^3.0.4" - object.values "^1.1.1" - read-pkg-up "^2.0.0" - resolve "^1.17.0" + object.values "^1.1.3" + pkg-up "^2.0.0" + read-pkg-up "^3.0.0" + resolve "^1.20.0" tsconfig-paths "^3.9.0" +eslint-plugin-unused-imports@1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-1.1.1.tgz#a5433f8b394461201129a246d8d92d9613e69597" + integrity sha512-EApvRx9Q3XQI96Tg7xPPqY6OuOy95wWMXAtc8RrwdIRk9bv8vkJKwOVoE4HeWJOQhHeHcI8lUbOqmup/PxjfOw== + dependencies: + eslint-rule-composer "^0.3.0" + +eslint-rule-composer@^0.3.0: + version "0.3.0" + resolved "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9" + integrity sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg== + eslint-scope@^4.0.3: version "4.0.3" resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" @@ -6361,7 +7142,7 @@ eslint-scope@^4.0.3: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-scope@^5.0.0, eslint-scope@^5.1.1: +eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== @@ -6369,12 +7150,19 @@ eslint-scope@^5.0.0, eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-utils@^2.0.0, eslint-utils@^2.1.0: +eslint-utils@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== dependencies: - eslint-visitor-keys "^1.1.0" + eslint-visitor-keys "^1.1.0" + +eslint-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" + integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + dependencies: + eslint-visitor-keys "^2.0.0" eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: version "1.3.0" @@ -6382,33 +7170,35 @@ eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== eslint-visitor-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" - integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== + version "2.1.0" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== -eslint@7.11.0: - version "7.11.0" - resolved "https://registry.npmjs.org/eslint/-/eslint-7.11.0.tgz#aaf2d23a0b5f1d652a08edacea0c19f7fadc0b3b" - integrity sha512-G9+qtYVCHaDi1ZuWzBsOWo2wSwd70TXnU6UHA3cTYHp7gCTXZcpggWFoUVAMRarg68qtPoNfFbzPh+VdOgmwmw== +eslint@7.29.0: + version "7.29.0" + resolved "https://registry.npmjs.org/eslint/-/eslint-7.29.0.tgz#ee2a7648f2e729485e4d0bd6383ec1deabc8b3c0" + integrity sha512-82G/JToB9qIy/ArBzIWG9xvvwL3R86AlCjtGw+A29OMZDqhTybz/MByORSukGxeI+YPCR4coYyITKk8BFH9nDA== dependencies: - "@babel/code-frame" "^7.0.0" - "@eslint/eslintrc" "^0.1.3" + "@babel/code-frame" "7.12.11" + "@eslint/eslintrc" "^0.4.2" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" debug "^4.0.1" doctrine "^3.0.0" enquirer "^2.3.5" + escape-string-regexp "^4.0.0" eslint-scope "^5.1.1" eslint-utils "^2.1.0" eslint-visitor-keys "^2.0.0" - espree "^7.3.0" - esquery "^1.2.0" + espree "^7.3.1" + esquery "^1.4.0" esutils "^2.0.2" - file-entry-cache "^5.0.1" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" functional-red-black-tree "^1.0.1" - glob-parent "^5.0.0" - globals "^12.1.0" + glob-parent "^5.1.2" + globals "^13.6.0" ignore "^4.0.6" import-fresh "^3.0.0" imurmurhash "^0.1.4" @@ -6416,7 +7206,7 @@ eslint@7.11.0: js-yaml "^3.13.1" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" - lodash "^4.17.19" + lodash.merge "^4.6.2" minimatch "^3.0.4" natural-compare "^1.4.0" optionator "^0.9.1" @@ -6425,28 +7215,28 @@ eslint@7.11.0: semver "^7.2.1" strip-ansi "^6.0.0" strip-json-comments "^3.1.0" - table "^5.2.3" + table "^6.0.9" text-table "^0.2.0" v8-compile-cache "^2.0.3" -espree@^7.3.0: - version "7.3.0" - resolved "https://registry.npmjs.org/espree/-/espree-7.3.0.tgz#dc30437cf67947cf576121ebd780f15eeac72348" - integrity sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw== +espree@^7.3.0, espree@^7.3.1: + version "7.3.1" + resolved "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" + integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== dependencies: acorn "^7.4.0" - acorn-jsx "^5.2.0" + acorn-jsx "^5.3.1" eslint-visitor-keys "^1.3.0" -esprima@^4.0.0, esprima@~4.0.0: +esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0: version "4.0.1" resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.2.0: - version "1.3.1" - resolved "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" - integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== dependencies: estraverse "^5.1.0" @@ -6457,7 +7247,7 @@ esrecurse@^4.1.0, esrecurse@^4.3.0: dependencies: estraverse "^5.2.0" -estraverse@^4.1.1: +estraverse@^4.1.1, estraverse@^4.2.0: version "4.3.0" resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== @@ -6478,9 +7268,9 @@ estree-walker@^1.0.1: integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== estree-walker@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.1.tgz#f8e030fb21cefa183b44b7ad516b747434e7a3e0" - integrity sha512-tF0hv+Yi2Ot1cwj9eYHtxC0jB9bmjacjQs6ZBTj82H8JwUywFuc+7E83NWfNMwHXZc11mjfFcVXPe9gEP4B8dg== + version "2.0.2" + resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== esutils@^2.0.2: version "2.0.3" @@ -6521,9 +7311,9 @@ events-listener@^1.1.0: integrity sha512-Kd3EgYfODHueq6GzVfs/VUolh2EgJsS8hkO3KpnDrxVjU3eq63eXM2ujXkhPP+OkeUOhL8CxdfZbQXzryb5C4g== events@^3.0.0, events@^3.2.0: - version "3.2.0" - resolved "https://registry.npmjs.org/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379" - integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg== + version "3.3.0" + resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== eventtargeter@0.8.0: version "0.8.0" @@ -6545,6 +7335,11 @@ exec-sh@^0.2.0: dependencies: merge "^1.2.0" +exec-sh@^0.3.2: + version "0.3.6" + resolved "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz#ff264f9e325519a60cb5e273692943483cca63bc" + integrity sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w== + execa@^0.7.0: version "0.7.0" resolved "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" @@ -6572,16 +7367,16 @@ execa@^1.0.0: strip-eof "^1.0.0" exegesis-express@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/exegesis-express/-/exegesis-express-2.0.0.tgz#e33b2ed35e52162ce78613868a771ee4cb5636a7" - integrity sha512-NKvKBsBa2OvU+1BFpWbz3PzoRMhA9q7/wU2oMmQ9X8lPy/FRatADvhlkGO1zYOMgeo35k1ZLO9ZV0uIs9pPnXg== + version "2.0.1" + resolved "https://registry.npmjs.org/exegesis-express/-/exegesis-express-2.0.1.tgz#f162cdd68ee93dc14d832b02b1dbeab053697ee9" + integrity sha512-8ORl1YRygYGPdR+zcClMqzaU+JQuvdNIw/s0RNwYluxNecEHkDEcXFmO6A5T79p7e48KI8iXJYt6KIn4Z9z4bg== dependencies: - exegesis "^2.0.0" + exegesis "^2.5.7" -exegesis@^2.0.0: - version "2.5.6" - resolved "https://registry.npmjs.org/exegesis/-/exegesis-2.5.6.tgz#2a5f198a857b6d820f6bfa0ad41fe29e6fe97446" - integrity sha512-e+YkH/zZTN2njiwrV8tY6tHGDsFu3LyR/YbrqdWvDZaAJ5YGWaBYyd3oX/Y26iGqQc+7jLEKLDTv2UPzjAYL8w== +exegesis@^2.5.7: + version "2.5.7" + resolved "https://registry.npmjs.org/exegesis/-/exegesis-2.5.7.tgz#232c4b01361bc2bf0d9d4c07549c479e77f2b7a3" + integrity sha512-Y0gEY3hgoLa80aMUm8rhhlIW3/KWo4uqN5hKJqok2GLh3maZjRLRC+p0gj33Jw3upAOKOXeRgScT5rtRoMyxwQ== dependencies: "@apidevtools/json-schema-ref-parser" "^9.0.3" ajv "^6.12.2" @@ -6590,10 +7385,10 @@ exegesis@^2.0.0: deep-freeze "0.0.1" events-listener "^1.1.0" glob "^7.1.3" - json-ptr "^1.3.1" - json-schema-traverse "^0.4.1" + json-ptr "^2.2.0" + json-schema-traverse "^1.0.0" lodash "^4.17.11" - openapi3-ts "^1.2.0" + openapi3-ts "^2.0.1" promise-breaker "^5.0.0" pump "^3.0.0" qs "^6.6.0" @@ -6605,6 +7400,11 @@ exit-code@^1.0.2: resolved "https://registry.npmjs.org/exit-code/-/exit-code-1.0.2.tgz#ce165811c9f117af6a5f882940b96ae7f9aecc34" integrity sha1-zhZYEcnxF69qX4gpQLlq5/muzDQ= +exit-on-epipe@~1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz#0bdd92e87d5285d267daa8171d0eb06159689692" + integrity sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw== + exit@^0.1.2: version "0.1.2" resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -6630,6 +7430,18 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2: dependencies: homedir-polyfill "^1.0.1" +expect@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz#c6b996bf26bf3fe18b67b2d0f51fc981ba934417" + integrity sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA== + dependencies: + "@jest/types" "^26.6.2" + ansi-styles "^4.0.0" + jest-get-type "^26.3.0" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" + jest-regex-util "^26.0.0" + express@4.17.1, express@^4.16.4, express@^4.17.1: version "4.17.1" resolved "https://registry.npmjs.org/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" @@ -6757,7 +7569,7 @@ fast-deep-equal@^1.0.0: resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" integrity sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ= -fast-deep-equal@^3.1.1: +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== @@ -6775,9 +7587,9 @@ fast-glob@^2.2.6: micromatch "^3.1.10" fast-glob@^3.0.3, fast-glob@^3.1.1: - version "3.2.4" - resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" - integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ== + version "3.2.5" + resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz#7939af2a656de79a4f1901903ee8adcaa7cb9661" + integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" @@ -6796,7 +7608,7 @@ fast-levenshtein@^1.0.0: resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.1.4.tgz#e6a754cc8f15e58987aa9cbd27af66fd6f4e5af9" integrity sha1-5qdUzI8V5YmHqpy9J69m/W9OWvk= -fast-levenshtein@^2.0.6: +fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= @@ -6806,7 +7618,7 @@ fast-safe-stringify@^2.0.4: resolved "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== -fast-text-encoding@^1.0.0: +fast-text-encoding@^1.0.0, fast-text-encoding@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz#ec02ac8e01ab8a319af182dae2681213cfe9ce53" integrity sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig== @@ -6819,9 +7631,9 @@ fast-url-parser@^1.1.3: punycode "^1.3.2" fastq@^1.6.0: - version "1.8.0" - resolved "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz#550e1f9f59bbc65fe185cb6a9b4d95357107f481" - integrity sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q== + version "1.11.0" + resolved "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz#bb9fb955a07130a918eb63c1f5161cc32a5d0858" + integrity sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g== dependencies: reusify "^1.0.4" @@ -6832,6 +7644,13 @@ faye-websocket@0.11.3: dependencies: websocket-driver ">=0.5.1" +fb-watchman@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" + integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== + dependencies: + bser "2.1.1" + fd-slicer@~1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" @@ -6840,9 +7659,9 @@ fd-slicer@~1.1.0: pend "~1.2.0" fecha@^4.2.0: - version "4.2.0" - resolved "https://registry.npmjs.org/fecha/-/fecha-4.2.0.tgz#3ffb6395453e3f3efff850404f0a59b6747f5f41" - integrity sha512-aN3pcx/DSmtyoovUudctc8+6Hl4T+hI9GBBHLjA76jdZl7+b1sgh5g4k+u/GL3dTy1/pnYzKp69FpJ0OicE3Wg== + version "4.2.1" + resolved "https://registry.npmjs.org/fecha/-/fecha-4.2.1.tgz#0a83ad8f86ef62a091e22bb5a039cd03d23eecce" + integrity sha512-MMMQ0ludy/nBs1/o0zVOiKTpG7qMbonKUzjJgQFEuvq6INZ1OraKPRAWkBq5vlKLOUMpmNYG1JoN3oDPUQ9m3Q== figgy-pudding@^3.4.1, figgy-pudding@^3.5.1: version "3.5.2" @@ -6871,18 +7690,23 @@ figures@^3.0.0: dependencies: escape-string-regexp "^1.0.5" -file-entry-cache@^5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" - integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== dependencies: - flat-cache "^2.0.1" + flat-cache "^3.0.4" file-uri-to-path@1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== +file-uri-to-path@2: + version "2.0.0" + resolved "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz#7b415aeba227d575851e0a5b0c640d7656403fba" + integrity sha512-hjPFI8oE/2iQPVe4gbrJ73Pp+Xfub2+WI2LlXDbsaJBwT5wuMh35WNWVYYTpnz895shtwfyutMFLFywpQAFdLg== + fileset@^2.0.3: version "2.0.3" resolved "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz#8e7548a96d3cc2327ee5e674168723a333bba2a0" @@ -6891,10 +7715,10 @@ fileset@^2.0.3: glob "^7.0.3" minimatch "^3.0.3" -filesize@^3.1.3: - version "3.6.1" - resolved "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz#090bb3ee01b6f801a8a8be99d31710b3422bb317" - integrity sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg== +filesize@^6.1.0: + version "6.3.0" + resolved "https://registry.npmjs.org/filesize/-/filesize-6.3.0.tgz#dff53cfb3f104c9e422f346d53be8dbcc971bf11" + integrity sha512-ytx0ruGpDHKWVoiui6+BY/QMNngtDQ/pJaFwfBpQif0J63+E8DLdFyqS3NkKQn7vIruUEpoGD9JUJSg7Kp+I0g== fill-range@^4.0.0: version "4.0.0" @@ -6913,6 +7737,11 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" +filter-obj@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" + integrity sha1-mzERErxsYSehbgFsbF1/GeCAXFs= + finalhandler@1.1.2, finalhandler@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" @@ -6949,6 +7778,11 @@ find-free-port@2.0.0: resolved "https://registry.npmjs.org/find-free-port/-/find-free-port-2.0.0.tgz#4b22e5f6579eb1a38c41ac6bcb3efed1b6da9b1b" integrity sha1-SyLl9leesaOMQaxryz7+0bbamxs= +find-package-json@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/find-package-json/-/find-package-json-1.2.0.tgz#4057d1b943f82d8445fe52dc9cf456f6b8b58083" + integrity sha512-+SOGcLGYDJHtyqHd87ysBhmaeQ95oWspDKnMXBrnQ9Eq4OkLNqejgoaD8xVWu6GPa0B6roa6KinCMEMcVeqONw== + find-up@3.0.0, find-up@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" @@ -6956,6 +7790,14 @@ find-up@3.0.0, find-up@^3.0.0: dependencies: locate-path "^3.0.0" +find-up@5.0.0, find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + find-up@^1.0.0: version "1.1.2" resolved "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" @@ -6979,14 +7821,6 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - find-versions@^3.2.0: version "3.2.0" resolved "https://registry.npmjs.org/find-versions/-/find-versions-3.2.0.tgz#10297f98030a786829681690545ef659ed1d254e" @@ -7033,39 +7867,65 @@ fined@^1.0.1: object.pick "^1.2.0" parse-filepath "^1.0.1" -firebase-admin@9.2.0: - version "9.2.0" - resolved "https://registry.npmjs.org/firebase-admin/-/firebase-admin-9.2.0.tgz#df5176e2d0c5711df6dbf7012320492a703538ea" - integrity sha512-LhnMYl71B4gP1FlTLfwaYlOWhBCAcNF+byb2CPTfaW/T4hkp4qlXOgo2bws/zbAv5X9GTFqGir3KexMslVGsIA== +firebase-admin@9.9.0: + version "9.9.0" + resolved "https://registry.npmjs.org/firebase-admin/-/firebase-admin-9.9.0.tgz#40442704b5ac0fddfdcdf4255601f8acb1e6fab3" + integrity sha512-04HT7JAAqcJYty95qf15IBD9CXf+vr7S8zNU6Zt1ayC1J05DLaCsUd19/sCNAjZ614KHexAYUtyLgZoJwu2wOQ== dependencies: - "@firebase/database" "^0.6.10" - "@firebase/database-types" "^0.5.2" - "@types/node" "^10.10.0" + "@firebase/database" "^0.10.0" + "@firebase/database-types" "^0.7.2" + "@types/node" ">=12.12.47" dicer "^0.3.0" jsonwebtoken "^8.5.1" + jwks-rsa "^2.0.2" node-forge "^0.10.0" optionalDependencies: - "@google-cloud/firestore" "^4.0.0" + "@google-cloud/firestore" "^4.5.0" "@google-cloud/storage" "^5.3.0" -firebase-functions@3.11.0: - version "3.11.0" - resolved "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.11.0.tgz#92f5a6af6a10641da6dc9b41b29974658b621a7b" - integrity sha512-i1uMhZ/M6i5SCI3ulKo7EWX0/LD+I5o6N+sk0HbOWfzyWfOl0iJTvQkR3BVDcjrlhPVC4xG1bDTLxd+DTkLqaw== +"firebase-exp@file:packages-exp/firebase-exp": + version "9.0.0-beta.6" + dependencies: + "@firebase/analytics-compat" "0.0.900" + "@firebase/analytics-exp" "0.0.900" + "@firebase/app-check-compat" "0.0.900" + "@firebase/app-check-exp" "0.0.900" + "@firebase/app-compat" "0.0.900" + "@firebase/app-exp" "0.0.900" + "@firebase/auth-compat" "0.0.900" + "@firebase/auth-exp" "0.0.900" + "@firebase/database" "0.10.6" + "@firebase/firestore" "2.3.8" + "@firebase/functions-compat" "0.0.900" + "@firebase/functions-exp" "0.0.900" + "@firebase/messaging-compat" "0.0.900" + "@firebase/messaging-exp" "0.0.900" + "@firebase/performance-compat" "0.0.900" + "@firebase/performance-exp" "0.0.900" + "@firebase/remote-config-compat" "0.0.900" + "@firebase/remote-config-exp" "0.0.900" + "@firebase/storage" "0.5.6" + +firebase-functions@3.14.1: + version "3.14.1" + resolved "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.14.1.tgz#3ac5bc70989365874f41d06bca3b42a233dd6039" + integrity sha512-hL/qm+i5i1qKYmAFMlQ4mwRngDkP+3YT3F4E4Nd5Hj2QKeawBdZiMGgEt6zqTx08Zq04vHiSnSM0z75UJRSg6Q== dependencies: "@types/express" "4.17.3" cors "^2.8.5" express "^4.17.1" lodash "^4.17.14" -firebase-tools@8.12.1: - version "8.12.1" - resolved "https://registry.npmjs.org/firebase-tools/-/firebase-tools-8.12.1.tgz#360b291cbe26545231764ab1f8fbd265ee8c48d3" - integrity sha512-nNXtLdlVTjBz5PEMMmInhEp23mBD5NRJ791j2BONTFwd+KH8dXBopAhLuSkRtGp8XNDSNcRwjaNPCoLYE5RoZg== +firebase-tools@9.14.0: + version "9.14.0" + resolved "https://registry.npmjs.org/firebase-tools/-/firebase-tools-9.14.0.tgz#30d837c7ce8454746e69c5bf7e4f3689ae686dcb" + integrity sha512-CHR1Xw5LJ+hDQ/SaRqvuNXJEmpbPsOEtNRj6oD44VFGRp9ZTjY3irilSj6uv7S2P1A1XLEGyO7jEpCH5mkc9RQ== dependencies: - "@google-cloud/pubsub" "^1.7.0" + "@google-cloud/pubsub" "^2.7.0" + "@types/archiver" "^5.1.0" JSONStream "^1.2.1" - archiver "^3.0.0" + abort-controller "^3.0.0" + archiver "^5.0.0" body-parser "^1.19.0" chokidar "^3.0.2" cjson "^0.3.1" @@ -7073,37 +7933,39 @@ firebase-tools@8.12.1: cli-table "^0.3.1" commander "^4.0.1" configstore "^5.0.1" + cors "^2.8.5" cross-env "^5.1.3" cross-spawn "^7.0.1" csv-streamify "^3.0.4" dotenv "^6.1.0" + exegesis "^2.5.7" exegesis-express "^2.0.0" exit-code "^1.0.2" express "^4.16.4" - filesize "^3.1.3" - fs-extra "^0.23.1" + filesize "^6.1.0" + fs-extra "^5.0.0" glob "^7.1.2" - google-auth-library "^5.5.0" - google-gax "~1.12.0" + google-auth-library "^6.1.3" inquirer "~6.3.1" js-yaml "^3.13.1" - jsonschema "^1.0.2" - jsonwebtoken "^8.2.1" + jsonwebtoken "^8.5.1" leven "^3.1.0" - lodash "^4.17.19" + lodash "^4.17.21" marked "^0.7.0" marked-terminal "^3.3.0" + mime "^2.5.2" minimatch "^3.0.4" morgan "^1.10.0" + node-fetch "^2.6.1" open "^6.3.0" ora "^3.4.0" - plist "^3.0.1" portfinder "^1.0.23" progress "^2.0.3" + proxy-agent "^4.0.0" request "^2.87.0" rimraf "^3.0.0" semver "^5.7.1" - superstatic "^7.0.0" + superstatic "^7.1.0" tar "^4.3.0" tcp-port-used "^1.0.1" tmp "0.0.33" @@ -7111,9 +7973,10 @@ firebase-tools@8.12.1: tweetsodium "0.0.5" universal-analytics "^0.4.16" unzipper "^0.10.10" - update-notifier "^4.1.0" + update-notifier "^5.1.0" uuid "^3.0.0" winston "^3.0.0" + winston-transport "^4.4.0" ws "^7.2.3" flagged-respawn@^1.0.0: @@ -7131,27 +7994,36 @@ flat-arguments@^1.0.0: lodash.isarguments "^3.0.0" lodash.isobject "^3.0.0" -flat-cache@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" - integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== dependencies: - flatted "^2.0.0" - rimraf "2.6.3" - write "1.0.3" + flatted "^3.1.0" + rimraf "^3.0.2" flat@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz#090bec8b05e39cba309747f1d588f04dbaf98db2" - integrity sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw== + version "4.1.1" + resolved "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz#a392059cc382881ff98642f5da4dde0a959f309b" + integrity sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA== dependencies: is-buffer "~2.0.3" -flatted@^2.0.0, flatted@^2.0.1: +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + +flatted@^2.0.1: version "2.0.2" resolved "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== +flatted@^3.1.0: + version "3.1.1" + resolved "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469" + integrity sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA== + flush-write-stream@^1.0.0, flush-write-stream@^1.0.2: version "1.1.1" resolved "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" @@ -7165,17 +8037,10 @@ fn.name@1.x.x: resolved "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== -follow-redirects@1.5.10: - version "1.5.10" - resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" - integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== - dependencies: - debug "=3.1.0" - -follow-redirects@^1.0.0: - version "1.13.0" - resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db" - integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA== +follow-redirects@^1.0.0, follow-redirects@^1.10.0: + version "1.14.1" + resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz#d9114ded0a1cfdd334e164e6662ad02bfd91ff43" + integrity sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg== for-in@^1.0.1, for-in@^1.0.2: version "1.0.2" @@ -7217,9 +8082,9 @@ form-data@^2.5.0: mime-types "^2.1.12" form-data@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682" - integrity sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg== + version "3.0.1" + resolved "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== dependencies: asynckit "^0.4.0" combined-stream "^1.0.8" @@ -7260,9 +8125,9 @@ from2@^2.1.0: readable-stream "^2.0.0" fromentries@^1.2.0: - version "1.2.1" - resolved "https://registry.npmjs.org/fromentries/-/fromentries-1.2.1.tgz#64c31665630479bc993cd800d53387920dc61b4d" - integrity sha512-Xu2Qh8yqYuDhQGOhD5iJGninErSfI9A3FrriD3tjUgV5VbJFeH8vfgZ9HnC6jWN80QDVNQK5vmxRAmEAp7Mevw== + version "1.3.2" + resolved "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz#e4bca6808816bf8f93b52750f1127f5a6fd86e3a" + integrity sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg== fs-constants@^1.0.0: version "1.0.0" @@ -7278,27 +8143,6 @@ fs-extra@8.1.0, fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@^0.23.1: - version "0.23.1" - resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-0.23.1.tgz#6611dba6adf2ab8dc9c69fab37cddf8818157e3d" - integrity sha1-ZhHbpq3yq43Jxp+rN83fiBgVfj0= - dependencies: - graceful-fs "^4.1.2" - jsonfile "^2.1.0" - path-is-absolute "^1.0.0" - rimraf "^2.2.8" - -fs-extra@^0.30.0: - version "0.30.0" - resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0" - integrity sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A= - dependencies: - graceful-fs "^4.1.2" - jsonfile "^2.1.0" - klaw "^1.0.0" - path-is-absolute "^1.0.0" - rimraf "^2.2.8" - fs-extra@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz#414d0110cdd06705734d055652c5411260c31abd" @@ -7339,6 +8183,11 @@ fs-mkdirp-stream@^1.0.0: graceful-fs "^4.1.11" through2 "^2.0.3" +fs-monkey@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.1.tgz#4a82f36944365e619f4454d9fff106553067b781" + integrity sha512-fcSa+wyTqZa46iWweI7/ZiUfegOZl0SG8+dltIwFXo7+zYU9J9kpS3NB6pZcSlJdhvIwp81Adx2XhZorncxiaA== + fs-write-stream-atomic@^1.0.8: version "1.0.10" resolved "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" @@ -7362,7 +8211,12 @@ fsevents@^1.2.7: bindings "^1.5.0" nan "^2.12.1" -fsevents@~2.1.1, fsevents@~2.1.2: +fsevents@^2.1.2, fsevents@~2.3.1, fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +fsevents@~2.1.1: version "2.1.3" resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== @@ -7377,6 +8231,14 @@ fstream@^1.0.12: mkdirp ">=0.5 0" rimraf "2" +ftp@^0.3.10: + version "0.3.10" + resolved "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz#9197d861ad8142f3e63d5a83bfe4c59f7330885d" + integrity sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0= + dependencies: + readable-stream "1.1.x" + xregexp "2.0.0" + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -7401,21 +8263,10 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" -gaxios@^2.1.0: - version "2.3.4" - resolved "https://registry.npmjs.org/gaxios/-/gaxios-2.3.4.tgz#eea99353f341c270c5f3c29fc46b8ead56f0a173" - integrity sha512-US8UMj8C5pRnao3Zykc4AAVr+cffoNKRTg9Rsf2GiuZCW69vgJj38VK2PzlPuQU73FZ/nTk9/Av6/JGcE1N9vA== - dependencies: - abort-controller "^3.0.0" - extend "^3.0.2" - https-proxy-agent "^5.0.0" - is-stream "^2.0.0" - node-fetch "^2.3.0" - -gaxios@^3.0.0: - version "3.2.0" - resolved "https://registry.npmjs.org/gaxios/-/gaxios-3.2.0.tgz#11b6f0e8fb08d94a10d4d58b044ad3bec6dd486a" - integrity sha512-+6WPeVzPvOshftpxJwRi2Ozez80tn/hdtOUag7+gajDHRJvAblKxTFSSMPtr2hmnLy7p0mvYz0rMXLBl8pSO7Q== +gaxios@^4.0.0: + version "4.2.1" + resolved "https://registry.npmjs.org/gaxios/-/gaxios-4.2.1.tgz#7463d3a06f56ddbffa745a242d2b4933b88b2ada" + integrity sha512-s+rTywpw6CmfB8r9TXYkpix7YFeuRjnR/AqhaJrQqsNhsAqej+IAiCc3hadzQH3gHyWth30tvYjxH8EVjQt/8Q== dependencies: abort-controller "^3.0.0" extend "^3.0.2" @@ -7423,41 +8274,33 @@ gaxios@^3.0.0: is-stream "^2.0.0" node-fetch "^2.3.0" -gcp-metadata@^3.4.0: - version "3.5.0" - resolved "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-3.5.0.tgz#6d28343f65a6bbf8449886a0c0e4a71c77577055" - integrity sha512-ZQf+DLZ5aKcRpLzYUyBS3yo3N0JSa82lNDO8rj3nMSlovLcz2riKFBsYgDzeXcv75oo5eqB2lx+B14UvPoCRnA== - dependencies: - gaxios "^2.1.0" - json-bigint "^0.3.0" - -gcp-metadata@^4.1.0: - version "4.2.0" - resolved "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.2.0.tgz#3b424355ccdc240ee07c5791e2fd6a60a283d89a" - integrity sha512-vQZD57cQkqIA6YPGXM/zc+PIZfNRFdukWGsGZ5+LcJzesi5xp6Gn7a02wRJi4eXPyArNMIYpPET4QMxGqtlk6Q== +gcp-metadata@^4.2.0: + version "4.2.1" + resolved "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.2.1.tgz#31849fbcf9025ef34c2297c32a89a1e7e9f2cd62" + integrity sha512-tSk+REe5iq/N+K+SK1XjZJUrFPuDqGZVzCy2vocIHIGmPlTGsa8owXMJwGkrXr73NO0AzhPW4MF2DEHz7P2AVw== dependencies: - gaxios "^3.0.0" + gaxios "^4.0.0" json-bigint "^1.0.0" -gcs-resumable-upload@^3.1.0: - version "3.1.1" - resolved "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-3.1.1.tgz#67c766a0555d6a352f9651b7603337207167d0de" - integrity sha512-RS1osvAicj9+MjCc6jAcVL1Pt3tg7NK2C2gXM5nqD1Gs0klF2kj5nnAFSBy97JrtslMIQzpb7iSuxaG8rFWd2A== +gcs-resumable-upload@^3.1.4: + version "3.1.4" + resolved "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-3.1.4.tgz#2e591889efb02247af26868de300b398346b17b5" + integrity sha512-5dyDfHrrVcIskiw/cPssVD4HRiwoHjhk1Nd6h5W3pQ/qffDvhfy4oNCr1f3ZXFPwTnxkCbibsB+73oOM+NvmJQ== dependencies: abort-controller "^3.0.0" configstore "^5.0.0" extend "^3.0.2" - gaxios "^3.0.0" - google-auth-library "^6.0.0" + gaxios "^4.0.0" + google-auth-library "^7.0.0" pumpify "^2.0.0" stream-events "^1.0.4" -geckodriver@1.20.0: - version "1.20.0" - resolved "https://registry.npmjs.org/geckodriver/-/geckodriver-1.20.0.tgz#cd16edb177b88e31affcb54b18a238cae88950a7" - integrity sha512-5nVF4ixR+ZGhVsc4udnVihA9RmSlO6guPV1d2HqxYsgAOUNh0HfzxbzG7E49w4ilXq/CSu87x9yWvrsOstrADQ== +geckodriver@1.22.3: + version "1.22.3" + resolved "https://registry.npmjs.org/geckodriver/-/geckodriver-1.22.3.tgz#324b3102944e8928e67bde61ca129afac417dece" + integrity sha512-HJvImEC5m/2J7aIn+AdiZml1yTOSFZAb8h8lmZBSUgGSCPdNTd0/6YxBVBsvzpaTuaDQHbMUr+8ikaFKF+Sj/A== dependencies: - adm-zip "0.4.16" + adm-zip "0.5.3" bluebird "3.7.2" got "5.6.0" https-proxy-agent "5.0.0" @@ -7468,10 +8311,10 @@ genfun@^5.0.0: resolved "https://registry.npmjs.org/genfun/-/genfun-5.0.0.tgz#9dd9710a06900a5c4a5bf57aca5da4e52fe76537" integrity sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA== -gensync@^1.0.0-beta.1: - version "1.0.0-beta.1" - resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" - integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== get-caller-file@^1.0.1: version "1.0.3" @@ -7488,6 +8331,15 @@ get-func-name@^2.0.0: resolved "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" @@ -7533,6 +8385,23 @@ get-stream@^5.1.0: dependencies: pump "^3.0.0" +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +get-uri@3: + version "3.0.2" + resolved "https://registry.npmjs.org/get-uri/-/get-uri-3.0.2.tgz#f0ef1356faabc70e1f9404fa3b66b2ba9bfc725c" + integrity sha512-+5s0SJbGoyiJTZZ2JTpFPLMPSch72KEqGOTvQsBqg0RBWvwhWUSYZFAtz3TPW0GXJuLBJPts1E241iHg+VRfhg== + dependencies: + "@tootallnate/once" "1" + data-uri-to-buffer "3" + debug "4" + file-uri-to-path "2" + fs-extra "^8.1.0" + ftp "^0.3.10" + get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" @@ -7590,9 +8459,9 @@ git-up@^4.0.0: parse-url "^5.0.0" git-url-parse@^11.1.2: - version "11.3.0" - resolved "https://registry.npmjs.org/git-url-parse/-/git-url-parse-11.3.0.tgz#1515b4574c4eb2efda7d25cc50b29ce8beaefaae" - integrity sha512-i3XNa8IKmqnUqWBcdWBjOcnyZYfN3C1WRvnKI6ouFWwsXCZEnlgbwbm55ZpJ3OJMhfEP/ryFhqW8bBhej3C5Ug== + version "11.4.4" + resolved "https://registry.npmjs.org/git-url-parse/-/git-url-parse-11.4.4.tgz#5d747debc2469c17bc385719f7d0427802d83d77" + integrity sha512-Y4o9o7vQngQDIU9IjyCmRJBin5iYjI5u9ZITnddRZpD7dcCFQj2sL2XuMNbLRE4b4B/4ENPsp2Q8P44fjAZ0Pw== dependencies: git-up "^4.0.0" @@ -7611,10 +8480,10 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0: - version "5.1.1" - resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" - integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== +glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@^5.1.2, glob-parent@~5.1.0: + version "5.1.2" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" @@ -7653,6 +8522,11 @@ glob-to-regexp@^0.3.0: resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + glob-watcher@^5.0.3: version "5.0.5" resolved "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.5.tgz#aa6bce648332924d9a8489be41e3e5c52d4186dc" @@ -7678,7 +8552,7 @@ glob@7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -glob@7.1.6, glob@^7.0.0, glob@^7.0.3, glob@^7.0.6, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: +glob@7.1.6: version "7.1.6" resolved "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -7690,12 +8564,31 @@ glob@7.1.6, glob@^7.0.0, glob@^7.0.3, glob@^7.0.6, glob@^7.1.1, glob@^7.1.2, glo once "^1.3.0" path-is-absolute "^1.0.0" +glob@7.1.7, glob@^7.0.0, glob@^7.0.3, glob@^7.0.6, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: + version "7.1.7" + resolved "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + global-dirs@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/global-dirs/-/global-dirs-2.0.1.tgz#acdf3bb6685bcd55cb35e8a052266569e9469201" - integrity sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A== + version "2.1.0" + resolved "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz#e9046a49c806ff04d6c1825e196c8f0091e8df4d" + integrity sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ== dependencies: - ini "^1.3.5" + ini "1.3.7" + +global-dirs@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz#70a76fe84ea315ab37b1f5576cbde7d48ef72686" + integrity sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA== + dependencies: + ini "2.0.0" global-modules@^1.0.0: version "1.0.0" @@ -7722,22 +8615,29 @@ globals@^11.1.0: resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^12.1.0: - version "12.4.0" - resolved "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" - integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== +globals@^13.6.0: + version "13.8.0" + resolved "https://registry.npmjs.org/globals/-/globals-13.8.0.tgz#3e20f504810ce87a8d72e55aecf8435b50f4c1b3" + integrity sha512-rHtdA6+PDBIjeEvA91rpqzEvk/k3/i7EeNQiryiWuJH0Hw9cpyJMAt2jtbAwUaRdhD+573X4vWw6IcjKPasi9Q== dependencies: - type-fest "^0.8.1" + type-fest "^0.20.2" + +globals@^13.9.0: + version "13.9.0" + resolved "https://registry.npmjs.org/globals/-/globals-13.9.0.tgz#4bf2bf635b334a173fb1daf7c5e6b218ecdc06cb" + integrity sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA== + dependencies: + type-fest "^0.20.2" globals@^9.18.0: version "9.18.0" resolved "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== -globby@^10.0.1: - version "10.0.2" - resolved "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz#277593e745acaa4646c3ab411289ec47a0392543" - integrity sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg== +globby@10.0.1: + version "10.0.1" + resolved "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz#4782c34cb75dd683351335c5829cc3420e606b22" + integrity sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A== dependencies: "@types/glob" "^7.1.1" array-union "^2.1.0" @@ -7749,9 +8649,21 @@ globby@^10.0.1: slash "^3.0.0" globby@^11.0.0, globby@^11.0.1: - version "11.0.1" - resolved "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" - integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ== + version "11.0.3" + resolved "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz#9b1f0cb523e171dd1ad8c7b2a9fb4b644b9593cb" + integrity sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" + +globby@^11.0.3: + version "11.0.4" + resolved "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" + integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== dependencies: array-union "^2.1.0" dir-glob "^3.0.1" @@ -7793,33 +8705,33 @@ glogg@^1.0.0: dependencies: sparkles "^1.0.0" -google-auth-library@^5.0.0, google-auth-library@^5.5.0: - version "5.10.1" - resolved "https://registry.npmjs.org/google-auth-library/-/google-auth-library-5.10.1.tgz#504ec75487ad140e68dd577c21affa363c87ddff" - integrity sha512-rOlaok5vlpV9rSiUu5EpR0vVpc+PhN62oF4RyX/6++DG1VsaulAFEMlDYBLjJDDPI6OcNOCGAKy9UVB/3NIDXg== +google-auth-library@^6.1.3: + version "6.1.6" + resolved "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz#deacdcdb883d9ed6bac78bb5d79a078877fdf572" + integrity sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ== dependencies: arrify "^2.0.0" base64-js "^1.3.0" ecdsa-sig-formatter "^1.0.11" fast-text-encoding "^1.0.0" - gaxios "^2.1.0" - gcp-metadata "^3.4.0" - gtoken "^4.1.0" + gaxios "^4.0.0" + gcp-metadata "^4.2.0" + gtoken "^5.0.4" jws "^4.0.0" - lru-cache "^5.0.0" + lru-cache "^6.0.0" -google-auth-library@^6.0.0: - version "6.1.0" - resolved "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.0.tgz#db3bbe2b3add5783442c84efdcb1c061721c1bfb" - integrity sha512-GbalszIADE1YPWhUyfFMrkLhFHnlAgoRcqGVW+MsLDPsuaOB5MRPk7NNafPDv9SherNE4EKzcYuxMJjaxzXMOw== +google-auth-library@^7.0.0, google-auth-library@^7.0.2: + version "7.0.4" + resolved "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.0.4.tgz#610cb010de71435dca47dfbe8dc7fbff23055d2c" + integrity sha512-o8irYyeijEiecTXeoEe8UKNEzV1X+uhR4b2oNdapDMZixypp0J+eHimGOyx5Joa3UAeokGngdtDLXtq9vDqG2Q== dependencies: arrify "^2.0.0" base64-js "^1.3.0" ecdsa-sig-formatter "^1.0.11" fast-text-encoding "^1.0.0" - gaxios "^3.0.0" - gcp-metadata "^4.1.0" - gtoken "^5.0.0" + gaxios "^4.0.0" + gcp-metadata "^4.2.0" + gtoken "^5.0.4" jws "^4.0.0" lru-cache "^6.0.0" @@ -7828,50 +8740,45 @@ google-closure-compiler-java@^20200112.0.0: resolved "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20200112.0.0.tgz#2b99f5e2869a573a1b35558ff3b6e6bc054a116f" integrity sha512-h/ExDCXAw88nOniQSbbK6et31kOwmaDxl6t52dnETCIzituQtGToPzy21vUel1o8o+FvWUybLoap+dEYBam1pA== -google-closure-compiler-java@^20200628.0.0: - version "20200628.0.0" - resolved "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20200628.0.0.tgz#cb9138001acd7fb6195b1d0c2d3c6205a85389d1" - integrity sha512-ikQEHiuaRR8d3w4QWsJqC2baDfoIyw/KqDW7LXyxbq6WpRiJ+ItTAtShVoqzQTyn3IXVL8viUMGb/AxkUv01RA== +google-closure-compiler-java@^20210601.0.0: + version "20210601.0.0" + resolved "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20210601.0.0.tgz#88dc11b334bee6a704d9674c5143fd2e0d553517" + integrity sha512-bH6nIwOmp4qDWvlbXx5/DE3XA2aDGQoCpmRYZJGONY1Sy6Xfbq0ioXRHH9eBDP9hxhCJ5Sd/K89A0NZ8Nz9RJA== google-closure-compiler-js@^20200112.0.0: version "20200112.0.0" resolved "https://registry.npmjs.org/google-closure-compiler-js/-/google-closure-compiler-js-20200112.0.0.tgz#cb9fc1636671f3ce927e668e29db69b65cae6f2d" integrity sha512-xW47rSuiRaql6q1YN7+b3FXIW74b1nCcENVwm9cigw1H5gWoBMBJOmpZiXnjMfmYC+MALjPQ8giMzvSeP+2X5A== -google-closure-compiler-js@^20200628.0.0: - version "20200628.0.0" - resolved "https://registry.npmjs.org/google-closure-compiler-js/-/google-closure-compiler-js-20200628.0.0.tgz#476d50d8b5155dd9d923a1394e0af76bcc8905ec" - integrity sha512-kGO/fvKDNOawBmzpWEzLW2EMb0ikTc0Y1hZEedLOex/cOm9zc5Cn8w2L0f963ydgFcj4fszzIgAtmZ9CRxqkfg== - google-closure-compiler-linux@^20200112.0.0: version "20200112.0.0" resolved "https://registry.npmjs.org/google-closure-compiler-linux/-/google-closure-compiler-linux-20200112.0.0.tgz#e6c7943cc0114046dbe9fc685e4d7d4eb536c1dc" integrity sha512-imTfdYP7BVTzzp3y7MuZP+98nEkbX7LZsZtxalNpl56vd+Ysc9/vOHXS14CdSoThaXIVlzX/lfjOlBRqPow+ew== -google-closure-compiler-linux@^20200628.0.0: - version "20200628.0.0" - resolved "https://registry.npmjs.org/google-closure-compiler-linux/-/google-closure-compiler-linux-20200628.0.0.tgz#25de6807f13f066a0329384b39c605f5b03484f2" - integrity sha512-SSglqHEW+PtHCOXPwhZFxFzRVxXdvyunkfwP0y2FAO2b0xF6lRfrZSgs0/lgkwlcO2RHnJtIhU8y4bzYMn24QQ== +google-closure-compiler-linux@^20210601.0.0: + version "20210601.0.0" + resolved "https://registry.npmjs.org/google-closure-compiler-linux/-/google-closure-compiler-linux-20210601.0.0.tgz#6e5dd7b00b96dc1fd1ba30e3401af85558768322" + integrity sha512-rnEQt7zz/1P1SfPhJiHQpfCgMPrsVVyEgDs09h67xn6+LXa9L0RP+hrJDEHqSWwjDPz0BkfUUv6zkqZvp1h/lw== google-closure-compiler-osx@^20200112.0.0: version "20200112.0.0" resolved "https://registry.npmjs.org/google-closure-compiler-osx/-/google-closure-compiler-osx-20200112.0.0.tgz#df7a22c0dc32702b47c8ac4521f79bbe439effad" integrity sha512-E3S1KqZw4+Zov0VXCkjomPrYhyuuV6jH9InBchQ7cZfipFJjhQmSRf39u4Mu0sINW7GXfODZbzBwOXhEIquFQw== -google-closure-compiler-osx@^20200628.0.0: - version "20200628.0.0" - resolved "https://registry.npmjs.org/google-closure-compiler-osx/-/google-closure-compiler-osx-20200628.0.0.tgz#d4e8c0a0cfea1fcf935f7af0db46cdcebeabe308" - integrity sha512-Ntd/kYqjYu7CScvne0yscZGqQV19y7BCu5PXxGtcs9G5KLe7Ep9RxPWznvFGZXky5vPc6Yq9p6E2uOhpA20cIg== +google-closure-compiler-osx@^20210601.0.0: + version "20210601.0.0" + resolved "https://registry.npmjs.org/google-closure-compiler-osx/-/google-closure-compiler-osx-20210601.0.0.tgz#e23356bc9ef6e68c2980f60a207f603767b50b21" + integrity sha512-A5r4s/WthR2iLMM0mxsluw8EW2AcOomC5ri/H6FjzpMq0RVEnLTgaGYdXolUAfEzH/7XtJJT2+JkYk3HSLCtrg== google-closure-compiler-windows@^20200112.0.0: version "20200112.0.0" resolved "https://registry.npmjs.org/google-closure-compiler-windows/-/google-closure-compiler-windows-20200112.0.0.tgz#8300d1e651f2c84ed565e729ccf40d6ed7e63771" integrity sha512-+5+UJFKXH0LGnYEHSVJxWwhtvX/MI6uebkAQkhma0057QsKs8fOToWuHL8/UbJULB4WUPa3DlHy0+Izs5z6lCQ== -google-closure-compiler-windows@^20200628.0.0: - version "20200628.0.0" - resolved "https://registry.npmjs.org/google-closure-compiler-windows/-/google-closure-compiler-windows-20200628.0.0.tgz#6b175fd2e432576c86575248b4634c8c58e738a3" - integrity sha512-Il4NIhzvemgEk2kC33tDj9HVpqXEU8SmS1f3AmkLMAUbSI5FdBBzTqjLRa8Y/ucEz4nTRqvpTvbOG3vhIVUUWw== +google-closure-compiler-windows@^20210601.0.0: + version "20210601.0.0" + resolved "https://registry.npmjs.org/google-closure-compiler-windows/-/google-closure-compiler-windows-20210601.0.0.tgz#b5400d06bbf0bbd2602ee3ae0c2bc7ebd5829692" + integrity sha512-6r94bPShnB0XXh9+5/qXGDHJN2PQGhF9yJPcgBZj+FAZlQGzlYkT0pkyp+loZT3lG+YRbjD28Lgo7xMcY4xgkA== google-closure-compiler@20200112.0.0: version "20200112.0.0" @@ -7889,97 +8796,50 @@ google-closure-compiler@20200112.0.0: google-closure-compiler-osx "^20200112.0.0" google-closure-compiler-windows "^20200112.0.0" -google-closure-compiler@20200628.0.0: - version "20200628.0.0" - resolved "https://registry.npmjs.org/google-closure-compiler/-/google-closure-compiler-20200628.0.0.tgz#939e8be9b27fb4504d051263ef0b96d75e444a57" - integrity sha512-ZMhFmlzNCWdL43UbTHzdFa4fgAfBR37TbAOKLSLUATT3RvCNu0AvX0rQBuJNzmI21ySsM9T3r5WXPBwiL0Hn4g== +google-closure-compiler@20210601.0.0: + version "20210601.0.0" + resolved "https://registry.npmjs.org/google-closure-compiler/-/google-closure-compiler-20210601.0.0.tgz#34597c33c9285ebd3a5364f5299f6c9ddc9fc88a" + integrity sha512-lzzEoG2VTB7uUjnWnMyeZMU163w69HJpM27yh8Up9Ha5McHZeESjt3NRwU8cWMbCRdY06nFbRCDIVCRcadHCiw== dependencies: chalk "2.x" - google-closure-compiler-java "^20200628.0.0" - google-closure-compiler-js "^20200628.0.0" + google-closure-compiler-java "^20210601.0.0" minimist "1.x" vinyl "2.x" vinyl-sourcemaps-apply "^0.2.0" optionalDependencies: - google-closure-compiler-linux "^20200628.0.0" - google-closure-compiler-osx "^20200628.0.0" - google-closure-compiler-windows "^20200628.0.0" - -google-closure-library@20200224.0.0: - version "20200224.0.0" - resolved "https://registry.npmjs.org/google-closure-library/-/google-closure-library-20200224.0.0.tgz#70c36576b21c81c514cfbfcc001780c2fdacd173" - integrity sha512-cF9LP7F5Klj4go5TB4cPpcCvC/qgSVNYgzVS+bzxPgLvIiVL8aWOwApj6rsCkPY9Yr675FouylqNE24F31LWeQ== - -google-closure-library@20200628.0.0: - version "20200628.0.0" - resolved "https://registry.npmjs.org/google-closure-library/-/google-closure-library-20200628.0.0.tgz#0a1b6f40a4b1dc408832d33bfe3494a2ef212f3f" - integrity sha512-G+PoGe8vtcjxszr1ie3GSrlxjO+gZDuOZClanGJMWHsefUynzPqJeIGh3zMpSsCAXZXtO6DpB59l99DVvRVcVQ== - -google-gax@^1.14.2: - version "1.15.3" - resolved "https://registry.npmjs.org/google-gax/-/google-gax-1.15.3.tgz#e88cdcbbd19c7d88cc5fd7d7b932c4d1979a5aca" - integrity sha512-3JKJCRumNm3x2EksUTw4P1Rad43FTpqrtW9jzpf3xSMYXx+ogaqTM1vGo7VixHB4xkAyATXVIa3OcNSh8H9zsQ== - dependencies: - "@grpc/grpc-js" "~1.0.3" - "@grpc/proto-loader" "^0.5.1" - "@types/fs-extra" "^8.0.1" - "@types/long" "^4.0.0" - abort-controller "^3.0.0" - duplexify "^3.6.0" - google-auth-library "^5.0.0" - is-stream-ended "^0.1.4" - lodash.at "^4.6.0" - lodash.has "^4.5.2" - node-fetch "^2.6.0" - protobufjs "^6.8.9" - retry-request "^4.0.0" - semver "^6.0.0" - walkdir "^0.4.0" - -google-gax@^2.2.0: - version "2.9.0" - resolved "https://registry.npmjs.org/google-gax/-/google-gax-2.9.0.tgz#84edef8715d82c0f91a6e5485b8f2803d2690f00" - integrity sha512-MFMwA7Fb8PEwjnYwfGXjZMidCNyMl3gSnvS/+kS8TQioJZQDpzK+W3dmwyNyig/U13+kbABqDnbkkAXJ5NiUkw== - dependencies: - "@grpc/grpc-js" "~1.1.1" - "@grpc/proto-loader" "^0.5.1" + google-closure-compiler-linux "^20210601.0.0" + google-closure-compiler-osx "^20210601.0.0" + google-closure-compiler-windows "^20210601.0.0" + +google-closure-library@20200830.0.0: + version "20200830.0.0" + resolved "https://registry.npmjs.org/google-closure-library/-/google-closure-library-20200830.0.0.tgz#9f3807e5a4af55ebf2c8a22853d53b8da39a48e8" + integrity sha512-s4ma73K+FTeVywSMjVOxQ435t6kPfSlxEtIflq7Gabp2fxAnc9i8vUpvT8ZP/GH89LwSJReIaBGtrn72rfNC5Q== + +google-closure-library@20210406.0.0: + version "20210406.0.0" + resolved "https://registry.npmjs.org/google-closure-library/-/google-closure-library-20210406.0.0.tgz#47d6036c1704661ad47cde2e35f19cc55e33e775" + integrity sha512-1lAC/KC9R2QM6nygniM0pRcGrv5bkCUrIZb2hXFxLtAkA+zRiVeWtRYpFWDHXXJzkavKjsn9upiffL4x/nmmVg== + +google-gax@^2.12.0: + version "2.13.0" + resolved "https://registry.npmjs.org/google-gax/-/google-gax-2.13.0.tgz#404bb9df62c3a0a414e2f5339eda4d751f540304" + integrity sha512-aKNJy2+Vv2I7flyNYbwpq0aYBHp6Qv32HZn+wr6ZhZ8xlSCLS9K9k7izfh2nd1rCJQcsqB6KMxHV0Vwny6Rc1g== + dependencies: + "@grpc/grpc-js" "~1.3.0" + "@grpc/proto-loader" "^0.6.1" "@types/long" "^4.0.0" abort-controller "^3.0.0" duplexify "^4.0.0" - google-auth-library "^6.0.0" + fast-text-encoding "^1.0.3" + google-auth-library "^7.0.2" is-stream-ended "^0.1.4" node-fetch "^2.6.1" - protobufjs "^6.9.0" - retry-request "^4.0.0" - -google-gax@~1.12.0: - version "1.12.0" - resolved "https://registry.npmjs.org/google-gax/-/google-gax-1.12.0.tgz#f926f7e6abda245db38ecbebbbf58daaf3a8f687" - integrity sha512-BeeoxVO6y9K20gUsexUwptutd0PfrTItrA02JWwwstlBIOAcvgFp86MHWufQsnrkPVhxBjHXq65aIkSejtJjDg== - dependencies: - "@grpc/grpc-js" "^0.6.12" - "@grpc/proto-loader" "^0.5.1" - "@types/long" "^4.0.0" - abort-controller "^3.0.0" - duplexify "^3.6.0" - google-auth-library "^5.0.0" - is-stream-ended "^0.1.4" - lodash.at "^4.6.0" - lodash.has "^4.5.2" - node-fetch "^2.6.0" - protobufjs "^6.8.8" + object-hash "^2.1.1" + protobufjs "^6.10.2" retry-request "^4.0.0" - semver "^6.0.0" - walkdir "^0.4.0" -google-p12-pem@^2.0.0: - version "2.0.4" - resolved "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-2.0.4.tgz#036462394e266472632a78b685f0cc3df4ef337b" - integrity sha512-S4blHBQWZRnEW44OcR7TL9WR+QCqByRvhNDZ/uuQfpxywfupikf/miba8js1jZi6ZOGv5slgSuoshCWh6EMDzg== - dependencies: - node-forge "^0.9.0" - -google-p12-pem@^3.0.0: +google-p12-pem@^3.0.3: version "3.0.3" resolved "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.3.tgz#673ac3a75d3903a87f05878f3c75e06fc151669e" integrity sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA== @@ -8030,10 +8890,10 @@ graceful-fs@4.1.15: resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== -graceful-fs@4.X, graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.5, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.3, graceful-fs@^4.2.4: - version "4.2.4" - resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" - integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== +graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.5, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.4, graceful-fs@^4.2.6: + version "4.2.6" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" + integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== grapheme-splitter@^1.0.4: version "1.0.4" @@ -8045,25 +8905,14 @@ growl@1.10.5: resolved "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== -gtoken@^4.1.0: - version "4.1.4" - resolved "https://registry.npmjs.org/gtoken/-/gtoken-4.1.4.tgz#925ff1e7df3aaada06611d30ea2d2abf60fcd6a7" - integrity sha512-VxirzD0SWoFUo5p8RDP8Jt2AGyOmyYcT/pOUgDKJCK+iSw0TMqwrVfY37RXTNmoKwrzmDHSk0GMT9FsgVmnVSA== - dependencies: - gaxios "^2.1.0" - google-p12-pem "^2.0.0" - jws "^4.0.0" - mime "^2.2.0" - -gtoken@^5.0.0: - version "5.0.3" - resolved "https://registry.npmjs.org/gtoken/-/gtoken-5.0.3.tgz#b76ef8e9a2fed6fef165e47f7d05b60c498e4d05" - integrity sha512-Nyd1wZCMRc2dj/mAD0LlfQLcAO06uKdpKJXvK85SGrF5+5+Bpfil9u/2aw35ltvEHjvl0h5FMKN5knEU+9JrOg== +gtoken@^5.0.4: + version "5.2.1" + resolved "https://registry.npmjs.org/gtoken/-/gtoken-5.2.1.tgz#4dae1fea17270f457954b4a45234bba5fc796d16" + integrity sha512-OY0BfPKe3QnMsY9MzTHTSKn+Vl2l1CcLe6BwDEQj00mbbkl5nyQ/7EUREstg4fQNZ8iYE7br4JJ7TdKeDOPWmw== dependencies: - gaxios "^3.0.0" - google-p12-pem "^3.0.0" + gaxios "^4.0.0" + google-p12-pem "^3.0.3" jws "^4.0.0" - mime "^2.2.0" gulp-cli@^2.2.0: version "2.3.0" @@ -8107,31 +8956,33 @@ gulp-filter@6.0.0: plugin-error "^1.0.1" streamfilter "^3.0.0" -gulp-replace@1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/gulp-replace/-/gulp-replace-1.0.0.tgz#b32bd61654d97b8d78430a67b3e8ce067b7c9143" - integrity sha512-lgdmrFSI1SdhNMXZQbrC75MOl1UjYWlOWNbNRnz+F/KHmgxt3l6XstBoAYIdadwETFyG/6i+vWUSCawdC3pqOw== +gulp-replace@1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/gulp-replace/-/gulp-replace-1.1.3.tgz#8641cdca78e683e8573ca4a012e7e4ebb7e4db60" + integrity sha512-HcPHpWY4XdF8zxYkDODHnG2+7a3nD/Y8Mfu3aBgMiCFDW3X2GiOKXllsAmILcxe3KZT2BXoN18WrpEFm48KfLQ== dependencies: - istextorbinary "2.2.1" - readable-stream "^2.0.1" - replacestream "^4.0.0" - -gulp-sourcemaps@2.6.5: - version "2.6.5" - resolved "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-2.6.5.tgz#a3f002d87346d2c0f3aec36af7eb873f23de8ae6" - integrity sha512-SYLBRzPTew8T5Suh2U8jCSDKY+4NARua4aqjj8HOysBh2tSgT9u4jc1FYirAdPx1akUxxDeK++fqw6Jg0LkQRg== - dependencies: - "@gulp-sourcemaps/identity-map" "1.X" - "@gulp-sourcemaps/map-sources" "1.X" - acorn "5.X" - convert-source-map "1.X" - css "2.X" - debug-fabulous "1.X" - detect-newline "2.X" - graceful-fs "4.X" - source-map "~0.6.0" - strip-bom-string "1.X" - through2 "2.X" + "@types/node" "^14.14.41" + "@types/vinyl" "^2.0.4" + istextorbinary "^3.0.0" + replacestream "^4.0.3" + yargs-parser ">=5.0.0-security.0" + +gulp-sourcemaps@3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-3.0.0.tgz#2e154e1a2efed033c0e48013969e6f30337b2743" + integrity sha512-RqvUckJkuYqy4VaIH60RMal4ZtG0IbQ6PXMNkNsshEGJ9cldUPRb/YCgboYae+CLAs1HQNb4ADTKCx65HInquQ== + dependencies: + "@gulp-sourcemaps/identity-map" "^2.0.1" + "@gulp-sourcemaps/map-sources" "^1.0.0" + acorn "^6.4.1" + convert-source-map "^1.0.0" + css "^3.0.0" + debug-fabulous "^1.0.0" + detect-newline "^2.0.0" + graceful-fs "^4.0.0" + source-map "^0.6.0" + strip-bom-string "^1.0.0" + through2 "^2.0.0" gulp@4.0.2: version "4.0.2" @@ -8150,10 +9001,17 @@ gulplog@^1.0.0: dependencies: glogg "^1.0.0" +gzip-size@6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" + integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q== + dependencies: + duplexer "^0.1.2" + handlebars@^4.7.2, handlebars@^4.7.6: - version "4.7.6" - resolved "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz#d4c05c1baf90e9945f77aa68a7a219aa4a7df74e" - integrity sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA== + version "4.7.7" + resolved "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" + integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== dependencies: minimist "^1.2.5" neo-async "^2.6.0" @@ -8167,7 +9025,7 @@ har-schema@^2.0.0: resolved "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= -har-validator@~5.1.0, har-validator@~5.1.3: +har-validator@~5.1.3: version "5.1.5" resolved "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== @@ -8187,6 +9045,11 @@ has-ansi@^2.0.0: dependencies: ansi-regex "^2.0.0" +has-bigints@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" + integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== + has-binary2@~1.0.2: version "1.0.3" resolved "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d" @@ -8214,10 +9077,10 @@ has-flag@^4.0.0: resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.0.0, has-symbols@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" - integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== +has-symbols@^1.0.0, has-symbols@^1.0.1, has-symbols@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== has-unicode@^2.0.0, has-unicode@^2.0.1: version "2.0.1" @@ -8290,9 +9153,9 @@ hash.js@^1.0.0, hash.js@^1.0.3: minimalistic-assert "^1.0.1" hasha@^5.0.0: - version "5.2.1" - resolved "https://registry.npmjs.org/hasha/-/hasha-5.2.1.tgz#0e5b492aa40de3819e80955f221d2fccef55b5aa" - integrity sha512-x15jnRSHTi3VmH+oHtVb9kgU/HuKOK8mjK8iCL3dPQXh4YJlUb9YSI8ZLiiqLAIvY2wuDIlZYZppy8vB2XISkQ== + version "5.2.2" + resolved "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz#a48477989b3b327aea3c04f53096d816d97522a1" + integrity sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ== dependencies: is-stream "^2.0.0" type-fest "^0.8.0" @@ -8303,11 +9166,11 @@ he@1.2.0, he@^1.1.1: integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== highlight.js@^9.17.1: - version "9.18.3" - resolved "https://registry.npmjs.org/highlight.js/-/highlight.js-9.18.3.tgz#a1a0a2028d5e3149e2380f8a865ee8516703d634" - integrity sha512-zBZAmhSupHIl5sITeMqIJnYCDfAEc3Gdkqj65wC1lpI468MMQeeQkhcIAvk+RylAkxrCcI9xy9piHiXeQ1BdzQ== + version "9.18.5" + resolved "https://registry.npmjs.org/highlight.js/-/highlight.js-9.18.5.tgz#d18a359867f378c138d6819edfc2a8acd5f29825" + integrity sha512-a5bFyofd/BHCX52/8i8uJkjr9DYwXIPnM/plwI6W7ezItLGqzt7X2G2nXuYSfsIJdkwwj/g9DG1LkcGJI/dDoA== -hmac-drbg@^1.0.0: +hmac-drbg@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= @@ -8329,9 +9192,16 @@ homedir-polyfill@^1.0.1: parse-passwd "^1.0.0" hosted-git-info@^2.1.4, hosted-git-info@^2.7.1: - version "2.8.8" - resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" - integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== + version "2.8.9" + resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== + +hosted-git-info@^4.0.1: + version "4.0.2" + resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.2.tgz#5e425507eede4fea846b7262f0838456c4209961" + integrity sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg== + dependencies: + lru-cache "^6.0.0" html-escaper@^2.0.0: version "2.0.2" @@ -8343,7 +9213,7 @@ http-cache-semantics@^3.8.1: resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w== -http-cache-semantics@^4.0.0: +http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== @@ -8371,9 +9241,9 @@ http-errors@1.7.3, http-errors@~1.7.2: toidentifier "1.0.0" http-parser-js@>=0.5.1: - version "0.5.2" - resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.2.tgz#da2e31d237b393aae72ace43882dd7e270a8ff77" - integrity sha512-opCO9ASqg5Wy2FNo7A0sxy71yGbbkJJXLdgMK04Tcypw9jr2MgWbyubb0+WdmDmGnFflO7fRbqbaihh/ENDlRQ== + version "0.5.3" + resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz#01d2709c79d41698bb01d4decc5e9da4e4a033d9" + integrity sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg== http-proxy-agent@^2.1.0: version "2.1.0" @@ -8383,7 +9253,7 @@ http-proxy-agent@^2.1.0: agent-base "4" debug "3.1.0" -http-proxy-agent@^4.0.0: +http-proxy-agent@^4.0.0, http-proxy-agent@^4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== @@ -8431,7 +9301,7 @@ https-browserify@^1.0.0: resolved "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= -https-proxy-agent@5.0.0, https-proxy-agent@^5.0.0: +https-proxy-agent@5, https-proxy-agent@5.0.0, https-proxy-agent@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== @@ -8459,10 +9329,10 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" -husky@4.3.0: - version "4.3.0" - resolved "https://registry.npmjs.org/husky/-/husky-4.3.0.tgz#0b2ec1d66424e9219d359e26a51c58ec5278f0de" - integrity sha512-tTMeLCLqSBqnflBZnlVDhpaIMucSGaYyX6855jM4AguGeWCeSzNdb1mfyWduTZ3pe3SJVvVWGL0jO1iKZVPfTA== +husky@4.3.6: + version "4.3.6" + resolved "https://registry.npmjs.org/husky/-/husky-4.3.6.tgz#ebd9dd8b9324aa851f1587318db4cccb7665a13c" + integrity sha512-o6UjVI8xtlWRL5395iWq9LKDyp/9TE7XMOTvIpEVzW638UcGxTmV5cfel6fsk/jbZSTlvfGVJf2svFtybcIZag== dependencies: chalk "^4.0.0" ci-info "^2.0.0" @@ -8494,10 +9364,10 @@ idb@3.0.2: resolved "https://registry.npmjs.org/idb/-/idb-3.0.2.tgz#c8e9122d5ddd40f13b60ae665e4862f8b13fa384" integrity sha512-+FLa/0sTXqyux0o6C+i2lOR0VoS60LU/jzUo5xjfY6+7sEEgy4Gz1O7yFBXvjd7N0NyIGWIRg8DcQSLEG+VSPw== -ieee754@^1.1.4: - version "1.1.13" - resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" - integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== +ieee754@^1.1.13, ieee754@^1.1.4: + version "1.2.1" + resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== iferr@^0.1.5: version "0.1.5" @@ -8505,9 +9375,9 @@ iferr@^0.1.5: integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= ignore-walk@^3.0.1: - version "3.0.3" - resolved "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37" - integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw== + version "3.0.4" + resolved "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.4.tgz#c9a09f69b7c7b479a5d74ac1a3c0d4236d2a6335" + integrity sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ== dependencies: minimatch "^3.0.4" @@ -8540,9 +9410,9 @@ import-fresh@^2.0.0: resolve-from "^3.0.0" import-fresh@^3.0.0, import-fresh@^3.2.1: - version "3.2.1" - resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" - integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== + version "3.3.0" + resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== dependencies: parent-module "^1.0.0" resolve-from "^4.0.0" @@ -8587,16 +9457,15 @@ indent-string@^4.0.0: resolved "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -indexeddbshim@7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/indexeddbshim/-/indexeddbshim-7.0.0.tgz#4398af5f172c77256982c38c2a643471c3ddf8f6" - integrity sha512-2/jdyXICFoWaoQgBMQflJSVxjWVKbNeISRhA8NRRiJtQBsjjwvT+YAOaZTixs30URK6Ty6ruWNcN2sjviuGqUw== +indexeddbshim@7.1.0: + version "7.1.0" + resolved "https://registry.npmjs.org/indexeddbshim/-/indexeddbshim-7.1.0.tgz#6b105ef5503b611f7d11176c94cbf4275757a973" + integrity sha512-sYXq2pGzvcNTjJw4YJwqkF5smBejEk2MDKAmPLTuElNat53+n4PIVdVCxK9HlGFjioLVWEHB0zFJYCNbbC0INA== dependencies: eventtargeter "0.8.0" - regenerator-runtime "^0.13.7" sync-promise "git+https://github.com/brettz9/sync-promise.git#full-sync-missing-promise-features" - typeson "5.18.2" - typeson-registry "1.0.0-alpha.38" + typeson "6.0.0" + typeson-registry "1.0.0-alpha.39" websql "git+https://github.com/brettz9/node-websql.git#configurable-secure2" indexof@0.0.1: @@ -8632,10 +9501,20 @@ inherits@2.0.3: resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -ini@^1.3.2, ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: - version "1.3.5" - resolved "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== +ini@1.3.7: + version "1.3.7" + resolved "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" + integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== + +ini@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" + integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== + +ini@^1.3.2, ini@^1.3.4, ini@~1.3.0: + version "1.3.8" + resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== init-package-json@^1.10.3: version "1.10.3" @@ -8658,21 +9537,22 @@ inline-source-map@~0.6.0: dependencies: source-map "~0.5.3" -inquirer@7.3.3: - version "7.3.3" - resolved "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" - integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== +inquirer@8.1.1: + version "8.1.1" + resolved "https://registry.npmjs.org/inquirer/-/inquirer-8.1.1.tgz#7c53d94c6d03011c7bb2a947f0dca3b98246c26a" + integrity sha512-hUDjc3vBkh/uk1gPfMAD/7Z188Q8cvTGl0nxwaCdwSbzFh6ZKkZh+s2ozVxbE5G9ZNRyeY0+lgbAIOUFsFf98w== dependencies: ansi-escapes "^4.2.1" - chalk "^4.1.0" + chalk "^4.1.1" cli-cursor "^3.1.0" cli-width "^3.0.0" external-editor "^3.0.3" figures "^3.0.0" - lodash "^4.17.19" + lodash "^4.17.21" mute-stream "0.0.8" + ora "^5.3.0" run-async "^2.4.0" - rxjs "^6.6.0" + rxjs "^6.6.6" string-width "^4.1.0" strip-ansi "^6.0.0" through "^2.3.6" @@ -8715,17 +9595,17 @@ inquirer@~6.3.1: strip-ansi "^5.1.0" through "^2.3.6" -install-artifact-from-github@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/install-artifact-from-github/-/install-artifact-from-github-1.0.2.tgz#e1e478dd29880b9112ecd684a84029603e234a9d" - integrity sha512-yuMFBSVIP3vD0SDBGUqeIpgOAIlFx8eQFknQObpkYEM5gsl9hy6R9Ms3aV+Vw9MMyYsoPMeex0XDnfgY7uzc+Q== +install-artifact-from-github@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/install-artifact-from-github/-/install-artifact-from-github-1.2.0.tgz#adcbd123c16a4337ec44ea76d0ebf253cc16b074" + integrity sha512-3OxCPcY55XlVM3kkfIpeCgmoSKnMsz2A3Dbhsq0RXpIknKQmrX1YiznCeW9cD2ItFmDxziA3w6Eg8d80AoL3oA== interpret@^1.0.0, interpret@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== -invariant@^2.2.2, invariant@^2.2.4: +invariant@^2.2.2: version "2.2.4" resolved "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== @@ -8737,12 +9617,12 @@ invert-kv@^1.0.0: resolved "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= -ip-regex@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" - integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= +ip-regex@^4.1.0: + version "4.3.0" + resolved "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz#687275ab0f57fa76978ff8f4dddc8a23d5990db5" + integrity sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q== -ip@1.1.5: +ip@1.1.5, ip@^1.1.5: version "1.1.5" resolved "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= @@ -8775,9 +9655,11 @@ is-accessor-descriptor@^1.0.0: kind-of "^6.0.0" is-arguments@^1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3" - integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA== + version "1.1.0" + resolved "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz#62353031dfbee07ceb34656a6bde59efecae8dd9" + integrity sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg== + dependencies: + call-bind "^1.0.0" is-arrayish@^0.2.1: version "0.2.1" @@ -8789,6 +9671,11 @@ is-arrayish@^0.3.1: resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== +is-bigint@^1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz#ffb381442503235ad245ea89e45b3dbff040ee5a" + integrity sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA== + is-binary-path@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" @@ -8803,20 +9690,27 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" +is-boolean-object@^1.1.0: + version "1.1.1" + resolved "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.1.tgz#3c0878f035cb821228d350d2e1e36719716a3de8" + integrity sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng== + dependencies: + call-bind "^1.0.2" + is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== is-buffer@~2.0.3: - version "2.0.4" - resolved "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623" - integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A== + version "2.0.5" + resolved "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" + integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== -is-callable@^1.1.4, is-callable@^1.2.2: - version "1.2.2" - resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" - integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== +is-callable@^1.1.4, is-callable@^1.2.3: + version "1.2.3" + resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e" + integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ== is-ci@^2.0.0: version "2.0.0" @@ -8825,6 +9719,13 @@ is-ci@^2.0.0: dependencies: ci-info "^2.0.0" +is-core-module@^2.2.0, is-core-module@^2.4.0: + version "2.4.0" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz#8e9fc8e15027b011418026e98f0e6f4d86305cc1" + integrity sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A== + dependencies: + has "^1.0.3" + is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -8840,9 +9741,9 @@ is-data-descriptor@^1.0.0: kind-of "^6.0.0" is-date-object@^1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" - integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== + version "1.0.4" + resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.4.tgz#550cfcc03afada05eea3dd30981c7b09551f73e5" + integrity sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A== is-descriptor@^0.1.0: version "0.1.6" @@ -8868,9 +9769,9 @@ is-directory@^0.3.1: integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= is-docker@^2.0.0: - version "2.1.1" - resolved "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz#4125a88e44e450d384e09047ede71adc2d144156" - integrity sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw== + version "2.2.1" + resolved "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== is-extendable@^0.1.0, is-extendable@^0.1.1: version "0.1.1" @@ -8912,9 +9813,9 @@ is-fullwidth-code-point@^3.0.0: integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== is-generator-function@^1.0.7: - version "1.0.7" - resolved "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz#d2132e529bb0000a7f80794d4bdf5cd5e5813522" - integrity sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw== + version "1.0.9" + resolved "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.9.tgz#e5f82c2323673e7fcad3d12858c83c4039f6399c" + integrity sha512-ZJ34p1uvIfptHCN7sFTjGibB9/oBg17sHqzDLfuwhvmN/qLVvIQXRQ8licZQ35WJ8KuEQt/etnnzQFI9C9Ue/A== is-glob@^3.1.0: version "3.1.0" @@ -8938,21 +9839,35 @@ is-installed-globally@^0.3.1: global-dirs "^2.0.1" is-path-inside "^3.0.1" +is-installed-globally@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" + integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== + dependencies: + global-dirs "^3.0.0" + is-path-inside "^3.0.2" + is-interactive@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== +is-lambda@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" + integrity sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU= + is-module@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= is-nan@^1.2.1: - version "1.3.0" - resolved "https://registry.npmjs.org/is-nan/-/is-nan-1.3.0.tgz#85d1f5482f7051c2019f5673ccebdb06f3b0db03" - integrity sha512-z7bbREymOqt2CCaZVly8aC4ML3Xhfi0ekuOnjO2L8vKdl+CttdVoGZQhd4adMFAsxQ5VeRVwORs4tU8RH+HFtQ== + version "1.3.2" + resolved "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d" + integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w== dependencies: + call-bind "^1.0.0" define-properties "^1.1.3" is-negated-glob@^1.0.0: @@ -8960,16 +9875,26 @@ is-negated-glob@^1.0.0: resolved "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" integrity sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI= -is-negative-zero@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461" - integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE= +is-negative-zero@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" + integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== is-npm@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig== +is-npm@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz#43e8d65cc56e1b67f8d47262cf667099193f45a8" + integrity sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA== + +is-number-object@^1.0.4: + version "1.0.5" + resolved "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz#6edfaeed7950cff19afedce9fbfca9ee6dd289eb" + integrity sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw== + is-number@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" @@ -9029,15 +9954,20 @@ is-path-inside@^1.0.0: path-is-inside "^1.0.1" is-path-inside@^3.0.1, is-path-inside@^3.0.2: - version "3.0.2" - resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017" - integrity sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg== + version "3.0.3" + resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= +is-plain-obj@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" @@ -9045,12 +9975,17 @@ is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" +is-plain-object@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz#662d92d24c0aa4302407b0d45d21f2251c85f85b" + integrity sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g== + is-plain-object@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== -is-promise@^2.1, is-promise@^2.1.0: +is-promise@^2.1.0, is-promise@^2.2.2: version "2.2.2" resolved "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== @@ -9067,12 +10002,13 @@ is-reference@^1.2.1: dependencies: "@types/estree" "*" -is-regex@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" - integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg== +is-regex@^1.1.2, is-regex@^1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz#d029f9aff6448b93ebbe3f33dac71511fdcbef9f" + integrity sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ== dependencies: - has-symbols "^1.0.1" + call-bind "^1.0.2" + has-symbols "^1.0.2" is-relative@^1.0.0: version "1.0.0" @@ -9087,9 +10023,9 @@ is-retry-allowed@^1.0.0: integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg== is-ssh@^1.3.0: - version "1.3.2" - resolved "https://registry.npmjs.org/is-ssh/-/is-ssh-1.3.2.tgz#a4b82ab63d73976fd8263cceee27f99a88bdae2b" - integrity sha512-elEw0/0c2UscLrNG+OAorbP539E3rhliKPg+hDMWN9VwrDXfYK+4PBEykDPfxlYYtQvl84TascnQyobfQLHEhQ== + version "1.3.3" + resolved "https://registry.npmjs.org/is-ssh/-/is-ssh-1.3.3.tgz#7f133285ccd7f2c2c7fc897b771b53d95a2b2c7e" + integrity sha512-NKzJmQzJfEEma3w5cJNcUMxoXfDjz0Zj0eyCalHn2E6VOwlzjZo0yuO2fcBSf8zhFuVCL/82/r5gRcoi6aEPVQ== dependencies: protocols "^1.1.0" @@ -9108,24 +10044,24 @@ is-stream@^2.0.0: resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== -is-string@^1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" - integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== +is-string@^1.0.5, is-string@^1.0.6: + version "1.0.6" + resolved "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz#3fe5d5992fb0d93404f32584d4b0179a71b54a5f" + integrity sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w== is-subdir@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/is-subdir/-/is-subdir-1.1.1.tgz#423e66902f9c5f159b9cc4826c820df083059538" - integrity sha512-VYpq0S7gPBVkkmfwkvGnx1EL9UVIo87NQyNcgMiNUdQCws3CJm5wj2nB+XPL7zigvjxhuZgp3bl2yBcKkSIj1w== + version "1.2.0" + resolved "https://registry.npmjs.org/is-subdir/-/is-subdir-1.2.0.tgz#b791cd28fab5202e91a08280d51d9d7254fd20d4" + integrity sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw== dependencies: better-path-resolve "1.0.0" -is-symbol@^1.0.2: - version "1.0.3" - resolved "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" - integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== dependencies: - has-symbols "^1.0.1" + has-symbols "^1.0.2" is-text-path@^1.0.1: version "1.0.1" @@ -9135,12 +10071,13 @@ is-text-path@^1.0.1: text-extensions "^1.0.0" is-typed-array@^1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.3.tgz#a4ff5a5e672e1a55f99c7f54e59597af5c1df04d" - integrity sha512-BSYUBOK/HJibQ30wWkWold5txYwMUXQct9YHAQJr8fSwvZoiglcqB0pd7vEN23+Tsi9IUEjztdOSzl4qLVYGTQ== + version "1.1.5" + resolved "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.5.tgz#f32e6e096455e329eb7b423862456aa213f0eb4e" + integrity sha512-S+GRDgJlR3PyEbsX/Fobd9cqpZBuvUS+8asRqYDMLCb2qMzt1oz5m5oxQCxOgUDxiWsOVNi4yaF+/uvdlHlYug== dependencies: - available-typed-arrays "^1.0.0" - es-abstract "^1.17.4" + available-typed-arrays "^1.0.2" + call-bind "^1.0.2" + es-abstract "^1.18.0-next.2" foreach "^2.0.5" has-symbols "^1.0.1" @@ -9156,7 +10093,12 @@ is-unc-path@^1.0.0: dependencies: unc-path-regex "^0.1.2" -is-url@^1.2.2: +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +is-url@^1.2.2, is-url@^1.2.4: version "1.2.4" resolved "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52" integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww== @@ -9181,7 +10123,7 @@ is-wsl@^1.1.0: resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= -is-wsl@^2.1.0: +is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== @@ -9193,14 +10135,14 @@ is-yarn-global@^0.3.0: resolved "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== -is2@2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/is2/-/is2-2.0.1.tgz#8ac355644840921ce435d94f05d3a94634d3481a" - integrity sha512-+WaJvnaA7aJySz2q/8sLjMb2Mw14KTplHmSwcSpZ/fWJPkUmqw3YTzSWbPJ7OAwRvdYTWF2Wg+yYJ1AdP5Z8CA== +is2@^2.0.6: + version "2.0.7" + resolved "https://registry.npmjs.org/is2/-/is2-2.0.7.tgz#d084e10cab3bd45d6c9dfde7a48599fcbb93fcac" + integrity sha512-4vBQoURAXC6hnLFxD4VW7uc04XiwTTl/8ydYJxKvPwkWQrSjInkuM5VZVg6BGr1/natq69zDuvO9lGpLClJqvA== dependencies: deep-is "^0.1.3" - ip-regex "^2.1.0" - is-url "^1.2.2" + ip-regex "^4.1.0" + is-url "^1.2.4" isarray@0.0.1: version "0.0.1" @@ -9218,9 +10160,9 @@ isarray@2.0.1: integrity sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4= isbinaryfile@^4.0.6: - version "4.0.6" - resolved "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.6.tgz#edcb62b224e2b4710830b67498c8e4e5a4d2610b" - integrity sha512-ORrEy+SNVqUhrCaal4hA4fBzhggQQ+BaLntyPOdoEiwlKZW9BZiJXjg3RMiruE4tPEI3pyVPpySHQF/dKWperg== + version "4.0.8" + resolved "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.8.tgz#5d34b94865bd4946633ecc78a026fc76c5b11fcf" + integrity sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w== isexe@^2.0.0: version "2.0.0" @@ -9404,14 +10346,13 @@ istanbul-reports@^3.0.0, istanbul-reports@^3.0.2: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -istextorbinary@2.2.1: - version "2.2.1" - resolved "https://registry.npmjs.org/istextorbinary/-/istextorbinary-2.2.1.tgz#a5231a08ef6dd22b268d0895084cf8d58b5bec53" - integrity sha512-TS+hoFl8Z5FAFMK38nhBkdLt44CclNRgDHWeMgsV8ko3nDlr/9UI2Sf839sW7enijf8oKsZYXRvM8g0it9Zmcw== +istextorbinary@^3.0.0: + version "3.3.0" + resolved "https://registry.npmjs.org/istextorbinary/-/istextorbinary-3.3.0.tgz#06b1c57d948da11461bd237c00ce09e9902964f2" + integrity sha512-Tvq1W6NAcZeJ8op+Hq7tdZ434rqnMx4CCZ7H0ff83uEloDvVbqAwaMTZcafKGJT0VHkYzuXUiCY4hlXQg6WfoQ== dependencies: - binaryextensions "2" - editions "^1.3.3" - textextensions "2" + binaryextensions "^2.2.0" + textextensions "^3.2.0" jasmine-core@~2.8.0: version "2.8.0" @@ -9432,6 +10373,133 @@ jasminewd2@^2.1.0: resolved "https://registry.npmjs.org/jasminewd2/-/jasminewd2-2.2.0.tgz#e37cf0b17f199cce23bea71b2039395246b4ec4e" integrity sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4= +jest-diff@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394" + integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA== + dependencies: + chalk "^4.0.0" + diff-sequences "^26.6.2" + jest-get-type "^26.3.0" + pretty-format "^26.6.2" + +jest-get-type@^26.3.0: + version "26.3.0" + resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" + integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig== + +jest-haste-map@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa" + integrity sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w== + dependencies: + "@jest/types" "^26.6.2" + "@types/graceful-fs" "^4.1.2" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.4" + jest-regex-util "^26.0.0" + jest-serializer "^26.6.2" + jest-util "^26.6.2" + jest-worker "^26.6.2" + micromatch "^4.0.2" + sane "^4.0.3" + walker "^1.0.7" + optionalDependencies: + fsevents "^2.1.2" + +jest-matcher-utils@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz#8e6fd6e863c8b2d31ac6472eeb237bc595e53e7a" + integrity sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw== + dependencies: + chalk "^4.0.0" + jest-diff "^26.6.2" + jest-get-type "^26.3.0" + pretty-format "^26.6.2" + +jest-message-util@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz#58173744ad6fc0506b5d21150b9be56ef001ca07" + integrity sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA== + dependencies: + "@babel/code-frame" "^7.0.0" + "@jest/types" "^26.6.2" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.4" + micromatch "^4.0.2" + pretty-format "^26.6.2" + slash "^3.0.0" + stack-utils "^2.0.2" + +jest-pnp-resolver@^1.2.2: + version "1.2.2" + resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" + integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== + +jest-regex-util@^26.0.0: + version "26.0.0" + resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28" + integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A== + +jest-resolve@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz#a3ab1517217f469b504f1b56603c5bb541fbb507" + integrity sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ== + dependencies: + "@jest/types" "^26.6.2" + chalk "^4.0.0" + graceful-fs "^4.2.4" + jest-pnp-resolver "^1.2.2" + jest-util "^26.6.2" + read-pkg-up "^7.0.1" + resolve "^1.18.1" + slash "^3.0.0" + +jest-serializer@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz#d139aafd46957d3a448f3a6cdabe2919ba0742d1" + integrity sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g== + dependencies: + "@types/node" "*" + graceful-fs "^4.2.4" + +jest-snapshot@^26.5.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz#f3b0af1acb223316850bd14e1beea9837fb39c84" + integrity sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og== + dependencies: + "@babel/types" "^7.0.0" + "@jest/types" "^26.6.2" + "@types/babel__traverse" "^7.0.4" + "@types/prettier" "^2.0.0" + chalk "^4.0.0" + expect "^26.6.2" + graceful-fs "^4.2.4" + jest-diff "^26.6.2" + jest-get-type "^26.3.0" + jest-haste-map "^26.6.2" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" + jest-resolve "^26.6.2" + natural-compare "^1.4.0" + pretty-format "^26.6.2" + semver "^7.3.2" + +jest-util@^26.5.2, jest-util@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1" + integrity sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q== + dependencies: + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + graceful-fs "^4.2.4" + is-ci "^2.0.0" + micromatch "^4.0.2" + jest-worker@^24.0.0: version "24.9.0" resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz#5dbfdb5b2d322e98567898238a9697bcce67b3e5" @@ -9440,10 +10508,10 @@ jest-worker@^24.0.0: merge-stream "^2.0.0" supports-color "^6.1.0" -jest-worker@^26.2.1: - version "26.3.0" - resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-26.3.0.tgz#7c8a97e4f4364b4f05ed8bca8ca0c24de091871f" - integrity sha512-Vmpn2F6IASefL+DVBhPzI2J9/GJUsqzomdeN+P+dK8/jKxbh8R3BtFnx3FIta7wYlPU62cpJMJQo4kuOowcMnw== +jest-worker@^26.2.1, jest-worker@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" + integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== dependencies: "@types/node" "*" merge-stream "^2.0.0" @@ -9463,10 +10531,17 @@ join-path@^1.1.1: url-join "0.0.1" valid-url "^1" +jose@^2.0.5: + version "2.0.5" + resolved "https://registry.npmjs.org/jose/-/jose-2.0.5.tgz#29746a18d9fff7dcf9d5d2a6f62cb0c7cd27abd3" + integrity sha512-BAiDNeDKTMgk4tvD0BbxJ8xHEHBZgpeRZ1zGPPsitSyMgjoMWiLGYAE7H7NpP5h0lPppQajQs871E8NHUrzVPA== + dependencies: + "@panva/asn1.js" "^1.0.0" + jquery@^3.4.1: - version "3.5.1" - resolved "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz#d7b4d08e1bfdb86ad2f1a3d039ea17304717abb5" - integrity sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg== + version "3.6.0" + resolved "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz#c72a09f15c1bdce142f49dbf1170bdf8adac2470" + integrity sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw== "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" @@ -9486,10 +10561,24 @@ js-yaml@3.13.1, js-yaml@~3.13.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@3.14.0, js-yaml@^3.13.0, js-yaml@^3.13.1, js-yaml@^3.6.1: - version "3.14.0" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" - integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== +js-yaml@4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz#f426bc0ff4b4051926cd588c71113183409a121f" + integrity sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q== + dependencies: + argparse "^2.0.1" + +js-yaml@4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +js-yaml@^3.13.0, js-yaml@^3.13.1, js-yaml@^3.6.1: + version "3.14.1" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== dependencies: argparse "^1.0.7" esprima "^4.0.0" @@ -9514,13 +10603,6 @@ jsesc@~0.5.0: resolved "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= -json-bigint@^0.3.0: - version "0.3.1" - resolved "https://registry.npmjs.org/json-bigint/-/json-bigint-0.3.1.tgz#0c1729d679f580d550899d6a2226c228564afe60" - integrity sha512-DGWnSzmusIreWlEupsUelHrhwmPPE+FiQvg+drKfk2p+bdEYa5mp4PJ8JsCWqae0M2jQNb0HPvnwvf1qOTThzQ== - dependencies: - bignumber.js "^9.0.0" - json-bigint@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1" @@ -9550,12 +10632,12 @@ json-parse-helpfulerror@^1.0.3: dependencies: jju "^1.1.0" -json-ptr@^1.3.1: - version "1.3.2" - resolved "https://registry.npmjs.org/json-ptr/-/json-ptr-1.3.2.tgz#17f45b322a843b1f2fbcc9b45132bd9b3ba8cd38" - integrity sha512-tFH40YQ+lG7mgYYM1kGZOhQngO4SbOEHZJlA4W+NtetWZ20EUU3BPU+30uWRKumuAJoSo5eqrsXD2h72ioS8ew== +json-ptr@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/json-ptr/-/json-ptr-2.2.0.tgz#a4de4ed638cb23ae4cd4b51f8bf972a1c2293f1e" + integrity sha512-w9f6/zhz4kykltXMG7MLJWMajxiPj0q+uzQPR1cggNAE/sXoq/C5vjUb/7QNcC3rJsVIIKy37ALTXy1O+3c8QQ== dependencies: - tslib "^2.0.0" + tslib "^2.2.0" json-schema-traverse@^0.3.0: version "0.3.1" @@ -9567,6 +10649,11 @@ json-schema-traverse@^0.4.1: resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + json-schema@0.2.3: version "0.2.3" resolved "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" @@ -9597,19 +10684,12 @@ json5@^1.0.1: minimist "^1.2.0" json5@^2.1.2: - version "2.1.3" - resolved "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" - integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== + version "2.2.0" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" + integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== dependencies: minimist "^1.2.5" -jsonfile@^2.1.0: - version "2.4.0" - resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" - integrity sha1-NzaitCi4e72gzIO1P6PWM6NcKug= - optionalDependencies: - graceful-fs "^4.1.6" - jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -9627,12 +10707,7 @@ jsonparse@^1.2.0: resolved "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA= -jsonschema@^1.0.2: - version "1.2.7" - resolved "https://registry.npmjs.org/jsonschema/-/jsonschema-1.2.7.tgz#4e6d6dc4d83dc80707055ba22c00ec6152c0e6e9" - integrity sha512-3dFMg9hmI9LdHag/BRIhMefCfbq1hicvYMy8YhZQorAdzOzWz7NjniSpn39yjpzUAMIWtGyyZuH2KNBloH7ZLw== - -jsonwebtoken@^8.2.1, jsonwebtoken@^8.5.1: +jsonwebtoken@^8.5.1: version "8.5.1" resolved "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== @@ -9658,10 +10733,10 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -jszip@^3.1.3, jszip@^3.2.2: - version "3.5.0" - resolved "https://registry.npmjs.org/jszip/-/jszip-3.5.0.tgz#b4fd1f368245346658e781fec9675802489e15f6" - integrity sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA== +jszip@^3.1.3, jszip@^3.5.0: + version "3.6.0" + resolved "https://registry.npmjs.org/jszip/-/jszip-3.6.0.tgz#839b72812e3f97819cc13ac4134ffced95dd6af9" + integrity sha512-jgnQoG9LKnWO3mnVNBnfhkh0QknICd1FGSrXcgrl67zioyJ4wgx25o9ZqwNtrROSflGBCGYnJfjrIyRIby1OoQ== dependencies: lie "~3.3.0" pako "~1.0.2" @@ -9669,14 +10744,14 @@ jszip@^3.1.3, jszip@^3.2.2: set-immediate-shim "~1.0.1" just-debounce@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz#87fccfaeffc0b68cd19d55f6722943f929ea35ea" - integrity sha1-h/zPrv/AtozRnVX2cilD+SnqNeo= + version "1.1.0" + resolved "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz#2f81a3ad4121a76bc7cb45dbf704c0d76a8e5ddf" + integrity sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ== just-extend@^4.0.2: - version "4.1.1" - resolved "https://registry.npmjs.org/just-extend/-/just-extend-4.1.1.tgz#158f1fdb01f128c411dc8b286a7b4837b3545282" - integrity sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA== + version "4.2.1" + resolved "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz#ef5e589afb61e5d66b24eca749409a8939a8c744" + integrity sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg== jwa@^1.4.1: version "1.4.1" @@ -9696,6 +10771,17 @@ jwa@^2.0.0: ecdsa-sig-formatter "1.0.11" safe-buffer "^5.0.1" +jwks-rsa@^2.0.2: + version "2.0.3" + resolved "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.0.3.tgz#4059f25e27f1d9cb5681dd12a98e46f8aa39fcbd" + integrity sha512-/rkjXRWAp0cS00tunsHResw68P5iTQru8+jHufLNv3JHc4nObFEndfEUSuPugh09N+V9XYxKUqi7QrkmCHSSSg== + dependencies: + "@types/express-jwt" "0.0.42" + debug "^4.1.0" + jose "^2.0.5" + limiter "^1.1.5" + lru-memoizer "^2.1.2" + jws@^3.2.2: version "3.2.2" resolved "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" @@ -9739,12 +10825,13 @@ karma-coverage-istanbul-reporter@2.1.1: istanbul-api "^2.1.6" minimatch "^3.0.4" -karma-firefox-launcher@1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-1.3.0.tgz#ebcbb1d1ddfada6be900eb8fae25bcf2dcdc8171" - integrity sha512-Fi7xPhwrRgr+94BnHX0F5dCl1miIW4RHnzjIGxF8GaIEp7rNqX7LSi7ok63VXs3PS/5MQaQMhGxw+bvD+pibBQ== +karma-firefox-launcher@2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-2.1.0.tgz#d0d328c93dfcf9b46f1ac83b4bb32f43aadb2050" + integrity sha512-dkiyqN2R6fCWt78rciOXJLFDWcQ7QEQi++HgebPJlw1y0ycDjGNDHuSrhdh48QG02fzZKK20WHFWVyBZ6CPngg== dependencies: - is-wsl "^2.1.0" + is-wsl "^2.2.0" + which "^2.0.1" karma-mocha-reporter@2.2.5: version "2.2.5" @@ -9767,16 +10854,6 @@ karma-safari-launcher@1.0.0: resolved "https://registry.npmjs.org/karma-safari-launcher/-/karma-safari-launcher-1.0.0.tgz#96982a2cc47d066aae71c553babb28319115a2ce" integrity sha1-lpgqLMR9BmquccVTursoMZEVos4= -karma-sauce-launcher@1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/karma-sauce-launcher/-/karma-sauce-launcher-1.2.0.tgz#6f2558ddef3cf56879fa27540c8ae9f8bfd16bca" - integrity sha512-lEhtGRGS+3Yw6JSx/vJY9iQyHNtTjcojrSwNzqNUOaDceKDu9dPZqA/kr69bUO9G2T6GKbu8AZgXqy94qo31Jg== - dependencies: - q "^1.5.0" - sauce-connect-launcher "^1.2.2" - saucelabs "^1.4.0" - wd "^1.4.0" - karma-sourcemap-loader@0.3.8: version "0.3.8" resolved "https://registry.npmjs.org/karma-sourcemap-loader/-/karma-sourcemap-loader-0.3.8.tgz#d4bae72fb7a8397328a62b75013d2df937bdcf9c" @@ -9791,20 +10868,20 @@ karma-spec-reporter@0.0.32: dependencies: colors "^1.1.2" -karma-summary-reporter@1.9.0: - version "1.9.0" - resolved "https://registry.npmjs.org/karma-summary-reporter/-/karma-summary-reporter-1.9.0.tgz#e0c2f97bbd0af5faed7ff5e37cf5b2dd10ff8306" - integrity sha512-aQ+GcHsj7bYR00mJTC/jSrVYt9GdmWv9JN1MtLSICOnfPaVBHFcFMPDdCkvb/ZIB2pb+VYwtRH+DjQ0yrTqfvQ== +karma-summary-reporter@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/karma-summary-reporter/-/karma-summary-reporter-2.0.0.tgz#50c26038d877e6f245bd4e3f80dbc5fcec5a7b97" + integrity sha512-m9MBLnlsm0wbGrFZF05U9EWZuLD5i14OiXQsWardujd0IadMEy3Te9nDkeJzWPp9vQAhbpv85ZynsNgXC6lNLQ== dependencies: - chalk "^1.1.3 || 2.x" + chalk "^4.0.0" -karma-typescript@5.2.0: - version "5.2.0" - resolved "https://registry.npmjs.org/karma-typescript/-/karma-typescript-5.2.0.tgz#3a1fdc322b829b698cdab5768ee71b940f021f15" - integrity sha512-idMJ0SKPLYudNiPaRw+GyOu0RQPJFpyUfrSN6/uFTzPLnJ7sLDd6xPMcxB+pNBHpL6s4fQfC5W9OlNblRvt2Dg== +karma-typescript@5.5.1: + version "5.5.1" + resolved "https://registry.npmjs.org/karma-typescript/-/karma-typescript-5.5.1.tgz#3ceb79ecec695c73d2633aff8714524b5440f38f" + integrity sha512-iQDh48sHv+1pCO/Fvlz4b6HwcmaTuAzx1H0Q6zkUQMQH+5sjz8GNFB9jVDiDblbZcgdr+luVBk7NGUV5mXj+eg== dependencies: - acorn "^8.0.1" - acorn-walk "^8.0.0" + acorn "^8.1.0" + acorn-walk "^8.0.2" assert "^2.0.0" async "^3.0.1" browser-resolve "^2.0.0" @@ -9919,13 +10996,6 @@ kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== -klaw@^1.0.0: - version "1.3.1" - resolved "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439" - integrity sha1-QIhDO0azsbolnXh4XY6W9zugJDk= - optionalDependencies: - graceful-fs "^4.1.9" - kuler@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" @@ -9939,7 +11009,7 @@ last-run@^1.1.0: default-resolution "^2.0.0" es6-weak-map "^2.0.1" -latest-version@^5.0.0: +latest-version@^5.0.0, latest-version@^5.1.0: version "5.1.0" resolved "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== @@ -10010,13 +11080,6 @@ leven@^3.1.0: resolved "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== -levenary@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz#842a9ee98d2075aa7faeedbe32679e9205f46f77" - integrity sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ== - dependencies: - leven "^3.1.0" - levn@^0.4.1: version "0.4.1" resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" @@ -10025,6 +11088,14 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + lie@~3.3.0: version "3.3.0" resolved "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" @@ -10046,6 +11117,11 @@ liftoff@^3.1.0: rechoir "^0.6.2" resolve "^1.1.7" +limiter@^1.1.5: + version "1.1.5" + resolved "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz#8f92a25b3b16c6131293a0cc834b4a838a2aa7c2" + integrity sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA== + lines-and-columns@^1.1.6: version "1.1.6" resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" @@ -10111,16 +11187,6 @@ load-json-file@^1.0.0: pinkie-promise "^2.0.0" strip-bom "^2.0.0" -load-json-file@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" - integrity sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg= - dependencies: - graceful-fs "^4.1.2" - parse-json "^2.2.0" - pify "^2.0.0" - strip-bom "^3.0.0" - load-json-file@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" @@ -10157,7 +11223,12 @@ loader-runner@^2.4.0: resolved "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== -loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: +loader-runner@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" + integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw== + +loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== @@ -10227,11 +11298,6 @@ lodash._shimkeys@~2.4.1: dependencies: lodash._objecttypes "~2.4.1" -lodash.at@^4.6.0: - version "4.6.0" - resolved "https://registry.npmjs.org/lodash.at/-/lodash.at-4.6.0.tgz#93cdce664f0a1994ea33dd7cd40e23afd11b0ff8" - integrity sha1-k83OZk8KGZTqM9181A4jr9EbD/g= - lodash.camelcase@^4.3.0: version "4.3.0" resolved "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" @@ -10247,6 +11313,11 @@ lodash.clonedeep@^4.5.0: resolved "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= + lodash.defaults@^4.2.0: version "4.2.0" resolved "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" @@ -10272,11 +11343,6 @@ lodash.get@^4.0.0, lodash.get@^4.4.2: resolved "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= -lodash.has@^4.5.2: - version "4.5.2" - resolved "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz#d19f4dc1095058cccbe2b0cdf4ee0fe4aa37c862" - integrity sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI= - lodash.includes@^4.3.0: version "4.3.0" resolved "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" @@ -10353,6 +11419,11 @@ lodash.memoize@~3.0.3: resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz#2dcbd2c287cbc0a55cc42328bd0c736150d53e3f" integrity sha1-LcvSwofLwKVcxCMovQxzYVDVPj8= +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + lodash.once@^4.0.0: version "4.1.1" resolved "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" @@ -10403,6 +11474,11 @@ lodash.toarray@^4.4.0: resolved "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" integrity sha1-JMS/zWsvuji/0FlNsRedjptlZWE= +lodash.truncate@^4.4.2: + version "4.4.2" + resolved "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" + integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= + lodash.union@^4.6.0: version "4.6.0" resolved "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" @@ -10420,16 +11496,16 @@ lodash.values@^2.4.1: dependencies: lodash.keys "~2.4.1" -lodash@4.17.19: - version "4.17.19" - resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" - integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== - -lodash@4.17.20, lodash@^4.0.0, lodash@^4.16.6, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@~4.17.15: +lodash@4.17.20: version "4.17.20" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== +lodash@4.17.21, lodash@^4.16.6, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.7.0, lodash@~4.17.15: + version "4.17.21" + resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + log-driver@^1.2.7: version "1.2.7" resolved "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz#63b95021f0702fedfa2c9bb0a24e7797d71871d8" @@ -10442,6 +11518,13 @@ log-symbols@3.0.0: dependencies: chalk "^2.4.2" +log-symbols@4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920" + integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA== + dependencies: + chalk "^4.0.0" + log-symbols@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18" @@ -10456,12 +11539,13 @@ log-symbols@^2.1.0, log-symbols@^2.2.0: dependencies: chalk "^2.0.1" -log-symbols@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920" - integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA== +log-symbols@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== dependencies: - chalk "^4.0.0" + chalk "^4.1.0" + is-unicode-supported "^0.1.0" log-update@^2.3.0: version "2.3.0" @@ -10537,7 +11621,7 @@ lru-cache@^4.0.1: pseudomap "^1.0.2" yallist "^2.1.2" -lru-cache@^5.0.0, lru-cache@^5.1.1: +lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== @@ -10551,7 +11635,23 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -lru-queue@0.1: +lru-cache@~4.0.0: + version "4.0.2" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz#1d17679c069cda5d040991a09dbc2c0db377e55e" + integrity sha1-HRdnnAac2l0ECZGgnbwsDbN35V4= + dependencies: + pseudomap "^1.0.1" + yallist "^2.0.0" + +lru-memoizer@^2.1.2: + version "2.1.4" + resolved "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.1.4.tgz#b864d92b557f00b1eeb322156a0409cb06dafac6" + integrity sha512-IXAq50s4qwrOBrXJklY+KhgZF+5y98PDaNo0gi/v2KQBFLyWr+JyFvijZXkGKjQj/h9c0OwoE+JZbwUXce76hQ== + dependencies: + lodash.clonedeep "^4.5.0" + lru-cache "~4.0.0" + +lru-queue@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3" integrity sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM= @@ -10568,7 +11668,7 @@ macos-release@^2.2.0: resolved "https://registry.npmjs.org/macos-release/-/macos-release-2.4.1.tgz#64033d0ec6a5e6375155a74b1a1eba8e509820ac" integrity sha512-H/QHeBIN1fIGJX517pvK8IEK53yQOW7YcEI55oYtgjDdoCQQz7eJS94qt5kNrscReEyuD/JcdFCm2XBEcGOITg== -magic-string@0.25.7, magic-string@^0.25.2, magic-string@^0.25.5, magic-string@^0.25.7: +magic-string@0.25.7, magic-string@^0.25.2, magic-string@^0.25.7: version "0.25.7" resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== @@ -10590,7 +11690,7 @@ make-dir@^2.0.0, make-dir@^2.1.0: pify "^4.0.1" semver "^5.6.0" -make-dir@^3.0.0, make-dir@^3.0.2: +make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== @@ -10619,6 +11719,27 @@ make-fetch-happen@^5.0.0: socks-proxy-agent "^4.0.0" ssri "^6.0.0" +make-fetch-happen@^8.0.14: + version "8.0.14" + resolved "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-8.0.14.tgz#aaba73ae0ab5586ad8eaa68bd83332669393e222" + integrity sha512-EsS89h6l4vbfJEtBZnENTOFk8mCRpY5ru36Xe5bcX1KYIli2mkSHqoFsp5O1wMDvTJJzxe/4THpCTtygjeeGWQ== + dependencies: + agentkeepalive "^4.1.3" + cacache "^15.0.5" + http-cache-semantics "^4.1.0" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + is-lambda "^1.0.1" + lru-cache "^6.0.0" + minipass "^3.1.3" + minipass-collect "^1.0.2" + minipass-fetch "^1.3.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + promise-retry "^2.0.1" + socks-proxy-agent "^5.0.0" + ssri "^8.0.0" + make-iterator@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz#29b33f312aa8f547c4a5e490f56afcec99133ad6" @@ -10626,6 +11747,13 @@ make-iterator@^1.0.0: dependencies: kind-of "^6.0.2" +makeerror@1.0.x: + version "1.0.11" + resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" + integrity sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw= + dependencies: + tmpl "1.0.x" + map-cache@^0.2.0, map-cache@^0.2.2: version "0.2.2" resolved "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" @@ -10642,9 +11770,9 @@ map-obj@^2.0.0: integrity sha1-plzSkIepJZi4eRJXpSPgISIqwfk= map-obj@^4.0.0: - version "4.1.0" - resolved "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz#b91221b542734b9f14256c0132c897c5d7256fd5" - integrity sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g== + version "4.2.1" + resolved "https://registry.npmjs.org/map-obj/-/map-obj-4.2.1.tgz#e4ea399dbc979ae735c83c863dd31bdf364277b7" + integrity sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ== map-visit@^1.0.0: version "1.0.0" @@ -10699,19 +11827,26 @@ media-typer@0.3.0: resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= +memfs@3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/memfs/-/memfs-3.2.0.tgz#f9438e622b5acd1daa8a4ae160c496fdd1325b26" + integrity sha512-f/xxz2TpdKv6uDn6GtHee8ivFyxwxmPuXatBb1FBwxYNuVpbM3k/Y1Z+vC0mH/dIXXrukYfe3qe5J32Dfjg93A== + dependencies: + fs-monkey "1.0.1" + memoizee@0.4.X, memoizee@^0.4.14: - version "0.4.14" - resolved "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz#07a00f204699f9a95c2d9e77218271c7cd610d57" - integrity sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg== + version "0.4.15" + resolved "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz#e6f3d2da863f318d02225391829a6c5956555b72" + integrity sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ== dependencies: - d "1" - es5-ext "^0.10.45" - es6-weak-map "^2.0.2" + d "^1.0.1" + es5-ext "^0.10.53" + es6-weak-map "^2.0.3" event-emitter "^0.3.5" - is-promise "^2.1" - lru-queue "0.1" - next-tick "1" - timers-ext "^0.1.5" + is-promise "^2.2.2" + lru-queue "^0.1.0" + next-tick "^1.1.0" + timers-ext "^0.1.7" memory-fs@^0.4.1: version "0.4.1" @@ -10765,31 +11900,16 @@ meow@^4.0.0: redent "^2.0.0" trim-newlines "^2.0.0" -meow@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/meow/-/meow-5.0.0.tgz#dfc73d63a9afc714a5e371760eb5c88b91078aa4" - integrity sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig== - dependencies: - camelcase-keys "^4.0.0" - decamelize-keys "^1.0.0" - loud-rejection "^1.0.0" - minimist-options "^3.0.1" - normalize-package-data "^2.3.4" - read-pkg-up "^3.0.0" - redent "^2.0.0" - trim-newlines "^2.0.0" - yargs-parser "^10.0.0" - -meow@^7.0.0: - version "7.1.1" - resolved "https://registry.npmjs.org/meow/-/meow-7.1.1.tgz#7c01595e3d337fcb0ec4e8eed1666ea95903d306" - integrity sha512-GWHvA5QOcS412WCo8vwKDlTelGLsCGBVevQB5Kva961rmNfun0PCbv5+xta2kUMFJyR8/oWnn7ddeKdosbAPbA== +meow@^6.0.0: + version "6.1.1" + resolved "https://registry.npmjs.org/meow/-/meow-6.1.1.tgz#1ad64c4b76b2a24dfb2f635fddcadf320d251467" + integrity sha512-3YffViIt2QWgTy6Pale5QpopX/IvU3LPL03jOTqp6pGj3VjesdO/U8CuHMKpnQr4shCNCM5fd5XFFvIIl6JBHg== dependencies: "@types/minimist" "^1.2.0" camelcase-keys "^6.2.2" decamelize-keys "^1.1.0" hard-rejection "^2.1.0" - minimist-options "4.1.0" + minimist-options "^4.0.2" normalize-package-data "^2.5.0" read-pkg-up "^7.0.1" redent "^3.0.0" @@ -10797,6 +11917,23 @@ meow@^7.0.0: type-fest "^0.13.1" yargs-parser "^18.1.3" +meow@^8.0.0: + version "8.1.2" + resolved "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz#bcbe45bda0ee1729d350c03cffc8395a36c4e897" + integrity sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q== + dependencies: + "@types/minimist" "^1.2.0" + camelcase-keys "^6.2.2" + decamelize-keys "^1.1.0" + hard-rejection "^2.1.0" + minimist-options "4.1.0" + normalize-package-data "^3.0.0" + read-pkg-up "^7.0.1" + redent "^3.0.0" + trim-newlines "^3.0.0" + type-fest "^0.18.0" + yargs-parser "^20.2.3" + merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" @@ -10842,12 +11979,12 @@ micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: to-regex "^3.0.2" micromatch@^4.0.0, micromatch@^4.0.2: - version "4.0.2" - resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" - integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== + version "4.0.4" + resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" + integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== dependencies: braces "^3.0.1" - picomatch "^2.0.5" + picomatch "^2.2.3" miller-rabin@^4.0.0: version "4.0.1" @@ -10857,32 +11994,27 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime-db@1.44.0: - version "1.44.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" - integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== - -"mime-db@>= 1.43.0 < 2": - version "1.45.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea" - integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w== +mime-db@1.47.0, "mime-db@>= 1.43.0 < 2": + version "1.47.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz#8cb313e59965d3c05cfbf898915a267af46a335c" + integrity sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw== -mime-types@^2.0.8, mime-types@^2.1.12, mime-types@^2.1.16, mime-types@~2.1.19, mime-types@~2.1.24: - version "2.1.27" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" - integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== +mime-types@^2.0.8, mime-types@^2.1.12, mime-types@^2.1.16, mime-types@^2.1.27, mime-types@~2.1.19, mime-types@~2.1.24: + version "2.1.30" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz#6e7be8b4c479825f85ed6326695db73f9305d62d" + integrity sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg== dependencies: - mime-db "1.44.0" + mime-db "1.47.0" mime@1.6.0, mime@^1.6.0: version "1.6.0" resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mime@^2.2.0, mime@^2.4.4, mime@^2.4.5: - version "2.4.6" - resolved "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1" - integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA== +mime@^2.2.0, mime@^2.4.4, mime@^2.4.5, mime@^2.5.2: + version "2.5.2" + resolved "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" + integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg== mimic-fn@^1.0.0: version "1.2.0" @@ -10909,7 +12041,7 @@ minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: resolved "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: +minimalistic-crypto-utils@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= @@ -10921,7 +12053,7 @@ minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.3, minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -minimist-options@4.1.0: +minimist-options@4.1.0, minimist-options@^4.0.2: version "4.1.0" resolved "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A== @@ -10938,15 +12070,54 @@ minimist-options@^3.0.1: arrify "^1.0.1" is-plain-obj "^1.1.0" -minimist@1.x, minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5: +minimist@1.x, minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5: version "1.2.5" resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -minimist@~0.0.1: - version "0.0.10" - resolved "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" - integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8= +minimist@~0.0.1: + version "0.0.10" + resolved "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" + integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8= + +minipass-collect@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" + integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== + dependencies: + minipass "^3.0.0" + +minipass-fetch@^1.3.2: + version "1.3.3" + resolved "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.3.3.tgz#34c7cea038c817a8658461bf35174551dce17a0a" + integrity sha512-akCrLDWfbdAWkMLBxJEeWTdNsjML+dt5YgOI4gJ53vuO0vrmYQkUPxa6j6V65s9CcePIr2SSWqjT2EcrNseryQ== + dependencies: + minipass "^3.1.0" + minipass-sized "^1.0.3" + minizlib "^2.0.0" + optionalDependencies: + encoding "^0.1.12" + +minipass-flush@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" + integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== + dependencies: + minipass "^3.0.0" + +minipass-pipeline@^1.2.2, minipass-pipeline@^1.2.4: + version "1.2.4" + resolved "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" + integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== + dependencies: + minipass "^3.0.0" + +minipass-sized@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz#70ee5a7c5052070afacfbc22977ea79def353b70" + integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g== + dependencies: + minipass "^3.0.0" minipass@^2.3.5, minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: version "2.9.0" @@ -10956,7 +12127,7 @@ minipass@^2.3.5, minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: safe-buffer "^5.1.2" yallist "^3.0.0" -minipass@^3.0.0: +minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: version "3.1.3" resolved "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd" integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg== @@ -10970,7 +12141,7 @@ minizlib@^1.2.1: dependencies: minipass "^2.9.0" -minizlib@^2.1.0, minizlib@^2.1.1: +minizlib@^2.0.0, minizlib@^2.1.0, minizlib@^2.1.1: version "2.1.2" resolved "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== @@ -11002,10 +12173,10 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mixme@^0.3.1: - version "0.3.5" - resolved "https://registry.npmjs.org/mixme/-/mixme-0.3.5.tgz#304652cdaf24a3df0487205e61ac6162c6906ddd" - integrity sha512-SyV9uPETRig5ZmYev0ANfiGeB+g6N2EnqqEfBbCGmmJ6MgZ3E4qv5aPbnHVdZ60KAHHXV+T3sXopdrnIXQdmjQ== +mixme@^0.5.0: + version "0.5.1" + resolved "https://registry.npmjs.org/mixme/-/mixme-0.5.1.tgz#b3da79a563b2da46efba9519830059e4c2a9e40f" + integrity sha512-NaeZIckeBFT7i0XBEpGyFcAE0/bLcQ9MHErTpnU3bLWVE5WZbxG5Y3fDsMxYGifTo5khDA42OquXCC2ngKJB+g== mkdirp-promise@^5.0.1: version "5.0.1" @@ -11026,6 +12197,19 @@ mkdirp@0.5.5, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkd dependencies: minimist "^1.2.5" +mocha-chai-jest-snapshot@1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/mocha-chai-jest-snapshot/-/mocha-chai-jest-snapshot-1.1.1.tgz#7e49f20d0c12e6792d7f7da2e4ee0c38950571cc" + integrity sha512-52GyyqRD/cI8AIiMTQzizKmLeKQvZRBLwWOHwlwytUKPnWSNpRy1MkIcJIlgUrs5ocrjujOWwtKwyVkLWT/DFQ== + dependencies: + "@jest/test-result" "^26.5.2" + chalk "^4.1.0" + find-package-json "^1.2.0" + jest-snapshot "^26.5.2" + jest-util "^26.5.2" + slash "^3.0.0" + yargs "^16.0.3" + mocha@7.2.0: version "7.2.0" resolved "https://registry.npmjs.org/mocha/-/mocha-7.2.0.tgz#01cc227b00d875ab1eed03a75106689cfed5a604" @@ -11056,15 +12240,46 @@ mocha@7.2.0: yargs-parser "13.1.2" yargs-unparser "1.6.0" +mocha@8.4.0: + version "8.4.0" + resolved "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz#677be88bf15980a3cae03a73e10a0fc3997f0cff" + integrity sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ== + dependencies: + "@ungap/promise-all-settled" "1.1.2" + ansi-colors "4.1.1" + browser-stdout "1.3.1" + chokidar "3.5.1" + debug "4.3.1" + diff "5.0.0" + escape-string-regexp "4.0.0" + find-up "5.0.0" + glob "7.1.6" + growl "1.10.5" + he "1.2.0" + js-yaml "4.0.0" + log-symbols "4.0.0" + minimatch "3.0.4" + ms "2.1.3" + nanoid "3.1.20" + serialize-javascript "5.0.1" + strip-json-comments "3.1.1" + supports-color "8.1.1" + which "2.0.2" + wide-align "1.1.3" + workerpool "6.1.0" + yargs "16.2.0" + yargs-parser "20.2.4" + yargs-unparser "2.0.0" + modify-values@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== -moment@2.27.0: - version "2.27.0" - resolved "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz#8bff4e3e26a236220dfe3e36de756b6ebaa0105d" - integrity sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ== +moment@2.29.1: + version "2.29.1" + resolved "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" + integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== morgan@^1.10.0, morgan@^1.8.2: version "1.10.0" @@ -11099,11 +12314,16 @@ ms@2.1.1: resolved "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== -ms@2.1.2, ms@^2.0.0, ms@^2.1.1: +ms@2.1.2: version "2.1.2" resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +ms@2.1.3, ms@^2.0.0, ms@^2.1.1: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + multimatch@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/multimatch/-/multimatch-3.0.0.tgz#0e2534cc6bc238d9ab67e1b9cd5fcd85a6dbf70b" @@ -11149,10 +12369,15 @@ mz@2.7.0, mz@^2.5.0: object-assign "^4.0.1" thenify-all "^1.0.0" -nan@^2.12.1, nan@^2.14.1: - version "2.14.1" - resolved "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" - integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== +nan@^2.12.1, nan@^2.14.2: + version "2.14.2" + resolved "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" + integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== + +nanoid@3.1.20: + version "3.1.20" + resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788" + integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw== nanomatch@^1.2.9: version "1.2.13" @@ -11187,9 +12412,9 @@ natural-compare@^1.4.0: integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= needle@^2.2.1: - version "2.5.2" - resolved "https://registry.npmjs.org/needle/-/needle-2.5.2.tgz#cf1a8fce382b5a280108bba90a14993c00e4010a" - integrity sha512-LbRIwS9BfkPvNwNHlsA41Q29kL2L/6VaOJ0qisM5lLWsTV3nP15abO5ITL6L81zqFhzjRKDAYjpcBcwM0AVvLQ== + version "2.6.0" + resolved "https://registry.npmjs.org/needle/-/needle-2.6.0.tgz#24dbb55f2509e2324b4a99d61f413982013ccdbe" + integrity sha512-KKYdza4heMsEfSWD7VPUIz3zX2XDwOyX2d+geb4vrERZMT5RMU6ujjaD+I5Yr54uZxQ2w6XRTAhHBbSCyovZBg== dependencies: debug "^3.2.6" iconv-lite "^0.4.4" @@ -11200,12 +12425,17 @@ negotiator@0.6.2: resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== -neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1: +neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1, neo-async@^2.6.2: version "2.6.2" resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== -next-tick@1: +netmask@^2.0.1: + version "2.0.2" + resolved "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz#8b01a07644065d536383835823bc52004ebac5e7" + integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg== + +next-tick@1, next-tick@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== @@ -11221,9 +12451,9 @@ nice-try@^1.0.4: integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== nise@^4.0.4: - version "4.0.4" - resolved "https://registry.npmjs.org/nise/-/nise-4.0.4.tgz#d73dea3e5731e6561992b8f570be9e363c4512dd" - integrity sha512-bTTRUNlemx6deJa+ZyoCUTRvH3liK5+N6VQZ4NIw90AgDXY6iPnsqplNFf6STcj+ePk0H/xqxnP75Lr0J0Fq3A== + version "4.1.0" + resolved "https://registry.npmjs.org/nise/-/nise-4.1.0.tgz#8fb75a26e90b99202fa1e63f448f58efbcdedaf6" + integrity sha512-eQMEmGN/8arp0xsvGoQ+B1qvSkR73B1nWSCh7nOt5neMCtwcQVYQGdzQMhcNscktTsWB54xnlSQFzOAPJD8nXA== dependencies: "@sinonjs/commons" "^1.7.0" "@sinonjs/fake-timers" "^6.0.0" @@ -11255,7 +12485,7 @@ node-fetch-npm@^2.0.2: json-parse-better-errors "^1.0.0" safe-buffer "^5.1.1" -node-fetch@2.6.1, node-fetch@^2.3.0, node-fetch@^2.5.0, node-fetch@^2.6.0, node-fetch@^2.6.1: +node-fetch@2.6.1, node-fetch@^2.3.0, node-fetch@^2.5.0, node-fetch@^2.6.1: version "2.6.1" resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== @@ -11265,11 +12495,6 @@ node-forge@^0.10.0: resolved "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== -node-forge@^0.9.0: - version "0.9.2" - resolved "https://registry.npmjs.org/node-forge/-/node-forge-0.9.2.tgz#b35a44c28889b2ea55cabf8c79e3563f9676190a" - integrity sha512-naKSScof4Wn+aoHU6HBsifh92Zeicm1GDQKd1vp3Y/kOi8ub0DozCa9KpvYNCXslFHYRmLNiqRopGdTGwNLpNw== - node-gyp@^5.0.2: version "5.1.1" resolved "https://registry.npmjs.org/node-gyp/-/node-gyp-5.1.1.tgz#eb915f7b631c937d282e33aed44cb7a025f62a3e" @@ -11287,22 +12512,27 @@ node-gyp@^5.0.2: tar "^4.4.12" which "^1.3.1" -node-gyp@^7.0.0: - version "7.1.0" - resolved "https://registry.npmjs.org/node-gyp/-/node-gyp-7.1.0.tgz#cb8aed7ab772e73ad592ae0c71b0e3741099fe39" - integrity sha512-rjlHQlnl1dqiDZxZYiKqQdrjias7V+81OVR5PTzZioCBtWkNdrKy06M05HLKxy/pcKikKRCabeDRoZaEc6nIjw== +node-gyp@^8.0.0: + version "8.0.0" + resolved "https://registry.npmjs.org/node-gyp/-/node-gyp-8.0.0.tgz#225af2b06b8419ae81f924bf25ae4c167f6378a5" + integrity sha512-Jod6NxyWtcwrpAQe0O/aXOpC5QfncotgtG73dg65z6VW/C6g/G4jiajXQUBIJ8pk/VfM6mBYE9BN/HvudTunUQ== dependencies: env-paths "^2.2.0" glob "^7.1.4" - graceful-fs "^4.2.3" - nopt "^4.0.3" + graceful-fs "^4.2.6" + make-fetch-happen "^8.0.14" + nopt "^5.0.0" npmlog "^4.1.2" - request "^2.88.2" - rimraf "^2.6.3" - semver "^7.3.2" - tar "^6.0.1" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.0" which "^2.0.2" +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= + node-libs-browser@^2.2.1: version "2.2.1" resolved "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" @@ -11362,10 +12592,10 @@ node-preload@^0.2.1: dependencies: process-on-spawn "^1.0.0" -node-releases@^1.1.61: - version "1.1.61" - resolved "https://registry.npmjs.org/node-releases/-/node-releases-1.1.61.tgz#707b0fca9ce4e11783612ba4a2fcba09047af16e" - integrity sha512-DD5vebQLg8jLCOzwupn954fbIiZht05DAZs0k2u8NStSe6h9XdsuIQL8hSRKYiU8WUQRznmSDrKGbv3ObOmC7g== +node-releases@^1.1.71: + version "1.1.72" + resolved "https://registry.npmjs.org/node-releases/-/node-releases-1.1.72.tgz#14802ab6b1039a79a0c7d662b610a5bbd76eacbe" + integrity sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw== node-status-codes@^1.0.0: version "1.0.0" @@ -11382,7 +12612,7 @@ noop-fn@^1.0.0: resolved "https://registry.npmjs.org/noop-fn/-/noop-fn-1.0.0.tgz#5f33d47f13d2150df93e0cb036699e982f78ffbf" integrity sha1-XzPUfxPSFQ35PgywNmmemC94/78= -nopt@^4.0.1, nopt@^4.0.3: +nopt@^4.0.1: version "4.0.3" resolved "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== @@ -11390,6 +12620,13 @@ nopt@^4.0.1, nopt@^4.0.3: abbrev "1" osenv "^0.1.4" +nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== + dependencies: + abbrev "1" + normalize-package-data@^2.0.0, normalize-package-data@^2.3.0, normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.3.5, normalize-package-data@^2.4.0, normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -11400,6 +12637,16 @@ normalize-package-data@^2.0.0, normalize-package-data@^2.3.0, normalize-package- semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" +normalize-package-data@^3.0.0: + version "3.0.2" + resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.2.tgz#cae5c410ae2434f9a6c1baa65d5bc3b9366c8699" + integrity sha512-6CdZocmfGaKnIHPVFhJJZ3GuR8SsLKvDANFp47Jmy51aKIr8akjAWTSxtpI+MBgBFdSMRyo4hMpDlT6dTffgZg== + dependencies: + hosted-git-info "^4.0.1" + resolve "^1.20.0" + semver "^7.3.4" + validate-npm-package-license "^3.0.1" + normalize-path@^2.0.1, normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" @@ -11430,9 +12677,9 @@ now-and-later@^2.0.0: once "^1.3.2" npm-bundled@^1.0.1: - version "1.1.1" - resolved "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b" - integrity sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA== + version "1.1.2" + resolved "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz#944c78789bd739035b70baa2ca5cc32b8d860bc1" + integrity sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ== dependencies: npm-normalize-package-bin "^1.0.1" @@ -11570,11 +12817,6 @@ object-assign@4.X, object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= -object-component@0.0.3: - version "0.0.3" - resolved "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291" - integrity sha1-8MaapQ78lbhmwYb0AKM3acsvEpE= - object-copy@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" @@ -11584,18 +12826,23 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-inspect@^1.8.0: - version "1.8.0" - resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" - integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== +object-hash@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/object-hash/-/object-hash-2.1.1.tgz#9447d0279b4fcf80cff3259bf66a1dc73afabe09" + integrity sha512-VOJmgmS+7wvXf8CjbQmimtCnEx3IAoLxI3fp2fbWehxrWBcAQFbk+vcwb6vzR0VZv/eNCJ/27j151ZTwqW/JeQ== + +object-inspect@^1.10.3, object-inspect@^1.9.0: + version "1.10.3" + resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz#c2aa7d2d09f50c99375704f7a0adf24c5782d369" + integrity sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw== object-is@^1.0.1: - version "1.1.3" - resolved "https://registry.npmjs.org/object-is/-/object-is-1.1.3.tgz#2e3b9e65560137455ee3bd62aec4d90a2ea1cc81" - integrity sha512-teyqLvFWzLkq5B9ki8FVWA902UER2qkxmdA4nLf+wjOLAWgxzCWZNCxpDq9MvE8MmhWNr+I8w3BN49Vx36Y6Xg== + version "1.1.5" + resolved "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" + integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.18.0-next.1" object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" @@ -11619,13 +12866,13 @@ object.assign@4.1.0: has-symbols "^1.0.0" object-keys "^1.0.11" -object.assign@^4.0.4, object.assign@^4.1.0, object.assign@^4.1.1: - version "4.1.1" - resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz#303867a666cdd41936ecdedfb1f8f3e32a478cdd" - integrity sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA== +object.assign@^4.0.4, object.assign@^4.1.0, object.assign@^4.1.2: + version "4.1.2" + resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== dependencies: + call-bind "^1.0.0" define-properties "^1.1.3" - es-abstract "^1.18.0-next.0" has-symbols "^1.0.1" object-keys "^1.1.1" @@ -11640,12 +12887,13 @@ object.defaults@^1.0.0, object.defaults@^1.1.0: isobject "^3.0.0" object.getownpropertydescriptors@^2.0.3: - version "2.1.0" - resolved "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" - integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== + version "2.1.2" + resolved "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz#1bd63aeacf0d5d2d2f31b5e393b03a7c601a23f7" + integrity sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" + es-abstract "^1.18.0-next.2" object.map@^1.0.0: version "1.0.1" @@ -11670,15 +12918,14 @@ object.reduce@^1.0.0: for-own "^1.0.0" make-iterator "^1.0.0" -object.values@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" - integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== +object.values@^1.1.3: + version "1.1.4" + resolved "https://registry.npmjs.org/object.values/-/object.values-1.1.4.tgz#0d273762833e816b693a637d30073e7051535b30" + integrity sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - function-bind "^1.1.1" - has "^1.0.3" + es-abstract "^1.18.2" octokit-pagination-methods@^1.1.0: version "1.1.0" @@ -11732,10 +12979,12 @@ open@^6.3.0: dependencies: is-wsl "^1.1.0" -openapi3-ts@^1.2.0: - version "1.4.0" - resolved "https://registry.npmjs.org/openapi3-ts/-/openapi3-ts-1.4.0.tgz#679d5a24be0efc36f5de4fc2c4b8513663e16f65" - integrity sha512-8DmE2oKayvSkIR3XSZ4+pRliBsx19bSNeIzkTPswY8r4wvjX86bMxsORdqwAwMxE8PefOcSAT2auvi/0TZe9yA== +openapi3-ts@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/openapi3-ts/-/openapi3-ts-2.0.1.tgz#b270aecea09e924f1886bc02a72608fca5a98d85" + integrity sha512-v6X3iwddhi276siej96jHGIqTx3wzVfMTmpGJEQDt7GPI7pI6sywItURLzpEci21SBRpPN/aOWSF5mVfFVNmcg== + dependencies: + yaml "^1.10.0" opencollective-postinstall@^2.0.2: version "2.0.3" @@ -11755,6 +13004,18 @@ optimist@~0.6.0: minimist "~0.0.1" wordwrap "~0.0.2" +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + optionator@^0.9.1: version "0.9.1" resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" @@ -11767,17 +13028,18 @@ optionator@^0.9.1: type-check "^0.4.0" word-wrap "^1.2.3" -ora@5.1.0: - version "5.1.0" - resolved "https://registry.npmjs.org/ora/-/ora-5.1.0.tgz#b188cf8cd2d4d9b13fd25383bc3e5cba352c94f8" - integrity sha512-9tXIMPvjZ7hPTbk8DFq1f7Kow/HU/pQYB60JbNq+QnGwcyhWVZaQ4hM9zQDEsPxw/muLpgiHSaumUZxCAmod/w== +ora@5.4.1, ora@^5.3.0: + version "5.4.1" + resolved "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" + integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== dependencies: + bl "^4.1.0" chalk "^4.1.0" cli-cursor "^3.1.0" - cli-spinners "^2.4.0" + cli-spinners "^2.5.0" is-interactive "^1.0.0" - log-symbols "^4.0.0" - mute-stream "0.0.8" + is-unicode-supported "^0.1.0" + log-symbols "^4.1.0" strip-ansi "^6.0.0" wcwidth "^1.0.1" @@ -11879,12 +13141,12 @@ p-limit@^2.0.0, p-limit@^2.2.0: dependencies: p-try "^2.0.0" -p-limit@^3.0.1, p-limit@^3.0.2: - version "3.0.2" - resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz#1664e010af3cadc681baafd3e2a437be7b0fb5fe" - integrity sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg== +p-limit@^3.0.1, p-limit@^3.0.2, p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== dependencies: - p-try "^2.0.0" + yocto-queue "^0.1.0" p-locate@^2.0.0: version "2.0.0" @@ -11974,6 +13236,30 @@ p-waterfall@^1.0.0: dependencies: p-reduce "^1.0.0" +pac-proxy-agent@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-4.1.0.tgz#66883eeabadc915fc5e95457324cb0f0ac78defb" + integrity sha512-ejNgYm2HTXSIYX9eFlkvqFp8hyJ374uDf0Zq5YUAifiSh1D6fo+iBivQZirGvVv8dCYUsLhmLBRhlAYvBKI5+Q== + dependencies: + "@tootallnate/once" "1" + agent-base "6" + debug "4" + get-uri "3" + http-proxy-agent "^4.0.1" + https-proxy-agent "5" + pac-resolver "^4.1.0" + raw-body "^2.2.0" + socks-proxy-agent "5" + +pac-resolver@^4.1.0: + version "4.2.0" + resolved "https://registry.npmjs.org/pac-resolver/-/pac-resolver-4.2.0.tgz#b82bcb9992d48166920bc83c7542abb454bd9bdd" + integrity sha512-rPACZdUyuxT5Io/gFKUeeZFfE5T7ve7cAkE5TUZRRfuKP0u5Hocwe48X7ZEm6mYB+bTB0Qf+xlVlA/RM/i6RCQ== + dependencies: + degenerator "^2.2.0" + ip "^1.1.5" + netmask "^2.0.1" + package-hash@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz#3537f654665ec3cc38827387fc904c163c54f506" @@ -11994,10 +13280,10 @@ package-json@^6.3.0: registry-url "^5.0.0" semver "^6.2.0" -package-name-regex@1.0.8: - version "1.0.8" - resolved "https://registry.npmjs.org/package-name-regex/-/package-name-regex-1.0.8.tgz#4b8388cace6cc415f462ab4a269583d9a2d9f131" - integrity sha512-g3vB2J62dLqf4m50VM4tJUC4sixw3JB+Igd0cF3P/gJhAvmvsmFEV2eWZTeLbwfkKEWTf3+gwQ2C6JFFRxWHEQ== +package-name-regex@2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/package-name-regex/-/package-name-regex-2.0.1.tgz#69e5e5412a7d5367d3cb965da6c4e480e5e9ffa4" + integrity sha512-U+K6/cuwHwr/8pUQrpNpKOIFSdS/EluTRSmtn92mug1UiPcff4t9AHs36e2xXJtpEtRfbg+JOj3Y/GLX+mzT6w== pad@^3.2.0: version "3.2.0" @@ -12068,9 +13354,9 @@ parse-json@^4.0.0: json-parse-better-errors "^1.0.1" parse-json@^5.0.0: - version "5.1.0" - resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz#f96088cdf24a8faa9aea9a009f2d9d942c999646" - integrity sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ== + version "5.2.0" + resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== dependencies: "@babel/code-frame" "^7.0.0" error-ex "^1.3.1" @@ -12088,12 +13374,14 @@ parse-passwd@^1.0.0: integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= parse-path@^4.0.0: - version "4.0.2" - resolved "https://registry.npmjs.org/parse-path/-/parse-path-4.0.2.tgz#ef14f0d3d77bae8dd4bc66563a4c151aac9e65aa" - integrity sha512-HSqVz6iuXSiL8C1ku5Gl1Z5cwDd9Wo0q8CoffdAghP6bz8pJa1tcMC+m4N+z6VAS8QdksnIGq1TB6EgR4vPR6w== + version "4.0.3" + resolved "https://registry.npmjs.org/parse-path/-/parse-path-4.0.3.tgz#82d81ec3e071dcc4ab49aa9f2c9c0b8966bb22bf" + integrity sha512-9Cepbp2asKnWTJ9x2kpw6Fe8y9JDbqwahGCTvklzd/cEq5C5JC59x2Xb0Kx+x0QZ8bvNquGO8/BWP0cwBHzSAA== dependencies: is-ssh "^1.3.0" protocols "^1.4.0" + qs "^6.9.4" + query-string "^6.13.8" parse-url@^5.0.0: version "5.0.2" @@ -12105,25 +13393,11 @@ parse-url@^5.0.0: parse-path "^4.0.0" protocols "^1.4.0" -parseqs@0.0.5: - version "0.0.5" - resolved "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d" - integrity sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0= - dependencies: - better-assert "~1.0.0" - parseqs@0.0.6: version "0.0.6" resolved "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz#8e4bb5a19d1cdc844a08ac974d34e273afa670d5" integrity sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w== -parseuri@0.0.5: - version "0.0.5" - resolved "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a" - integrity sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo= - dependencies: - better-assert "~1.0.0" - parseuri@0.0.6: version "0.0.6" resolved "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz#e1496e829e3ac2ff47f39a4dd044b32823c4a25a" @@ -12213,7 +13487,7 @@ path-to-regexp@0.1.7: resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= -path-to-regexp@^1.7.0: +path-to-regexp@^1.7.0, path-to-regexp@^1.8.0: version "1.8.0" resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== @@ -12229,13 +13503,6 @@ path-type@^1.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" -path-type@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" - integrity sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM= - dependencies: - pify "^2.0.0" - path-type@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" @@ -12248,15 +13515,15 @@ path-type@^4.0.0: resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -pathval@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" - integrity sha1-uULm1L3mUwBe9rcTYd74cn0GReA= +pathval@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" + integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== pbkdf2@^3.0.3: - version "3.1.1" - resolved "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz#cb8724b0fada984596856d1a6ebafd3584654b94" - integrity sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg== + version "3.1.2" + resolved "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" + integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== dependencies: create-hash "^1.1.2" create-hmac "^1.1.4" @@ -12274,10 +13541,10 @@ performance-now@^2.1.0: resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1, picomatch@^2.2.2: - version "2.2.2" - resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" - integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3: + version "2.2.3" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz#465547f359ccc206d3c48e46a1bcb89bf7ee619d" + integrity sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg== pidtree@^0.3.0: version "0.3.1" @@ -12332,6 +13599,13 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f" + integrity sha1-yBmscoBZpGHKscOImivjxJoATX8= + dependencies: + find-up "^2.1.0" + please-upgrade-node@^3.2.0: version "3.2.0" resolved "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" @@ -12339,15 +13613,6 @@ please-upgrade-node@^3.2.0: dependencies: semver-compare "^1.0.0" -plist@^3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/plist/-/plist-3.0.1.tgz#a9b931d17c304e8912ef0ba3bdd6182baf2e1f8c" - integrity sha512-GpgvHHocGRyQm74b6FWEZZVRroHKE1I0/BTjAmySaohK+cUn+hZpbqXkc3KWgW3gQYkqcQej35FohcT0FRlkRQ== - dependencies: - base64-js "^1.2.3" - xmlbuilder "^9.0.7" - xmldom "0.1.x" - plugin-error@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz#77016bd8919d0ac377fdcdd0322328953ca5781c" @@ -12372,10 +13637,19 @@ posix-character-classes@^0.1.0: resolved "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= +postcss@^7.0.16: + version "7.0.35" + resolved "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz#d2be00b998f7f211d8a276974079f2e92b970e24" + integrity sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.1.0" + preferred-pm@^3.0.0: - version "3.0.2" - resolved "https://registry.npmjs.org/preferred-pm/-/preferred-pm-3.0.2.tgz#bbdbef1014e34a7490349bf70d6d244b8d57a5e1" - integrity sha512-yGIxyBkK/OWOppgCXfOeOXOeNrddyK1DzqS6XpOokRZb2ogXTpHRhKDTO7d0pjF/2p2sV9pEkKL4e0tNZI1y2A== + version "3.0.3" + resolved "https://registry.npmjs.org/preferred-pm/-/preferred-pm-3.0.3.tgz#1b6338000371e3edbce52ef2e4f65eb2e73586d6" + integrity sha512-+wZgbxNES/KlJs9q40F/1sfOd/j7f1O9JaHcW5Dsn3aUUOZg3L2bjpVUcKV2jvtElYfoTuQiNeMfQJ4kwUAhCQ== dependencies: find-up "^5.0.0" find-yarn-workspace-root2 "1.2.16" @@ -12387,6 +13661,11 @@ prelude-ls@^1.2.1: resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + prepend-http@^1.0.1: version "1.0.4" resolved "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" @@ -12397,21 +13676,36 @@ prepend-http@^2.0.0: resolved "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= -prettier@2.1.2: - version "2.1.2" - resolved "https://registry.npmjs.org/prettier/-/prettier-2.1.2.tgz#3050700dae2e4c8b67c4c3f666cdb8af405e1ce5" - integrity sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg== +prettier@2.3.1: + version "2.3.1" + resolved "https://registry.npmjs.org/prettier/-/prettier-2.3.1.tgz#76903c3f8c4449bc9ac597acefa24dc5ad4cbea6" + integrity sha512-p+vNbgpLjif/+D+DwAZAbndtRrR0md0MwfmOVN9N+2RgyACMT+7tfaRnT+WDPkqnuVwleyuBIG2XBxKDme3hPA== -prettier@^1.18.2: +prettier@^1.19.1: version "1.19.1" resolved "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== +pretty-format@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" + integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg== + dependencies: + "@jest/types" "^26.6.2" + ansi-regex "^5.0.0" + ansi-styles "^4.0.0" + react-is "^17.0.1" + pretty-hrtime@^1.0.0: version "1.0.3" resolved "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE= +printj@~1.1.0: + version "1.1.2" + resolved "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222" + integrity sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ== + process-nextick-args@^2.0.0, process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -12449,10 +13743,10 @@ promise-inflight@^1.0.1: resolved "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= -promise-polyfill@8.1.3: - version "8.1.3" - resolved "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz#8c99b3cf53f3a91c68226ffde7bde81d7f904116" - integrity sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g== +promise-polyfill@8.2.0: + version "8.2.0" + resolved "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.2.0.tgz#367394726da7561457aba2133c9ceefbd6267da0" + integrity sha512-k/TC0mIcPVF6yHhUvwAp7cvL6I2fFV7TzF1DuGPI8mBh4QQazf36xCKEHKTZKRysEoTQoQdKyP25J8MPJp7j5g== promise-polyfill@^6.0.1: version "6.1.0" @@ -12467,6 +13761,14 @@ promise-retry@^1.1.1: err-code "^1.0.0" retry "^0.10.0" +promise-retry@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" + integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== + dependencies: + err-code "^2.0.2" + retry "^0.12.0" + promzard@^0.3.0: version "0.3.0" resolved "https://registry.npmjs.org/promzard/-/promzard-0.3.0.tgz#26a5d6ee8c7dee4cb12208305acfb93ba382a9ee" @@ -12479,10 +13781,10 @@ proto-list@~1.2.1: resolved "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= -protobufjs@6.10.1, protobufjs@^6.8.1, protobufjs@^6.8.6, protobufjs@^6.8.8, protobufjs@^6.8.9, protobufjs@^6.9.0: - version "6.10.1" - resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.1.tgz#e6a484dd8f04b29629e9053344e3970cccf13cd2" - integrity sha512-pb8kTchL+1Ceg4lFd5XUpK8PdWacbvV5SK2ULH2ebrYtl4GjJmS24m6CKME67jzV53tbJxHlnNOSqQHbTsR9JQ== +protobufjs@6.11.2, protobufjs@^6.10.0, protobufjs@^6.10.2, protobufjs@^6.8.6: + version "6.11.2" + resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.2.tgz#de39fabd4ed32beaa08e9bb1e30d08544c1edf8b" + integrity sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw== dependencies: "@protobufjs/aspromise" "^1.1.2" "@protobufjs/base64" "^1.1.2" @@ -12495,7 +13797,7 @@ protobufjs@6.10.1, protobufjs@^6.8.1, protobufjs@^6.8.6, protobufjs@^6.8.8, prot "@protobufjs/pool" "^1.1.0" "@protobufjs/utf8" "^1.1.0" "@types/long" "^4.0.1" - "@types/node" "^13.7.0" + "@types/node" ">=13.7.0" long "^4.0.0" protocols@^1.1.0, protocols@^1.4.0: @@ -12539,17 +13841,36 @@ proxy-addr@~2.0.5: forwarded "~0.1.2" ipaddr.js "1.9.1" +proxy-agent@^4.0.0: + version "4.0.1" + resolved "https://registry.npmjs.org/proxy-agent/-/proxy-agent-4.0.1.tgz#326c3250776c7044cd19655ccbfadf2e065a045c" + integrity sha512-ODnQnW2jc/FUVwHHuaZEfN5otg/fMbvMxz9nMSUQfJ9JU7q2SZvSULSsjLloVgJOiv9yhc8GlNMKc4GkFmcVEA== + dependencies: + agent-base "^6.0.0" + debug "4" + http-proxy-agent "^4.0.0" + https-proxy-agent "^5.0.0" + lru-cache "^5.1.1" + pac-proxy-agent "^4.1.0" + proxy-from-env "^1.0.0" + socks-proxy-agent "^5.0.0" + +proxy-from-env@^1.0.0, proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + prr@~1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= -pseudomap@^1.0.2: +pseudomap@^1.0.1, pseudomap@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= -psl@^1.1.24, psl@^1.1.28: +psl@^1.1.28: version "1.8.0" resolved "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== @@ -12605,7 +13926,7 @@ punycode@1.3.2: resolved "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= -punycode@^1.2.4, punycode@^1.3.2, punycode@^1.4.1: +punycode@^1.2.4, punycode@^1.3.2: version "1.4.1" resolved "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= @@ -12615,10 +13936,10 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -pupa@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/pupa/-/pupa-2.0.1.tgz#dbdc9ff48ffbea4a26a069b6f9f7abb051008726" - integrity sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA== +pupa@^2.0.1, pupa@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62" + integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== dependencies: escape-goat "^2.0.0" @@ -12627,7 +13948,7 @@ q@1.4.1: resolved "https://registry.npmjs.org/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e" integrity sha1-VXBbzZPF82c1MMLCy8DCs63cKG4= -q@^1.4.1, q@^1.5.0, q@^1.5.1: +q@^1.4.1, q@^1.5.1: version "1.5.1" resolved "https://registry.npmjs.org/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= @@ -12642,21 +13963,28 @@ qs@6.7.0: resolved "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== -qs@^6.4.0, qs@^6.6.0: - version "6.9.4" - resolved "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz#9090b290d1f91728d3c22e54843ca44aea5ab687" - integrity sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ== - -qs@~6.4.0: - version "6.4.0" - resolved "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" - integrity sha1-E+JtKK1rD/qpExLNO/cI7TUecjM= +qs@^6.4.0, qs@^6.6.0, qs@^6.9.4: + version "6.10.1" + resolved "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a" + integrity sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg== + dependencies: + side-channel "^1.0.4" qs@~6.5.2: version "6.5.2" resolved "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== +query-string@^6.13.8: + version "6.14.1" + resolved "https://registry.npmjs.org/query-string/-/query-string-6.14.1.tgz#7ac2dca46da7f309449ba0f86b1fd28255b0c86a" + integrity sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw== + dependencies: + decode-uri-component "^0.2.0" + filter-obj "^1.1.0" + split-on-first "^1.0.0" + strict-uri-encode "^2.0.0" + querystring-es3@^0.2.0, querystring-es3@^0.2.1: version "0.2.1" resolved "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" @@ -12667,6 +13995,11 @@ querystring@0.2.0: resolved "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + quick-lru@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz#4360b17c61136ad38078397ff11416e186dcfbb8" @@ -12707,7 +14040,7 @@ raw-body@2.4.0: iconv-lite "0.4.24" unpipe "1.0.0" -raw-body@^2.3.3: +raw-body@^2.2.0, raw-body@^2.3.3: version "2.4.1" resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c" integrity sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA== @@ -12727,14 +14060,19 @@ rc@^1.2.7, rc@^1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" -re2@^1.15.0: - version "1.15.4" - resolved "https://registry.npmjs.org/re2/-/re2-1.15.4.tgz#2ffc3e4894fb60430393459978197648be01a0a9" - integrity sha512-7w3K+Daq/JjbX/dz5voMt7B9wlprVBQnMiypyCojAZ99kcAL+3LiJ5uBoX/u47l8eFTVq3Wj+V0pmvU+CT8tOg== +re2@^1.15.8: + version "1.16.0" + resolved "https://registry.npmjs.org/re2/-/re2-1.16.0.tgz#f311eb4865b1296123800ea8e013cec8dab25590" + integrity sha512-eizTZL2ZO0ZseLqfD4t3Qd0M3b3Nr0MBWpX81EbPMIud/1d/CSfUIx2GQK8fWiAeHoSekO5EOeFib2udTZLwYw== dependencies: - install-artifact-from-github "^1.0.2" - nan "^2.14.1" - node-gyp "^7.0.0" + install-artifact-from-github "^1.2.0" + nan "^2.14.2" + node-gyp "^8.0.0" + +react-is@^17.0.1: + version "17.0.2" + resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== read-all-stream@^3.0.0: version "3.1.0" @@ -12778,14 +14116,6 @@ read-pkg-up@^1.0.1: find-up "^1.0.0" read-pkg "^1.0.0" -read-pkg-up@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" - integrity sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4= - dependencies: - find-up "^2.0.0" - read-pkg "^2.0.0" - read-pkg-up@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" @@ -12812,15 +14142,6 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" -read-pkg@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" - integrity sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg= - dependencies: - load-json-file "^2.0.0" - normalize-package-data "^2.3.2" - path-type "^2.0.0" - read-pkg@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" @@ -12870,7 +14191,17 @@ read@1, read@~1.0.1: string_decoder "~1.1.1" util-deprecate "~1.0.1" -"readable-stream@2 || 3", readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0: +readable-stream@1.1.x: + version "1.1.14" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" + integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +"readable-stream@2 || 3", readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -12891,6 +14222,13 @@ readable-stream@~2.0.0: string_decoder "~0.10.x" util-deprecate "~1.0.1" +readdir-glob@^1.0.0: + version "1.1.1" + resolved "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.1.tgz#f0e10bb7bf7bfa7e0add8baffdc54c3f7dbee6c4" + integrity sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA== + dependencies: + minimatch "^3.0.4" + readdir-scoped-modules@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309" @@ -12917,10 +14255,10 @@ readdirp@~3.2.0: dependencies: picomatch "^2.0.4" -readdirp@~3.4.0: - version "3.4.0" - resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz#9fdccdf9e9155805449221ac645e8303ab5b9ada" - integrity sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ== +readdirp@~3.5.0: + version "3.5.0" + resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" + integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ== dependencies: picomatch "^2.2.1" @@ -12970,16 +14308,16 @@ regenerate-unicode-properties@^8.2.0: regenerate "^1.4.0" regenerate@^1.4.0: - version "1.4.1" - resolved "https://registry.npmjs.org/regenerate/-/regenerate-1.4.1.tgz#cad92ad8e6b591773485fbe05a485caf4f457e6f" - integrity sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A== + version "1.4.2" + resolved "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" + integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== regenerator-runtime@^0.11.0: version "0.11.1" resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== -regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7: +regenerator-runtime@^0.13.4: version "0.13.7" resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== @@ -12999,12 +14337,12 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" -regexpp@^3.0.0, regexpp@^3.1.0: +regexpp@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== -regexpu-core@^4.7.0: +regexpu-core@^4.7.1: version "4.7.1" resolved "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz#2dea5a9a07233298fbf0db91fa9abc4c6e0f8ad6" integrity sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ== @@ -13017,9 +14355,9 @@ regexpu-core@^4.7.0: unicode-match-property-value-ecmascript "^1.2.0" registry-auth-token@^4.0.0: - version "4.2.0" - resolved "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.0.tgz#1d37dffda72bbecd0f581e4715540213a65eb7da" - integrity sha512-P+lWzPrsgfN+UEpDS3U8AQKg/UjZX6mQSJueZj3EK+vNESoqBSpBUD3gmu4sF9lOsjXWjF11dQKUqemf3veq1w== + version "4.2.1" + resolved "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz#6d7b4006441918972ccd5fedcd41dc322c79b250" + integrity sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw== dependencies: rc "^1.2.8" @@ -13036,9 +14374,9 @@ regjsgen@^0.5.1: integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A== regjsparser@^0.6.4: - version "0.6.4" - resolved "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz#a769f8684308401a66e9b529d2436ff4d0666272" - integrity sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw== + version "0.6.9" + resolved "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.9.tgz#b489eef7c9a2ce43727627011429cf833a7183e6" + integrity sha512-ZqbNRz1SNjLAiYuwY0zoXW8Ne675IX5q+YHioAGbCw4X96Mjl2+dcX9B2ciaeyYjViDAfvIjFpQjJgLttTEERQ== dependencies: jsesc "~0.5.0" @@ -13072,9 +14410,9 @@ remove-trailing-separator@^1.0.1, remove-trailing-separator@^1.1.0: integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= repeat-element@^1.1.2: - version "1.1.3" - resolved "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" - integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== + version "1.1.4" + resolved "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9" + integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ== repeat-string@^1.6.1: version "1.6.1" @@ -13102,7 +14440,7 @@ replace-homedir@^1.0.0: is-absolute "^1.0.0" remove-trailing-separator "^1.1.0" -replacestream@^4.0.0: +replacestream@^4.0.3: version "4.0.3" resolved "https://registry.npmjs.org/replacestream/-/replacestream-4.0.3.tgz#3ee5798092be364b1cdb1484308492cb3dff2f36" integrity sha512-AC0FiLS352pBBiZhd4VXB1Ab/lh0lEgpP+GGvZqbQh8a5cmXVoTe5EX/YeTFArnp4SRGTHh1qCHu9lGs1qG8sA== @@ -13111,32 +14449,6 @@ replacestream@^4.0.0: object-assign "^4.0.1" readable-stream "^2.0.2" -request@2.88.0: - version "2.88.0" - resolved "https://registry.npmjs.org/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" - integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.0" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.4.3" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - request@2.88.2, request@^2.87.0, request@^2.88.0, request@^2.88.2: version "2.88.2" resolved "https://registry.npmjs.org/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" @@ -13168,6 +14480,11 @@ require-directory@^2.1.1: resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + require-main-filename@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" @@ -13225,7 +14542,15 @@ resolve-url@^0.2.1: resolved "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@1.17.0, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.3.2, resolve@^1.3.3, resolve@^1.4.0, resolve@~1.17.0: +resolve@1.20.0, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.3.2, resolve@^1.3.3, resolve@^1.4.0, resolve@~1.20.0: + version "1.20.0" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" + integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== + dependencies: + is-core-module "^2.2.0" + path-parse "^1.0.6" + +resolve@~1.17.0: version "1.17.0" resolved "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== @@ -13267,6 +14592,11 @@ retry-request@^4.0.0, retry-request@^4.1.1: dependencies: debug "^4.1.1" +retry@0.12.0, retry@^0.12.0: + version "0.12.0" + resolved "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= + retry@^0.10.0: version "0.10.1" resolved "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" @@ -13278,9 +14608,9 @@ reusify@^1.0.4: integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== rfdc@^1.1.4: - version "1.1.4" - resolved "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz#ba72cc1367a0ccd9cf81a870b3b58bd3ad07f8c2" - integrity sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug== + version "1.3.0" + resolved "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" + integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== rimraf@2, rimraf@^2.2.8, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@^2.7.1: version "2.7.1" @@ -13289,13 +14619,6 @@ rimraf@2, rimraf@^2.2.8, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2. dependencies: glob "^7.1.3" -rimraf@2.6.3: - version "2.6.3" - resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" - integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== - dependencies: - glob "^7.1.3" - rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -13311,27 +14634,38 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" -rollup-plugin-copy-assets@1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/rollup-plugin-copy-assets/-/rollup-plugin-copy-assets-1.1.0.tgz#017e22bef9a9f6ddc632bae60cd660e0270c7706" - integrity sha512-hfBZns2x15XC6N97jhGUiYoNQp1jZ1/mFrM3sbf3GIqGaDEcFPe7+J1H50HIMLG4nVM8YxTJOhMsrXnD4E+2NQ== +rollup-plugin-copy-assets@2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/rollup-plugin-copy-assets/-/rollup-plugin-copy-assets-2.0.3.tgz#9a9098894c3ded16d2eee8c4108055e332b5f59f" + integrity sha512-ETShhQGb9SoiwcNrvb3BhUNSGR89Jao0+XxxfzzLW1YsUzx8+rMO4z9oqWWmo6OHUmfNQRvqRj0cAyPkS9lN9w== dependencies: - fs-extra "^5.0.0" + fs-extra "^7.0.1" -rollup-plugin-license@2.2.0: - version "2.2.0" - resolved "https://registry.npmjs.org/rollup-plugin-license/-/rollup-plugin-license-2.2.0.tgz#0d19139bbe44dda500fbf15530af07c91949e348" - integrity sha512-xXb1vviEwlJMX+VGUSsglcMA/Rh9d2QzEm94awy4FlnsPqGrXoTYYGOR3UXR6gYIxiJFkr7qmkKF/NXfre/y8g== +rollup-plugin-copy@3.4.0: + version "3.4.0" + resolved "https://registry.npmjs.org/rollup-plugin-copy/-/rollup-plugin-copy-3.4.0.tgz#f1228a3ffb66ffad8606e2f3fb7ff23141ed3286" + integrity sha512-rGUmYYsYsceRJRqLVlE9FivJMxJ7X6jDlP79fmFkL8sJs7VVMSVyA2yfyL+PGyO/vJs4A87hwhgVfz61njI+uQ== + dependencies: + "@types/fs-extra" "^8.0.1" + colorette "^1.1.0" + fs-extra "^8.1.0" + globby "10.0.1" + is-plain-object "^3.0.0" + +rollup-plugin-license@2.5.0: + version "2.5.0" + resolved "https://registry.npmjs.org/rollup-plugin-license/-/rollup-plugin-license-2.5.0.tgz#9c0ba9b3562564c555b48142c420af80a9909f32" + integrity sha512-HUjGV+i1tRxi/zL4WpeNCLJZfEJBbCcDmwGJCjKBvcLDIK6VNW1JmYKjSJJOqJjNqRIvKt6/BLSQB9RwNDLtQw== dependencies: commenting "1.1.0" - glob "7.1.6" - lodash "4.17.19" + glob "7.1.7" + lodash "4.17.21" magic-string "0.25.7" mkdirp "1.0.4" - moment "2.27.0" - package-name-regex "1.0.8" + moment "2.29.1" + package-name-regex "2.0.1" spdx-expression-validate "2.0.0" - spdx-satisfies "5.0.0" + spdx-satisfies "5.0.1" rollup-plugin-replace@2.2.0: version "2.2.0" @@ -13359,16 +14693,16 @@ rollup-plugin-terser@7.0.2: serialize-javascript "^4.0.0" terser "^5.0.0" -rollup-plugin-typescript2@0.27.3: - version "0.27.3" - resolved "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.27.3.tgz#cd9455ac026d325b20c5728d2cc54a08a771b68b" - integrity sha512-gmYPIFmALj9D3Ga1ZbTZAKTXq1JKlTQBtj299DXhqYz9cL3g/AQfUvbb2UhH+Nf++cCq941W2Mv7UcrcgLzJJg== +rollup-plugin-typescript2@0.30.0: + version "0.30.0" + resolved "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.30.0.tgz#1cc99ac2309bf4b9d0a3ebdbc2002aecd56083d3" + integrity sha512-NUFszIQyhgDdhRS9ya/VEmsnpTe+GERDMmFo0Y+kf8ds51Xy57nPNGglJY+W6x1vcouA7Au7nsTgsLFj2I0PxQ== dependencies: - "@rollup/pluginutils" "^3.1.0" + "@rollup/pluginutils" "^4.1.0" find-cache-dir "^3.3.1" fs-extra "8.1.0" - resolve "1.17.0" - tslib "2.0.1" + resolve "1.20.0" + tslib "2.1.0" rollup-plugin-uglify@6.0.4: version "6.0.4" @@ -13387,12 +14721,12 @@ rollup-pluginutils@^2.6.0: dependencies: estree-walker "^0.6.1" -rollup@2.29.0: - version "2.29.0" - resolved "https://registry.npmjs.org/rollup/-/rollup-2.29.0.tgz#0c5c5968530b21ca0e32f8b94b7cd9346cfb0eec" - integrity sha512-gtU0sjxMpsVlpuAf4QXienPmUAhd6Kc7owQ4f5lypoxBW18fw2UNYZ4NssLGsri6WhUZkE/Ts3EMRebN+gNLiQ== +rollup@2.52.2: + version "2.52.2" + resolved "https://registry.npmjs.org/rollup/-/rollup-2.52.2.tgz#a7e90d10ddae3e8472c2857bd9f44b09ef34a47a" + integrity sha512-4RlFC3k2BIHlUsJ9mGd8OO+9Lm2eDF5P7+6DNQOp5sx+7N/1tFM01kELfbxlMX3MxT6owvLB1ln4S3QvvQlbUA== optionalDependencies: - fsevents "~2.1.2" + fsevents "~2.3.2" router@^1.3.1: version "1.3.5" @@ -13407,10 +14741,10 @@ router@^1.3.1: setprototypeof "1.2.0" utils-merge "1.0.1" -rsvp@^3.6.2: - version "3.6.2" - resolved "https://registry.npmjs.org/rsvp/-/rsvp-3.6.2.tgz#2e96491599a96cde1b515d5674a8f7a91452926a" - integrity sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw== +rsvp@^4.8.4, rsvp@^4.8.5: + version "4.8.5" + resolved "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" + integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== run-async@^2.2.0, run-async@^2.4.0: version "2.4.1" @@ -13418,9 +14752,11 @@ run-async@^2.2.0, run-async@^2.4.0: integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== run-parallel@^1.1.9: - version "1.1.9" - resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" - integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== + version "1.2.0" + resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" run-queue@^1.0.0, run-queue@^1.0.3: version "1.0.3" @@ -13429,13 +14765,20 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" -rxjs@6.6.3, rxjs@^6.3.3, rxjs@^6.4.0, rxjs@^6.5.1, rxjs@^6.6.0: +rxjs@6.6.3: version "6.6.3" resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552" integrity sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ== dependencies: tslib "^1.9.0" +rxjs@^6.3.3, rxjs@^6.4.0, rxjs@^6.5.1, rxjs@^6.6.6: + version "6.6.7" + resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" + integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== + dependencies: + tslib "^1.9.0" + safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -13458,7 +14801,22 @@ safe-regex@^1.1.0: resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sauce-connect-launcher@^1.2.2, sauce-connect-launcher@^1.2.7: +sane@^4.0.3: + version "4.1.0" + resolved "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded" + integrity sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA== + dependencies: + "@cnakazawa/watch" "^1.0.3" + anymatch "^2.0.0" + capture-exit "^2.0.0" + exec-sh "^0.3.2" + execa "^1.0.0" + fb-watchman "^2.0.0" + micromatch "^3.1.4" + minimist "^1.1.1" + walker "~1.0.5" + +sauce-connect-launcher@^1.2.7: version "1.3.2" resolved "https://registry.npmjs.org/sauce-connect-launcher/-/sauce-connect-launcher-1.3.2.tgz#dfc675a258550809a8eaf457eb9162b943ddbaf0" integrity sha512-wf0coUlidJ7rmeClgVVBh6Kw55/yalZCY/Un5RgjSnTXRAeGqagnTsTYpZaqC4dCtrY4myuYpOAZXCdbO7lHfQ== @@ -13469,7 +14827,7 @@ sauce-connect-launcher@^1.2.2, sauce-connect-launcher@^1.2.7: lodash "^4.16.6" rimraf "^2.5.4" -saucelabs@^1.4.0, saucelabs@^1.5.0: +saucelabs@^1.5.0: version "1.5.0" resolved "https://registry.npmjs.org/saucelabs/-/saucelabs-1.5.0.tgz#9405a73c360d449b232839919a86c396d379fd9d" integrity sha512-jlX3FGdWvYf4Q3LFfFWS1QvPg3IGCGWxIc8QBFdPTbpTJnt/v17FHXYVAn7C8sHf1yUXo2c7yIM0isDryfYtHQ== @@ -13547,14 +14905,25 @@ selenium-webdriver@3.6.0, selenium-webdriver@^3.0.1: tmp "0.0.30" xml2js "^0.4.17" -selenium-webdriver@^4.0.0-alpha.7: - version "4.0.0-alpha.7" - resolved "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.0.0-alpha.7.tgz#e3879d8457fd7ad8e4424094b7dc0540d99e6797" - integrity sha512-D4qnTsyTr91jT8f7MfN+OwY0IlU5+5FmlO5xlgRUV6hDEV8JyYx2NerdTEqDDkNq7RZDYc4VoPALk8l578RBHw== +selenium-webdriver@4.0.0-beta.1: + version "4.0.0-beta.1" + resolved "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.0.0-beta.1.tgz#db645b0d775f26e4e12235db05796a1bc1e7efda" + integrity sha512-DJ10z6Yk+ZBaLrt1CLElytQ/FOayx29ANKDtmtyW1A6kCJx3+dsc5fFMOZxwzukDniyYsC3OObT5pUAsgkjpxQ== dependencies: - jszip "^3.2.2" + jszip "^3.5.0" rimraf "^2.7.1" - tmp "0.0.30" + tmp "^0.2.1" + ws "^7.3.1" + +selenium-webdriver@^4.0.0-alpha.7, selenium-webdriver@^4.0.0-beta.2: + version "4.0.0-beta.3" + resolved "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.0.0-beta.3.tgz#8c29512a27ca9c1f95a96a9a8f488304c894390e" + integrity sha512-R0mGHpQkSKgIWiPgcKDcckh4A6aaK0KTyWxs5ieuiI7zsXQ+Kb6neph+dNoeqq3jSBGyv3ONo2w3oohoL4D/Rg== + dependencies: + jszip "^3.5.0" + rimraf "^2.7.1" + tmp "^0.2.1" + ws "^7.3.1" semver-compare@^1.0.0: version "1.0.0" @@ -13590,16 +14959,25 @@ semver@7.0.0: resolved "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== -semver@7.3.2, semver@^7.0.0, semver@^7.2.1, semver@^7.3.2, semver@~7.3.0: - version "7.3.2" - resolved "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" - integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== +semver@7.3.4: + version "7.3.4" + resolved "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" + integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== + dependencies: + lru-cache "^6.0.0" -semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: +semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^7.0.0, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@~7.3.0: + version "7.3.5" + resolved "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + send@0.17.1: version "0.17.1" resolved "https://registry.npmjs.org/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" @@ -13619,6 +14997,13 @@ send@0.17.1: range-parser "~1.2.1" statuses "~1.5.0" +serialize-javascript@5.0.1, serialize-javascript@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" + integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA== + dependencies: + randombytes "^2.1.0" + serialize-javascript@^2.1.2: version "2.1.2" resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" @@ -13729,19 +15114,28 @@ shelljs@0.8.4, shelljs@^0.8.3: interpret "^1.0.0" rechoir "^0.6.2" +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.3" resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== -simple-git@2.21.0: - version "2.21.0" - resolved "https://registry.npmjs.org/simple-git/-/simple-git-2.21.0.tgz#d25d3fdc6a139cd7f80f197541a6f9f6e9d4cbc8" - integrity sha512-rohCHmEjD/ESXFLxF4bVeqgdb4Awc65ZyyuCKl3f7BvgMbZOBa/Ye3HN/GFnvruiUOAWWNupxhz3Rz5/3vJLTg== +simple-git@2.40.0: + version "2.40.0" + resolved "https://registry.npmjs.org/simple-git/-/simple-git-2.40.0.tgz#1f9da964caa032290ec25cb06ccacc2c17d99d75" + integrity sha512-7IO/eQwrN5kvS38TTu9ljhG9tx2nn0BTqZOmqpPpp51TvE44YIvLA6fETqEVA8w/SeEfPaVv6mk7Tsk9Jns+ag== dependencies: "@kwsites/file-exists" "^1.1.1" "@kwsites/promise-deferred" "^1.1.1" - debug "^4.1.1" + debug "^4.3.1" simple-swizzle@^0.2.2: version "0.2.2" @@ -13750,20 +15144,20 @@ simple-swizzle@^0.2.2: dependencies: is-arrayish "^0.3.1" -sinon-chai@3.5.0: - version "3.5.0" - resolved "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.5.0.tgz#c9a78304b0e15befe57ef68e8a85a00553f5c60e" - integrity sha512-IifbusYiQBpUxxFJkR3wTU68xzBN0+bxCScEaKMjBvAQERg6FnTTc1F17rseLb1tjmkJ23730AXpFI0c47FgAg== +sinon-chai@3.7.0: + version "3.7.0" + resolved "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.7.0.tgz#cfb7dec1c50990ed18c153f1840721cf13139783" + integrity sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g== -sinon@9.2.0: - version "9.2.0" - resolved "https://registry.npmjs.org/sinon/-/sinon-9.2.0.tgz#1d333967e30023609f7347351ebc0dc964c0f3c9" - integrity sha512-eSNXz1XMcGEMHw08NJXSyTHIu6qTCOiN8x9ODACmZpNQpr0aXTBXBnI4xTzQzR+TEpOmLiKowGf9flCuKIzsbw== +sinon@9.2.2: + version "9.2.2" + resolved "https://registry.npmjs.org/sinon/-/sinon-9.2.2.tgz#b83cf5d43838f99cfa3644453f4c7db23e7bd535" + integrity sha512-9Owi+RisvCZpB0bdOVFfL314I6I4YoRlz6Isi4+fr8q8YQsDPoCe5UnmNtKHRThX3negz2bXHWIuiPa42vM8EQ== dependencies: "@sinonjs/commons" "^1.8.1" "@sinonjs/fake-timers" "^6.0.1" "@sinonjs/formatio" "^5.0.1" - "@sinonjs/samsam" "^5.2.0" + "@sinonjs/samsam" "^5.3.0" diff "^4.0.2" nise "^4.0.4" supports-color "^7.1.0" @@ -13783,14 +15177,14 @@ slice-ansi@0.0.4: resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" integrity sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU= -slice-ansi@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" - integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== dependencies: - ansi-styles "^3.2.0" - astral-regex "^1.0.0" - is-fullwidth-code-point "^2.0.0" + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" slide@^1.1.5, slide@^1.1.6: version "1.1.6" @@ -13853,30 +15247,27 @@ socket.io-adapter@~1.1.0: resolved "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz#ab3f0d6f66b8fc7fca3959ab5991f82221789be9" integrity sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g== -socket.io-client@2.3.0: - version "2.3.0" - resolved "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.3.0.tgz#14d5ba2e00b9bcd145ae443ab96b3f86cbcc1bb4" - integrity sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA== +socket.io-client@2.4.0: + version "2.4.0" + resolved "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.4.0.tgz#aafb5d594a3c55a34355562fc8aea22ed9119a35" + integrity sha512-M6xhnKQHuuZd4Ba9vltCLT9oa+YvTsP8j9NcEiLElfIg8KeYPyhWOes6x4t+LTAC8enQbE/995AdTem2uNyKKQ== dependencies: backo2 "1.0.2" - base64-arraybuffer "0.1.5" component-bind "1.0.0" - component-emitter "1.2.1" - debug "~4.1.0" - engine.io-client "~3.4.0" + component-emitter "~1.3.0" + debug "~3.1.0" + engine.io-client "~3.5.0" has-binary2 "~1.0.2" - has-cors "1.1.0" indexof "0.0.1" - object-component "0.0.3" - parseqs "0.0.5" - parseuri "0.0.5" + parseqs "0.0.6" + parseuri "0.0.6" socket.io-parser "~3.3.0" to-array "0.1.4" socket.io-parser@~3.3.0: - version "3.3.1" - resolved "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.1.tgz#f07d9c8cb3fb92633aa93e76d98fd3a334623199" - integrity sha512-1QLvVAe8dTz+mKmZ07Swxt+LAo4Y1ff50rlyoEx00TQmDFVQYPfcqGvIDJLGaBdhdNCecXtyKpD+EgKGcmmbuQ== + version "3.3.2" + resolved "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.2.tgz#ef872009d0adcf704f2fbe830191a14752ad50b6" + integrity sha512-FJvDBuOALxdCI9qwRrO/Rfp9yfndRtc1jSgVgV8FDraihmSP/MLGD5PEuJrNfjALvcQ+vMDM/33AWOYP/JSjDg== dependencies: component-emitter "~1.3.0" debug "~3.1.0" @@ -13892,17 +15283,26 @@ socket.io-parser@~3.4.0: isarray "2.0.1" socket.io@^2.3.0: - version "2.3.0" - resolved "https://registry.npmjs.org/socket.io/-/socket.io-2.3.0.tgz#cd762ed6a4faeca59bc1f3e243c0969311eb73fb" - integrity sha512-2A892lrj0GcgR/9Qk81EaY2gYhCBxurV0PfmmESO6p27QPrUK1J3zdns+5QPqvUYK2q657nSj0guoIil9+7eFg== + version "2.4.1" + resolved "https://registry.npmjs.org/socket.io/-/socket.io-2.4.1.tgz#95ad861c9a52369d7f1a68acf0d4a1b16da451d2" + integrity sha512-Si18v0mMXGAqLqCVpTxBa8MGqriHGQh8ccEOhmsmNS3thNCGBwO8WGrwMibANsWtQQ5NStdZwHqZR3naJVFc3w== dependencies: debug "~4.1.0" - engine.io "~3.4.0" + engine.io "~3.5.0" has-binary2 "~1.0.2" socket.io-adapter "~1.1.0" - socket.io-client "2.3.0" + socket.io-client "2.4.0" socket.io-parser "~3.4.0" +socks-proxy-agent@5, socks-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-5.0.0.tgz#7c0f364e7b1cf4a7a437e71253bed72e9004be60" + integrity sha512-lEpa1zsWCChxiynk+lCycKuC502RxDWLKJZoIhnxrWNjLSDGYRFflHA1/228VkRcnv9TIb8w98derGbpKxJRgA== + dependencies: + agent-base "6" + debug "4" + socks "^2.3.3" + socks-proxy-agent@^4.0.0: version "4.0.2" resolved "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz#3c8991f3145b2799e70e11bd5fbc8b1963116386" @@ -13911,6 +15311,14 @@ socks-proxy-agent@^4.0.0: agent-base "~4.2.1" socks "~2.3.2" +socks@^2.3.3: + version "2.6.1" + resolved "https://registry.npmjs.org/socks/-/socks-2.6.1.tgz#989e6534a07cf337deb1b1c94aaa44296520d30e" + integrity sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA== + dependencies: + ip "^1.1.5" + smart-buffer "^4.1.0" + socks@~2.3.2: version "2.3.3" resolved "https://registry.npmjs.org/socks/-/socks-2.3.3.tgz#01129f0a5d534d2b897712ed8aceab7ee65d78e3" @@ -13926,15 +15334,15 @@ sort-keys@^2.0.0: dependencies: is-plain-obj "^1.0.0" -source-list-map@^2.0.0: +source-list-map@^2.0.0, source-list-map@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== -source-map-loader@1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/source-map-loader/-/source-map-loader-1.1.1.tgz#1dd964294cfcc3d9bab65f46af97a38d8ae0c65d" - integrity sha512-m2HjSWP2R1yR9P31e4+ciGHFOPvW6GmqHgZkneOkrME2VvWysXTGi4o0yS28iKWWP3vAUmAoa+3x5ZRI2BIX6A== +source-map-loader@1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/source-map-loader/-/source-map-loader-1.1.3.tgz#7dbc2fe7ea09d3e43c51fd9fc478b7f016c1f820" + integrity sha512-6YHeF+XzDOrT/ycFJNI53cgEsp/tHTMl37hi7uVyqFAlTXW109JazaQCkbc+jjoL2637qkH1amLi+JzrIpt5lA== dependencies: abab "^2.0.5" iconv-lite "^0.6.2" @@ -13943,7 +15351,7 @@ source-map-loader@1.1.1: source-map "^0.6.1" whatwg-mimetype "^2.3.0" -source-map-resolve@^0.5.0, source-map-resolve@^0.5.2: +source-map-resolve@^0.5.0: version "0.5.3" resolved "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== @@ -13978,16 +15386,16 @@ source-map-support@~0.4.0: source-map "^0.5.6" source-map-url@^0.4.0: - version "0.4.0" - resolved "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" - integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= + version "0.4.1" + resolved "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" + integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.3: version "0.5.7" resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= -source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== @@ -14065,24 +15473,29 @@ spdx-expression-validate@2.0.0: spdx-expression-parse "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.6" - resolved "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz#c80757383c28abf7296744998cbc106ae8b854ce" - integrity sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw== + version "3.0.8" + resolved "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.8.tgz#eb1e97ad99b11bf3f82a3b71a0472dd9a00f2ecf" + integrity sha512-NDgA96EnaLSvtbM7trJj+t1LUR3pirkDCcz9nOUlPb5DMBGsH7oES6C3hs3j7R9oHEa1EMvReS/BUAIT5Tcr0g== spdx-ranges@^2.0.0: version "2.1.1" resolved "https://registry.npmjs.org/spdx-ranges/-/spdx-ranges-2.1.1.tgz#87573927ba51e92b3f4550ab60bfc83dd07bac20" integrity sha512-mcdpQFV7UDAgLpXEE/jOMqvK4LBoO0uTQg0uvXUewmEFhpiZx5yJSZITHB8w1ZahKdhfZqP5GPEOKLyEq5p8XA== -spdx-satisfies@5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/spdx-satisfies/-/spdx-satisfies-5.0.0.tgz#d740b8f14caeada36fb307629dee87146970a256" - integrity sha512-/hGhwh20BeGmkA+P/lm06RvXD94JduwNxtx/oX3B5ClPt1/u/m5MCaDNo1tV3Y9laLkQr/NRde63b9lLMhlNfw== +spdx-satisfies@5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/spdx-satisfies/-/spdx-satisfies-5.0.1.tgz#9feeb2524686c08e5f7933c16248d4fdf07ed6a6" + integrity sha512-Nwor6W6gzFp8XX4neaKQ7ChV4wmpSh2sSDemMFSzHxpTw460jxFYeOn+jq4ybnSSw/5sc3pjka9MQPouksQNpw== dependencies: spdx-compare "^1.0.0" spdx-expression-parse "^3.0.0" spdx-ranges "^2.0.0" +split-on-first@^1.0.0: + version "1.1.0" + resolved "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" + integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== + split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" resolved "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" @@ -14097,6 +15510,13 @@ split2@^2.0.0: dependencies: through2 "^2.0.2" +split2@^3.0.0: + version "3.2.2" + resolved "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz#bf2cf2a37d838312c249c89206fd7a17dd12365f" + integrity sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg== + dependencies: + readable-stream "^3.0.0" + split@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" @@ -14133,17 +15553,31 @@ sshpk@^1.7.0: tweetnacl "~0.14.0" ssri@^6.0.0, ssri@^6.0.1: - version "6.0.1" - resolved "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8" - integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA== + version "6.0.2" + resolved "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5" + integrity sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q== dependencies: figgy-pudding "^3.5.1" +ssri@^8.0.0, ssri@^8.0.1: + version "8.0.1" + resolved "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" + integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== + dependencies: + minipass "^3.1.1" + stack-trace@0.0.10, stack-trace@0.0.x: version "0.0.10" resolved "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= +stack-utils@^2.0.2: + version "2.0.3" + resolved "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz#cd5f030126ff116b78ccb3c027fe302713b61277" + integrity sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw== + dependencies: + escape-string-regexp "^2.0.0" + static-extend@^0.1.1: version "0.1.2" resolved "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" @@ -14205,9 +15639,9 @@ stream-http@^2.7.2: xtend "^4.0.0" stream-http@^3.1.0: - version "3.1.1" - resolved "https://registry.npmjs.org/stream-http/-/stream-http-3.1.1.tgz#0370a8017cf8d050b9a8554afe608f043eaff564" - integrity sha512-S7OqaYu0EkFpgeGFb/NPOoPLxFko7TPqtEeFg5DXPB4v/KETHG0Ln6fRFrNezoelpaDKmycEmmZ81cC9DAwgYg== + version "3.2.0" + resolved "https://registry.npmjs.org/stream-http/-/stream-http-3.2.0.tgz#1872dfcf24cb15752677e40e5c3f9cc1926028b5" + integrity sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A== dependencies: builtin-status-codes "^3.0.0" inherits "^2.0.4" @@ -14219,12 +15653,12 @@ stream-shift@^1.0.0: resolved "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== -stream-transform@^2.0.1: - version "2.0.2" - resolved "https://registry.npmjs.org/stream-transform/-/stream-transform-2.0.2.tgz#3cb7a14c802eb39bc40caaab0535e584f3a65caf" - integrity sha512-J+D5jWPF/1oX+r9ZaZvEXFbu7znjxSkbNAHJ9L44bt/tCVuOEWZlDqU9qJk7N2xBU1S+K2DPpSKeR/MucmCA1Q== +stream-transform@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/stream-transform/-/stream-transform-2.1.0.tgz#e68cc062cced5b8ee669ae97f4be473eee5d9227" + integrity sha512-bwQO+75rzQbug7e5OOHnOR3FgbJ0fCjHmDIdynkwUaFzleBXugGmv2dx3sX3aIHUQRLjrcisRPgN9BWl63uGgw== dependencies: - mixme "^0.3.1" + mixme "^0.5.0" streamfilter@^3.0.0: version "3.0.0" @@ -14247,6 +15681,11 @@ streamsearch@0.1.2: resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= +strict-uri-encode@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" + integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY= + string-argv@~0.3.1: version "0.3.1" resolved "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" @@ -14286,37 +15725,38 @@ string-width@^3.0.0, string-width@^3.1.0: strip-ansi "^5.1.0" string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: - version "4.2.0" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" - integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== + version "4.2.2" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5" + integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA== dependencies: emoji-regex "^8.0.0" is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" string.prototype.padend@^3.0.0: - version "3.1.0" - resolved "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.0.tgz#dc08f57a8010dc5c153550318f67e13adbb72ac3" - integrity sha512-3aIv8Ffdp8EZj8iLwREGpQaUZiPyrWrpzMBHvkiSW/bK/EGve9np07Vwy7IJ5waydpGXzQZu/F8Oze2/IWkBaA== + version "3.1.2" + resolved "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.2.tgz#6858ca4f35c5268ebd5e8615e1327d55f59ee311" + integrity sha512-/AQFLdYvePENU3W5rgurfWSMU6n+Ww8n/3cUt7E+vPBB/D7YDG8x+qjoFs4M/alR2bW7Qg6xMjVwWUOvuQ0XpQ== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" + es-abstract "^1.18.0-next.2" -string.prototype.trimend@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" - integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== +string.prototype.trimend@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" + integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.5" -string.prototype.trimstart@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" - integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== +string.prototype.trimstart@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" + integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.5" string_decoder@^1.0.0, string_decoder@^1.1.1, string_decoder@^1.3.0: version "1.3.0" @@ -14365,7 +15805,7 @@ strip-ansi@^6.0.0: dependencies: ansi-regex "^5.0.0" -strip-bom-string@1.X: +strip-bom-string@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92" integrity sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI= @@ -14416,7 +15856,7 @@ strip-json-comments@2.0.1, strip-json-comments@~2.0.1: resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= -strip-json-comments@^3.1.0, strip-json-comments@^3.1.1, strip-json-comments@~3.1.1: +strip-json-comments@3.1.1, strip-json-comments@^3.1.0, strip-json-comments@^3.1.1, strip-json-comments@~3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== @@ -14435,24 +15875,19 @@ stubs@^3.0.0: resolved "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz#e8d2ba1fa9c90570303c030b6900f7d5f89abe5b" integrity sha1-6NK6H6nJBXAwPAMLaQD31fiavls= -superstatic@^7.0.0: - version "7.0.1" - resolved "https://registry.npmjs.org/superstatic/-/superstatic-7.0.1.tgz#cf82b0fd03100d294636ec76ccfa4eda6f255676" - integrity sha512-oph3y5srRKrF8qeCVnQXbysb7U9ixPZQBlqniQymZimJwy2D1xba0EMouCFquhkwRrZYLgd7YPtkSBaPwyFYZA== +superstatic@^7.1.0: + version "7.1.0" + resolved "https://registry.npmjs.org/superstatic/-/superstatic-7.1.0.tgz#42cc773a0f500fb691841e0533d0b8c31f25997f" + integrity sha512-yBU8iw07nM3Bu4jFc8lnKwLey0cj61OaGmFJZcYC2X+kEpXVmXzERJ3OTAHZAESe1OTeNIuWadt81U5IULGGAA== dependencies: - as-array "^2.0.0" - async "^1.5.2" basic-auth-connect "^1.0.0" chalk "^1.1.3" - char-spinner "^1.0.1" compare-semver "^1.0.0" compression "^1.7.0" connect "^3.6.2" - connect-query "^1.0.0" destroy "^1.0.4" fast-url-parser "^1.1.3" - fs-extra "^0.30.0" - glob "^7.1.2" + fs-extra "^8.1.0" glob-slasher "^1.0.1" home-dir "^1.0.0" is-url "^1.2.2" @@ -14464,14 +15899,13 @@ superstatic@^7.0.0: nash "^3.0.0" on-finished "^2.2.0" on-headers "^1.0.0" - path-to-regexp "^1.7.0" + path-to-regexp "^1.8.0" router "^1.3.1" - rsvp "^3.6.2" + rsvp "^4.8.5" string-length "^1.0.0" - try-require "^1.0.0" - update-notifier "^4.1.0" + update-notifier "^4.1.1" optionalDependencies: - re2 "^1.15.0" + re2 "^1.15.8" supports-color@6.0.0: version "6.0.0" @@ -14480,6 +15914,13 @@ supports-color@6.0.0: dependencies: has-flag "^3.0.0" +supports-color@8.1.1: + version "8.1.1" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -14531,25 +15972,32 @@ symbol-observable@^1.1.0: version "1.0.1" resolved "git+https://github.com/brettz9/sync-promise.git#25845a49a00aa2d2c985a5149b97c86a1fcdc75a" -table@^5.2.3: - version "5.4.6" - resolved "https://registry.npmjs.org/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" - integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== +table@^6.0.9: + version "6.7.1" + resolved "https://registry.npmjs.org/table/-/table-6.7.1.tgz#ee05592b7143831a8c94f3cee6aae4c1ccef33e2" + integrity sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg== dependencies: - ajv "^6.10.2" - lodash "^4.17.14" - slice-ansi "^2.1.0" - string-width "^3.0.0" + ajv "^8.0.1" + lodash.clonedeep "^4.5.0" + lodash.truncate "^4.4.2" + slice-ansi "^4.0.0" + string-width "^4.2.0" + strip-ansi "^6.0.0" tapable@^1.0.0, tapable@^1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== -tar-stream@^2.1.0: - version "2.1.4" - resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.4.tgz#c4fb1a11eb0da29b893a5b25476397ba2d053bfa" - integrity sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw== +tapable@^2.1.1, tapable@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/tapable/-/tapable-2.2.0.tgz#5c373d281d9c672848213d0e037d1c4165ab426b" + integrity sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw== + +tar-stream@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== dependencies: bl "^4.0.3" end-of-stream "^1.4.1" @@ -14582,10 +16030,10 @@ tar@^4, tar@^4.3.0, tar@^4.4.10, tar@^4.4.12, tar@^4.4.8: safe-buffer "^5.1.2" yallist "^3.0.3" -tar@^6.0.1: - version "6.0.5" - resolved "https://registry.npmjs.org/tar/-/tar-6.0.5.tgz#bde815086e10b39f1dcd298e89d596e1535e200f" - integrity sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg== +tar@^6.0.2, tar@^6.1.0: + version "6.1.0" + resolved "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz#d1724e9bcc04b977b18d5c573b333a2207229a83" + integrity sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" @@ -14595,12 +16043,12 @@ tar@^6.0.1: yallist "^4.0.0" tcp-port-used@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.1.tgz#46061078e2d38c73979a2c2c12b5a674e6689d70" - integrity sha512-rwi5xJeU6utXoEIiMvVBMc9eJ2/ofzB+7nLOdnZuFTmNCLqRiQh2sMG9MqCxHU/69VC/Fwp5dV9306Qd54ll1Q== + version "1.0.2" + resolved "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.2.tgz#9652b7436eb1f4cfae111c79b558a25769f6faea" + integrity sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA== dependencies: - debug "4.1.0" - is2 "2.0.1" + debug "4.3.1" + is2 "^2.0.6" teeny-request@^7.0.0: version "7.0.1" @@ -14638,9 +16086,9 @@ term-size@^1.2.0: execa "^0.7.0" term-size@^2.1.0: - version "2.2.0" - resolved "https://registry.npmjs.org/term-size/-/term-size-2.2.0.tgz#1f16adedfe9bdc18800e1776821734086fcc6753" - integrity sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw== + version "2.2.1" + resolved "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54" + integrity sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg== terser-webpack-plugin@^1.4.3: version "1.4.5" @@ -14657,10 +16105,22 @@ terser-webpack-plugin@^1.4.3: webpack-sources "^1.4.0" worker-farm "^1.7.0" -terser@5.3.5: - version "5.3.5" - resolved "https://registry.npmjs.org/terser/-/terser-5.3.5.tgz#9e080baa0568f96654621b20eb9effa440b1484e" - integrity sha512-Qw3CZAMmmfU824AoGKalx+riwocSI5Cs0PoGp9RdSLfmxkmJgyBxqLBP/isDNtFyhHnitikvRMZzyVgeq+U+Tg== +terser-webpack-plugin@^5.1.1: + version "5.1.2" + resolved "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.1.2.tgz#51d295eb7cc56785a67a372575fdc46e42d5c20c" + integrity sha512-6QhDaAiVHIQr5Ab3XUWZyDmrIPCHMiqJVljMF91YKyqwKkL5QHnYMkrMBy96v9Z7ev1hGhSEw1HQZc2p/s5Z8Q== + dependencies: + jest-worker "^26.6.2" + p-limit "^3.1.0" + schema-utils "^3.0.0" + serialize-javascript "^5.0.1" + source-map "^0.6.1" + terser "^5.7.0" + +terser@5.7.0, terser@^5.0.0, terser@^5.7.0: + version "5.7.0" + resolved "https://registry.npmjs.org/terser/-/terser-5.7.0.tgz#a761eeec206bc87b605ab13029876ead938ae693" + integrity sha512-HP5/9hp2UaZt5fYkuhNBR8YyRcT8juw8+uFbAme53iN9hblvKnLUTKkmwJG6ocWpIKf8UK4DoeWG4ty0J6S6/g== dependencies: commander "^2.20.0" source-map "~0.7.2" @@ -14675,15 +16135,6 @@ terser@^4.1.2: source-map "~0.6.1" source-map-support "~0.5.12" -terser@^5.0.0: - version "5.3.3" - resolved "https://registry.npmjs.org/terser/-/terser-5.3.3.tgz#2592a1cf079df55101fe2b2cb2330f951863860b" - integrity sha512-vRQDIlD+2Pg8YMwVK9kMM3yGylG95EIwzBai1Bw7Ot4OBfn3VP1TZn3EWx4ep2jERN/AmnVaTiGuelZSN7ds/A== - dependencies: - commander "^2.20.0" - source-map "~0.7.2" - source-map-support "~0.5.19" - test-exclude@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" @@ -14708,10 +16159,10 @@ text-table@^0.2.0: resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= -textextensions@2: - version "2.6.0" - resolved "https://registry.npmjs.org/textextensions/-/textextensions-2.6.0.tgz#d7e4ab13fe54e32e08873be40d51b74229b00fc4" - integrity sha512-49WtAWS+tcsy93dRt6P0P3AMD2m5PvXRhuEA0kaXos5ZLlujtYmpmFsB+QvWUSxE1ZsstmYXfQ7L40+EcQgpAQ== +textextensions@^3.2.0: + version "3.3.0" + resolved "https://registry.npmjs.org/textextensions/-/textextensions-3.3.0.tgz#03530d5287b86773c08b77458589148870cc71d3" + integrity sha512-mk82dS8eRABNbeVJrEiN5/UMSCliINAuz8mkUwH4SwslkNP//gbEzlWNS5au0z5Dpx40SQxzqZevZkn+WYJ9Dw== thenify-all@^1.0.0: version "1.6.0" @@ -14743,7 +16194,7 @@ through2@2.0.1: readable-stream "~2.0.0" xtend "~4.0.0" -through2@2.X, through2@^2.0.0, through2@^2.0.2, through2@^2.0.3, through2@~2.0.0: +through2@^2.0.0, through2@^2.0.2, through2@^2.0.3, through2@~2.0.0: version "2.0.5" resolved "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== @@ -14751,7 +16202,7 @@ through2@2.X, through2@^2.0.0, through2@^2.0.2, through2@^2.0.3, through2@~2.0.0 readable-stream "~2.3.6" xtend "~4.0.1" -through2@^3.0.0: +through2@^3.0.0, through2@^3.0.1: version "3.0.2" resolved "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz#99f88931cfc761ec7678b41d5d7336b5b6a07bf4" integrity sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ== @@ -14759,6 +16210,13 @@ through2@^3.0.0: inherits "^2.0.4" readable-stream "2 || 3" +through2@^4.0.0: + version "4.0.2" + resolved "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz#a7ce3ac2a7a8b0b966c80e7c49f0484c3b239764" + integrity sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw== + dependencies: + readable-stream "3" + through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@^2.3.8: version "2.3.8" resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -14775,13 +16233,13 @@ timed-out@^2.0.0: integrity sha1-84sK6B03R9YoAB9B2vxlKs5nHAo= timers-browserify@^2.0.11, timers-browserify@^2.0.4: - version "2.0.11" - resolved "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f" - integrity sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ== + version "2.0.12" + resolved "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz#44a45c11fbf407f34f97bccd1577c652361b00ee" + integrity sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ== dependencies: setimmediate "^1.0.4" -timers-ext@^0.1.5: +timers-ext@^0.1.5, timers-ext@^0.1.7: version "0.1.7" resolved "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz#6f57ad8578e07a3fb9f91d9387d65647555e25c6" integrity sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ== @@ -14820,6 +16278,11 @@ tmp@0.2.1, tmp@^0.2.1: dependencies: rimraf "^3.0.0" +tmpl@1.0.x: + version "1.0.4" + resolved "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" + integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE= + to-absolute-glob@^2.0.0: version "2.0.2" resolved "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz#1865f43d9e74b0822db9f145b78cff7d0f7c849b" @@ -14897,14 +16360,6 @@ toidentifier@1.0.0: resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== -tough-cookie@~2.4.3: - version "2.4.3" - resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" - integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== - dependencies: - psl "^1.1.24" - punycode "^1.4.1" - tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" @@ -14927,10 +16382,10 @@ tr46@^1.0.1: dependencies: punycode "^2.1.0" -tr46@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz#03273586def1595ae08fedb38d7733cee91d2479" - integrity sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg== +tr46@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240" + integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw== dependencies: punycode "^2.1.1" @@ -14969,33 +16424,29 @@ triple-beam@^1.2.0, triple-beam@^1.3.0: resolved "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== -try-require@^1.0.0: - version "1.2.1" - resolved "https://registry.npmjs.org/try-require/-/try-require-1.2.1.tgz#34489a2cac0c09c1cc10ed91ba011594d4333be2" - integrity sha1-NEiaLKwMCcHMEO2RugEVlNQzO+I= - -ts-essentials@7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/ts-essentials/-/ts-essentials-7.0.0.tgz#eb807945d65e258bae8914f541543758f879f6c5" - integrity sha512-DptrzFAwb5afnsTdKxfScHqLbVZHl7YsxvDT+iT8tbXWFGSzbXALhfWlal25HBesqlX0NZd6wz9KBGnJcWScdQ== +ts-essentials@7.0.1: + version "7.0.1" + resolved "https://registry.npmjs.org/ts-essentials/-/ts-essentials-7.0.1.tgz#d205508cae0cdadfb73c89503140cf2228389e2d" + integrity sha512-8lwh3QJtIc1UWhkQtr9XuksXu3O0YQdEE5g79guDfhCaU1FWTDIEDZ1ZSx4HTHUmlJZ8L812j3BZQ4a0aOUkSA== -ts-loader@8.0.5: - version "8.0.5" - resolved "https://registry.npmjs.org/ts-loader/-/ts-loader-8.0.5.tgz#fa42b9305247eb964843df1ecb0e589b1bff0f77" - integrity sha512-MvLXmjDxl2Mhv17nvkrB6BrpC8FTwSb7K38oIgdUI6BMx4XgVbljmcoOzlrYn4wyjNTFQ3utd7s2TyigJyR3YA== +ts-loader@8.3.0: + version "8.3.0" + resolved "https://registry.npmjs.org/ts-loader/-/ts-loader-8.3.0.tgz#83360496d6f8004fab35825279132c93412edf33" + integrity sha512-MgGly4I6cStsJy27ViE32UoqxPTN9Xly4anxxVyaIWR+9BGxboV4EyJBGfR3RePV7Ksjj3rHmPZJeIt+7o4Vag== dependencies: - chalk "^2.3.0" + chalk "^4.1.0" enhanced-resolve "^4.0.0" - loader-utils "^1.0.2" + loader-utils "^2.0.0" micromatch "^4.0.0" - semver "^6.0.0" + semver "^7.3.4" -ts-node@9.0.0: - version "9.0.0" - resolved "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz#e7699d2a110cc8c0d3b831715e417688683460b3" - integrity sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg== +ts-node@9.1.1: + version "9.1.1" + resolved "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz#51a9a450a3e959401bda5f004a72d54b936d376d" + integrity sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg== dependencies: arg "^4.1.0" + create-require "^1.1.0" diff "^4.0.1" make-error "^1.1.1" source-map-support "^0.5.17" @@ -15011,20 +16462,20 @@ tsconfig-paths@^3.9.0: minimist "^1.2.0" strip-bom "^3.0.0" -tslib@2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz#410eb0d113e5b6356490eec749603725b021b43e" - integrity sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ== +tslib@2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" + integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== -tslib@^1.11.1, tslib@^1.13.0, tslib@^1.8.1, tslib@^1.9.0: - version "1.13.0" - resolved "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" - integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== +tslib@^1.13.0, tslib@^1.8.1, tslib@^1.9.0: + version "1.14.1" + resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0: - version "2.0.2" - resolved "https://registry.npmjs.org/tslib/-/tslib-2.0.2.tgz#462295631185db44b21b1ea3615b63cd1c038242" - integrity sha512-wAH28hcEKwna96/UacuWaVspVLkg4x1aDM9JlzqaQTOFczCktkVAb5fmXChgandR1EraDPs2w8P+ozM+oafwxg== +tslib@^2.0.1, tslib@^2.1.0, tslib@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c" + integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w== tslint@6.1.3: version "6.1.3" @@ -15052,10 +16503,10 @@ tsutils@^2.29.0: dependencies: tslib "^1.8.1" -tsutils@^3.17.1: - version "3.17.1" - resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" - integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g== +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== dependencies: tslib "^1.8.1" @@ -15069,7 +16520,7 @@ tty-browserify@^0.0.1: resolved "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz#3f05251ee17904dfd0677546670db9651682b811" integrity sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw== -tty-table@^2.7.0: +tty-table@^2.8.10: version "2.8.13" resolved "https://registry.npmjs.org/tty-table/-/tty-table-2.8.13.tgz#d484a416381973eaebbdf19c79136b390e5c6d70" integrity sha512-eVV/+kB6fIIdx+iUImhXrO22gl7f6VmmYh0Zbu6C196fe1elcHXd7U6LcLXu0YoVPc2kNesWiukYcdK8ZmJ6aQ== @@ -15113,21 +16564,38 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.5, type-detect@^4.0.8: version "4.0.8" resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== -type-fest@^0.11.0: - version "0.11.0" - resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" - integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== - type-fest@^0.13.1: version "0.13.1" resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg== +type-fest@^0.18.0: + version "0.18.1" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" + integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + type-fest@^0.3.0: version "0.3.1" resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz#63d00d204e059474fe5e1b7c011112bbd1dc29e1" @@ -15157,9 +16625,9 @@ type@^1.0.1: integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== type@^2.0.0: - version "2.1.0" - resolved "https://registry.npmjs.org/type/-/type-2.1.0.tgz#9bdc22c648cf8cf86dd23d32336a41cfb6475e3f" - integrity sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA== + version "2.5.0" + resolved "https://registry.npmjs.org/type/-/type-2.5.0.tgz#0a2e78c2e77907b252abe5f298c1b01c63f0db3d" + integrity sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw== typedarray-to-buffer@^3.1.5: version "3.1.5" @@ -15201,33 +16669,38 @@ typedoc@0.16.11: typescript "3.7.x" typescript@3.7.x: - version "3.7.5" - resolved "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae" - integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw== + version "3.7.7" + resolved "https://registry.npmjs.org/typescript/-/typescript-3.7.7.tgz#c931733e2ec10dda56b855b379cc488a72a81199" + integrity sha512-MmQdgo/XenfZPvVLtKZOq9jQQvzaUAUpcKW8Z43x9B2fOm4S5g//tPtMweZUIP+SoBqrVPEIm+dJeQ9dfO0QdA== -typescript@4.0.2: - version "4.0.2" - resolved "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz#7ea7c88777c723c681e33bf7988be5d008d05ac2" - integrity sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ== +typescript@4.2.2: + version "4.2.2" + resolved "https://registry.npmjs.org/typescript/-/typescript-4.2.2.tgz#1450f020618f872db0ea17317d16d8da8ddb8c4c" + integrity sha512-tbb+NVrLfnsJy3M59lsDgrzWIflR4d4TIUjz+heUnHZwdF7YsrMTKoRERiIvI2lvBG95dfpLxB21WZhys1bgaQ== -typescript@~3.9.7: - version "3.9.7" - resolved "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" - integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== +typescript@~4.1.3: + version "4.1.5" + resolved "https://registry.npmjs.org/typescript/-/typescript-4.1.5.tgz#123a3b214aaff3be32926f0d8f1f6e704eb89a72" + integrity sha512-6OSu9PTIzmn9TCDiovULTnET6BgXtDYL4Gg4szY+cGsc3JP1dQL8qvE8kShTRx1NIw4Q9IBHlwODjkjWEtMUyA== -typeson-registry@1.0.0-alpha.38: - version "1.0.0-alpha.38" - resolved "https://registry.npmjs.org/typeson-registry/-/typeson-registry-1.0.0-alpha.38.tgz#42b19c21997ab80e38b3c67d6c1aad1499fea5b9" - integrity sha512-6lt2IhbNT9hyow5hljZqjWtVDXBIaC1X8bBGlBva0Pod2f42g23bVqww09ruquwSC48I8BSSCPi+B2dFHM5ihQ== +typeson-registry@1.0.0-alpha.39: + version "1.0.0-alpha.39" + resolved "https://registry.npmjs.org/typeson-registry/-/typeson-registry-1.0.0-alpha.39.tgz#9e0f5aabd5eebfcffd65a796487541196f4b1211" + integrity sha512-NeGDEquhw+yfwNhguLPcZ9Oj0fzbADiX4R0WxvoY8nGhy98IbzQy1sezjoEFWOywOboj/DWehI+/aUlRVrJnnw== dependencies: - base64-arraybuffer-es6 "^0.6.0" - typeson "^5.18.2" - whatwg-url "^8.1.0" + base64-arraybuffer-es6 "^0.7.0" + typeson "^6.0.0" + whatwg-url "^8.4.0" -typeson@5.18.2, typeson@^5.18.2: - version "5.18.2" - resolved "https://registry.npmjs.org/typeson/-/typeson-5.18.2.tgz#0d217fc0e11184a66aa7ca0076d9aa7707eb7bc2" - integrity sha512-Vetd+OGX05P4qHyHiSLdHZ5Z5GuQDrHHwSdjkqho9NSCYVSLSfRMjklD/unpHH8tXBR9Z/R05rwJSuMpMFrdsw== +typeson@6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/typeson/-/typeson-6.0.0.tgz#a1e8465028376565f9efed96fef6879a9a844c10" + integrity sha512-WFgL4bEdyyfH6VfzC39AcSfeGqTFycW8TvWQy/hbtN8ssbuXSrkSdW2OCt0bUmUZdmFR0wrszyr0CIhvvs4RQw== + +typeson@^6.0.0: + version "6.1.0" + resolved "https://registry.npmjs.org/typeson/-/typeson-6.1.0.tgz#5b2a53705a5f58ff4d6f82f965917cabd0d7448b" + integrity sha512-6FTtyGr8ldU0pfbvW/eOZrEtEkczHRUtduBnA90Jh9kMPCiFNnXIon3vF41N0S4tV1HHQt4Hk1j4srpESziCaA== ua-parser-js@0.7.22: version "0.7.22" @@ -15235,9 +16708,9 @@ ua-parser-js@0.7.22: integrity sha512-YUxzMjJ5T71w6a8WWVcMGM6YWOTX27rCoIQgLXiWaxqXSx9D7DNjiGWn1aJIRSQ5qr0xuhra77bSIh6voR/46Q== uglify-js@^3.1.4, uglify-js@^3.4.9: - version "3.11.0" - resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.11.0.tgz#67317658d76c21e0e54d3224aee2df4ee6c3e1dc" - integrity sha512-e1KQFRCpOxnrJsJVqDUCjURq+wXvIn7cK2sRAx9XL3HYLL9aezOP4Pb1+Y3/o693EPk111Yj2Q+IUXxcpHlygQ== + version "3.13.7" + resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.7.tgz#25468a3b39b1c875df03f0937b2b7036a93f3fee" + integrity sha512-1Psi2MmnZJbnEsgJJIlfnd7tFlJfitusmR7zDI8lXlFI0ACD4/Rm/xdrU8bh6zF0i74aiVoBtkRiFulkrmh3AA== uid-number@0.0.6: version "0.0.6" @@ -15249,15 +16722,25 @@ umask@^1.1.0: resolved "https://registry.npmjs.org/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d" integrity sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0= +unbox-primitive@^1.0.0, unbox-primitive@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" + integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== + dependencies: + function-bind "^1.1.1" + has-bigints "^1.0.1" + has-symbols "^1.0.2" + which-boxed-primitive "^1.0.2" + unc-path-regex@^0.1.2: version "0.1.2" resolved "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo= underscore@>=1.8.3, underscore@^1.9.1: - version "1.11.0" - resolved "https://registry.npmjs.org/underscore/-/underscore-1.11.0.tgz#dd7c23a195db34267186044649870ff1bab5929e" - integrity sha512-xY96SsN3NA461qIRKZ/+qox37YXPtSBswMGfiNptr+wrt6ds4HaMw23TP612fEyGekRE6LNRiLYr/aqbHXNedw== + version "1.13.1" + resolved "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz#0c1c6bd2df54b6b69f2314066d65b6cde6fcf9d1" + integrity sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g== undertaker-registry@^1.0.0: version "1.0.1" @@ -15414,7 +16897,7 @@ upath@^1.1.1, upath@^1.2.0: resolved "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== -update-notifier@^4.1.0: +update-notifier@^4.1.1: version "4.1.3" resolved "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz#be86ee13e8ce48fb50043ff72057b5bd598e1ea3" integrity sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A== @@ -15433,10 +16916,30 @@ update-notifier@^4.1.0: semver-diff "^3.1.1" xdg-basedir "^4.0.0" +update-notifier@^5.1.0: + version "5.1.0" + resolved "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz#4ab0d7c7f36a231dd7316cf7729313f0214d9ad9" + integrity sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw== + dependencies: + boxen "^5.0.0" + chalk "^4.1.0" + configstore "^5.0.1" + has-yarn "^2.1.0" + import-lazy "^2.1.0" + is-ci "^2.0.0" + is-installed-globally "^0.4.0" + is-npm "^5.0.0" + is-yarn-global "^0.3.0" + latest-version "^5.1.0" + pupa "^2.1.1" + semver "^7.3.4" + semver-diff "^3.1.1" + xdg-basedir "^4.0.0" + uri-js@^4.2.2: - version "4.4.0" - resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz#aa714261de793e8a82347a7bcc9ce74e86f28602" - integrity sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g== + version "4.4.1" + resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== dependencies: punycode "^2.1.0" @@ -15531,14 +17034,14 @@ uuid@^3.0.0, uuid@^3.0.1, uuid@^3.3.2, uuid@^3.3.3: integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== uuid@^8.0.0: - version "8.3.0" - resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz#ab738085ca22dc9a8c92725e459b1d507df5d6ea" - integrity sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ== + version "8.3.2" + resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== v8-compile-cache@^2.0.3: - version "2.1.1" - resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" - integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ== + version "2.3.0" + resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== v8flags@^3.2.0: version "3.2.0" @@ -15577,11 +17080,6 @@ value-or-function@^3.0.0: resolved "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz#1c243a50b595c1be54a754bfece8563b9ff8d813" integrity sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM= -vargs@^0.1.0: - version "0.1.0" - resolved "https://registry.npmjs.org/vargs/-/vargs-0.1.0.tgz#6b6184da6520cc3204ce1b407cac26d92609ebff" - integrity sha1-a2GE2mUgzDIEzhtAfKwm2SYJ6/8= - vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" @@ -15661,10 +17159,12 @@ void-elements@^2.0.0: resolved "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w= -walkdir@^0.4.0: - version "0.4.1" - resolved "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz#dc119f83f4421df52e3061e514228a2db20afa39" - integrity sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ== +walker@^1.0.7, walker@~1.0.5: + version "1.0.7" + resolved "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" + integrity sha1-L3+bj9ENZ3JisYqITijRlhjgKPs= + dependencies: + makeerror "1.0.x" watch@1.0.2: version "1.0.2" @@ -15674,23 +17174,31 @@ watch@1.0.2: exec-sh "^0.2.0" minimist "^1.2.0" -watchpack-chokidar2@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz#9948a1866cbbd6cb824dea13a7ed691f6c8ddff0" - integrity sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA== +watchpack-chokidar2@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz#38500072ee6ece66f3769936950ea1771be1c957" + integrity sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww== dependencies: chokidar "^2.1.8" watchpack@^1.7.4: - version "1.7.4" - resolved "https://registry.npmjs.org/watchpack/-/watchpack-1.7.4.tgz#6e9da53b3c80bb2d6508188f5b200410866cd30b" - integrity sha512-aWAgTW4MoSJzZPAicljkO1hsi1oKj/RRq/OJQh2PKI2UKL04c2Bs+MBOB+BBABHTXJpf9mCwHN7ANCvYsvY2sg== + version "1.7.5" + resolved "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz#1267e6c55e0b9b5be44c2023aed5437a2c26c453" + integrity sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ== dependencies: graceful-fs "^4.1.2" neo-async "^2.5.0" optionalDependencies: chokidar "^3.4.1" - watchpack-chokidar2 "^2.0.0" + watchpack-chokidar2 "^2.0.1" + +watchpack@^2.0.0: + version "2.2.0" + resolved "https://registry.npmjs.org/watchpack/-/watchpack-2.2.0.tgz#47d78f5415fe550ecd740f99fe2882323a58b1ce" + integrity sha512-up4YAn/XHgZHIxFBVCdlMiWDj6WaLKpwVeGQk2I5thdYxF/KmF0aaz6TfJZ/hfl1h/XlcDr7k1KH7ThDagpFaA== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" wcwidth@^1.0.0, wcwidth@^1.0.1: version "1.0.1" @@ -15699,19 +17207,6 @@ wcwidth@^1.0.0, wcwidth@^1.0.1: dependencies: defaults "^1.0.3" -wd@^1.4.0: - version "1.13.0" - resolved "https://registry.npmjs.org/wd/-/wd-1.13.0.tgz#79d82c57e0bb7fcca2f291ed87f51cf6b7701ff0" - integrity sha512-Y73EADwIrz1AAmy5G70r/fIM2tzSTdLWjIgCqGlQOr2/k2cC2nho4kWacZdO3xmdsegeQvUkcsGOB74+gC9Wxg== - dependencies: - archiver "^3.0.0" - async "^2.0.0" - lodash "^4.0.0" - mkdirp "^0.5.1" - q "^1.5.1" - request "2.88.0" - vargs "^0.1.0" - webdriver-js-extender@2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/webdriver-js-extender/-/webdriver-js-extender-2.1.0.tgz#57d7a93c00db4cc8d556e4d3db4b5db0a80c3bb7" @@ -15721,9 +17216,9 @@ webdriver-js-extender@2.1.0: selenium-webdriver "^3.0.1" webdriver-manager@^12.0.6: - version "12.1.7" - resolved "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-12.1.7.tgz#ed4eaee8f906b33c146e869b55e850553a1b1162" - integrity sha512-XINj6b8CYuUYC93SG3xPkxlyUc3IJbD6Vvo75CVGuG9uzsefDzWQrhz0Lq8vbPxtb4d63CZdYophF8k8Or/YiA== + version "12.1.8" + resolved "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-12.1.8.tgz#5e70e73eaaf53a0767d5745270addafbc5905fd4" + integrity sha512-qJR36SXG2VwKugPcdwhaqcLQOD7r8P2Xiv9sfNbfZrKBnX243iAkOueX1yAmeNgIKhJ3YAT/F2gq6IiEZzahsg== dependencies: adm-zip "^0.4.9" chalk "^1.1.1" @@ -15748,9 +17243,9 @@ webidl-conversions@^6.1.0: integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== webpack-dev-middleware@^3.7.0: - version "3.7.2" - resolved "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz#0019c3db716e3fa5cecbf64f2ab88a74bab331f3" - integrity sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw== + version "3.7.3" + resolved "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz#0639372b143262e2b84ab95d3b91a7597061c2c5" + integrity sha512-djelc/zGiz9nZj/U7PTBi2ViorGJXEWo/3ltkPbDyxCXhhEXkW0ce99falaok4TPj+AsxLiXJR0EBOb0zh9fKQ== dependencies: memory-fs "^0.4.1" mime "^2.4.4" @@ -15774,10 +17269,18 @@ webpack-sources@^1.4.0, webpack-sources@^1.4.1: source-list-map "^2.0.0" source-map "~0.6.1" -webpack-stream@6.1.0: - version "6.1.0" - resolved "https://registry.npmjs.org/webpack-stream/-/webpack-stream-6.1.0.tgz#047348e36793432f329c7b5ff13e6e9b6872c152" - integrity sha512-kFMnDzFTzyvVmn4ajaj0xEJavvYizd3I/KmQ6C5aUstcAkNwZUidxkk/uEaEPSydaAn66v8ZcP1+bhKSshNJUQ== +webpack-sources@^2.1.1: + version "2.2.0" + resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.2.0.tgz#058926f39e3d443193b6c31547229806ffd02bac" + integrity sha512-bQsA24JLwcnWGArOKUxYKhX3Mz/nK1Xf6hxullKERyktjNMC4x8koOeaDNTA2fEJ09BdWLbM/iTW0ithREUP0w== + dependencies: + source-list-map "^2.0.1" + source-map "^0.6.1" + +webpack-stream@6.1.1: + version "6.1.1" + resolved "https://registry.npmjs.org/webpack-stream/-/webpack-stream-6.1.1.tgz#8db6b1efdbebf796b1e30ac1256645f4a3cf5867" + integrity sha512-XRd4ydP5H43twoLfh541VRtEg0ig/GJQnR0D3npjSOhjHsAUaYDEFoAUZwH+tfWSHyp6OyVJPYHRPmRoyCDYBA== dependencies: fancy-log "^1.3.3" lodash.clone "^4.3.2" @@ -15789,10 +17292,15 @@ webpack-stream@6.1.0: vinyl "^2.1.0" webpack "^4.26.1" -webpack@4.44.2, webpack@^4.26.1: - version "4.44.2" - resolved "https://registry.npmjs.org/webpack/-/webpack-4.44.2.tgz#6bfe2b0af055c8b2d1e90ed2cd9363f841266b72" - integrity sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q== +webpack-virtual-modules@0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.4.1.tgz#cae5a7085d34331d077225f77037bea233dbfdad" + integrity sha512-BH/RKOHk223WdBDLFqghztx3DF5AqR3CKg3ue1KN9S1SAaXP68Kj/4rF0lsdysxXaanzx7aWl1u0+lnfj7+OtQ== + +webpack@4.46.0, webpack@^4.26.1: + version "4.46.0" + resolved "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz#bf9b4404ea20a073605e0a011d188d77cb6ad542" + integrity sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q== dependencies: "@webassemblyjs/ast" "1.9.0" "@webassemblyjs/helper-module-context" "1.9.0" @@ -15802,7 +17310,7 @@ webpack@4.44.2, webpack@^4.26.1: ajv "^6.10.2" ajv-keywords "^3.4.1" chrome-trace-event "^1.0.2" - enhanced-resolve "^4.3.0" + enhanced-resolve "^4.5.0" eslint-scope "^4.0.3" json-parse-better-errors "^1.0.2" loader-runner "^2.4.0" @@ -15818,6 +17326,35 @@ webpack@4.44.2, webpack@^4.26.1: watchpack "^1.7.4" webpack-sources "^1.4.1" +webpack@^5: + version "5.37.1" + resolved "https://registry.npmjs.org/webpack/-/webpack-5.37.1.tgz#2deb5acd350583c1ab9338471f323381b0b0c14b" + integrity sha512-btZjGy/hSjCAAVHw+cKG+L0M+rstlyxbO2C+BOTaQ5/XAnxkDrP5sVbqWhXgo4pL3X2dcOib6rqCP20Zr9PLow== + dependencies: + "@types/eslint-scope" "^3.7.0" + "@types/estree" "^0.0.47" + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/wasm-edit" "1.11.0" + "@webassemblyjs/wasm-parser" "1.11.0" + acorn "^8.2.1" + browserslist "^4.14.5" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.8.0" + es-module-lexer "^0.4.0" + eslint-scope "^5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.4" + json-parse-better-errors "^1.0.2" + loader-runner "^4.2.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^3.0.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.1.1" + watchpack "^2.0.0" + webpack-sources "^2.1.1" + websocket-driver@>=0.5.1: version "0.7.4" resolved "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" @@ -15861,15 +17398,26 @@ whatwg-url@^7.0.0: tr46 "^1.0.1" webidl-conversions "^4.0.2" -whatwg-url@^8.1.0: - version "8.3.0" - resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.3.0.tgz#d1e11e565334486cdb280d3101b9c3fd1c867582" - integrity sha512-BQRf/ej5Rp3+n7k0grQXZj9a1cHtsp4lqj01p59xBWFKdezR8sO37XnpafwNqiFac/v2Il12EIMjX/Y4VZtT8Q== +whatwg-url@^8.4.0: + version "8.6.0" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.6.0.tgz#27c0205a4902084b872aecb97cf0f2a7a3011f4c" + integrity sha512-os0KkeeqUOl7ccdDT1qqUcS4KH4tcBTSKK5Nl5WKb2lyxInIZ/CpjkqKa1Ss12mjfdcRX9mHmPPs7/SxG1Hbdw== dependencies: - lodash.sortby "^4.7.0" - tr46 "^2.0.2" + lodash "^4.7.0" + tr46 "^2.1.0" webidl-conversions "^6.1.0" +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + which-module@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" @@ -15894,12 +17442,13 @@ which-pm@2.0.0: path-exists "^4.0.0" which-typed-array@^1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.2.tgz#e5f98e56bda93e3dac196b01d47c1156679c00b2" - integrity sha512-KT6okrd1tE6JdZAy3o2VhMoYPh3+J6EMZLyrxBQsZflI1QCZIxMrIYLkosd8Twf+YfknVIHmYQPgJt238p8dnQ== + version "1.1.4" + resolved "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.4.tgz#8fcb7d3ee5adf2d771066fba7cf37e32fe8711ff" + integrity sha512-49E0SpUe90cjpoc7BOJwyPHRqSAd12c10Qm2amdEZrJPCY2NDxaW01zHITrem+rnETY3dwrbH3UUrUwagfCYDA== dependencies: available-typed-arrays "^1.0.2" - es-abstract "^1.17.5" + call-bind "^1.0.0" + es-abstract "^1.18.0-next.1" foreach "^2.0.5" function-bind "^1.1.1" has-symbols "^1.0.1" @@ -15912,7 +17461,7 @@ which@1.3.1, which@^1.2.1, which@^1.2.14, which@^1.2.9, which@^1.3.1: dependencies: isexe "^2.0.0" -which@^2.0.1, which@^2.0.2: +which@2.0.2, which@^2.0.1, which@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== @@ -15970,7 +17519,7 @@ winston@^3.0.0: triple-beam "^1.3.0" winston-transport "^4.4.0" -word-wrap@^1.2.3: +word-wrap@^1.2.3, word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== @@ -15992,6 +17541,11 @@ worker-farm@^1.7.0: dependencies: errno "~0.1.7" +workerpool@6.1.0: + version "6.1.0" + resolved "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz#a8e038b4c94569596852de7a8ea4228eefdeb37b" + integrity sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg== + wrap-ansi@^2.0.0: version "2.1.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" @@ -16100,24 +17654,10 @@ write-pkg@^3.1.0: sort-keys "^2.0.0" write-json-file "^2.2.0" -write@1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" - integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== - dependencies: - mkdirp "^0.5.1" - -ws@^7.1.2, ws@^7.2.3: - version "7.3.1" - resolved "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz#d0547bf67f7ce4f12a72dfe31262c68d7dc551c8" - integrity sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA== - -ws@~6.1.0: - version "6.1.4" - resolved "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9" - integrity sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA== - dependencies: - async-limiter "~1.0.0" +ws@^7.2.3, ws@^7.3.1, ws@~7.4.2: + version "7.4.5" + resolved "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz#a484dd851e9beb6fdb420027e3885e8ce48986c1" + integrity sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g== xdg-basedir@^4.0.0: version "4.0.0" @@ -16132,52 +17672,47 @@ xml2js@^0.4.17: sax ">=0.6.0" xmlbuilder "~11.0.0" -xmlbuilder@^9.0.7: - version "9.0.7" - resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" - integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= - xmlbuilder@~11.0.0: version "11.0.1" resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== -xmldom@0.1.x: - version "0.1.31" - resolved "https://registry.npmjs.org/xmldom/-/xmldom-0.1.31.tgz#b76c9a1bd9f0a9737e5a72dc37231cf38375e2ff" - integrity sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ== - -xmlhttprequest-ssl@~1.5.4: - version "1.5.5" - resolved "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e" - integrity sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4= +xmlhttprequest-ssl@~1.6.2: + version "1.6.3" + resolved "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.3.tgz#03b713873b01659dfa2c1c5d056065b27ddc2de6" + integrity sha512-3XfeQE/wNkvrIktn2Kf0869fC0BN6UpydVasGIeSm2B1Llihf7/0UfZM+eCkOw3P7bP4+qPgqhm7ZoxuJtFU0Q== xmlhttprequest@1.8.0: version "1.8.0" resolved "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" integrity sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw= +xregexp@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943" + integrity sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM= + xtend@^4.0.0, xtend@^4.0.2, xtend@~4.0.0, xtend@~4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== y18n@^3.2.1: - version "3.2.1" - resolved "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" - integrity sha1-bRX7qITAhnnA136I53WegR4H+kE= + version "3.2.2" + resolved "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696" + integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ== y18n@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" - integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== + version "4.0.3" + resolved "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" + integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== -y18n@^5.0.1: - version "5.0.2" - resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.2.tgz#48218df5da2731b4403115c39a1af709c873f829" - integrity sha512-CkwaeZw6dQgqgPGeTWKMXCRmMcBgETFlTml1+ZOO+q7kGst8NREJ+eWwFNPVUQ4QGdAaklbqCZHH6Zuep1RjiA== +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== -yallist@^2.1.2: +yallist@^2.0.0, yallist@^2.1.2: version "2.1.2" resolved "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= @@ -16193,9 +17728,9 @@ yallist@^4.0.0: integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== yaml@^1.10.0: - version "1.10.0" - resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" - integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg== + version "1.10.2" + resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== yargs-parser@13.1.2, yargs-parser@^13.1.2: version "13.1.2" @@ -16205,20 +17740,15 @@ yargs-parser@13.1.2, yargs-parser@^13.1.2: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@5.0.0-security.0: - version "5.0.0-security.0" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0-security.0.tgz#4ff7271d25f90ac15643b86076a2ab499ec9ee24" - integrity sha512-T69y4Ps64LNesYxeYGYPvfoMTt/7y1XtfpIslUeK4um+9Hu7hlGoRtaDLvdXb7+/tfq4opVa2HRY5xGip022rQ== - dependencies: - camelcase "^3.0.0" - object.assign "^4.1.0" +yargs-parser@20.2.4: + version "20.2.4" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" + integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== -yargs-parser@^10.0.0: - version "10.1.0" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8" - integrity sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ== - dependencies: - camelcase "^4.1.0" +yargs-parser@>=5.0.0-security.0: + version "20.2.9" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== yargs-parser@^15.0.1: version "15.0.1" @@ -16236,10 +17766,18 @@ yargs-parser@^18.1.2, yargs-parser@^18.1.3: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^20.0.0: - version "20.2.1" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.1.tgz#28f3773c546cdd8a69ddae68116b48a5da328e77" - integrity sha512-yYsjuSkjbLMBp16eaOt7/siKTjNVjMm3SoJnIg3sEh/JsvqVVDyjRKmaJV4cl+lNIgq6QEco2i3gDebJl7/vLA== +yargs-parser@^20.2.2, yargs-parser@^20.2.3: + version "20.2.7" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz#61df85c113edfb5a7a4e36eb8aa60ef423cbc90a" + integrity sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw== + +yargs-parser@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.1.tgz#7ede329c1d8cdbbe209bd25cdb990e9b1ebbb394" + integrity sha512-wpav5XYiddjXxirPoCTUPbqM0PXvJ9hiBMvuJgInvo4/lAOTZzUprArw17q2O1P2+GHhbBr18/iQwjL5Z9BqfA== + dependencies: + camelcase "^3.0.0" + object.assign "^4.1.0" yargs-unparser@1.6.0: version "1.6.0" @@ -16250,6 +17788,16 @@ yargs-unparser@1.6.0: lodash "^4.17.15" yargs "^13.3.0" +yargs-unparser@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" + integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== + dependencies: + camelcase "^6.0.0" + decamelize "^4.0.0" + flat "^5.0.2" + is-plain-obj "^2.1.0" + yargs@13.3.2, yargs@^13.3.0: version "13.3.2" resolved "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" @@ -16266,18 +17814,18 @@ yargs@13.3.2, yargs@^13.3.0: y18n "^4.0.0" yargs-parser "^13.1.2" -yargs@16.0.3: - version "16.0.3" - resolved "https://registry.npmjs.org/yargs/-/yargs-16.0.3.tgz#7a919b9e43c90f80d4a142a89795e85399a7e54c" - integrity sha512-6+nLw8xa9uK1BOEOykaiYAJVh6/CjxWXK/q9b5FpRgNslt8s22F2xMBqVIKgCRjNgGvGPBy8Vog7WN7yh4amtA== +yargs@16.2.0, yargs@^16.0.3, yargs@^16.1.1: + version "16.2.0" + resolved "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== dependencies: - cliui "^7.0.0" - escalade "^3.0.2" + cliui "^7.0.2" + escalade "^3.1.1" get-caller-file "^2.0.5" require-directory "^2.1.1" string-width "^4.2.0" - y18n "^5.0.1" - yargs-parser "^20.0.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" yargs@^14.2.2: version "14.2.3" @@ -16314,9 +17862,9 @@ yargs@^15.0.2, yargs@^15.1.0, yargs@^15.3.1: yargs-parser "^18.1.2" yargs@^7.1.0: - version "7.1.1" - resolved "https://registry.npmjs.org/yargs/-/yargs-7.1.1.tgz#67f0ef52e228d4ee0d6311acede8850f53464df6" - integrity sha512-huO4Fr1f9PmiJJdll5kwoS2e4GqzGSsMT3PPMpOwoVkOK8ckqAewMTZyA6LXVQWflleb/Z8oPBEvNsMft0XE+g== + version "7.1.2" + resolved "https://registry.npmjs.org/yargs/-/yargs-7.1.2.tgz#63a0a5d42143879fdbb30370741374e0641d55db" + integrity sha512-ZEjj/dQYQy0Zx0lgLMLR8QuaqTihnxirir7EwUHp1Axq4e3+k8jXU5K0VLbNvedv1f4EWtBonDIZm0NUr+jCcA== dependencies: camelcase "^3.0.0" cliui "^3.2.0" @@ -16330,7 +17878,7 @@ yargs@^7.1.0: string-width "^1.0.2" which-module "^1.0.0" y18n "^3.2.1" - yargs-parser "5.0.0-security.0" + yargs-parser "^5.0.1" yauzl@^2.10.0: version "2.10.0" @@ -16350,6 +17898,11 @@ yn@3.1.1: resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + z-schema@~3.18.3: version "3.18.4" resolved "https://registry.npmjs.org/z-schema/-/z-schema-3.18.4.tgz#ea8132b279533ee60be2485a02f7e3e42541a9a2" @@ -16361,11 +17914,11 @@ z-schema@~3.18.3: optionalDependencies: commander "^2.7.1" -zip-stream@^2.1.2: - version "2.1.3" - resolved "https://registry.npmjs.org/zip-stream/-/zip-stream-2.1.3.tgz#26cc4bdb93641a8590dd07112e1f77af1758865b" - integrity sha512-EkXc2JGcKhO5N5aZ7TmuNo45budRaFGHOmz24wtJR7znbNqDPmdZtUauKX6et8KAVseAMBOyWJqEpXcHTBsh7Q== +zip-stream@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz#51dd326571544e36aa3f756430b313576dc8fc79" + integrity sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A== dependencies: archiver-utils "^2.1.0" - compress-commons "^2.1.1" - readable-stream "^3.4.0" + compress-commons "^4.1.0" + readable-stream "^3.6.0"
` element. + */ +export class DocTableCell extends DocNode { + public readonly content: DocSection; + + public constructor( + parameters: IDocTableCellParameters, + sectionChildNodes?: ReadonlyArray + ) { + super(parameters); + + this.content = new DocSection( + { configuration: this.configuration }, + sectionChildNodes + ); + } + + /** @override */ + public get kind(): string { + return CustomDocNodeKind.TableCell; + } +} diff --git a/repo-scripts/api-documenter/src/nodes/DocTableRow.ts b/repo-scripts/api-documenter/src/nodes/DocTableRow.ts new file mode 100644 index 00000000000..4d14b7b3d03 --- /dev/null +++ b/repo-scripts/api-documenter/src/nodes/DocTableRow.ts @@ -0,0 +1,86 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { IDocNodeParameters, DocNode, DocPlainText } from '@microsoft/tsdoc'; +import { CustomDocNodeKind } from './CustomDocNodeKind'; +import { DocTableCell } from './DocTableCell'; + +/** + * Constructor parameters for {@link DocTableRow}. + */ +export interface IDocTableRowParameters extends IDocNodeParameters {} + +/** + * Represents table row, similar to an HTML `