File

src/app/_services/user.service.ts

Index

Properties
Methods

Constructor

constructor(httpClient: HttpClient, loggingService: LoggingService, tokenService: TokenService)
Parameters :
Name Type Optional
httpClient HttpClient No
loggingService LoggingService No
tokenService TokenService No

Methods

addAccount
addAccount(account: AccountDetails, cacheSize: number, top: boolean)
Parameters :
Name Type Optional Default value
account AccountDetails No
cacheSize number No
top boolean No true
Returns : void
approveAction
approveAction(id: string)
Parameters :
Name Type Optional
id string No
Returns : Observable<any>
Async changeAccountInfo
changeAccountInfo(address: string, name: string, phoneNumber: string, age: string, type: string, bio: string, gender: string, businessCategory: string, userLocation: string, location: string, locationType: string, oldPhoneNumber: string)
Parameters :
Name Type Optional
address string No
name string No
phoneNumber string No
age string No
type string No
bio string No
gender string No
businessCategory string No
userLocation string No
location string No
locationType string No
oldPhoneNumber string No
Returns : Promise<boolean>
Async getAccountByAddress
getAccountByAddress(accountAddress: string, limit: number, history: boolean, top: boolean)
Parameters :
Name Type Optional Default value
accountAddress string No
limit number No 100
history boolean No false
top boolean No true
Async getAccountByPhone
getAccountByPhone(phoneNumber: string, limit: number)
Parameters :
Name Type Optional Default value
phoneNumber string No
limit number No 100
getAccountDetailsFromMeta
getAccountDetailsFromMeta(userKey: string)
Parameters :
Name Type Optional
userKey string No
Returns : Observable<any>
getAccountStatus
getAccountStatus(phone: string)
Parameters :
Name Type Optional
phone string No
Returns : Observable<any>
getAccountTypes
getAccountTypes()
Returns : Observable<any>
getActionById
getActionById(id: string)
Parameters :
Name Type Optional
id string No
Returns : Observable<any>
getActions
getActions()
Returns : void
getCategories
getCategories()
Returns : void
getCategoryByProduct
getCategoryByProduct(product: string, categories: object)
Parameters :
Name Type Optional
product string No
categories object No
Returns : string
getGenders
getGenders()
Returns : Observable<any>
getLockedAccounts
getLockedAccounts(offset: number, limit: number)
Parameters :
Name Type Optional
offset number No
limit number No
Returns : Observable<any>
getTransactionTypes
getTransactionTypes()
Returns : Observable<any>
Async init
init()
Returns : Promise<void>
Async loadAccounts
loadAccounts(limit: number, offset: number)
Parameters :
Name Type Optional Default value
limit number No 100
offset number No 0
Returns : Promise<void>
Async loadChangesToAccountStructure
loadChangesToAccountStructure(name: string, phoneNumber: string, age: string, type: string, bio: string, gender: string, businessCategory: string, userLocation: string, location: string, locationType: string)
Parameters :
Name Type Optional
name string No
phoneNumber string No
age string No
type string No
bio string No
gender string No
businessCategory string No
userLocation string No
location string No
locationType string No
resetAccountsList
resetAccountsList()
Returns : void
resetPin
resetPin(phone: string)
Parameters :
Name Type Optional
phone string No
Returns : Observable<any>
revokeAction
revokeAction(id: string)
Parameters :
Name Type Optional
id string No
Returns : Observable<any>
Async updateMeta
updateMeta(syncableAccount: Syncable, accountKey: string, headers: HttpHeaders)
Parameters :
Name Type Optional
syncableAccount Syncable No
accountKey string No
headers HttpHeaders No
Returns : Promise<boolean>
Async updatePhonePointers
updatePhonePointers(address: string, oldPhoneNumber: string, newPhoneNumber: string)
Parameters :
Name Type Optional
address string No
oldPhoneNumber string No
newPhoneNumber string No
Returns : Promise<void>
wrap
wrap(syncable: Syncable, signer: Signer)
Parameters :
Name Type Optional
syncable Syncable No
signer Signer No
Returns : Promise<Envelope>

