chromium/chrome/browser/resources/password_manager/user_utils_mixin.ts

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import {WebUiListenerMixin} from 'chrome://resources/cr_elements/web_ui_listener_mixin.js';
import {assert} from 'chrome://resources/js/assert.js';
import type {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {dedupingMixin} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import {PasswordManagerImpl} from './password_manager_proxy.js';
import type {AccountInfo, SyncInfo} from './sync_browser_proxy.js';
import {SyncBrowserProxyImpl} from './sync_browser_proxy.js';

type Constructor<T> = new (...args: any[]) => T;

/**
 * This mixin bundles functionality related to syncing, sign in status and
 * account storage.
 */
export const UserUtilMixin = dedupingMixin(
    <T extends Constructor<PolymerElement>>(superClass: T): T&
    Constructor<UserUtilMixinInterface> => {
      class UserUtilMixin extends WebUiListenerMixin
      (superClass) implements UserUtilMixinInterface {
        static get properties() {
          return {
            /**
             * Indicates whether user opted in using passwords stored on
             * their account.
             */
            isOptedInForAccountStorage: {
              type: Boolean,
              value: false,
            },

            /* Account storage eligibility. */
            isEligibleForAccountStorage: {
              type: Boolean,
              value: false,
              computed: 'computeIsEligibleForAccountStorage_(syncInfo_)',
            },

            /**
             * If true, the edit dialog and removal notification show
             * information about which location(s) a password is stored.
             */
            isAccountStoreUser: {
              type: Boolean,
              computed: 'computeIsAccountStoreUser_(' +
                  'isOptedInForAccountStorage, isEligibleForAccountStorage)',
            },

            isSyncingPasswords: {
              type: Boolean,
              value: true,
              computed: 'computeIsSyncingPasswords_(syncInfo_)',
            },

            /* Email of the primary account. */
            accountEmail: {
              type: String,
              value: '',
              computed: 'computeAccountEmail_(accountInfo_)',
            },

            /* Email of the primary account. */
            avatarImage: {
              type: String,
              value: '',
              computed: 'computeAvatarImage_(accountInfo_)',
            },
          };
        }

        isOptedInForAccountStorage: boolean;
        isEligibleForAccountStorage: boolean;
        isAccountStoreUser: boolean;
        isSyncingPasswords: boolean;
        accountEmail: string;
        avatarImage: string;
        private syncInfo_: SyncInfo;
        private accountInfo_: AccountInfo;

        private setIsOptedInForAccountStorageListener_:
            ((isOptedIn: boolean) => void)|null = null;

        override connectedCallback() {
          super.connectedCallback();

          // Create listener functions.
          this.setIsOptedInForAccountStorageListener_ = (optedIn) =>
              this.isOptedInForAccountStorage = optedIn;
          const syncInfoChanged = (syncInfo: SyncInfo) => this.syncInfo_ =
              syncInfo;
          const accountInfoChanged = (accountInfo: AccountInfo) =>
              this.accountInfo_ = accountInfo;

          // Request initial data.
          PasswordManagerImpl.getInstance().isOptedInForAccountStorage().then(
              this.setIsOptedInForAccountStorageListener_);
          SyncBrowserProxyImpl.getInstance().getSyncInfo().then(
              syncInfoChanged);
          SyncBrowserProxyImpl.getInstance().getAccountInfo().then(
              accountInfoChanged);

          // Listen for changes.
          PasswordManagerImpl.getInstance().addAccountStorageOptInStateListener(
              this.setIsOptedInForAccountStorageListener_);
          this.addWebUiListener('sync-info-changed', syncInfoChanged);
          this.addWebUiListener('stored-accounts-changed', accountInfoChanged);
        }

        override disconnectedCallback() {
          super.disconnectedCallback();

          assert(this.setIsOptedInForAccountStorageListener_);
          PasswordManagerImpl.getInstance()
              .removeAccountStorageOptInStateListener(
                  this.setIsOptedInForAccountStorageListener_);
          this.setIsOptedInForAccountStorageListener_ = null;
        }

        optInForAccountStorage() {
          PasswordManagerImpl.getInstance().optInForAccountStorage(true);
        }

        optOutFromAccountStorage() {
          PasswordManagerImpl.getInstance().optInForAccountStorage(false);
        }

        private computeIsEligibleForAccountStorage_(): boolean {
          return !!this.syncInfo_ && this.syncInfo_.isEligibleForAccountStorage;
        }

        private computeIsSyncingPasswords_(): boolean {
          return !!this.syncInfo_ && this.syncInfo_.isSyncingPasswords;
        }

        private computeAccountEmail_(): string {
          return (this.accountInfo_ ? this.accountInfo_.email : '');
        }

        private computeAvatarImage_(): string {
          return this.accountInfo_.avatarImage || '';
        }

        private computeIsAccountStoreUser_(): boolean {
          return this.isEligibleForAccountStorage &&
              this.isOptedInForAccountStorage;
        }
      }

      return UserUtilMixin;
    });


export interface UserUtilMixinInterface {
  isOptedInForAccountStorage: boolean;
  isEligibleForAccountStorage: boolean;
  isAccountStoreUser: boolean;
  isSyncingPasswords: boolean;
  accountEmail: string;
  avatarImage: string;
  optInForAccountStorage(): void;
  optOutFromAccountStorage(): void;
}