diff --git a/README.md b/README.md index e6108756..8d9d7492 100644 --- a/README.md +++ b/README.md @@ -43,16 +43,16 @@ yarn add bnc-assist #### Script Tag The library uses [semantic versioning](https://semver.org/spec/v2.0.0.html). -The current version is 0.7.5. +The current version is 0.8.0. There are minified and non-minified versions. Put this script at the top of your `` ```html - + - + ``` ### Initialize the Library diff --git a/lib/images/coinbase.png b/lib/images/coinbase.png new file mode 100644 index 00000000..c3c3d68a Binary files /dev/null and b/lib/images/coinbase.png differ diff --git a/lib/images/coinbase@2x.png b/lib/images/coinbase@2x.png new file mode 100644 index 00000000..57c14ef3 Binary files /dev/null and b/lib/images/coinbase@2x.png differ diff --git a/lib/images/mobile-wallet-required-white.png b/lib/images/mobile-wallet-required-white.png new file mode 100644 index 00000000..c8ba113d Binary files /dev/null and b/lib/images/mobile-wallet-required-white.png differ diff --git a/lib/images/mobile-wallet-required-white@2x.png b/lib/images/mobile-wallet-required-white@2x.png new file mode 100644 index 00000000..62e0cf73 Binary files /dev/null and b/lib/images/mobile-wallet-required-white@2x.png differ diff --git a/lib/images/mobile-wallet-required.png b/lib/images/mobile-wallet-required.png new file mode 100644 index 00000000..3586ca84 Binary files /dev/null and b/lib/images/mobile-wallet-required.png differ diff --git a/lib/images/mobile-wallet-required@2x.png b/lib/images/mobile-wallet-required@2x.png new file mode 100644 index 00000000..4f502b38 Binary files /dev/null and b/lib/images/mobile-wallet-required@2x.png differ diff --git a/lib/images/trust-original.png b/lib/images/trust-original.png new file mode 100644 index 00000000..f21a676e Binary files /dev/null and b/lib/images/trust-original.png differ diff --git a/lib/images/trust-wallet.png b/lib/images/trust-wallet.png new file mode 100644 index 00000000..3d1a072d Binary files /dev/null and b/lib/images/trust-wallet.png differ diff --git a/lib/images/trust.png b/lib/images/trust.png new file mode 100644 index 00000000..45e62914 Binary files /dev/null and b/lib/images/trust.png differ diff --git a/lib/images/trust@2x.png b/lib/images/trust@2x.png new file mode 100644 index 00000000..f21a676e Binary files /dev/null and b/lib/images/trust@2x.png differ diff --git a/package.json b/package.json index b0404506..31e2846a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bnc-assist", - "version": "0.7.5", + "version": "0.8.0", "description": "Blocknative Assist js library for Dapp developers", "main": "lib/assist.min.js", "scripts": { @@ -19,10 +19,7 @@ "type": "git", "url": "git+https://github.com/blocknative/assist.git" }, - "keywords": [ - "dapp", - "ethereum" - ], + "keywords": ["dapp", "ethereum"], "author": "Block Native", "license": "MIT", "bugs": { @@ -79,7 +76,5 @@ "truffle-contract": "^4.0.15", "web3": "^1.0.0-beta.37" }, - "eslintIgnore": [ - "package.json" - ] -} \ No newline at end of file + "eslintIgnore": ["package.json"] +} diff --git a/src/__integration-tests__/ui-rendering/__snapshots__/index.test.js.snap b/src/__integration-tests__/ui-rendering/__snapshots__/index.test.js.snap index 80e99fa6..cba48105 100644 --- a/src/__integration-tests__/ui-rendering/__snapshots__/index.test.js.snap +++ b/src/__integration-tests__/ui-rendering/__snapshots__/index.test.js.snap @@ -3,7 +3,7 @@ exports[`dom-rendering event activeContract-txAwaitingApproval should trigger correct DOM render [Custom state 0] 1`] = ` " -
" + " `; exports[`dom-rendering event activeContract-txAwaitingApproval should trigger correct DOM render [Custom state 1] 1`] = ` " -
" + " `; exports[`dom-rendering event activeContract-txConfirmReminder should trigger correct DOM render [Custom state 0] 1`] = ` " -
" + " `; exports[`dom-rendering event activeContract-txConfirmReminder should trigger correct DOM render [Custom state 1] 1`] = ` " -
" + " `; exports[`dom-rendering event activeContract-txConfirmed should trigger correct DOM render [Custom state 0] 1`] = ` " -
" + " `; exports[`dom-rendering event activeContract-txConfirmed should trigger correct DOM render [Custom state 1] 1`] = ` " -
" + " `; exports[`dom-rendering event activeContract-txConfirmedClient should trigger correct DOM render [Custom state 0] 1`] = ` " -
" + " `; exports[`dom-rendering event activeContract-txConfirmedClient should trigger correct DOM render [Custom state 1] 1`] = ` " -
" + " `; exports[`dom-rendering event activeContract-txFailed should trigger correct DOM render [Custom state 0] 1`] = ` " -
" + " `; exports[`dom-rendering event activeContract-txFailed should trigger correct DOM render [Custom state 1] 1`] = ` " -
" + " `; exports[`dom-rendering event activeContract-txPending should trigger correct DOM render [Custom state 0] 1`] = ` " -
" `; exports[`dom-rendering event activeContract-txPending should trigger correct DOM render [Custom state 1] 1`] = ` " -
" `; exports[`dom-rendering event activeContract-txRequest should trigger correct DOM render [Custom state 0] 1`] = ` " -
" `; exports[`dom-rendering event activeContract-txRequest should trigger correct DOM render [Custom state 1] 1`] = ` " -
" `; exports[`dom-rendering event activeContract-txSendFail should trigger correct DOM render [Custom state 0] 1`] = ` " -
" + " `; exports[`dom-rendering event activeContract-txSendFail should trigger correct DOM render [Custom state 1] 1`] = ` " -
" + " `; exports[`dom-rendering event activeContract-txSent should trigger correct DOM render [Custom state 0] 1`] = ` " -
" `; exports[`dom-rendering event activeContract-txSent should trigger correct DOM render [Custom state 1] 1`] = ` " -
" `; exports[`dom-rendering event activeContract-txSpeedUp should trigger correct DOM render [Custom state 0] 1`] = ` " -
" `; exports[`dom-rendering event activeContract-txSpeedUp should trigger correct DOM render [Custom state 1] 1`] = ` " -
" `; exports[`dom-rendering event activeContract-txStall should trigger correct DOM render [Custom state 0] 1`] = ` " -
" + " `; exports[`dom-rendering event activeContract-txStall should trigger correct DOM render [Custom state 1] 1`] = ` " -
" + " `; exports[`dom-rendering event activePreflight-mobileBlocked should trigger correct DOM render 1`] = ` @@ -489,6 +489,7 @@ exports[`dom-rendering event activePreflight-mobileBlocked should trigger correc

Our distributed application does not support mobile browsers. Please visit our site on a desktop browser. Thank you!


+

Powered by @@ -702,7 +703,7 @@ exports[`dom-rendering event activePreflight-newOnboardComplete should trigger c exports[`dom-rendering event activePreflight-nsfFail should trigger correct DOM render [Custom state 0] 1`] = ` " -

  • +
    • @@ -714,15 +715,15 @@ exports[`dom-rendering event activePreflight-nsfFail should trigger correct DOM NaN sec -

      +

    -
" +
" `; exports[`dom-rendering event activePreflight-nsfFail should trigger correct DOM render [Custom state 1] 1`] = ` " -
" + " `; exports[`dom-rendering event activePreflight-txRepeat should trigger correct DOM render [Custom state 0] 1`] = ` " -
" + " `; exports[`dom-rendering event activePreflight-txRepeat should trigger correct DOM render [Custom state 1] 1`] = ` " -
" + " `; exports[`dom-rendering event activePreflight-walletEnable should trigger correct DOM render 1`] = ` @@ -797,7 +798,7 @@ exports[`dom-rendering event activePreflight-walletEnable should trigger correct

- CHECK THAT I'M CONNECTED + CHECK THAT I’M CONNECTED

@@ -1076,7 +1077,7 @@ exports[`dom-rendering event activePreflight-welcomeUser should trigger correct exports[`dom-rendering event activeTransaction-txAwaitingApproval should trigger correct DOM render [Custom state 0] 1`] = ` " -
" + " `; exports[`dom-rendering event activeTransaction-txAwaitingApproval should trigger correct DOM render [Custom state 1] 1`] = ` " -
" + " `; exports[`dom-rendering event activeTransaction-txConfirmReminder should trigger correct DOM render [Custom state 0] 1`] = ` " -
" + " `; exports[`dom-rendering event activeTransaction-txConfirmReminder should trigger correct DOM render [Custom state 1] 1`] = ` " -
" + " `; exports[`dom-rendering event activeTransaction-txConfirmed should trigger correct DOM render [Custom state 0] 1`] = ` " -
" + " `; exports[`dom-rendering event activeTransaction-txConfirmed should trigger correct DOM render [Custom state 1] 1`] = ` " -
" + " `; exports[`dom-rendering event activeTransaction-txConfirmedClient should trigger correct DOM render [Custom state 0] 1`] = ` " -
" + " `; exports[`dom-rendering event activeTransaction-txConfirmedClient should trigger correct DOM render [Custom state 1] 1`] = ` " -
" + " `; exports[`dom-rendering event activeTransaction-txFailed should trigger correct DOM render [Custom state 0] 1`] = ` " -
" + " `; exports[`dom-rendering event activeTransaction-txFailed should trigger correct DOM render [Custom state 1] 1`] = ` " -
" + " `; exports[`dom-rendering event activeTransaction-txPending should trigger correct DOM render [Custom state 0] 1`] = ` " -
" `; exports[`dom-rendering event activeTransaction-txPending should trigger correct DOM render [Custom state 1] 1`] = ` " -
" `; exports[`dom-rendering event activeTransaction-txRequest should trigger correct DOM render [Custom state 0] 1`] = ` " -
" `; exports[`dom-rendering event activeTransaction-txRequest should trigger correct DOM render [Custom state 1] 1`] = ` " -
" `; exports[`dom-rendering event activeTransaction-txSendFail should trigger correct DOM render [Custom state 0] 1`] = ` " -
" + " `; exports[`dom-rendering event activeTransaction-txSendFail should trigger correct DOM render [Custom state 1] 1`] = ` " -
" + " `; exports[`dom-rendering event activeTransaction-txSent should trigger correct DOM render [Custom state 0] 1`] = ` " -
" `; exports[`dom-rendering event activeTransaction-txSent should trigger correct DOM render [Custom state 1] 1`] = ` " -
" `; exports[`dom-rendering event activeTransaction-txSpeedUp should trigger correct DOM render [Custom state 0] 1`] = ` " -
" `; exports[`dom-rendering event activeTransaction-txSpeedUp should trigger correct DOM render [Custom state 1] 1`] = ` " -
" `; exports[`dom-rendering event activeTransaction-txStall should trigger correct DOM render [Custom state 0] 1`] = ` " -
" + " `; exports[`dom-rendering event activeTransaction-txStall should trigger correct DOM render [Custom state 1] 1`] = ` " -
" + " `; exports[`dom-rendering event initialize-mobileBlocked should trigger correct DOM render 1`] = ` @@ -1562,6 +1563,7 @@ exports[`dom-rendering event initialize-mobileBlocked should trigger correct DOM

Our distributed application does not support mobile browsers. Please visit our site on a desktop browser. Thank you!


+

Powered by @@ -1605,7 +1607,8 @@ exports[`dom-rendering event onboard-browserFail should trigger correct DOM rend Firefox

-
+ +

Powered by @@ -1906,7 +1909,7 @@ exports[`dom-rendering event onboard-walletEnable should trigger correct DOM ren

- CHECK THAT I'M CONNECTED + CHECK THAT I’M CONNECTED

@@ -2185,7 +2188,7 @@ exports[`dom-rendering event onboard-welcomeUser should trigger correct DOM rend exports[`dom-rendering event userInitiatedNotify-error should trigger correct DOM render 1`] = ` " -
  • +
    • @@ -2197,15 +2200,15 @@ exports[`dom-rendering event userInitiatedNotify-error should trigger correct DO NaN sec -

      +

    -
" +
" `; exports[`dom-rendering event userInitiatedNotify-pending should trigger correct DOM render 1`] = ` " -
" `; exports[`dom-rendering event userInitiatedNotify-success should trigger correct DOM render 1`] = ` " -
" + " `; diff --git a/src/__tests__/js/contract/index.test.js b/src/__tests__/js/contract/index.test.js index 43393a05..187f08b5 100644 --- a/src/__tests__/js/contract/index.test.js +++ b/src/__tests__/js/contract/index.test.js @@ -108,18 +108,6 @@ multidepRequire.forEachVersion('web3', (version, Web3) => { }).toThrowError('This network is not supported') }) }) - describe('when user is on mobile and mobile is not blocked', () => { - beforeEach(() => { - updateState({ - mobileDevice: true, - config: { mobileBlocked: false } - }) - }) - test('it should return the contract unmodified', () => { - const decoratedContract = assistInstance.Contract(contract) - expect(decoratedContract).toEqual(contract) - }) - }) describe('when state.web3Instance is falsy', () => { beforeEach(() => { updateState({ web3Instance: undefined }) diff --git a/src/css/media-queries.css b/src/css/media-queries.css new file mode 100644 index 00000000..f3e0e0e4 --- /dev/null +++ b/src/css/media-queries.css @@ -0,0 +1,15 @@ +@media only screen and (min-device-width: 320px) and (max-device-width: 736px) { + #blocknative-notifications { + padding: 0; + } + li.bn-notification { + border-width: 0px 2px 0px 0px; + position: relative; + } + + a#bn-transaction-branding { + position: absolute; + bottom: 5px; + right: 5px; + } +} diff --git a/src/css/styles.css b/src/css/styles.css index 0380bacc..afb26fbd 100644 --- a/src/css/styles.css +++ b/src/css/styles.css @@ -95,6 +95,7 @@ video { font-size: 100%; font: inherit; vertical-align: baseline; + box-sizing: border-box; } /* HTML5 display-role reset for older browsers */ article, @@ -213,7 +214,7 @@ b { body { font-family: 'Source Sans Pro', 'Open Sans', 'Helvetica Neue', Arial, - sans-serif; + sans-serif; } .clearfix::after { @@ -236,6 +237,7 @@ body { justify-content: center; align-items: center; opacity: 0; + padding: 1rem; transition: opacity 150ms ease-in-out; } @@ -328,6 +330,7 @@ img.bn-onboard-img { max-width: 100%; height: auto; border-radius: 4px; + margin: 0 auto; } .bn-onboard-close { @@ -406,6 +409,9 @@ img.bn-onboard-img { /* Notifications */ #blocknative-notifications { + display: flex; + flex-flow: column nowrap; + width: 100%; position: fixed; opacity: 0; padding: 10px; @@ -416,22 +422,35 @@ img.bn-onboard-img { display: none; } +.bn-notifications { + display: flex; + flex-flow: column nowrap; + list-style-type: none; + width: 100%; +} + +.bn-notifications-scroll { + display: flex; + width: 100%; +} + .bn-notification { + display: flex; + width: 100%; + flex-flow: row nowrap; background: #fff; - border-left: 2px solid transparent; border-radius: 2px; padding: 13px 10px; text-align: left; margin-bottom: 5px; box-shadow: 0px 2px 15px rgba(0, 0, 0, 0.1); - width: 320px; /* something to consider (changed from max-width) */ - margin-left: 10px; /* keeps notification from bumping edge on mobile.*/ opacity: 0; transition: transform 350ms ease-in-out, opacity 300ms linear; } -ul.bn-notifications { - list-style-type: none; +.bn-notification-info { + margin-left: 10px; + max-width: 85%; } .bn-notification.bn-progress { @@ -452,6 +471,16 @@ ul.bn-notifications { li.bn-notification.bn-right-border { border-width: 0px 2px 0px 0px; + flex-direction: row-reverse; + justify-content: space-between; +} + +li.bn-notification.bn-top-border { + border-width: 2px 0px 0px 0px; +} + +li.bn-notification.bn-bottom-border { + border-width: 0px 0px 2px 0px; } li.bn-notification.bn-right-border .bn-notification-info { @@ -459,7 +488,6 @@ li.bn-notification.bn-right-border .bn-notification-info { } .bn-status-icon { - float: left; width: 18px; height: 18px; background-image: url('https://assist.blocknative.com/images/jJu8b0B.png'); @@ -506,9 +534,6 @@ li.bn-notification.bn-right-border .bn-notification-info { vertical-align: sub; } -.bn-notification-info { - margin-left: 30px; -} .bn-notification-meta { color: #aeaeae; font-size: 0.79em; @@ -519,7 +544,6 @@ li.bn-notification.bn-right-border .bn-notification-info { } a#bn-transaction-branding { - margin: 0 10px; padding-top: 10px; display: inline-block; width: 18px; @@ -531,12 +555,26 @@ a#bn-transaction-branding { -moz-transition: width 0.2s ease-out; -o-transition: width 0.2s ease-out; transition: width 0.2s ease-out; + align-self: flex-end; + margin: 0 10px; } a#bn-transaction-branding:hover { width: 75px; } +a#bn-transaction-branding.align-start { + align-self: flex-start; +} + +a#bn-transaction-branding.margin-top { + margin: 10px 10px; +} + +a#bn-transaction-branding.mobile-margin { + margin: 5px 5px; +} + /* Retina Settings */ /* http://miekd.com/articles/using-css-sprites-to-optimize-your-website-for-retina-displays/*/ @media only screen and (-webkit-min-device-pixel-ratio: 2), @@ -1139,3 +1177,18 @@ input[type='button'].bn-btn-block { .bn-btn-danger.bn-btn-outline:hover { color: #fff; } + +/* GENERAL */ + +.flex-row { + display: flex; + justify-content: center; + align-items: center; +} + +.flex-column { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} diff --git a/src/js/helpers/iframe.js b/src/js/helpers/iframe.js index 418de015..97fff6b1 100644 --- a/src/js/helpers/iframe.js +++ b/src/js/helpers/iframe.js @@ -1,5 +1,7 @@ import { positionElement, updateNotificationsPosition } from '~/js/views/dom' import darkModeStyles from '~/css/dark-mode.css' +import assistStyles from '~/css/styles.css' +import mediaQueries from '~/css/media-queries.css' import { updateState, state } from './state' @@ -52,13 +54,16 @@ export function updateStyle({ darkMode, css, notificationsPosition }) { } } -export function createIframe(browserDocument, assistStyles, style = {}) { +export function createIframe(browserDocument, style = {}) { const initialIframeContent = ` + @@ -94,7 +99,7 @@ export function hideIframe() { state.iframe.style.pointerEvents = 'none' } -export function resizeIframe({ height, width, transitionHeight }) { +export async function resizeIframe({ height, width, transitionHeight }) { if (transitionHeight) { state.iframe.style.transition = 'height 200ms ease-in-out' } else { diff --git a/src/js/helpers/utilities.js b/src/js/helpers/utilities.js index ba66aba4..04c038e2 100644 --- a/src/js/helpers/utilities.js +++ b/src/js/helpers/utilities.js @@ -23,12 +23,21 @@ export function capitalize(str) { } export function formatNumber(num) { + // if already bignumber instance return it + if (typeof num === 'object') return num const numString = String(num) if (numString.includes('+')) { - const exponent = numString.split('+')[1] - const precision = Number(exponent) + 1 + let exponent = Number(numString.split('+')[1]) + // non firefox limits precision to 21 + if (exponent >= 21) { + exponent = 20 + num = 1e20 + } + const precision = exponent + 1 + return num.toPrecision(precision) } + return num } @@ -132,6 +141,12 @@ export function eventCodeToStep(eventCode) { return 'mobile' case 'browserFail': return 'browser' + case 'mobileWalletFail': + return 'mobileWallet' + case 'mobileNetworkFail': + return 'mobileNetwork' + case 'mobileWalletEnable': + return 'mobileWalletEnable' case 'welcomeUser': return 0 case 'walletFail': @@ -182,7 +197,9 @@ export const timeouts = { hideElement: 200, showElement: 120, autoRemoveNotification: 4000, - pollForReceipt: 1000 + pollForReceipt: 1000, + swipeTime: 250, + lockScreen: 500 } export function stepToImageKey(step) { diff --git a/src/js/helpers/web3.js b/src/js/helpers/web3.js index 582999f8..a1c74f48 100644 --- a/src/js/helpers/web3.js +++ b/src/js/helpers/web3.js @@ -218,7 +218,11 @@ export function getTransactionParams( }) } -export function hasSufficientBalance({ value = 0, gas = 0, gasPrice = 0 }) { +export async function hasSufficientBalance({ + value = 0, + gas = 0, + gasPrice = 0 +}) { return new Promise(async resolve => { const version = state.web3Version && state.web3Version.slice(0, 3) @@ -263,11 +267,9 @@ export function getAccounts() { } export function checkUnlocked() { - return ( - window.ethereum && - window.ethereum._metamask && - window.ethereum._metamask.isUnlocked() - ) + return window.ethereum._metamask && window.ethereum._metamask.isUnlocked + ? window.ethereum._metamask.isUnlocked() + : Promise.resolve(true) } export function requestLoginEnable() { diff --git a/src/js/helpers/websockets.js b/src/js/helpers/websockets.js index dc9c7241..30b3e3bc 100644 --- a/src/js/helpers/websockets.js +++ b/src/js/helpers/websockets.js @@ -119,8 +119,10 @@ export function handleSocketMessage(msg) { }) break case 'confirmed': - // handle race condition: https://github.com/blocknative/assist/issues/174 - if (eventCode === 'txConfirmedClient') return + // have already dealt with txConfirmedClient event + if (eventCode === 'txConfirmedClient') { + return + } txObj = getTxObjFromQueue(transaction.id) diff --git a/src/js/index.js b/src/js/index.js index d85e723b..29a6ffc1 100644 --- a/src/js/index.js +++ b/src/js/index.js @@ -1,6 +1,5 @@ import '@babel/polyfill' import { promisify } from 'bluebird' -import assistStyles from '~/css/styles.css' import { state, updateState, filteredState } from './helpers/state' import { handleEvent } from './helpers/events' @@ -77,7 +76,7 @@ function init(config) { // Commit a cardinal sin and create an iframe (to isolate the CSS) if (!state.iframe && !headlessMode) { - createIframe(document, assistStyles, style) + createIframe(document, style) } // Check if on mobile and mobile is blocked @@ -156,25 +155,25 @@ function init(config) { throw errorObj } - if (headlessMode || mobileDevice) { - // If user is on mobile and it is blocked, warn that it isn't supported - if (mobileBlocked) { - return new Promise((resolve, reject) => { - handleEvent( - { eventCode: 'mobileBlocked', categoryCode: 'onboard' }, - { - onClose: () => { - const errorObj = new Error('User is on a mobile device') - errorObj.eventCode = 'mobileBlocked' - reject(errorObj) - } + // If user is on mobile and it is blocked, warn that it isn't supported + if (mobileDevice && mobileBlocked) { + return new Promise((resolve, reject) => { + handleEvent( + { eventCode: 'mobileBlocked', categoryCode: 'onboard' }, + { + onClose: () => { + const errorObj = new Error('User is on a mobile device') + errorObj.eventCode = 'mobileBlocked' + reject(errorObj) } - ) + } + ) - updateState({ validBrowser: false }) - }) - } + updateState({ validBrowser: false }) + }) + } + if (headlessMode) { return new Promise(async (resolve, reject) => { if (mobileDevice && window.ethereum) { await window.ethereum.enable().catch() @@ -191,8 +190,7 @@ function init(config) { } = state if ( - mobileDevice || - !validBrowser || + (!validBrowser && !mobileDevice) || !web3Wallet || !accessToAccounts || !correctNetwork || @@ -224,9 +222,8 @@ function init(config) { const { validApiKey, supportedNetwork, - mobileDevice, web3Instance, - config: { mobileBlocked, truffleContract } + config: { truffleContract } } = state if (!validApiKey) { @@ -241,11 +238,6 @@ function init(config) { throw errorObj } - // if user is on mobile, and mobile is allowed by Dapp then just pass the contract back - if (mobileDevice && !mobileBlocked) { - return contractObj - } - // Check if we have an instance of web3 if (!web3Instance) { if (window.web3) { @@ -416,11 +408,6 @@ function init(config) { configureWeb3() } - // if user is on mobile, and mobile is allowed by Dapp just put the transaction through - if (state.mobileDevice && !state.config.mobileBlocked) { - return state.web3Instance.eth.sendTransaction(txObject, callback) - } - const sendMethod = state.legacyWeb3 ? promisify(state.web3Instance.eth.sendTransaction) : state.web3Instance.eth.sendTransaction diff --git a/src/js/logic/user.js b/src/js/logic/user.js index 0a685854..2c262e74 100644 --- a/src/js/logic/user.js +++ b/src/js/logic/user.js @@ -17,12 +17,17 @@ import { closeModal, addOnboardWarning } from '~/js/views/dom' export function checkUserEnvironment() { return new Promise(async resolve => { - checkValidBrowser() + if (!state.mobileDevice) { + checkValidBrowser() + } checkForWallet() if (!state.web3Wallet) { - storeItem('_assist_newUser', 'true') + if (!state.mobileDevice) { + storeItem('_assist_newUser', 'true') + } + resolve() return } @@ -66,7 +71,7 @@ export function prepareForTransaction(categoryCode, originalResolve) { } if (getItem('_assist_newUser') === 'true') { - if (!state.validBrowser) { + if (!state.validBrowser && !state.mobileDevice) { handleEvent( { eventCode: 'browserFail', @@ -106,6 +111,14 @@ export function prepareForTransaction(categoryCode, originalResolve) { } if (!state.web3Instance) { + if (state.mobileDevice) { + try { + await getWeb3Wallet(categoryCode) + } catch (errorObj) { + reject(errorObj) + } + return + } configureWeb3() } @@ -185,7 +198,7 @@ function getWeb3Wallet(categoryCode) { return new Promise((resolve, reject) => { handleEvent( { - eventCode: 'walletFail', + eventCode: state.mobileDevice ? 'mobileWalletFail' : 'walletFail', categoryCode, wallet: { provider: state.currentProvider @@ -197,11 +210,19 @@ function getWeb3Wallet(categoryCode) { const errorObj = new Error( 'User does not have a web3 wallet installed' ) - errorObj.eventCode = 'walletFail' + errorObj.eventCode = state.mobileDevice + ? 'mobileWalletFail' + : 'walletFail' reject(errorObj) }, timeouts.changeUI), onClick: () => { - window.location.reload() + if (state.mobileDevice) { + window.location = `https://links.trustwalletapp.com/a/key_live_lfvIpVeI9TFWxPCqwU8rZnogFqhnzs4D?&event=openURL&url=${ + window.location.href + }` + } else { + window.location.reload() + } } } ) @@ -329,7 +350,9 @@ function enableWallet(categoryCode, originalResolve) { // cancelling, so we show enable account UI handleEvent( { - eventCode: 'walletEnable', + eventCode: state.mobileDevice + ? 'mobileWalletEnable' + : 'walletEnable', categoryCode, wallet: { provider: state.currentProvider @@ -339,7 +362,9 @@ function enableWallet(categoryCode, originalResolve) { onClose: () => setTimeout(() => { const errorObj = new Error('User needs to enable wallet') - errorObj.eventCode = 'walletEnable' + errorObj.eventCode = state.mobileDevice + ? 'mobileWalletEnable' + : 'walletEnable' reject(errorObj) }, timeouts.changeUI), onClick: async () => { @@ -398,7 +423,7 @@ function enableWallet(categoryCode, originalResolve) { // Show UI to inform user to connect handleEvent( { - eventCode: 'walletEnable', + eventCode: state.mobileDevice ? 'mobileWalletEnable' : 'walletEnable', categoryCode, wallet: { provider: state.currentProvider @@ -408,7 +433,9 @@ function enableWallet(categoryCode, originalResolve) { onClose: () => setTimeout(() => { const errorObj = new Error('User needs to enable wallet') - errorObj.eventCode = 'walletEnable' + errorObj.eventCode = state.mobileDevice + ? 'mobileWalletEnable' + : 'walletEnable' reject(errorObj) }, timeouts.changeUI), onClick: async () => { @@ -459,7 +486,7 @@ function enableWallet(categoryCode, originalResolve) { // Show UI to inform user to connect handleEvent( { - eventCode: 'walletEnable', + eventCode: state.mobileDevice ? 'mobileWalletEnable' : 'walletEnable', categoryCode, wallet: { provider: state.currentProvider @@ -469,7 +496,9 @@ function enableWallet(categoryCode, originalResolve) { onClose: () => setTimeout(() => { const errorObj = new Error('User needs to enable wallet') - errorObj.eventCode = 'walletEnable' + errorObj.eventCode = state.mobileDevice + ? 'mobileWalletEnable' + : 'walletEnable' reject(errorObj) }, timeouts.changeUI), onClick: async () => { @@ -518,7 +547,9 @@ function unlockWallet(categoryCode, originalResolve) { // Show onboard login UI handleEvent( { - eventCode: 'walletLoginEnable', + eventCode: state.mobileDevice + ? 'mobileWalletEnable' + : 'walletLoginEnable', categoryCode, wallet: { provider: state.currentProvider @@ -528,7 +559,9 @@ function unlockWallet(categoryCode, originalResolve) { onClose: () => setTimeout(() => { const errorObj = new Error('User needs to login to wallet') - errorObj.eventCode = 'walletLoginEnable' + errorObj.eventCode = state.mobileDevice + ? 'mobileWalletEnable' + : 'walletLoginEnable' reject(errorObj) }, timeouts.changeUI), onClick: async () => { @@ -591,7 +624,7 @@ export function getCorrectNetwork(categoryCode) { return new Promise(async (resolve, reject) => { handleEvent( { - eventCode: 'networkFail', + eventCode: state.mobileDevice ? 'mobileNetworkFail' : 'networkFail', categoryCode, walletNetworkID: state.userCurrentNetworkId, walletName: state.currentProvider @@ -600,7 +633,9 @@ export function getCorrectNetwork(categoryCode) { onClose: () => setTimeout(() => { const errorObj = new Error('User is on the wrong network') - errorObj.eventCode = 'networkFail' + errorObj.eventCode = state.mobileDevice + ? 'mobileNetworkFail' + : 'networkFail' reject(errorObj) }, timeouts.changeUI), onClick: async () => { diff --git a/src/js/views/content.js b/src/js/views/content.js index 8f468a27..0f0d32e2 100644 --- a/src/js/views/content.js +++ b/src/js/views/content.js @@ -46,6 +46,18 @@ import chromeLogo2x from '../../../lib/images/maxXVIH.png' import firefoxLogo from '../../../lib/images/WjOSJTh.png' import firefoxLogo2x from '../../../lib/images/kodZvyO.png' +import mobileWalletLight from '../../../lib/images/mobile-wallet-required-white.png' +import mobileWalletLight2x from '../../../lib/images/mobile-wallet-required-white@2x.png' + +import mobileWallet from '../../../lib/images/mobile-wallet-required.png' +import mobileWallet2x from '../../../lib/images/mobile-wallet-required@2x.png' + +import trustLogo from '../../../lib/images/trust.png' +import trustLogo2x from '../../../lib/images/trust@2x.png' + +import coinbaseLogo from '../../../lib/images/coinbase.png' +import coinbaseLogo2x from '../../../lib/images/coinbase@2x.png' + export const notSupported = { mobileNotSupported: { heading: 'Mobile Not Supported', @@ -58,10 +70,17 @@ export const notSupported = { `This Dapp is not supported in ${ state.userAgent.browser.name }. Please visit us in one of the following browsers. Thank You!` + }, + mobileWalletNotSupported: { + heading: 'Install A Mobile Dapp Wallet', + description: () => + 'A mobile ethereum wallet is needed to use this dapp. We recommend Trust or Coinbase wallet.' } } export const onboardHeading = { + mobileNetwork: { advanced: 'Switch to the Correct Network' }, + mobileWalletEnable: { advanced: 'Connect Wallet' }, '0': { basic: 'Let’s Get You Started' }, '1': { basic: 'Install MetaMask' }, '2': { @@ -84,6 +103,18 @@ export const onboardHeading = { } export const onboardDescription = { + mobileNetwork: { + advanced: () => + `We’ve detected that you need to be on the ${networkName( + state.config.networkId + ) || + 'mainnet'} network for this application but you have MetaMask set to ${networkName( + state.userCurrentNetworkId + )}. Please switch to the correct network.` + }, + mobileWalletEnable: { + advanced: () => 'Please allow connection to your wallet' + }, '0': { basic: () => 'To use this feature you’ll need to be set up and ready to use the blockchain. This onboarding guide will walk you through each step of the process. It won’t take long and at any time you can come back and pick up where you left off.' @@ -135,6 +166,9 @@ export const onboardDescription = { } export const onboardButton = { + mobileWallet: { advanced: 'CHECK MY MOBILE WALLET' }, + mobileNetwork: { advanced: 'CHECK MY NETWORK' }, + mobileWalletEnable: { advanced: 'CHECK THAT I’M CONNECTED' }, '0': { basic: 'I’M READY' }, '1': { basic: 'CHECK THAT I HAVE METAMASK' @@ -144,8 +178,8 @@ export const onboardButton = { advanced: 'CHECK THAT I’M LOGGED IN' }, '3': { - basic: "CHECK THAT I'M CONNECTED", - advanced: "CHECK THAT I'M CONNECTED" + basic: 'CHECK THAT I’M CONNECTED', + advanced: 'CHECK THAT I’M CONNECTED' }, '4': { basic: 'CHECK THAT I’M ON THE RIGHT NETWORK', @@ -213,6 +247,26 @@ export const imageSrc = { src: firefoxLogo, srcset: firefoxLogo2x }, + trustLogo: { + src: trustLogo, + srcset: trustLogo2x + }, + coinbaseLogo: { + src: coinbaseLogo, + srcset: coinbaseLogo2x + }, + mobileWallet: { + src: mobileWallet, + srcset: mobileWallet2x + }, + mobileWalletLight: { + src: mobileWalletLight, + srcset: mobileWalletLight2x + }, + mobileNetwork: { + src: network, + srcset: network2x + }, '0': { src: welcome, srcset: welcome2x diff --git a/src/js/views/dom.js b/src/js/views/dom.js index b5aa5eb7..2ec52de3 100644 --- a/src/js/views/dom.js +++ b/src/js/views/dom.js @@ -125,6 +125,7 @@ export function closeModal() { window.removeEventListener('resize', handleWindowResize) const modal = state.iframeDocument.querySelector('.bn-onboard-modal-shade') modal.style.opacity = '0' + removeTouchHandlers(modal) const notifications = getById('blocknative-notifications') if (notifications) { @@ -154,11 +155,23 @@ export function openModal(modal, handlers = {}) { closeModal() } + if (state.mobileDevice) { + closeButton.ontouchstart = () => { + onClick && onClick() + } + } + const completeStepButton = modal.querySelector('.bn-btn') if (completeStepButton) { completeStepButton.onclick = () => { onClick && onClick() } + + if (state.mobileDevice) { + completeStepButton.ontouchstart = () => { + onClick && onClick() + } + } } setTimeout(() => { @@ -200,6 +213,35 @@ export function browserLogos() { ` } +function walletLogos() { + const { trustLogo, coinbaseLogo } = imageSrc + + return ` +

+ + Chrome Logo +
+ Trust +
+ + Firefox Logo +
+ Coinbase +
+

+ ` +} + export function onboardBranding() { const { blockNativeLogo, blockNativeLogoLight } = imageSrc const { style } = state.config @@ -225,6 +267,7 @@ export function notSupportedModal(type) { const info = notSupported[`${type}NotSupported`] const { style } = state.config const darkMode = style && style.darkMode + const variant = darkMode ? 'Light' : '' return `
@@ -232,12 +275,19 @@ export function notSupportedModal(type) { - ${notSupportedImage(`${type}${darkMode ? 'Light' : ''}`)} + ${notSupportedImage(type + variant)}

${info.heading}

${info.description()}


- ${type === 'browser' ? `${browserLogos()}
` : ''} + ${ + type === 'browser' + ? browserLogos() + : type === 'mobileWallet' + ? walletLogos() + : '' + } +
${onboardBranding()}
@@ -298,19 +348,36 @@ export function onboardMain(type, step) { ? onboardButton[step][type]() : onboardButton[step][type] - const defaultImages = imageSrc[step] + const { + config: { style } + } = state + + const darkMode = style && style.darkMode + const variant = darkMode ? 'Light' : '' + + const defaultImages = imageSrc[step + variant] || imageSrc[step] const { images } = state.config const stepKey = stepToImageKey(step) const devImages = images && images[stepKey] + const onboardImages = { + src: (devImages && devImages.src) || (defaultImages && defaultImages.src), + srcset: + (devImages && devImages.srcset && devImages.srcset) || + (defaultImages && defaultImages.srcset) + } return ` - Blocknative + defaultImages.srcset} 2x"/>` + : '' + }

${heading}

${description}

@@ -387,6 +454,9 @@ export function getAllByQuery(query) { } export function createTransactionBranding() { + const position = + (state.config.style && state.config.style.notificationsPosition) || '' + const blockNativeBrand = createElement( 'a', null, @@ -395,9 +465,17 @@ export function createTransactionBranding() { ) blockNativeBrand.href = 'https://www.blocknative.com/' blockNativeBrand.target = '_blank' - const position = - (state.config.style && state.config.style.notificationsPosition) || '' - blockNativeBrand.style.float = position.includes('Left') ? 'initial' : 'right' + if (state.mobileDevice) { + blockNativeBrand.classList.add('mobile-margin') + } else { + if (position.includes('Left')) { + blockNativeBrand.classList.add('align-start') + } + + if (position.includes('top')) { + blockNativeBrand.classList.add('margin-top') + } + } return blockNativeBrand } @@ -432,7 +510,7 @@ export function notificationContent(type, message, time = {}) { ${elapsedTime} -

+

` } @@ -473,7 +551,9 @@ export function showElement(element, timeout) { export function hideElement(element) { setTimeout(() => { element.style.opacity = '0' - element.style.transform = `translateX(${getPolarity()}600px)` + element.style.transform = `translate${ + state.mobileDevice ? 'Y' : 'X' + }(${getPolarity()}${state.mobileDevice ? '150' : '600'}px)` }, timeouts.hideElement) } @@ -492,11 +572,17 @@ function getPolarity() { const position = (state.config.style && state.config.style.notificationsPosition) || '' + if (state.mobileDevice) { + return position.includes('top') ? '-' : '' + } + return position.includes('Left') ? '-' : '' } export function offsetElement(el) { - el.style.transform = `translate(${getPolarity()}600px)` + el.style.transform = `translate${ + state.mobileDevice ? 'Y' : 'X' + }(${getPolarity()}${state.mobileDevice ? '150' : '600'}px)` return el } @@ -504,8 +590,16 @@ export function positionElement(el) { const position = (state.config.style && state.config.style.notificationsPosition) || '' - el.style.left = position.includes('Left') ? '0px' : 'initial' - el.style.right = position.includes('Right') || !position ? '0px' : 'initial' + el.style.left = state.mobileDevice + ? 'initial' + : position.includes('Left') + ? '0px' + : 'initial' + el.style.right = state.mobileDevice + ? 'initial' + : position.includes('Right') || !position + ? '0px' + : 'initial' el.style.bottom = position.includes('bottom') || !position ? '0px' : 'initial' el.style.top = position.includes('top') ? '0px' : 'initial' @@ -562,8 +656,9 @@ export function setNotificationsHeight() { // if no notifications to manipulate return if (!scrollContainer) return const maxHeight = window.innerHeight - const brandingHeight = getById('bn-transaction-branding').clientHeight + 26 - const widgetHeight = scrollContainer.scrollHeight + brandingHeight + const branding = getById('bn-transaction-branding') + const brandingHeight = branding ? branding.clientHeight + 26 : 0 + const widgetHeight = scrollContainer.clientHeight + brandingHeight const tooBig = widgetHeight > maxHeight @@ -575,16 +670,89 @@ export function setNotificationsHeight() { } const notificationsContainer = getById('blocknative-notifications') - const toolTipBuffer = !tooBig ? 50 : 0 + const toolTipBuffer = !tooBig && !state.mobileDevice ? 50 : 0 resizeIframe({ height: notificationsContainer.clientHeight + toolTipBuffer, - width: 371, + width: state.mobileDevice ? window.innerWidth : 371, transitionHeight: true }) } function setHeight(el, overflow, height) { - el.style['overflow-y'] = overflow - el.style.height = height + if (el) { + el.style['overflow-y'] = overflow + el.style.height = height + } +} + +export function addTouchHandlers(element, type) { + element.addEventListener('touchstart', handleTouchStart(element), false) + element.addEventListener('touchmove', handleTouchMove(element), false) + element.addEventListener('touchend', handleTouchEnd(element, type), false) +} + +export function removeTouchHandlers(element, type) { + element.removeEventListener('touchstart', handleTouchStart(element), false) + element.removeEventListener('touchmove', handleTouchMove(element), false) + element.removeEventListener('touchend', handleTouchEnd(element, type), false) +} + +export function handleTouchStart(element) { + return e => { + e.stopPropagation() + e.preventDefault() + const touch = e.changedTouches[0] + element.attributes['data-startY'] = touch.pageY + element.attributes['data-startX'] = touch.pageX + element.attributes['data-startTime'] = Date.now() + element.attributes['data-translateY'] = 0 + } +} + +export function handleTouchMove(element) { + return e => { + e.stopPropagation() + e.preventDefault() + const touch = e.changedTouches[0] + const startY = element.attributes['data-startY'] + const translateY = element.attributes['data-translateY'] + const distanceY = touch.pageY - startY + + const newTranslateY = distanceY + translateY + + if (newTranslateY > -40 && newTranslateY < 40) { + element.style.transform = `translateY(${newTranslateY}px)` + element.attributes['data-translateY'] = newTranslateY + } + } +} + +export function handleTouchEnd(element, type) { + return e => { + e.stopPropagation() + e.preventDefault() + const touch = e.changedTouches[0] + const startY = element.attributes['data-startY'] + const startX = element.attributes['data-startX'] + const startTime = element.attributes['data-startTime'] + const distanceY = touch.pageY - startY + const distanceX = touch.pageX - startX + const elapsedTime = Date.now() - startTime + const validDistance = + distanceY <= -40 || distanceY >= 40 || distanceX <= -40 || distanceX >= 40 + const validSwipe = elapsedTime <= timeouts.swipeTime && validDistance + + if (!validSwipe) { + element.style.transform = 'translateY(0)' + element.attributes['data-translateY'] = 0 + } else { + removeTouchHandlers(element) + if (type === 'notification') { + removeNotification(element) + } else { + closeModal() + } + } + } } diff --git a/src/js/views/event-to-ui.js b/src/js/views/event-to-ui.js index 2c869911..ca6bcb22 100644 --- a/src/js/views/event-to-ui.js +++ b/src/js/views/event-to-ui.js @@ -25,7 +25,9 @@ import { setNotificationsHeight, startTimerInterval, removeAllNotifications, - positionElement + positionElement, + addTouchHandlers, + removeTouchHandlers } from './dom' import { transactionMsgs } from './content' @@ -38,10 +40,13 @@ const eventToUI = { mobileBlocked: notSupportedUI, welcomeUser: onboardingUI, walletFail: onboardingUI, + mobileWalletFail: notSupportedUI, walletLogin: onboardingUI, + mobileWalletEnable: onboardingUI, walletLoginEnable: onboardingUI, walletEnable: onboardingUI, networkFail: onboardingUI, + mobileNetworkFail: onboardingUI, nsfFail: onboardingUI, newOnboardComplete: onboardingUI }, @@ -49,10 +54,13 @@ const eventToUI = { mobileBlocked: notSupportedUI, welcomeUser: onboardingUI, walletFail: onboardingUI, + mobileWalletFail: notSupportedUI, walletLogin: onboardingUI, + mobileWalletEnable: onboardingUI, walletLoginEnable: onboardingUI, walletEnable: onboardingUI, networkFail: onboardingUI, + mobileNetworkFail: onboardingUI, nsfFail: notificationsUI, newOnboardComplete: onboardingUI, txRepeat: notificationsUI @@ -128,6 +136,11 @@ function onboardingUI(eventObj, handlers) { 'bn-onboard-modal-shade', onboardModal(type, eventCodeToStep(eventCode)) ) + + if (state.mobileDevice) { + addTouchHandlers(modal.children[0], 'modal') + } + openModal(modal, handlers) } @@ -243,23 +256,38 @@ function notificationsUI({ createElement( 'li', `bn-notification bn-${type} bn-${eventCode} bn-${id} ${ - position.includes('Left') ? 'bn-right-border' : '' + state.mobileDevice + ? position.includes('top') + ? 'bn-bottom-border' + : 'bn-top-border' + : position.includes('Left') + ? 'bn-right-border' + : '' }`, notificationContent(type, message, { startTime, showTime, timeStamp }) ) ) + if (state.mobileDevice) { + notification.appendChild(createTransactionBranding()) + addTouchHandlers(notification, 'notification') + } + notificationsList.appendChild(notification) if (!existingNotifications) { notificationsScroll.appendChild(notificationsList) if (position.includes('top')) { - notificationsContainer.appendChild(blockNativeBrand) + if (!state.mobileDevice) { + notificationsContainer.appendChild(blockNativeBrand) + } notificationsContainer.appendChild(notificationsScroll) } else { notificationsContainer.appendChild(notificationsScroll) - notificationsContainer.appendChild(blockNativeBrand) + if (!state.mobileDevice) { + notificationsContainer.appendChild(blockNativeBrand) + } } state.iframeDocument.body.appendChild(notificationsContainer) showElement(notificationsContainer, timeouts.showElement) @@ -283,11 +311,23 @@ function notificationsUI({ setTimeout(setNotificationsHeight, timeouts.changeUI) } + if (state.mobileDevice) { + dismissButton.addEventListener('touchstart', () => { + intervalId && clearInterval(intervalId) + removeTouchHandlers(notification, 'notification') + removeNotification(notification) + setTimeout(setNotificationsHeight, timeouts.changeUI) + }) + } + const notificationShouldTimeout = (type === 'complete' && categoryCode !== 'userInitiatedNotify') || customTimeout if (notificationShouldTimeout) { setTimeout(() => { + if (state.mobileDevice) { + removeTouchHandlers(notification, 'notification') + } removeNotification(notification) setTimeout(setNotificationsHeight, timeouts.changeUI) }, customTimeout || timeouts.autoRemoveNotification)