Properties

accounts
Type : Array<AccountDetails>
Default value : []
Private accountsList
Type : BehaviorSubject<Array<AccountDetails>>
Default value : new BehaviorSubject< Array<AccountDetails> >(this.accounts)
accountsSubject
Type : Observable<Array<AccountDetails>>
Default value : this.accountsList.asObservable()
actions
Type : Array<any>
Default value : []
Private actionsList
Type : BehaviorSubject<any>
Default value : new BehaviorSubject<any>(this.actions)
actionsSubject
Type : Observable<Array<any>>
Default value : this.actionsList.asObservable()
categories
Type : object
Default value : {}
Private categoriesList
Type : BehaviorSubject<object>
Default value : new BehaviorSubject<object>(this.categories)
categoriesSubject
Type : Observable<object>
Default value : this.categoriesList.asObservable()
headers
Type : HttpHeaders
Default value : new HttpHeaders({ 'x-cic-automerge': 'client' })
history
Type : Array<any>
Default value : []
Private historyList
Type : BehaviorSubject<any>
Default value : new BehaviorSubject<any>(this.history)
historySubject
Type : Observable<Array<any>>
Default value : this.historyList.asObservable()
keystore
Type : MutableKeyStore
registry
Type : CICRegistry
signer
Type : Signer
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { environment } from '@src/environments/environment';
import { first } from 'rxjs/operators';
import { ArgPair, Envelope, Phone, Syncable, User } from 'cic-client-meta';
import { AccountDetails } from '@app/_models';
import { LoggingService } from '@app/_services/logging.service';
import { TokenService } from '@app/_services/token.service';
import { MutableKeyStore, PGPSigner, Signer } from '@app/_pgp';
import { RegistryService } from '@app/_services/registry.service';
import { CICRegistry } from '@cicnet/cic-client';
import { personValidation, updateSyncable, vcardValidation } from '@app/_helpers';
import { add0x, strip0x } from '@src/assets/js/ethtx/dist/hex';
import { KeystoreService } from '@app/_services/keystore.service';
import * as Automerge from 'automerge';
const vCard = require('vcard-parser');

@Injectable({
  providedIn: 'root',
})
export class UserService {
  headers: HttpHeaders = new HttpHeaders({ 'x-cic-automerge': 'client' });
  keystore: MutableKeyStore;
  signer: Signer;
  registry: CICRegistry;

  accounts: Array<AccountDetails> = [];
  private accountsList: BehaviorSubject<Array<AccountDetails>> = new BehaviorSubject<
    Array<AccountDetails>
  >(this.accounts);
  accountsSubject: Observable<Array<AccountDetails>> = this.accountsList.asObservable();

  actions: Array<any> = [];
  private actionsList: BehaviorSubject<any> = new BehaviorSubject<any>(this.actions);
  actionsSubject: Observable<Array<any>> = this.actionsList.asObservable();

  categories: object = {};
  private categoriesList: BehaviorSubject<object> = new BehaviorSubject<object>(this.categories);
  categoriesSubject: Observable<object> = this.categoriesList.asObservable();

  history: Array<any> = [];
  private historyList: BehaviorSubject<any> = new BehaviorSubject<any>(this.history);
  historySubject: Observable<Array<any>> = this.historyList.asObservable();

  constructor(
    private httpClient: HttpClient,
    private loggingService: LoggingService,
    private tokenService: TokenService
  ) {}

  async init(): Promise<void> {
    this.keystore = await KeystoreService.getKeystore();
    this.signer = new PGPSigner(this.keystore);
    this.registry = await RegistryService.getRegistry();
  }

  resetPin(phone: string): Observable<any> {
    const params: HttpParams = new HttpParams().set('phoneNumber', phone);
    return this.httpClient.put(`${environment.cicUssdUrl}/pin`, { params });
  }

