aboutsummaryrefslogtreecommitdiffstats
path: root/packages/instant/src/index.umd.ts
blob: 0acf3f2ad1666884ccc5787c39d0e332afdf1788 (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
import { AssetBuyer, BigNumber } from '@0x/asset-buyer';
import { assetDataUtils } from '@0x/order-utils';
import { Provider } from 'ethereum-types';
import * as _ from 'lodash';
import * as React from 'react';
import * as ReactDOM from 'react-dom';

import {
    DEFAULT_ZERO_EX_CONTAINER_SELECTOR,
    GIT_SHA as GIT_SHA_FROM_CONSTANT,
    INJECTED_DIV_CLASS,
    INJECTED_DIV_ID,
    NPM_PACKAGE_VERSION,
} from './constants';
import { assetMetaDataMap } from './data/asset_meta_data_map';
import { ZeroExInstantOverlay, ZeroExInstantOverlayProps } from './index';
import { Network, OrderSource } from './types';
import { analytics } from './util/analytics';
import { assert } from './util/assert';
import { providerFactory } from './util/provider_factory';
import { util } from './util/util';

const isInstantRendered = (): boolean => !!document.getElementById(INJECTED_DIV_ID);

const validateInstantRenderConfig = (config: ZeroExInstantConfig, selector: string) => {
    assert.isValidOrderSource('orderSource', config.orderSource);
    if (!_.isUndefined(config.defaultSelectedAssetData)) {
        assert.isHexString('defaultSelectedAssetData', config.defaultSelectedAssetData);
    }
    if (!_.isUndefined(config.additionalAssetMetaDataMap)) {
        assert.isValidAssetMetaDataMap('additionalAssetMetaDataMap', config.additionalAssetMetaDataMap);
    }
    if (!_.isUndefined(config.defaultAssetBuyAmount)) {
        assert.isNumber('defaultAssetBuyAmount', config.defaultAssetBuyAmount);
    }
    if (!_.isUndefined(config.networkId)) {
        assert.isNumber('networkId', config.networkId);
    }
    if (!_.isUndefined(config.availableAssetDatas)) {
        assert.areValidAssetDatas('availableAssetDatas', config.availableAssetDatas);
    }
    if (!_.isUndefined(config.onClose)) {
        assert.isFunction('onClose', config.onClose);
    }
    if (!_.isUndefined(config.zIndex)) {
        assert.isNumber('zIndex', config.zIndex);
    }
    if (!_.isUndefined(config.affiliateInfo)) {
        assert.isValidAffiliateInfo('affiliateInfo', config.affiliateInfo);
    }
    if (!_.isUndefined(config.provider)) {
        assert.isWeb3Provider('provider', config.provider);
    }
    if (!_.isUndefined(config.walletDisplayName)) {
        assert.isString('walletDisplayName', config.walletDisplayName);
    }
    if (!_.isUndefined(config.shouldDisablePushToHistory)) {
        assert.isBoolean('shouldDisablePushToHistory', config.shouldDisablePushToHistory);
    }
    if (!_.isUndefined(config.shouldDisableAnalyticsTracking)) {
        assert.isBoolean('shouldDisableAnalyticsTracking', config.shouldDisableAnalyticsTracking);
    }
    assert.isString('selector', selector);
};

// Render instant and return a callback that allows you to remove it from the DOM.
const renderInstant = (config: ZeroExInstantConfig, selector: string) => {
    const appendToIfExists = document.querySelector(selector);
    assert.assert(!_.isNull(appendToIfExists), `Could not find div with selector: ${selector}`);
    const appendTo = appendToIfExists as Element;
    const injectedDiv = document.createElement('div');
    injectedDiv.setAttribute('id', INJECTED_DIV_ID);
    injectedDiv.setAttribute('class', INJECTED_DIV_CLASS);
    appendTo.appendChild(injectedDiv);
    const closeInstant = () => {
        analytics.trackInstantClosed();
        if (!_.isUndefined(config.onClose)) {
            config.onClose();
        }
        appendTo.removeChild(injectedDiv);
    };
    const instantOverlayProps = {
        ...config,
        // If we are using the history API, just go back to close
        onClose: () => (config.shouldDisablePushToHistory ? closeInstant() : window.history.back()),
    };
    ReactDOM.render(React.createElement(ZeroExInstantOverlay, instantOverlayProps), injectedDiv);
    return closeInstant;
};

export interface ZeroExInstantConfig extends ZeroExInstantOverlayProps {
    shouldDisablePushToHistory?: boolean;
}

export const render = (config: ZeroExInstantConfig, selector: string = DEFAULT_ZERO_EX_CONTAINER_SELECTOR) => {
    validateInstantRenderConfig(config, selector);
    if (config.shouldDisablePushToHistory) {
        if (!isInstantRendered()) {
            renderInstant(config, selector);
        }
        return;
    }
    // Before we render, push to history saying that instant is showing for this part of the history.
    window.history.pushState({ zeroExInstantShowing: true }, '0x Instant');
    let removeInstant = renderInstant(config, selector);
    // If the integrator defined a popstate handler, save it to __zeroExInstantIntegratorsPopStateHandler
    // unless we have already done so on a previous render.
    const anyWindow = window as any;
    const popStateExistsAndNotSetPreviously = window.onpopstate && !anyWindow.__zeroExInstantIntegratorsPopStateHandler;
    anyWindow.__zeroExInstantIntegratorsPopStateHandler = popStateExistsAndNotSetPreviously
        ? anyWindow.onpopstate.bind(window)
        : util.boundNoop;
    const onPopStateHandler = (e: PopStateEvent) => {
        anyWindow.__zeroExInstantIntegratorsPopStateHandler(e);
        const newState = e.state;
        if (newState && newState.zeroExInstantShowing) {
            // We have returned to a history state that expects instant to be rendered.
            if (!isInstantRendered()) {
                removeInstant = renderInstant(config, selector);
            }
        } else {
            // History has changed to a different state.
            if (isInstantRendered()) {
                removeInstant();
            }
        }
    };
    window.onpopstate = onPopStateHandler;
};

export const assetDataForERC20TokenAddress = (tokenAddress: string): string => {
    assert.isETHAddressHex('tokenAddress', tokenAddress);
    return assetDataUtils.encodeERC20AssetData(tokenAddress);
};

export const hasMetaDataForAssetData = (assetData: string): boolean => {
    assert.isHexString('assetData', assetData);
    return assetMetaDataMap[assetData] !== undefined;
};

export const hasLiquidityForAssetDataAsync = async (
    assetData: string,
    orderSource: OrderSource,
    networkId: Network = Network.Mainnet,
    provider?: Provider,
): Promise<boolean> => {
    assert.isHexString('assetData', assetData);
    assert.isValidOrderSource('orderSource', orderSource);
    assert.isNumber('networkId', networkId);

    if (provider !== undefined) {
        assert.isWeb3Provider('provider', provider);
    }

    const bestProvider: Provider = provider || providerFactory.getFallbackNoSigningProvider(networkId);

    const assetBuyerOptions = { networkId };

    const assetBuyer = _.isString(orderSource)
        ? AssetBuyer.getAssetBuyerForStandardRelayerAPIUrl(bestProvider, orderSource, assetBuyerOptions)
        : AssetBuyer.getAssetBuyerForProvidedOrders(bestProvider, orderSource, assetBuyerOptions);

    const liquidity = await assetBuyer.getLiquidityForAssetDataAsync(assetData);
    return liquidity.ethValueAvailableInWei.gt(new BigNumber(0));
};

// Write version info to the exported object for debugging
export const GIT_SHA = GIT_SHA_FROM_CONSTANT;
export const NPM_VERSION = NPM_PACKAGE_VERSION;