import {Injectable} from '@angular/core'; import {BehaviorSubject, Observable, Subject, throwError, of} 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 {AccountIndex} from '@app/_eth'; import {MutableKeyStore, PGPSigner, Signer} from '@app/_pgp'; import {RegistryService} from '@app/_services/registry.service'; import {CICRegistry} from 'cic-client'; import {AuthService} from '@app/_services/auth.service'; import {personValidation, vcardValidation} from '@app/_helpers'; import {add0x} from '@src/assets/js/ethtx/dist/hex'; 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 = []; private accountsList: BehaviorSubject> = new BehaviorSubject>(this.accounts); accountsSubject: Observable> = this.accountsList.asObservable(); actions: Array = []; private actionsList: BehaviorSubject = new BehaviorSubject(this.actions); actionsSubject: Observable> = this.actionsList.asObservable(); constructor( private httpClient: HttpClient, private loggingService: LoggingService, private tokenService: TokenService, private registryService: RegistryService, private authService: AuthService ) { //this.authService.init().then(() => { // this.keystore = authService.mutableKeyStore; // this.signer = new PGPSigner(this.keystore); //}); //this.registry = registryService.getRegistry(); //this.registry.load(); } async load(): Promise { try { // TODO this method is called by ngOnInit so we need to // emit an observalbe or conver ngonInit to promise // TODO alig the load/init methods naming await this.authService.init(); // await this.registryService.load(); // TODO key store is defined this.keystore = this.authService.mutableKeyStore; this.signer = new PGPSigner(this.keystore); this.registry = this.registryService.getRegistry(); return of(0); } catch (error) { console.log('ERROR: Failed to initiialize User Service', error) return throwError(error); } } resetPin(phone: string): Observable { const params: HttpParams = new HttpParams().set('phoneNumber', phone); return this.httpClient.get(`${environment.cicUssdUrl}/pin`, {params}); } getAccountStatus(phone: string): Observable { const params: HttpParams = new HttpParams().set('phoneNumber', phone); return this.httpClient.get(`${environment.cicUssdUrl}/pin`, {params}); } getLockedAccounts(offset: number, limit: number): Observable { 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 ): Promise { const accountInfo: any = { vcard: { fn: [{}], n: [{}], tel: [{}] }, location: {} }; accountInfo.vcard.fn[0].value = name; accountInfo.vcard.n[0].value = name.split(' '); accountInfo.vcard.tel[0].value = phoneNumber; accountInfo.products = [bio]; accountInfo.gender = gender; accountInfo.age = age; accountInfo.type = type; accountInfo.category = businessCategory; accountInfo.location.area = location; accountInfo.location.area_name = userLocation; accountInfo.location.area_type = locationType; await vcardValidation(accountInfo.vcard); accountInfo.vcard = btoa(vCard.generate(accountInfo.vcard)); const accountKey: string = await User.toKey(address); this.getAccountDetailsFromMeta(accountKey).pipe(first()).subscribe(async res => { const syncableAccount: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap(); const update: Array = []; for (const prop in accountInfo) { update.push(new ArgPair(prop, accountInfo[prop])); } syncableAccount.update(update, 'client-branch'); await personValidation(syncableAccount.m.data); await this.updateMeta(syncableAccount, accountKey, this.headers); }, async error => { this.loggingService.sendErrorLevelMessage('Can\'t find account info in meta service', this, {error}); const syncableAccount: Syncable = new Syncable(accountKey, accountInfo); await this.updateMeta(syncableAccount, accountKey, this.headers); }); return accountKey; } async updateMeta(syncableAccount: Syncable, accountKey: string, headers: HttpHeaders): Promise { const envelope: Envelope = await this.wrap(syncableAccount , this.signer); const reqBody: string = envelope.toJSON(); this.httpClient.put(`${environment.cicMetaUrl}/${accountKey}`, reqBody , { headers }).pipe(first()).subscribe(res => { this.loggingService.sendInfoLevelMessage(`Response: ${res}`); }); } getActions(): void { this.httpClient.get(`${environment.cicCacheUrl}/actions`).pipe(first()).subscribe(res => this.actionsList.next(res)); } getActionById(id: string): Observable { return this.httpClient.get(`${environment.cicCacheUrl}/actions/${id}`); } approveAction(id: string): Observable { return this.httpClient.post(`${environment.cicCacheUrl}/actions/${id}`, { approval: true }); } revokeAction(id: string): Observable { return this.httpClient.post(`${environment.cicCacheUrl}/actions/${id}`, { approval: false }); } getAccountDetailsFromMeta(userKey: string): Observable { return this.httpClient.get(`${environment.cicMetaUrl}/${userKey}`, { headers: this.headers }); } wrap(syncable: Syncable, signer: Signer): Promise { return new Promise(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 { this.resetAccountsList(); let accountAddresses: Array; try { const accountIndexAddress: string = await this.registry.getContractAddressByName('AccountRegistry'); const accountIndexQuery = new AccountIndex(accountIndexAddress); const totalAccounts = await accountIndexQuery.totalAccounts() const accountAddresses = await accountIndexQuery.last(totalAccounts); this.loggingService.sendInfoLevelMessage(accountAddresses); } catch (error){ // TODO real logging: console.log("ERROR: failed to load accounts \n", error) } for (const accountAddress of accountAddresses.slice(offset, offset + limit)) { await this.getAccountByAddress(accountAddress, limit); } } async getAccountByAddress(accountAddress: string, limit: number = 100): Promise> { let accountSubject: Subject = new Subject(); this.getAccountDetailsFromMeta(await User.toKey(add0x(accountAddress))).pipe(first()).subscribe(async res => { const account: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap(); const accountInfo = account.m.data; await personValidation(accountInfo); 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.accounts.unshift(accountInfo); if (this.accounts.length > limit) { this.accounts.length = limit; } this.accountsList.next(this.accounts); accountSubject.next(accountInfo); }); return accountSubject.asObservable(); } async getAccountByPhone(phoneNumber: string, limit: number = 100): Promise> { let accountSubject: Subject = new Subject(); 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 = await this.getAccountByAddress(address, limit); account.subscribe(result => { accountSubject.next(result); }); }); return accountSubject.asObservable(); } resetAccountsList(): void { this.accounts = []; this.accountsList.next(this.accounts); } searchAccountByName(name: string): any { return; } getCategories(): Observable { return this.httpClient.get(`${environment.cicMetaUrl}/categories`); } getCategoryByProduct(product: string): Observable { return this.httpClient.get(`${environment.cicMetaUrl}/categories/${product.toLowerCase()}`); } getAccountTypes(): Observable { return this.httpClient.get(`${environment.cicMetaUrl}/accounttypes`); } getTransactionTypes(): Observable { return this.httpClient.get(`${environment.cicMetaUrl}/transactiontypes`); } getGenders(): Observable { return this.httpClient.get(`${environment.cicMetaUrl}/genders`); } }