  getAccountStatus(phone: string): Observable<any> {
    const params: HttpParams = new HttpParams().set('phoneNumber', phone);
    return this.httpClient.get(`${environment.cicUssdUrl}/pin`, { params });
  }

  getLockedAccounts(offset: number, limit: number): Observable<any> {
    return this.httpClient.get(`${environment.cicUssdUrl}/accounts/locked/${offset}/${limit}`);
  }

  async changeAccountInfo(
    address: string,
    name: string,
    phoneNumber: string,
    age: string,
    type: string,
    bio: string,
    gender: string,
    businessCategory: string,
    userLocation: string,
    location: string,
    locationType: string,
    oldPhoneNumber: string
  ): Promise<boolean> {
    const accountInfo = await this.loadChangesToAccountStructure(
      name,
      phoneNumber,
      age,
      type,
      bio,
      gender,
      businessCategory,
      userLocation,
      location,
      locationType
    );
    const accountKey: string = await User.toKey(address);
    return new Promise((resolve) => {
      let status: boolean = false;
      this.getAccountDetailsFromMeta(accountKey)
        .pipe(first())
        .subscribe(
          async (res) => {
            const syncableAccount: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap();
            const update: Array<ArgPair> = [];
            for (const prop of Object.keys(accountInfo)) {
              update.push(new ArgPair(prop, accountInfo[prop]));
            }
            updateSyncable(update, 'client-branch', syncableAccount);
            await personValidation(syncableAccount.m.data);
            status = await this.updateMeta(syncableAccount, accountKey, this.headers);
            if (status && phoneNumber !== oldPhoneNumber) {
              await this.updatePhonePointers(address, oldPhoneNumber, phoneNumber);
            }
            resolve(status);
          },
          async (error) => {
            this.loggingService.sendErrorLevelMessage(
              'Cannot find account info in meta service',
              this,
              { error }
            );
            const syncableAccount: Syncable = new Syncable(accountKey, accountInfo);
            status = await this.updateMeta(syncableAccount, accountKey, this.headers);
            if (status && phoneNumber !== oldPhoneNumber) {
              await this.updatePhonePointers(address, oldPhoneNumber, phoneNumber);
            }
            resolve(status);
          }
        );
    });
  }

  async updateMeta(
    syncableAccount: Syncable,
    accountKey: string,
    headers: HttpHeaders
  ): Promise<boolean> {
    const envelope: Envelope = await this.wrap(syncableAccount, this.signer);
    const reqBody: string = envelope.toJSON();
    return new Promise((resolve) => {
      this.httpClient
        .put(`${environment.cicMetaUrl}/${accountKey}`, reqBody, { headers })
        .pipe(first())
        .subscribe(
          (res) => {
            this.loggingService.sendInfoLevelMessage(`Response: ${res}`);
            resolve(true);
          },
          (error) => {
            this.loggingService.sendErrorLevelMessage('Metadata update failed!', this, { error });
            resolve(false);
          }
        );
    });
  }

  getActions(): void {
    this.httpClient
      .get(`${environment.cicCacheUrl}/actions`)
      .pipe(first())
      .subscribe((res) => this.actionsList.next(res));
  }

  getActionById(id: string): Observable<any> {
    return this.httpClient.get(`${environment.cicCacheUrl}/actions/${id}`);
  }

  approveAction(id: string): Observable<any> {
    return this.httpClient.post(`${environment.cicCacheUrl}/actions/${id}`, { approval: true });
  }

  revokeAction(id: string): Observable<any> {
    return this.httpClient.post(`${environment.cicCacheUrl}/actions/${id}`, { approval: false });
  }

  getAccountDetailsFromMeta(userKey: string): Observable<any> {
    return this.httpClient.get(`${environment.cicMetaUrl}/${userKey}`, { headers: this.headers });
  }

