Home Reference Source

src/http/HttpClient.js

import config from '../config.json';
import ErrorHandler from './ErrorHandler';
import DeveloperHeader from './DeveloperHeader';
import VersionHeader from './VersionHeader';
import setAdditionalHeaders from './setAdditionalHeaders';
import Util from '../Util';
import axios from 'axios';

/**
 * Client for making unauthenticated requests to the Token gateway.
 */
export class HttpClient {
    constructor({
        env = 'prd',
        developerKey,
        globalRpcErrorCallback,
        loggingEnabled,
        customSdkUrl,
        customResponseInterceptor,
    }) {
        if (!(config.urls[env] || customSdkUrl)) {
            throw new Error('Invalid environment string. Please use one of: ' +
                JSON.stringify(config.urls));
        }
        this._instance = axios.create({
            baseURL: customSdkUrl || config.urls[env],
        });
        if (loggingEnabled) {
            Util.setUpHttpErrorLogging(this._instance);
        }
        Util.setUpCustomResponseInterceptor(this._instance, customResponseInterceptor);
        const versionHeader = new VersionHeader();
        const developerHeader = new DeveloperHeader(developerKey);
        this._instance.interceptors.request.use(request => {
            versionHeader.addVersionHeader(request);
            developerHeader.addDeveloperHeader(request);
            setAdditionalHeaders(request);
            return request;
        });

        const errorHandler = new ErrorHandler(globalRpcErrorCallback);
        this._instance.interceptors.response.use(null, error => {
            throw errorHandler.handleError(error);
        });
    }

    async normalizeAlias(alias) {
        const request = {
            method: 'get',
            url: `/aliases/normalize/${alias.type}/${alias.value}/${alias.realm || 'token'}`,
        };
        return this._instance(request);
    }

    /**
     * Gets a member given an alias.
     *
     * @param {Object} alias - alias to lookup
     * @return {Object} response to the API call
     */
    async resolveAlias(alias) {
        const request = {
            method: 'get',
            url: `/resolve-alias?alias.value=${alias.value}&alias.type=${alias.type}&alias.realm=${alias.realm || ''}&alias.realmId=${alias.realmId || ''}`, // eslint-disable-line max-len
        };
        return this._instance(request);
    }

    /**
     * Gets the member's information.
     *
     * @param {string} memberId - member ID to lookup the member for
     * @return {Object} response to the API call
     */
    async getMember(memberId) {
        const request = {
            method: 'get',
            url: `/members/${memberId}`,
        };
        return this._instance(request);
    }

    /**
     * Gets banks or countries.
     *
     * @param {Object} options - optional parameters
     * @param {boolean} getCountries - get countries instead of banks if true
     * @return {Object} response to the API call
     */
    async getBanksOrCountries(options = {}, getCountries) {
        const formattedOptions = Object.assign({}, {
            // Can be at most 1000
            ids: options.ids || [],
            supportedPaymentNetworks: options.supportedPaymentNetworks || [],
            search: options.search || '',
            country: options.country || '',
            countries: options.countries || [],
            // Default to 1 if not specified
            page: options.page,
            // Can be at most 200, default to 200 if not specified
            perPage: options.perPage,
            // Optional provider
            provider: options.provider || '',
            // Optional destination country
            destinationCountry: options.destinationCountry || '',
            // (Optional) Filter for banks that support or don't support certain features. See Bank for the feature keys we support.
            // Set "true" for banks that support the feature or "false" for banks that don't support the feature.
            // e.g. ["supports_linking_uri": "true"] means only banks who supports the linking uri feature.
            bankFeatures: options.bankFeatures || '',
            // Optional tpp member id
            memberId: options.memberId || '',
            // Optional add headers such as dev key to this request
            headers: options.headers,
        });
        const {
            ids,
            supportedPaymentNetworks,
            search,
            country,
            countries,
            page,
            perPage,
            provider,
            destinationCountry,
            bankFeatures,
            memberId,
            headers,
        } = formattedOptions;
        let url = `/banks${getCountries ? '/countries' : ''}?`;
        for (const id of ids) {
            url += `ids=${encodeURIComponent(id)}&`;
        }
        for (const paymentNetwork of supportedPaymentNetworks) {
            url += `supportedPaymentNetworks=${encodeURIComponent(paymentNetwork)}&`;
        }
        if (search) url += `search=${encodeURIComponent(search)}&`;
        if (country) url += `country=${encodeURIComponent(country)}&`;
        for (const country of countries) {
            url += `countries=${encodeURIComponent(country)}&`;
        }
        if (page) url += `page=${encodeURIComponent(page)}&`;
        if (perPage) url += `perPage=${encodeURIComponent(perPage)}&`;
        if (provider) url += `provider=${encodeURIComponent(provider)}&`;
        if (destinationCountry)
            url += `destinationCountry=${encodeURIComponent(destinationCountry)}&`;
        if (memberId) url += `memberId=${encodeURIComponent(memberId)}&`;
        if (bankFeatures) {
            for (const key in bankFeatures) {
                url += `bank_features.${key}.value=${encodeURIComponent(bankFeatures[key])}&`;
            }
        }

        const request = {
            method: 'get',
            url: url,
            requestHeaders: headers,
        };
        return this._instance(request);
    }

