aboutsummaryrefslogtreecommitdiffstats
path: root/packages/connect/src/http_client.ts
blob: 20a2f9cc01c23b5401e0f4d6d7ebf5dfbfba4c44 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
import { assert } from '@0xproject/assert';
import { schemas } from '@0xproject/json-schemas';
import { SignedOrder } from '@0xproject/types';
import { fetchAsync } from '@0xproject/utils';
import * as _ from 'lodash';
import * as queryString from 'query-string';

import { schemas as clientSchemas } from './schemas/schemas';
import {
    APIOrder,
    AssetPairsItem,
    AssetPairsRequestOpts,
    Client,
    HttpRequestOptions,
    HttpRequestType,
    OrderbookRequest,
    OrderbookResponse,
    OrderConfigRequest,
    OrderConfigResponse,
    OrdersRequestOpts,
    PagedRequestOpts,
    PaginatedCollection,
} from './types';
import { relayerResponseJsonParsers } from './utils/relayer_response_json_parsers';

const TRAILING_SLASHES_REGEX = /\/+$/;
const DEFAULT_PAGED_REQUEST_OPTS: PagedRequestOpts = {
    page: 1,
    perPage: 100,
};

/**
 * This class includes all the functionality related to interacting with a set of HTTP endpoints
 * that implement the standard relayer API v0
 */