  wrap(syncable: Syncable, signer: Signer): Promise<Envelope> {
    return new Promise<Envelope>(async (resolve, reject) => {
      syncable.setSigner(signer);
      syncable.onwrap = async (env) => {
        if (env === undefined) {
          reject();
          return;
        }
        resolve(env);
      };
      await syncable.sign();
    });
  }

  async loadAccounts(limit: number = 100, offset: number = 0): Promise<void> {
    try {
      const accountRegistry = await RegistryService.getAccountRegistry();
      const accountAddresses: Array<string> = await accountRegistry.last(offset + limit);
      this.loggingService.sendInfoLevelMessage(accountAddresses);
      if (typeof Worker !== 'undefined') {
        const worker = new Worker('@app/_workers/fetch-accounts.worker', { type: 'module' });
        worker.onmessage = ({ data }) => {
          if (data) {
            this.tokenService.load.subscribe(async (status: boolean) => {
              if (status) {
                data.balance = await this.tokenService.getTokenBalance(
                  data.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0]
                );
              }
            });
            this.addAccount(data, limit, false);
          }
        };
        worker.postMessage({
          addresses: accountAddresses.slice(offset, offset + limit),
          url: environment.cicMetaUrl,
          token: sessionStorage.getItem(btoa('CICADA_SESSION_TOKEN')),
        });
      } else {
        this.loggingService.sendInfoLevelMessage(
          'Web workers are not supported in this environment'
        );
        for (const accountAddress of accountAddresses.slice(offset, offset + limit)) {
          await this.getAccountByAddress(accountAddress, limit, false, false);
        }
      }
    } catch (error) {
      this.loggingService.sendErrorLevelMessage('Unable to load accounts.', 'user.service', error);
      throw error;
    }
  }

  async getAccountByAddress(
    accountAddress: string,
    limit: number = 100,
    history: boolean = false,
    top: boolean = true
  ): Promise<Observable<AccountDetails>> {
    const accountSubject: Subject<any> = new Subject<any>();
    this.getAccountDetailsFromMeta(await User.toKey(add0x(accountAddress)))
      .pipe(first())
      .subscribe(async (res) => {
        const account: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap();
        if (history) {
          try {
            // @ts-ignore
            this.historyList.next(Automerge.getHistory(account.m).reverse());
          } catch (error) {
            this.loggingService.sendErrorLevelMessage('No history found', this, { error });
          }
        }
        const accountInfo = account.m.data;
        await personValidation(accountInfo);
        this.tokenService.load.subscribe(async (status: boolean) => {
          if (status) {
            accountInfo.balance = await this.tokenService.getTokenBalance(
              accountInfo.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0]
            );
          }
        });
        accountInfo.vcard = vCard.parse(atob(accountInfo.vcard));
        await vcardValidation(accountInfo.vcard);
        this.addAccount(accountInfo, limit, top);
        accountSubject.next(accountInfo);
      });
    return accountSubject.asObservable();
  }

  async getAccountByPhone(
    phoneNumber: string,
    limit: number = 100
  ): Promise<Observable<AccountDetails>> {
    const accountSubject: Subject<any> = new Subject<any>();
    this.getAccountDetailsFromMeta(await Phone.toKey(phoneNumber))
      .pipe(first())
      .subscribe(async (res) => {
        const response: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap();
        const address: string = response.m.data;
        const account: Observable<AccountDetails> = await this.getAccountByAddress(address, limit);
        account.subscribe((result) => {
          accountSubject.next(result);
        });
      });
    return accountSubject.asObservable();
  }

  resetAccountsList(): void {
    this.accounts = [];
    this.accountsList.next(this.accounts);
  }

  getCategories(): void {
    this.httpClient
      .get(`${environment.cicMetaUrl}/categories`)
      .pipe(first())
      .subscribe((res: object) => this.categoriesList.next(res));
  }

  getCategoryByProduct(product: string, categories: object): string {
    const keywords = product.toLowerCase().split(' ');
    for (const keyword of keywords) {
      const queriedCategory: string = Object.keys(categories).find((key) =>
        categories[key].includes(keyword)
      );
      if (queriedCategory) {
        return queriedCategory;
      }
    }
    return 'other';
  }

  getAccountTypes(): Observable<any> {
    return this.httpClient.get(`${environment.cicMetaUrl}/accounttypes`);
  }

  getTransactionTypes(): Observable<any> {
    return this.httpClient.get(`${environment.cicMetaUrl}/transactiontypes`);
  }

  getGenders(): Observable<any> {
    return this.httpClient.get(`${environment.cicMetaUrl}/genders`);
  }

  addAccount(account: AccountDetails, cacheSize: number, top: boolean = true): void {
    const savedIndex = this.accounts.findIndex(
      (acc) =>
        acc.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0] ===
        account.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0]
    );
    if (top) {
      if (savedIndex === 0) {
        return;
      }
      if (savedIndex > 0) {
        this.accounts.splice(savedIndex, 1);
      }
      this.accounts.unshift(account);
      if (this.accounts.length > cacheSize) {
        this.accounts.length = Math.min(this.accounts.length, cacheSize);
      }
    } else {
      if (
        this.accounts.length >= cacheSize ||
        (savedIndex !== -1 && savedIndex === this.accounts.length - 1)
      ) {
        return;
      }
      if (savedIndex !== -1 && savedIndex < this.accounts.length - 1) {
        this.accounts.splice(savedIndex, 1);
      }
      this.accounts.push(account);
    }
    this.accountsList.next(this.accounts);
  }

  async loadChangesToAccountStructure(
    name: string,
    phoneNumber: string,
    age: string,
    type: string,
    bio: string,
    gender: string,
    businessCategory: string,
    userLocation: string,
    location: string,
    locationType: string
  ): Promise<AccountDetails> {
    const accountInfo: any = {
      vcard: {
        fn: [{}],
        n: [{}],
        tel: [{}],
      },
      location: {},
    };
    if (name) {
      accountInfo.vcard.fn[0].value = name;
      accountInfo.vcard.n[0].value = name.split(' ');
    }
    if (phoneNumber) {
      accountInfo.vcard.tel[0].value = phoneNumber;
    }
    if (bio) {
      accountInfo.products = [bio];
    }
    if (gender) {
      accountInfo.gender = gender;
    }
    if (age) {
      accountInfo.age = age;
    }
    if (type) {
      accountInfo.type = type;
    }
    if (businessCategory) {
      accountInfo.category = businessCategory;
    }
    if (location) {
      accountInfo.location.area = location;
    }
    if (userLocation) {
      accountInfo.location.area_name = userLocation;
    }
    if (locationType) {
      accountInfo.location.area_type = locationType;
    }
    await vcardValidation(accountInfo.vcard);
    accountInfo.vcard = btoa(vCard.generate(accountInfo.vcard));
    return accountInfo;
  }

  async updatePhonePointers(
    address: string,
    oldPhoneNumber: string,
    newPhoneNumber: string
  ): Promise<void> {
    const oldPhoneKey: string = await Phone.toKey(oldPhoneNumber);
    const newPhoneKey: string = await Phone.toKey(newPhoneNumber);
    const newPhoneData: Syncable = new Syncable(newPhoneKey, strip0x(address));
    const newPhoneSetStatus = await this.updateMeta(newPhoneData, newPhoneKey, this.headers);
    if (!newPhoneSetStatus) {
      const error = `Failed to update new phone number pointer: ${newPhoneNumber}`;
      this.loggingService.sendErrorLevelMessage(error, this, { error });
    } else {
      const oldPhoneData: Syncable = new Syncable(oldPhoneKey, '');
      const oldPhoneSetStatus = await this.updateMeta(oldPhoneData, oldPhoneKey, this.headers);
      if (!oldPhoneSetStatus) {
        const error = `Failed to update old phone number pointer: ${oldPhoneNumber}`;
        this.loggingService.sendErrorLevelMessage(error, this, { error });
      }
    }
  }
}

result-matching ""

    No results matching ""