src/main/Member.js
// @flow
import AuthHttpClient from '../http/AuthHttpClient';
import HttpClient from '../http/HttpClient';
import KeyStoreCryptoEngine from '../security/engines/KeyStoreCryptoEngine';
import Util from '../Util';
import Account from './Account';
import type {
Alias,
Balance,
BankInfo,
Key,
KeyLevel,
OauthBankAuthorization,
RecoveryRule,
Transaction,
Notification,
Signature,
TransferDestination,
StandingOrder,
} from '..';
/**
* Represents a Token member.
*/
export class Member {
_id: string;
_client: AuthHttpClient;
_unauthenticatedClient: HttpClient;
_options: Object;
constructor(options: {
// Token environment to target
env: string,
memberId: string,
// CryptoEngine for the member's keys
cryptoEngine: Class<KeyStoreCryptoEngine>,
// dev key
developerKey?: string,
// callback to invoke on any cross-cutting RPC
globalRpcErrorCallback?: ({name: string, message: string}) => void,
// enable HTTP error logging if true
loggingEnabled?: boolean,
// override the default SDK URL
customSdkUrl?: string,
// custom HTTP response interceptor for axios
customResponseInterceptor?: Object,
}) {
const {memberId} = options;
this._id = memberId;
this._options = options;
}
/**
* Gets the member ID.
*
* @return the member ID
*/
memberId(): string {
return this._id;
}
/**
* Gets the member's last hash.
*
* @return the hash of the member object
*/
lastHash(): string {
return Util.callAsync(this.lastHash, async () => {
const member = await this._getMember();
return member.lastHash;
});
}
/**
* Gets all of the member's aliases.
*
* @return the member's aliases
*/
aliases(): Promise<Array<Alias>> {
return Util.callAsync(this.aliases, async () => {
const res = await this._client.getAliases();
return res.data.aliases || [];
});
}
/**
* Gets the member's first alias.
*
* @return the member's first alias
*/
firstAlias(): Promise<?Alias> {
return Util.callAsync(this.firstAlias, async () => {
const res = await this._client.getAliases();
return res.data.aliases && res.data.aliases[0];
});
}
/**
* Gets the member's public keys.
*
* @return keys objects
*/
keys(): Promise<Array<Key>> {
return Util.callAsync(this.keys, async () => {
const member = await this._getMember();
return member.keys || [];
});
}
/**
* Approves a new key for this member.
*
* @param key - key to add
* @return empty promise
*/
approveKey(key: Key): Promise<void> {
return Util.callAsync(this.approveKey, async () => {
const prevHash = await this.lastHash();
await this._client.approveKey(prevHash, key);
});
}
/**
* Approves new keys for this member.
*
* @param keys - keys to add
* @return empty promise
*/
approveKeys(keys: Array<Key>): Promise<void> {
return Util.callAsync(this.approveKeys, async () => {
const prevHash = await this.lastHash();
await this._client.approveKeys(prevHash, keys);
});
}
/**
* Removes a key from this member.
*
* @param keyId - keyId to remove. Note, keyId is the hash of the pk
* @return empty promise
*/
removeKey(keyId: string): Promise<void> {
return Util.callAsync(this.removeKey, async () => {
const prevHash = await this.lastHash();
await this._client.removeKey(prevHash, keyId);
});
}
/**
* Removes keys from this member.
*
* @param keyIds - keyIds to remove. Note, keyId is the hash of the pk
* @return empty promise
*/
removeKeys(keyIds: Array<string>): Promise<void> {
return Util.callAsync(this.removeKeys, async () => {
const prevHash = await this.lastHash();
await this._client.removeKeys(prevHash, keyIds);
});
}
/**
* Adds an alias to this member.
*
* @param alias - alias to add
* @return empty promise
*/
addAlias(alias: Alias): Promise<void> {
return this.addAliases([alias]);
}
/**
* Adds aliases to this member.
*
* @param aliases - aliases to add
* @return empty promise
*/
addAliases(aliases: Array<Alias>): Promise<void> {
return Util.callAsync(this.addAliases, async () => {
const member = await this._getMember();
const normalized = await Promise.all(aliases.map(alias =>
this._normalizeAlias(alias, member.partnerId)));
const prevHash = await this.lastHash();
await this._client.addAliases(prevHash, normalized);
});
}
/**
* Removes an alias from the member.
*
* @param alias - alias to remove
* @return empty promise
*/
removeAlias(alias: Alias): Promise<void> {
return Util.callAsync(this.removeAlias, async () => {
const prevHash = await this.lastHash();
await this._client.removeAlias(prevHash, alias);
});
}
/**
* Removes aliases from the member.
*
* @param aliases - aliases to remove
* @return empty promise
*/
removeAliases(aliases: Array<Alias>): Promise<void> {
return Util.callAsync(this.removeAliases, async () => {
const prevHash = await this.lastHash();
await this._client.removeAliases(prevHash, aliases);
});
}
/**
* Set the 'normal consumer' rule as member's recovery rule.
* (As of Nov 2017, this rule was: To recover, verify an alias.)
*
* @return promise containing RecoveryRule proto buffer.
*/
useDefaultRecoveryRule(): Promise<RecoveryRule> {
return Util.callAsync(this.useDefaultRecoveryRule, async () => {
const agentResponse = await this._client.getDefaultRecoveryAgent();
const prevHash = await this.lastHash();
const rule = {
recoveryRule: {
primaryAgent: agentResponse.data.memberId,
},
};
const res = await this._client.addRecoveryRule(prevHash, rule);
return res.data.member.recoveryRule;
});
}
/**
* Gets the info of a bank, including a link for pairing accounts at this bank.
*
* @param bankId - ID of the bank
* @return info
*/
getBankInfo(bankId: string): Promise<BankInfo> {
return Util.callAsync(this.getBankInfo, async () => {
const res = await this._client.getBankInfo(bankId);
return res.data.info;
});
}
/**
* Looks up the balance of an account.
*
* @param accountId - ID of the account
* @param keyLevel - key level
* @return Promise of get balance response object
*/
getBalance(
accountId: string,
keyLevel: KeyLevel
): Promise<Balance> {
return Util.callAsync(this.getBalance, async () => {
const res = await this._client.getBalance(accountId, keyLevel);
switch (res.data.status){
case 'SUCCESSFUL_REQUEST':
return res.data.balance;
case 'MORE_SIGNATURES_NEEDED':
throw new Error('Balance step up required');
default:
throw new Error(res.data.status);
}
});
}
/**
* Looks up the balances of an array of accounts.
*
* @param accountIds - array of account IDs
* @param keyLevel - key level
* @return Promise of get balances response object
*/
getBalances(
accountIds: Array<string>,
keyLevel: KeyLevel
): Promise<Array<Balance>> {
return Util.callAsync(this.getBalances, async () => {
const res = await this._client.getBalances(accountIds, keyLevel);
return res.data.response && res.data.response.map(b => {
switch (b.status) {
case 'SUCCESSFUL_REQUEST':
return b.balance;
case 'MORE_SIGNATURES_NEEDED':
throw new Error('Balance step up required');
default:
throw new Error(b.status);
}
});
});
}
/**
* Looks up a transaction.
*
* @param accountId - ID of the account
* @param transactionId - which transaction to look up
* @param keyLevel - key level
* @return the Transaction
*/
getTransaction(
accountId: string,
transactionId: string,
keyLevel: KeyLevel
): Promise<Transaction> {
return Util.callAsync(this.getTransaction, async () => {
const res = await this._client.getTransaction(accountId, transactionId, keyLevel);
switch (res.data.status){
case 'SUCCESSFUL_REQUEST':
return res.data.transaction;
case 'MORE_SIGNATURES_NEEDED':
throw new Error('Transaction step up required');
default:
throw new Error(res.data.status);
}
});
}
/**
* Looks up all of the member's transactions for an account.
*
* @param accountId - ID of the account
* @param offset - where to start looking
* @param limit - how many to retrieve
* @param keyLevel - key level
* @return Transactions
*/
getTransactions(
accountId: string,
offset: string,
limit: number,
keyLevel: KeyLevel,
startDate: string,
endDate: string
): Promise<{transactions: Array<Transaction>, offset: string}> {
return Util.callAsync(this.getTransactions, async () => {
const res = await this._client.getTransactions(accountId, offset, limit, keyLevel, startDate, endDate);
switch (res.data.status){
case 'SUCCESSFUL_REQUEST':
return {
transactions: res.data.transactions || [],
offset: res.data.offset,
};
case 'MORE_SIGNATURES_NEEDED':
throw new Error('Transactions step up required');
default:
throw new Error(res.data.status);
}
});
}
/**
* Confirms if an account has sufficient funds for a purchase.
*
* @param accountId
* @param amount
* @param currency
* @return true if account has sufficient funds
*/
confirmFunds(
accountId: string,
amount: number | string,
currency: string
): Promise<boolean> {
return Util.callAsync(this.confirmFunds, async () => {
const res = await this._client.confirmFunds(accountId, amount, currency);
return res.data.fundsAvailable;
});
}
/**
* Signs a token payload with specified level of keys.
*
* @param tokenPayload
* @param keyLevel
* @returns the proto signature
*/
signTokenPayload(
tokenPayload: Object,
keyLevel: KeyLevel,
): Promise<Signature> {
return Util.callAsync(this.signTokenPayload, async () => {
return await this._client.tokenOperationSignature(tokenPayload, 'endorsed', keyLevel);
});
}
/**
* Deletes the member.
*
* @return response to the api call
*/
deleteMember(): Promise<void> {
return Util.callAsync(this.deleteMember, async () => {
await this._client.deleteMember();
});
}
/**
* Resolves transfer destinations for the given account ID.
*
* @param accountId - ID of account to resolve destinations for
* @return resolved transfer destinations
*/
resolveTransferDestinations(accountId: string): Promise<TransferDestination> {
return Util.callAsync(this.resolveTransferDestinations, async () => {
const res = await this._client.resolveTransferDestinations(accountId);
return res.transferDestinations;
});
}
/**
* Creates a test bank account in a fake bank
*
* @param balance - balance of the account
* @param currency - currency of the account
* @return bank authorization to use with linkAccounts
*/
createTestBankAccount(
balance: number,
currency: string
): Promise<OauthBankAuthorization> {
return Util.callAsync(this.createTestBankAccount, async () => {
const res = await this._client.createTestBankAccount(balance, currency);
return res.data.authorization;
});
}
/**
* Creates a test bank account in a fake bank and link it
*
* @param balance - balance of the account
* @param currency - currency of the account
* @return bank authorization to use with linkAccounts
*/
createAndLinkTestBankAccount(
balance: number,
currency: string
): Promise<Account> {
return Util.callAsync(this.createTestBankAccount, async () => {
const res = await this._client.createTestBankAccount(balance, currency);
const res2 = await this._client.linkAccountsOauth(res.data.authorization);
return res2.data.accounts && new Account(res2.data.accounts[0], this);
});
}
/**
* Gets test bank notification.
*
* @param subscriberId - ID of subscriber
* @param notificationId - ID of notification
* @return response to the API call
*/
getTestBankNotification(
subscriberId: string,
notificationId: string
): Promise<Notification> {
return Util.callAsync(this.getTestBankNotification, async () => {
const res = await this._client.getTestBankNotification(subscriberId, notificationId);
return res.data.notification;
});
}
/**
* Gets test bank notifications.
*
* @param subscriberId - ID of subscriber
* @return response to the API call
*/
getTestBankNotifications(subscriberId: string): Promise<Array<Notification>> {
return Util.callAsync(this.getTestBankNotifications, async () => {
const res = await this._client.getTestBankNotifications(subscriberId);
return res.data.notifications || [];
});
}
/**
* Looks up an existing standing order for a given account.
*
* @param accountId
* @param standingOrderId
* @param keyLevel
* @returns standing order
*/
getStandingOrder(
accountId: string,
standingOrderId: string,
keyLevel: KeyLevel,
): Promise<StandingOrder> {
return Util.callAsync(this.getStandingOrder, async () => {
const res = await this._client
.getStandingOrder(accountId, standingOrderId, keyLevel);
switch (res.data.status) {
case 'SUCCESSFUL_REQUEST':
return res.data.standingOrder;
case 'MORE_SIGNATURES_NEEDED':
throw new Error('Standing order step up required');
default:
throw new Error(res.data.status);
}
});
}
/**
* Looks up standing orders for a given account.
*
* @param accountId
* @param offset
* @param limit
* @param keyLevel
* @returns standing orders
*/
getStandingOrders(
accountId: string,
offset: string,
limit: number,
keyLevel: KeyLevel,
): Promise<{standingOrders: Array<StandingOrder>, offset: string}> {
return Util.callAsync(this.getStandingOrders, async () => {
const res = await this._client
.getStandingOrders(accountId, offset, limit, keyLevel);
switch (res.data.status) {
case 'SUCCESSFUL_REQUEST':
return {
standingOrders: res.data.standingOrders || [],
offset: res.data.offset,
};
case 'MORE_SIGNATURES_NEEDED':
throw new Error('Standing order step up required');
default:
throw new Error(res.data.status);
}
});
}
_getMember(): Object {
return Util.callAsync(this._getMember, async () => {
const res = await this._unauthenticatedClient.getMember(this._id);
return res.data.member;
});
}
_normalizeAlias(alias: Alias, partnerId: string): Promise<Alias> {
return Util.callAsync(this._normalizeAlias, async () => {
const normalized =
(await this._unauthenticatedClient.normalizeAlias(alias)).data.alias;
if (partnerId && partnerId !== 'token') {
// Realm must equal member's partner ID if affiliated
if (normalized.realm && normalized.realm !== partnerId) {
throw new Error('Alias realm must equal partner ID: ' + partnerId);
}
normalized.realm = partnerId;
}
if (alias.realmId) {
normalized.realmId = alias.realmId;
}
return normalized;
});
}
}
export default Member;