export class HttpClient implements Client {
    private readonly _apiEndpointUrl: string;
    /**
     * Format parameters to be appended to http requests into query string form
     */
    private static _buildQueryStringFromHttpParams(params?: object): string {
        // if params are undefined or empty, return an empty string
        if (_.isUndefined(params) || _.isEmpty(params)) {
            return '';
        }
        // stringify the formatted object
        const stringifiedParams = queryString.stringify(params);
        return `?${stringifiedParams}`;
    }
    /**
     * Instantiates a new HttpClient instance
     * @param   url    The relayer API base HTTP url you would like to interact with
     * @return  An instance of HttpClient
     */
    constructor(url: string) {
        assert.isWebUri('url', url);
        this._apiEndpointUrl = url.replace(TRAILING_SLASHES_REGEX, ''); // remove trailing slashes
    }
    /**
     * Retrieve assetData pair info from the API
     * @param   requestOpts     Options specifying assetData information to retrieve and page information, defaults to { page: 1, perPage: 100 }
     * @return  The resulting AssetPairsItems that match the request
     */
    public async getAssetPairsAsync(requestOpts?: AssetPairsRequestOpts & PagedRequestOpts): Promise<PaginatedCollection<AssetPairsItem>> {
        if (!_.isUndefined(requestOpts)) {
            assert.doesConformToSchema('requestOpts', requestOpts, clientSchemas.AssetPairsRequestOptsSchema);
            assert.doesConformToSchema('requestOpts', requestOpts, clientSchemas.pagedRequestOptsSchema);
        }
        const httpRequestOpts = {
            params: _.defaults({}, requestOpts, DEFAULT_PAGED_REQUEST_OPTS),
        };
        const responseJson = await this._requestAsync('/assetData_pairs', HttpRequestType.Get, httpRequestOpts);
        const assetDataPairs = relayerResponseJsonParsers.parseAssetDataPairsJson(responseJson);
        return assetDataPairs;
    }
    /**
     * Retrieve orders from the API
     * @param   requestOpts     Options specifying orders to retrieve and page information, defaults to { page: 1, perPage: 100 }
     * @return  The resulting SignedOrders that match the request
     */
    public async getOrdersAsync(requestOpts?: OrdersRequestOpts & PagedRequestOpts): Promise<PaginatedCollection<APIOrder>> {
        if (!_.isUndefined(requestOpts)) {
            assert.doesConformToSchema('requestOpts', requestOpts, clientSchemas.ordersRequestOptsSchema);
            assert.doesConformToSchema('requestOpts', requestOpts, clientSchemas.pagedRequestOptsSchema);
        }
        const httpRequestOpts = {
            params: _.defaults({}, requestOpts, DEFAULT_PAGED_REQUEST_OPTS),
        };
        const responseJson = await this._requestAsync(`/orders`, HttpRequestType.Get, httpRequestOpts);
        const orders = relayerResponseJsonParsers.parseOrdersJson(responseJson);
        return orders;
    }
    /**
     * Retrieve a specific order from the API
     * @param   orderHash     An orderHash generated from the desired order
     * @return  The SignedOrder that matches the supplied orderHash
     */
    public async getOrderAsync(orderHash: string): Promise<APIOrder> {
        assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema);
        const responseJson = await this._requestAsync(`/order/${orderHash}`, HttpRequestType.Get);
        const order = relayerResponseJsonParsers.parseOrderJson(responseJson);
        return order;
    }
    /**
     * Retrieve an orderbook from the API
     * @param   request         An OrderbookRequest instance describing the specific orderbook to retrieve
     * @param   requestOpts     Options specifying page information, defaults to { page: 1, perPage: 100 }
     * @return  The resulting OrderbookResponse that matches the request
     */
    public async getOrderbookAsync(
        request: OrderbookRequest,
        requestOpts?: PagedRequestOpts,
    ): Promise<OrderbookResponse> {
        assert.doesConformToSchema('request', request, clientSchemas.orderBookRequestSchema);
        if (!_.isUndefined(requestOpts)) {
            assert.doesConformToSchema('requestOpts', requestOpts, clientSchemas.pagedRequestOptsSchema);
        }
        const httpRequestOpts = {
            params: _.defaults({}, request, requestOpts, DEFAULT_PAGED_REQUEST_OPTS),
        };
        const responseJson = await this._requestAsync('/orderbook', HttpRequestType.Get, httpRequestOpts);
        const orderbook = relayerResponseJsonParsers.parseOrderbookResponseJson(responseJson);
        return orderbook;
    }
    /**
     * Retrieve fee information from the API
     * @param   request     A OrderConfigRequest instance describing the specific fees to retrieve
     * @return  The resulting OrderConfigResponse that matches the request
     */
    public async getOrderConfigAsync(request: OrderConfigRequest): Promise<OrderConfigResponse> {
        assert.doesConformToSchema('request', request, clientSchemas.feesRequestSchema);
        const httpRequestOpts = {
            payload: request,
        };
        const responseJson = await this._requestAsync('/fees', HttpRequestType.Post, httpRequestOpts);
        const fees = relayerResponseJsonParsers.parseOrderConfigResponseJson(responseJson);
        return fees;
    }

    public async getFeeRecipientsAsync(): Promise<PaginatedCollection<string>> {
        // TODO
        return;
    }

    /**
     * Submit a signed order to the API
     * @param   signedOrder     A SignedOrder instance to submit
     */
    public async submitOrderAsync(signedOrder: SignedOrder): Promise<void> {
        assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
        const requestOpts = {
            payload: signedOrder,
        };
        await this._requestAsync('/order', HttpRequestType.Post, requestOpts);
    }
    private async _requestAsync(
        path: string,
        requestType: HttpRequestType,
        requestOptions?: HttpRequestOptions,
    ): Promise<any> {
        const params = _.get(requestOptions, 'params');
        const payload = _.get(requestOptions, 'payload');
        const query = HttpClient._buildQueryStringFromHttpParams(params);
        const url = `${this._apiEndpointUrl}${path}${query}`;
        const headers = new Headers({
            'content-type': 'application/json',
        });
        const response = await fetchAsync(url, {
            method: requestType,
            body: JSON.stringify(payload),
            headers,
        });
        const text = await response.text();
        if (!response.ok) {
            const errorString = `${response.status} - ${response.statusText}\n${requestType} ${url}\n${text}`;
            throw Error(errorString);
        }
        const result = !_.isEmpty(text) ? JSON.parse(text) : undefined;
        return result;
    }
}