diff options
Diffstat (limited to 'packages/website/ts/utils')
-rw-r--r-- | packages/website/ts/utils/analytics.ts | 104 | ||||
-rw-r--r-- | packages/website/ts/utils/configs.ts | 10 | ||||
-rw-r--r-- | packages/website/ts/utils/constants.ts | 2 | ||||
-rw-r--r-- | packages/website/ts/utils/doc_utils.ts | 6 | ||||
-rw-r--r-- | packages/website/ts/utils/error_reporter.ts | 23 | ||||
-rw-r--r-- | packages/website/ts/utils/fetch_utils.ts | 9 | ||||
-rw-r--r-- | packages/website/ts/utils/utils.ts | 70 |
7 files changed, 139 insertions, 85 deletions
diff --git a/packages/website/ts/utils/analytics.ts b/packages/website/ts/utils/analytics.ts index f4bfa083f..e5a1ddfa4 100644 --- a/packages/website/ts/utils/analytics.ts +++ b/packages/website/ts/utils/analytics.ts @@ -1,27 +1,83 @@ import * as _ from 'lodash'; -import * as ReactGA from 'react-ga'; -import { InjectedWeb3 } from 'ts/types'; -import { configs } from 'ts/utils/configs'; +import { ObjectMap, Order } from 'ts/types'; import { utils } from 'ts/utils/utils'; -export const analytics = { - init(): void { - ReactGA.initialize(configs.GOOGLE_ANALYTICS_ID); - }, - logEvent(category: string, action: string, label: string, value?: any): void { - ReactGA.event({ - category, - action, - label, - value, - }); - }, - async logProviderAsync(web3IfExists: InjectedWeb3): Promise<void> { - await utils.onPageLoadAsync(); - const providerType = - !_.isUndefined(web3IfExists) && !_.isUndefined(web3IfExists.currentProvider) - ? utils.getProviderType(web3IfExists.currentProvider) - : 'NONE'; - ReactGA.ga('set', 'dimension1', providerType); - }, -}; +export interface HeapAnalytics { + loaded: boolean; + identify(id: string, idType: string): void; + track(eventName: string, eventProperties?: ObjectMap<string | number>): void; + resetIdentity(): void; + addUserProperties(properties: ObjectMap<string | number>): void; + addEventProperties(properties: ObjectMap<string | number>): void; + removeEventProperty(property: string): void; + clearEventProperties(): void; +} +export class Analytics { + private _heap: HeapAnalytics; + public static init(): Analytics { + return new Analytics(Analytics.getHeap()); + } + public static getHeap(): HeapAnalytics { + const heap = (window as any).heap; + if (!_.isUndefined(heap)) { + return heap; + } else { + throw new Error('Could not find the Heap SDK on the page.'); + } + } + constructor(heap: HeapAnalytics) { + this._heap = heap; + } + // tslint:disable:no-floating-promises + // HeapAnalytics Wrappers + public identify(id: string, idType: string): void { + this._heapLoadedGuardAsync(() => this._heap.identify(id, idType)); + } + public track(eventName: string, eventProperties?: ObjectMap<string | number>): void { + this._heapLoadedGuardAsync(() => this._heap.track(eventName, eventProperties)); + } + public resetIdentity(): void { + this._heapLoadedGuardAsync(() => this._heap.resetIdentity()); + } + public addUserProperties(properties: ObjectMap<string | number>): void { + this._heapLoadedGuardAsync(() => this._heap.addUserProperties(properties)); + } + public addEventProperties(properties: ObjectMap<string | number>): void { + this._heapLoadedGuardAsync(() => this._heap.addEventProperties(properties)); + } + public removeEventProperty(property: string): void { + this._heapLoadedGuardAsync(() => this._heap.removeEventProperty(property)); + } + public clearEventProperties(): void { + this._heapLoadedGuardAsync(() => this._heap.clearEventProperties()); + } + // tslint:enable:no-floating-promises + // Custom methods + public trackOrderEvent(eventName: string, order: Order): void { + const orderLoggingData = { + takerTokenAmount: order.signedOrder.takerTokenAmount, + makeTokenAmount: order.signedOrder.makerTokenAmount, + takerToken: order.metadata.takerToken.symbol, + makerToken: order.metadata.makerToken.symbol, + }; + this.track(eventName, orderLoggingData); + } + /** + * Heap is not available as a UMD module, and additionally has the strange property of replacing itself with + * a different object once it's loaded. + * Instead of having an await call before every analytics use, we opt to have the awaiting logic in here by + * guarding every API call with the guard below. + */ + private async _heapLoadedGuardAsync(callback: () => void): Promise<void> { + if (this._heap.loaded) { + callback(); + return undefined; + } + await utils.onPageLoadPromise; + // HACK: Reset heap to loaded heap + this._heap = (window as any).heap; + callback(); + } +} + +export const analytics = Analytics.init(); diff --git a/packages/website/ts/utils/configs.ts b/packages/website/ts/utils/configs.ts index 97aabd13d..a1c64f9cb 100644 --- a/packages/website/ts/utils/configs.ts +++ b/packages/website/ts/utils/configs.ts @@ -1,11 +1,6 @@ -import * as _ from 'lodash'; -import { Environments, OutdatedWrappedEtherByNetworkId, PublicNodeUrlsByNetworkId } from 'ts/types'; +import { OutdatedWrappedEtherByNetworkId, PublicNodeUrlsByNetworkId } from 'ts/types'; const BASE_URL = window.location.origin; -const isDevelopment = _.includes( - ['https://0xproject.localhost:3572', 'https://localhost:3572', 'https://127.0.0.1'], - BASE_URL, -); const INFURA_API_KEY = 'T5WSC8cautR4KXyYgsRs'; export const configs = { @@ -19,9 +14,8 @@ export const configs = { DEFAULT_TRACKED_TOKEN_SYMBOLS: ['WETH', 'ZRX'], DOMAIN_STAGING: 'staging-0xproject.s3-website-us-east-1.amazonaws.com', DOMAIN_DOGFOOD: 'dogfood.0xproject.com', - DOMAIN_DEVELOPMENT: '0xproject.localhost:3572', + DOMAINS_DEVELOPMENT: ['0xproject.localhost:3572', 'localhost:3572', '127.0.0.1'], DOMAIN_PRODUCTION: '0xproject.com', - ENVIRONMENT: isDevelopment ? Environments.DEVELOPMENT : Environments.PRODUCTION, GOOGLE_ANALYTICS_ID: 'UA-98720122-1', LAST_LOCAL_STORAGE_FILL_CLEARANCE_DATE: '2017-11-22', LAST_LOCAL_STORAGE_TRACKED_TOKEN_CLEARANCE_DATE: '2018-7-5', diff --git a/packages/website/ts/utils/constants.ts b/packages/website/ts/utils/constants.ts index e43f541bf..20441cd75 100644 --- a/packages/website/ts/utils/constants.ts +++ b/packages/website/ts/utils/constants.ts @@ -58,7 +58,7 @@ export const constants = { PROJECT_URL_MAKER: 'https://makerdao.com', PROJECT_URL_ARAGON: 'https://aragon.one', PROJECT_URL_BLOCKNET: 'https://blocknet.co', - PROJECT_URL_0CEAN: 'http://the0cean.com', + PROJECT_URL_0CEAN: 'https://theocean.trade', PROJECT_URL_IMTOKEN: 'https://tokenlon.token.im/', PROJECT_URL_AUGUR: 'https://augur.net', PROJECT_URL_AUCTUS: 'https://auctus.org', diff --git a/packages/website/ts/utils/doc_utils.ts b/packages/website/ts/utils/doc_utils.ts index 7768835fb..1627b9b0c 100644 --- a/packages/website/ts/utils/doc_utils.ts +++ b/packages/website/ts/utils/doc_utils.ts @@ -1,5 +1,5 @@ import { DoxityDocObj, TypeDocNode } from '@0xproject/react-docs'; -import { logUtils } from '@0xproject/utils'; +import { fetchAsync, logUtils } from '@0xproject/utils'; import findVersions = require('find-versions'); import * as _ from 'lodash'; import { S3FileObject, VersionToFilePath } from 'ts/types'; @@ -16,7 +16,7 @@ export const docUtils = { return versionToFilePath; }, async getVersionFileNamesAsync(s3DocJsonRoot: string, folderName: string): Promise<string[]> { - const response = await fetch(s3DocJsonRoot); + const response = await fetchAsync(s3DocJsonRoot); if (response.status !== 200) { // TODO: Show the user an error message when the docs fail to load const errMsg = await response.text(); @@ -73,7 +73,7 @@ export const docUtils = { }, async getJSONDocFileAsync(filePath: string, s3DocJsonRoot: string): Promise<TypeDocNode | DoxityDocObj> { const endpoint = `${s3DocJsonRoot}/${filePath}`; - const response = await fetch(endpoint); + const response = await fetchAsync(endpoint); if (response.status !== 200) { // TODO: Show the user an error message when the docs fail to load const errMsg = await response.text(); diff --git a/packages/website/ts/utils/error_reporter.ts b/packages/website/ts/utils/error_reporter.ts index 2d0661b25..6008fffed 100644 --- a/packages/website/ts/utils/error_reporter.ts +++ b/packages/website/ts/utils/error_reporter.ts @@ -1,7 +1,7 @@ import { logUtils } from '@0xproject/utils'; -import { Environments } from 'ts/types'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; +import { utils } from 'ts/utils/utils'; // Suggested way to include Rollbar with Webpack // https://github.com/rollbar/rollbar.js/tree/master/examples/webpack @@ -12,7 +12,7 @@ const rollbarConfig = { itemsPerMinute: 10, maxItems: 500, payload: { - environment: configs.ENVIRONMENT, + environment: utils.getEnvironment(), client: { javascript: { source_map_enabled: true, @@ -40,21 +40,14 @@ import Rollbar = require('../../public/js/rollbar.umd.min.js'); const rollbar = Rollbar.init(rollbarConfig); export const errorReporter = { - async reportAsync(err: Error): Promise<any> { - if (configs.ENVIRONMENT === Environments.DEVELOPMENT) { + report(err: Error): void { + if (utils.isDevelopment()) { return; // Let's not log development errors to rollbar } - - return new Promise((resolve, _reject) => { - rollbar.error(err, (rollbarErr: Error) => { - if (rollbarErr) { - logUtils.log(`Error reporting to rollbar, ignoring: ${rollbarErr}`); - // We never want to reject and cause the app to throw because of rollbar - resolve(); - } else { - resolve(); - } - }); + rollbar.error(err, (rollbarErr: Error) => { + if (rollbarErr) { + logUtils.log(`Error reporting to rollbar, ignoring: ${rollbarErr}`); + } }); }, }; diff --git a/packages/website/ts/utils/fetch_utils.ts b/packages/website/ts/utils/fetch_utils.ts index 513f7e479..e9a88b6b3 100644 --- a/packages/website/ts/utils/fetch_utils.ts +++ b/packages/website/ts/utils/fetch_utils.ts @@ -1,4 +1,4 @@ -import { logUtils } from '@0xproject/utils'; +import { fetchAsync, logUtils } from '@0xproject/utils'; import * as _ from 'lodash'; import * as queryString from 'query-string'; @@ -9,8 +9,7 @@ const logErrorIfPresent = (response: Response, requestedURL: string) => { const errorText = `Error requesting url: ${requestedURL}, ${response.status}: ${response.statusText}`; logUtils.log(errorText); const error = Error(errorText); - // tslint:disable-next-line:no-floating-promises - errorReporter.reportAsync(error); + errorReporter.report(error); throw error; } }; @@ -19,14 +18,14 @@ export const fetchUtils = { async requestAsync(baseUrl: string, path: string, queryParams?: object): Promise<any> { const query = queryStringFromQueryParams(queryParams); const url = `${baseUrl}${path}${query}`; - const response = await fetch(url); + const response = await fetchAsync(url); logErrorIfPresent(response, url); const result = await response.json(); return result; }, async postAsync(baseUrl: string, path: string, body: object): Promise<Response> { const url = `${baseUrl}${path}`; - const response = await fetch(url, { + const response = await fetchAsync(url, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/packages/website/ts/utils/utils.ts b/packages/website/ts/utils/utils.ts index 9ca7f607b..739bb7b66 100644 --- a/packages/website/ts/utils/utils.ts +++ b/packages/website/ts/utils/utils.ts @@ -30,8 +30,6 @@ import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import * as u2f from 'ts/vendor/u2f_api'; -const isDogfood = (): boolean => _.includes(window.location.href, configs.DOMAIN_DOGFOOD); - export const utils = { assert(condition: boolean, message: string): void { if (!condition) { @@ -177,18 +175,6 @@ export const utils = { _.includes(errMsg, ledgerDenialErrMsg); return isUserDeniedErrMsg; }, - getCurrentEnvironment(): string { - switch (location.host) { - case configs.DOMAIN_DEVELOPMENT: - return 'development'; - case configs.DOMAIN_STAGING: - return 'staging'; - case configs.DOMAIN_PRODUCTION: - return 'production'; - default: - return 'production'; - } - }, getAddressBeginAndEnd(address: string): string { const truncatedAddress = `${address.substring(0, 6)}...${address.substr(-4)}`; // 0x3d5a...b287 return truncatedAddress; @@ -313,14 +299,13 @@ export const utils = { const baseUrl = `https://${window.location.hostname}${hasPort ? `:${port}` : ''}`; return baseUrl; }, - async onPageLoadAsync(): Promise<void> { + onPageLoadPromise: new Promise((resolve, _reject) => { if (document.readyState === 'complete') { - return; // Already loaded + resolve(); + return; } - return new Promise<void>((resolve, _reject) => { - window.onload = () => resolve(); - }); - }, + window.onload = resolve; + }), getProviderType(provider: Provider): Providers | string { const constructorName = provider.constructor.name; let parsedProviderName = constructorName; @@ -346,10 +331,10 @@ export const utils = { return parsedProviderName; }, getBackendBaseUrl(): string { - return isDogfood() ? configs.BACKEND_BASE_STAGING_URL : configs.BACKEND_BASE_PROD_URL; + return utils.isDogfood() ? configs.BACKEND_BASE_STAGING_URL : configs.BACKEND_BASE_PROD_URL; }, isDevelopment(): boolean { - return configs.ENVIRONMENT === Environments.DEVELOPMENT; + return _.includes(configs.DOMAINS_DEVELOPMENT, window.location.origin); }, isStaging(): boolean { return _.includes(window.location.href, configs.DOMAIN_STAGING); @@ -357,7 +342,27 @@ export const utils = { isExternallyInjected(providerType: ProviderType, injectedProviderName: string): boolean { return providerType === ProviderType.Injected && injectedProviderName !== constants.PROVIDER_NAME_PUBLIC; }, - isDogfood, + isDogfood(): boolean { + return _.includes(window.location.href, configs.DOMAIN_DOGFOOD); + }, + isProduction(): boolean { + return _.includes(window.location.href, configs.DOMAIN_PRODUCTION); + }, + getEnvironment(): Environments { + if (utils.isDogfood()) { + return Environments.DOGFOOD; + } + if (utils.isDevelopment()) { + return Environments.DEVELOPMENT; + } + if (utils.isStaging()) { + return Environments.STAGING; + } + if (utils.isProduction()) { + return Environments.PRODUCTION; + } + return Environments.UNKNOWN; + }, shouldShowJobsPage(): boolean { return this.isDevelopment() || this.isStaging() || this.isDogfood(); }, @@ -380,21 +385,28 @@ export const utils = { getFormattedAmountFromToken(token: Token, tokenState: TokenState): string { return utils.getFormattedAmount(tokenState.balance, token.decimals); }, + format(value: BigNumber, format: string): string { + const formattedAmount = numeral(value).format(format); + if (_.isNaN(formattedAmount)) { + // https://github.com/adamwdraper/Numeral-js/issues/596 + return numeral(new BigNumber(0)).format(format); + } + return formattedAmount; + }, getFormattedAmount(amount: BigNumber, decimals: number): string { const unitAmount = Web3Wrapper.toUnitAmount(amount, decimals); // if the unit amount is less than 1, show the natural number of decimal places with a max of 4 // if the unit amount is greater than or equal to 1, show only 2 decimal places - const precision = unitAmount.lt(1) - ? Math.min(constants.TOKEN_AMOUNT_DISPLAY_PRECISION, unitAmount.decimalPlaces()) - : 2; + const lessThanOnePrecision = Math.min(constants.TOKEN_AMOUNT_DISPLAY_PRECISION, unitAmount.decimalPlaces()); + const greaterThanOnePrecision = 2; + const precision = unitAmount.lt(1) ? lessThanOnePrecision : greaterThanOnePrecision; const format = `0,0.${_.repeat('0', precision)}`; - const formattedAmount = numeral(unitAmount).format(format); - return formattedAmount; + return utils.format(unitAmount, format); }, getUsdValueFormattedAmount(amount: BigNumber, decimals: number, price: BigNumber): string { const unitAmount = Web3Wrapper.toUnitAmount(amount, decimals); const value = unitAmount.mul(price); - return numeral(value).format(constants.NUMERAL_USD_FORMAT); + return utils.format(value, constants.NUMERAL_USD_FORMAT); }, openUrl(url: string): void { window.open(url, '_blank'); |