    /**
     * Returns the Token member.
     *
     * @return {Promise} response to the API call
     */
    async getTokenMember() {
        const resolveAliasRes = await this.resolveAlias(Util.tokenAlias());
        const tokenMemberId = resolveAliasRes.data.member.id;
        const getMemberRes = await this.getMember(tokenMemberId);
        return getMemberRes.data.member;
    }

    /**
     * Creates a memberId.
     *
     * @param  {string} memberType - type of member to create. 'PERSONAL' if undefined
     * @param  {string} tokenRequestId - (optional) token request ID if the member is being claimed
     * @param  {string} realmId - (optional) member id of the Member to which this new member will belong
     * @return {Object} response to the API call
     */
    async createMemberId(memberType, tokenRequestId, realmId) {
        if (memberType === undefined) {
            memberType = 'PERSONAL';
        }
        if (tokenRequestId && memberType !== 'TRANSIENT') {
            throw new Error('Can only claim transient members');
        }
        const req = {
            memberType,
            tokenRequestId,
            realmId,
        };
        const request = {
            method: 'post',
            url: '/members',
            data: req,
        };
        return this._instance(request);
    }

    /**
     * Approve a first key for a member (self signed).
     *
     * @param {string} memberId - ID of the member
     * @param {Object} key - key to approve
     * @param {Object} cryptoEngine - engine to use for signing
     * @return {Object} response to the API call
     */
    async approveFirstKey(memberId, key, cryptoEngine) {
        const signer = await cryptoEngine.createSigner(config.KeyLevel.PRIVILEGED);
        const update = {
            memberId: memberId,
            operations: [
                {
                    addKey: {
                        key: {
                            id: key.id,
                            publicKey: Util.strKey(key.publicKey),
                            level: key.level,
                            algorithm: key.algorithm,
                            ...key.expiresAtMs && {expiresAtMs: key.expiresAtMs},
                        },
                    },
                },
            ],
        };
        const req = {
            update,
            updateSignature: {
                memberId: memberId,
                keyId: signer.getKeyId(),
                signature: await signer.signJson(update),
            },
        };
        const request = {
            method: 'post',
            url: `/members/${memberId}/updates`,
            data: req,
        };
        return this._instance(request);
    }

    /**
     * Approve the first keys for a member (self signed).
     *
     * @param {string} memberId - ID of the member
     * @param {Array} keys - keys to approve
     * @param {Object} cryptoEngine - engine to use for signing
     * @return {Object} response to the API call
     */
    async approveFirstKeys(memberId, keys, cryptoEngine) {
        const signer = await cryptoEngine.createSigner(config.KeyLevel.PRIVILEGED);
        const update = {
            memberId: memberId,
            operations: keys.map(key => ({
                addKey: {
                    key: {
                        id: key.id,
                        publicKey: Util.strKey(key.publicKey),
                        level: key.level,
                        algorithm: key.algorithm,
                        ...key.expiresAtMs && {expiresAtMs: key.expiresAtMs},
                    },
                },
            })),
        };
        const req = {
            update,
            updateSignature: {
                memberId: memberId,
                keyId: signer.getKeyId(),
                signature: await signer.signJson(update),
            },
        };
        const request = {
            method: 'post',
            url: `/members/${memberId}/updates`,
            data: req,
        };
        return this._instance(request);
    }
}

export default HttpClient;