import { assert } from '@0x/assert'; import { intervalUtils } from '@0x/utils'; import { OrderSet } from '../order_set'; import { OrderStore } from '../order_store'; import { SRAPollingOrderProviderOpts } from '../types'; import { BaseSRAOrderProvider } from './base_sra_order_provider'; export class SRAPollingOrderProvider extends BaseSRAOrderProvider { private readonly _assetPairKeyToPollingIntervalId: Map = new Map(); private readonly _pollingIntervalMs: number; /** * Instantiates a HTTP [Standard Relayer API](https://github.com/0xProject/standard-relayer-api) * Polling Order Provider * @param opts `SRAPollingOrderProviderOpts` containing the httpEndpoint to an SRA backend and polling options * @param orderStore The `OrderStore` where orders are added and removed from */ constructor(opts: SRAPollingOrderProviderOpts, orderStore: OrderStore) { super(orderStore, opts.httpEndpoint, opts.perPage); assert.isNumber('pollingIntervalMs', opts.pollingIntervalMs); this._pollingIntervalMs = opts.pollingIntervalMs; } /** * Creates a http polling subscription and fetches the current orders from SRA. * @param makerAssetData the maker asset Data * @param takerAssetData the taker asset Data */ public async createSubscriptionForAssetPairAsync(makerAssetData: string, takerAssetData: string): Promise { const assetPairKey = OrderStore.getKeyForAssetPair(makerAssetData, takerAssetData); // Do nothing if we already have a polling interval or websocket created for this asset pair if (this._assetPairKeyToPollingIntervalId.has(assetPairKey)) { return; } await this._fetchAndCreatePollingAsync(makerAssetData, takerAssetData); } /** * Destroys the order provider, removing any subscriptions */ public async destroyAsync(): Promise { for (const [assetPairKey, id] of this._assetPairKeyToPollingIntervalId) { clearInterval(id); this._assetPairKeyToPollingIntervalId.delete(assetPairKey); } } /** * Fetches all of the orders for both sides of the orderbook and stores them. A polling subscription * is created performing this action every pollingIntervalMs */ private async _fetchAndCreatePollingAsync(makerAssetData: string, takerAssetData: string): Promise { const assetPairKey = OrderStore.getKeyForAssetPair(makerAssetData, takerAssetData); // first time we have had this request, preload the local storage const orders = await this._fetchLatestOrdersAsync(makerAssetData, takerAssetData); // Set the OrderSet for the polling to diff against await this._updateStoreAsync({ added: orders, removed: [], assetPairKey }); // Create a HTTP polling subscription const pollingIntervalId = (this._createPollingSubscription(makerAssetData, takerAssetData) as any) as number; this._assetPairKeyToPollingIntervalId.set(assetPairKey, pollingIntervalId); } /** * Creates the polling interval fetching the orders, calculating the diff and updating the store */ private _createPollingSubscription(makerAssetData: string, takerAssetData: string): NodeJS.Timer { const assetPairKey = OrderStore.getKeyForAssetPair(makerAssetData, takerAssetData); const pollingIntervalId = intervalUtils.setAsyncExcludingInterval( async () => { const previousOrderSet = await this._orderStore.getOrderSetForAssetPairAsync(assetPairKey); const orders = await this._fetchLatestOrdersAsync(makerAssetData, takerAssetData); const orderSet = new OrderSet(); await orderSet.addManyAsync(orders); const diff = await previousOrderSet.diffAsync(orderSet); await this._updateStoreAsync({ ...diff, assetPairKey }); }, this._pollingIntervalMs, (_: Error) => { // TODO(dave4506) Add richer errors throw new Error(`Fetching latest orders for asset pair ${makerAssetData}/${takerAssetData}`); }, ); return pollingIntervalId; } }