From b97445a119e6f88634a5b9fb004a178dfc355a91 Mon Sep 17 00:00:00 2001 From: Chris Meisl Date: Wed, 10 Apr 2019 17:28:40 -0600 Subject: [PATCH] release 0.5.1 (#85) * added uuid for each transaction, general refactoring (#71) * added uuid for each transaction, general refactoring * update to version 0.5.1 * Filter the state object (#68) * Filtered the state object returned from getState to just the essential pieces of state that would be required by a dapp dev * added missing parameter minimumBalance and reordered parameters in readme * fixed image bug for browser onboarding (#75) * added check to not render iframe if user is on mobile and mobile isn't blocked so that touch events work correctly on mobile (#74) * made txRepeat notification persist when txConfirmReminder notification is shown (#78) * Added inline custom tx messages (#79) * Enhancement/dark mode option (#77) * added darkMode option to config * Added new light blocknative logo, made sure basic modal is set to dark mode as well * fixed focus text color * added new darkmode assets for browser and mobile not supported modals * Enhancement/position notifications (#81) * Added config option to position transaction notifications * subtle tweaks to UI * fixed typo in readme * fixed transaction timer bug (#83) --- README.md | 82 ++-- lib/images/bn-branding-white.png | Bin 0 -> 1826 bytes lib/images/bn-branding-white@2x.png | Bin 0 -> 3941 bytes lib/images/browser-not-supported-white.png | Bin 0 -> 848 bytes lib/images/browser-not-supported-white@2x.png | Bin 0 -> 1746 bytes lib/images/mobile-not-supported-white.png | Bin 0 -> 763 bytes lib/images/mobile-not-supported-white@2x.png | Bin 0 -> 1482 bytes package.json | 5 +- src/__tests__/helpers/iframe.test.js | 2 + src/css/dark-mode.css | 50 +++ src/css/styles.css | 92 ++-- src/js/helpers/iframe.js | 62 +-- src/js/helpers/utilities.js | 52 ++- src/js/helpers/web3.js | 64 +-- src/js/index.js | 43 +- src/js/logic/contract-methods.js | 12 +- src/js/logic/send-transaction.js | 404 ++++++++++-------- src/js/views/content.js | 21 + src/js/views/dom.js | 158 ++++--- src/js/views/event-to-ui.js | 139 +++--- 20 files changed, 726 insertions(+), 460 deletions(-) create mode 100644 lib/images/bn-branding-white.png create mode 100644 lib/images/bn-branding-white@2x.png create mode 100644 lib/images/browser-not-supported-white.png create mode 100644 lib/images/browser-not-supported-white@2x.png create mode 100644 lib/images/mobile-not-supported-white.png create mode 100644 lib/images/mobile-not-supported-white@2x.png create mode 100644 src/css/dark-mode.css diff --git a/README.md b/README.md index 60b3c3d3..f3fe07a8 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.5.0. +The current version is 0.5.1. There are minified and non-minified versions. Put this script at the top of your `` ```html - + - + ``` ### Initialize the Library @@ -167,19 +167,22 @@ var config = { src: String, // Image URL for complete onboard modal srcset: String // Image URL(s) for complete onboard modal } + }, + style: { + darkMode: Boolean, // Set Assist UI to dark mode + notificationsPosition: String // Defines which corner transaction notifications will be positioned. Options: 'topLeft', 'topRight', 'bottomRight', 'bottomLeft'. ['bottomRight'] } } ``` ### Custom Transaction Messages -The functions provided to the `messages` object in the config, will be -called with the following object so that a custom message string can be returned: +Custom transaction messages can be set to override the default messages `Assist` provides. To do this you provide callback functions for each `eventCode` that you want to override. The callback functions must return a `String` and will be called with the following object to provide context information about the transaction: ```javascript { transaction: { - to: String, // The address the transaction was going to + to: String, // The address the transaction is going to gas: String, // Gas (wei) gasPrice: String, // Gas price (wei) hash: String, // The transaction hash @@ -194,21 +197,39 @@ called with the following object so that a custom message string can be returned } ``` -#### Example +You can provide a `messages` object to the `config` to set global message overrides. Each callback can parse the context object that is passed to it and decide what to return or just return a standard message for each `eventCode`: ```javascript var config = { //... messages: { + txSent: function(data) { + return 'Your transaction has been sent to the network' + }, txConfirmed: function(data) { if (data.contract.methodName === 'contribute') { return 'Congratulations! You are now a contributor to the campaign' } } + // .... } } ``` +Sometimes you want more granular control over the transaction messages and you have all the relevant information you need to create a custom transaction message at the time of calling the method. In that case you can also add custom transactions messages inline with your transaction calls which take precedent over the messages set globally in the config. + +#### Example + +```javascript +// 0.2 style send +myContract.vote(param1, param2, options, callback, {messages: {txPending: () => `Voting for ${param1} in progress`}}) + +// 1.0 style send +myContract.vote(param1, param2).send(options, {messages: {txPending: () => `Voting for ${param1} in progress`}}) +``` + +The `messages` object _must_ always be the _last_ argument provided to the send method for it to be recognized. + ### Ethereum Network Ids The available ids for the `networkId` property of the config object: @@ -361,41 +382,18 @@ assistInstance.Transaction(txObject) ```javascript state = { - version: String, - validApiKey: Boolean, - supportedNetwork: Boolean, - config: Object, - userAgent: String, - mobileDevice: Boolean, - validBrowser: Boolean, - legacyWeb3: Boolean, - modernWeb3: Boolean, - web3Version: String, - web3Instance: Object, - currentProvider: String, - web3Wallet: Boolean, - legacyWallet: Boolean, - modernWallet: Boolean, - accessToAccounts: Boolean, - walletLoggedIn: Boolean, - walletEnabled: Boolean, - walletEnableCalled: Boolean, - walletEnableCanceled: Boolean, - accountBalance: String, - correctNetwork: Boolean, - minimumBalance: String, - correctNetwork: Boolean, - userCurrentNetworkId: Number, - socket: Object, - pendingSocketConnection: Boolean, - socketConnection: Boolean, - accountAddress: String, - transactionQueue: Array, - transactionAwaitingApproval: Boolean, - iframe: Object, - iframeDocument: Object, - iframeWindow: Object, - connectionId: String + mobileDevice: Boolean, // User is on a mobile device + validBrowser: Boolean, // User is on a valid web3 browser + currentProvider: String, // Current provider being used to connect to the network + web3Wallet: Boolean, // User has a web3Wallet installed + accessToAccounts: Boolean, // Dapp has access to accounts + walletLoggedIn: Boolean, // User is logged in to wallet + walletEnabled: Boolean, // User has enabled EIP 1102 compliant wallet + accountAddress: String, // Address of the user's selected account + accountBalance: String, // User account balance + minimumBalance: String, // User has the minimum balance specified in the config + userCurrentNetworkId: Number, // Network id of the network the user is currently on + correctNetwork: Boolean, // User is on the network specified in the config } ``` diff --git a/lib/images/bn-branding-white.png b/lib/images/bn-branding-white.png new file mode 100644 index 0000000000000000000000000000000000000000..c60f167351a999dc2df92d4cefdf49889ebfc324 GIT binary patch literal 1826 zcmV+-2i^FIP)X1^@s6KyPAA000K*Nkl&VvK-Bv4Tb=}?F-Q696#OiKoLPH7e)ZN!Az~#Bmrr$7U z4z@ZPW`4)9|GejmH1?HNm~gqJXvrLN;o=b_UX82whB1F`6LZ0mNtWUz+X?2PB`3|r zOYcrCxS=D7SN&>T0K~_bvnbqBy!b0l=y_|&Ql#ZCg1K-JY$Z!8cx{`}9TWA`rg8ox zUbU$MB3p5EP-?-_#~Ae3^0H+x6)Z+--Xafk{$eEN#Nl=4Ox&?{$UQONhX;_<=H=Po zsi%Bx#Y++_g^Tl+mosLeib*C;X&Qa%nu|M@4@G=xf5fHrYb>fa>f4SvtCI2(U@l(l z37E#1+|xpQnK2KKuN#L&sS$`V_Cs7sI1=< zCNoyWjQw3of83B1i4Wfx`d0d5V?rU~Pom|eHY%zz6A!H!gJ@%4PfYT}Nytm_Y~I;Eg;P4SCh8+=$8s|JU5d&z^rj!f@yV-0 zaCqTFRLq@<3-hO-I(iza7EC)^5j*XjpJ$Kjjk$B1kibhknqz=v+8yXZC= zM51)2Fp|G$1o@Ht3!0OG@M zplV|fE#Q;OX?Qq(>Jy5iy+eOG9NuFfi5`Gf(ShMqbc@t*-CdMc`gMCnDJG!Qk6LP&J{=?ZFBPt*WvO+vxcl7>)Q5}nJbKlzX- z-RK+5KLlNYsn^YgDuqrMU({9>F`;`*P7M#1g9nMB}!&cOt~kr6$HIHUK4#RZtrI2;AkkA%-LzN*{DkhWBkJ9BHHi{W*OO2R z1&}ChHBl$h3hF?j;B)vnTgwnT~_5CXnWwZ(}D((aldQu(x(Pk25Fuhb0 z)pE57*)QLOvs=3J^>3~TtK^b{cBrU#2I6#72=ayw#-n#VipWoGr}})mVE~Dq{`FTj zP*YvhPoO4E=|gXl(254u)Q$9`?p0n#qOqQYVknM8V|(LK5kSe*oK|R_FQU&hZmEfC zoV*c96SiPg?6)ZS@H(8^8H5W(O*K);NmQBckNuB?;IkzQG3o6e5V$-GfobWdf>Ip= zNObCo+Kk*JG>Psep#-R7<#Eji+Gi9&LO#@76V*UV>B;({YRlUi!lV85EEDbqF}dFpy3P1=m)i5u|U3-4oP-ZY#kXospoKTp(ZhYx<) z(E>ZSgd^7Y7P_R`5nyp6*t`pdRLAK~j7p-XBJQV^w2JJSSE(sk=}HoMo{Y*9x(|bI zDUrURVI=gSW+`}+hLBJqiK0J`CM*4@LZy)?57SoaOoQlN5~UN_$(KZlp!JkUDb%ed zs%iR`bIA-#NS(YM_uuD2UuPcf%6Jp*+$j8%(*_52biga?mtn9i4FM)M+B0Ub$;GId zFr+w+cS>{&Ao;sAQ>3T6_qX}}TR%%p>c-DZ(>I+kP1{tFI(Y*geZYZGXE{RcdoaPd z5f3|_M%?PxJrM&9Q`cQ`U2I(NU;E4_SQFV~(6VVi%Cn)$zATh_5)>n>AQOXeUk<~Jd!&8z+!8Mv&U zvv^cbXPGH%Tgfp_XuiShUKad=(?C)`uSOr}RaDeEY;*D8kTp5INgC(%e+on(h>paT QoB#j-07*qoM6N<$f|=-Xj{pDw literal 0 HcmV?d00001 diff --git a/lib/images/bn-branding-white@2x.png b/lib/images/bn-branding-white@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..7d2480f2d6e73be69b3c03d72c6f5c6c49ae134b GIT binary patch literal 3941 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 literal 0 HcmV?d00001 diff --git a/lib/images/browser-not-supported-white.png b/lib/images/browser-not-supported-white.png new file mode 100644 index 0000000000000000000000000000000000000000..c4c8f7e27bdd41f1b41f1d71652a2106a8c1fdff GIT binary patch literal 848 zcmV-W1F!svP)Px&3Q0skRCwC$+CfMZQ5eASZ&wXvjVwf4Arb`FOW8q^z(Dd47IYGC8hA16l5|)C z54oU}f?{3kBz4h)1$Ed64M98#CV3F{q84fiVw%eO@36Du%W-RD?=H{j~@UhWx1-?24I*2lo0E=*jCw%4&SG2s4 zUdDMvxiXiZl6~2hyv_|pN2?v#8<8bv$)}RM!Nv4*4Tk<}&%8~Igo$)z$`sjDUlOng8fK}RTm0_+`DKY_;OymM>zQ8cI zL_j5SPs%pX4!GVf;^gbu~qAjNah$-f=s3XW4_Q_RTq6@=_eh6SyM){#f3zX(5GF{FFgrFYKDa`*=myCM>ubWcYZ@IVlgGR* zC#bIq+OC-=&uMaJhP`?iGCXpWOfl;jFo)R~uuid)4ex&{y z@tMcoVK)fpQ;w$8j`xY3a>p~^H7+K%_g$7f0G^`KcuiiQk8k`@6uZF`56Q7^Cpjt( av+*x6TzEnbza1O^0000Px*j7da6RCwC$om*@aRUF1ow}n>O2qI0mL<2~G@IV7Zix2`d@c~NYB0dnq#c-*B zQK=y$DxwdS1dtFg0g5jMYSa*jcp(vENhnYXhGHtkC?JGV1p_5kL1_E=&|P+B=bYIy zw{@BMzVD6y`A;`LCNt;qKW79)nq!!NjW~kyxD85IUHAj1u@{T*n1!fF2fPTENX(Nu z@G%}&Lmq`QWaNu(<4rl_>9|EYKI$_(ln!}1I?2UneT#e}S5;&p^Uq%zH>1LZrhzSDc6`^_7 zF*t4GzBsKQIaetD2QCwvW51$+{HjoPJqXRI=POvQ(T7}&u^5It<)3&0<1k1W&Bt(z zL0>{vLoPvG;@?}bQUZDweoe%G!m|>H%^|8sc<}izV5!4e^gG(RK_aWHaPCNwgCq6VbSI{;m}TiTHTcD2R|# zkc$=Z)rt6XiulAte7!V!7e$1ef;?6c-%5za|BJRC}k z{e8c0At3N5uB63(#>0mAQX%`rY)!2qAlp7utMo+@K_z-LHv^3fiv#=gpu_~m)5Z@+RqM z*^Ad}t)sn9$nHG^ceEhi!4N`r>wVg5!utr>ou@ildY?+j&Ykb@4AC=+?Qeu^Ra zNR|__Gxx!D$B?h14EU^4l4Ez%wj4Gi=+xMaX6kRMZF=Wjz^jo^AVf3?yWucWaF0mRYiv zWysh~$SIfF)Ck#)(kB zkOg|7MHBK)M}zRxKJi|JT%|pUS>vFj)~KqeLmuqlBjnbvIvmi-yPcIY^m7>UUI)|h zwU+E!&CI<~hCId5n#U{paY}B4Y?NXqvgnSgaq@&@4 zd?TkI-v}qLg^EkO`TP37L=ynRW_Ue&FaOLUZhu3dpYs<$};0 zVsqr)IH@4{g5C7vFZhAhbR+&!a-1me0jMB#2XTQ(F?p?B%H`8-H4wbIjc+MxNA#PchUy)6o@K%R0jX;ulLPx#07*qoM6N<$f)IO1djJ3c literal 0 HcmV?d00001 diff --git a/lib/images/mobile-not-supported-white.png b/lib/images/mobile-not-supported-white.png new file mode 100644 index 0000000000000000000000000000000000000000..aa74b8f4229cbcd2652134bb65b6a8d09584ef38 GIT binary patch literal 763 zcmVp%ZFBO)R!;R0bM z@hiPtVWmWcVn#?@#yBjhcfSZmCRi&XTqHPoEFy$ep`U81eCiv;h>$9JDXPV{uvhmn zPYg5g$G47aO1==R{8zwd)cDrhcEG9xR(W?4m;}Z?Od~`cZFDn4mKI4nXp>-9#!^;~>kmH{=A|uk8Ynct~=!uht28LfaW? zgS#W&Do?W$ut8aahL~|d(yr>CI{|Mh>$Rbg3zBA48+HOlH|&6^AC5>~sk#m);Bmca zGQA-6;nwH`+@mToP3%x>J z`pAMf$f%ULA72j*$LAss$R5(%fy)`yH3Mw3K|V6w`wQcr*d?Y1PzQU+5fLGU#q8v% zsecvTl2p0nO*O)Mep_a*-g6qI$S`nw8RyW<^72ydxZqx}#>Z8J0$PYzUUcmgCvaIJ zo$ZuUO9OROP(oJnewYOQQ{WJtzV$=$y{r!NTfVhxhO5jfD%ld)wqdin{qGA37`^^J z2?}^jeQ_x$U^d{~$ix5u002ovPDHLkV1j2>UyJ|% literal 0 HcmV?d00001 diff --git a/lib/images/mobile-not-supported-white@2x.png b/lib/images/mobile-not-supported-white@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fce5aef8d026062f25c946f9bfee3616e0f163eb GIT binary patch literal 1482 zcmV;*1vUDKP)Ltei9LCEDk&pr2f&N=s5pS$PWbM9y7 z-upb~`Td@A42+KMZP(oBvFm`4|0DFZAE1?JI* zw@O7YFvCM>fy zt`|@yA!cD1l2Ic)v^S4Y{aTO|Vi*#4qD#PCg5*v)u*ao3s0#E>@qs?j2l_yFfLD;} zN^MdFdVZ+$fj-a&`amD(1AU+mbPza!VwO|Q=QMMK@7d2THuDl?OmZ8T$t$!-rrV?C z1nVgv5PRS_9->(z6j{N=u>%H}&oS+b_ikP#ITpZd_FA4{v@rhXRRm+bu;N!EaqO|SHT4e5+siqxbx}<-|(*tHmS}zTm4cj4#^O{HXfQ3Taamj?;I`Lg$kJt5p4+w2L zq%j4n#MQ$4YxRJS3vHXjggvYg7YXnFP!G6FXsZevnXy8=Tlx)Y0GCSUvxb92agEG2 zJ>bKV%X`B?qWBfRrw1$*+P;erg3}|uEWGO-J>bnk+vx~FG(F-c(#uv2V3OoodV0h_ zraG}xct?ruSS$wEDiva_YQ!m$?bqr8#lHTqQaD(J_=d2pP5)}9T+*KH2r+>UVaq0c zV45TnxLq-*Zccns(q3j|PO?q1CEqr23%^Rj!%0?v_e!>W#W>qUu~b`|)x~o{vh7Xf z?1?IIh%BoU_l42}E^wImk@d(z0>>p=`zUgdI1tn~SO^{!OAmUMCmbWLvl}1W66U~f zD^AnRiS4A=1*UK`%)yV0+zt?-P7j+~>1CP|A>ivwf|y@YK%8l8wbtbBo^q$6{6(hu zrB14u6Ot?(PcF~#jp?s%jgxtjrAoPvV|>95_R*?vPt;b)nYWc`TF6-k6FD10;`@%| z>Sl7%5!@ilqm}KP#9`aKZi=Isj!kBxE#5wEhrp5DkibbYfl7nj&;<%rKTDUHr z50lZ0+NzHL`E1~Cg^PjDc<@}JA?E955-FsTJ^U_7w;rI1`$D3MF0MSfgel}Rhf)fe z#zf_{GXByiALs*ppbzwcKF|mL4}rB*yHYz;f!--T&Va5=P&wbq121OoH1+8S9%Q?AM*R@KbIi%2ryeX#{OC#E5Vwvzf3Vz>z)iuAn5^*}@94;S zkYV8Yk~s0xaRM~S_4JK31X;*23>}FQD2gQ!$FN=7DmAn|^ kTESvUNE1gG1CQ5#0bZeKa{u1_9{>OV07*qoM6N<$g5LDfZU6uP literal 0 HcmV?d00001 diff --git a/package.json b/package.json index 1b4361dd..f7a85480 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bnc-assist", - "version": "0.5.0", + "version": "0.5.1", "description": "Blocknative Assist js library for Dapp developers", "main": "lib/assist.min.js", "scripts": { @@ -28,7 +28,8 @@ "@babel/polyfill": "^7.0.0", "babel-eslint": "^10.0.1", "bluebird": "^3.5.3", - "bowser": "^2.0.0-beta.3" + "bowser": "^2.0.0-beta.3", + "uuid": "^3.3.2" }, "devDependencies": { "@babel/core": "^7.2.2", diff --git a/src/__tests__/helpers/iframe.test.js b/src/__tests__/helpers/iframe.test.js index f1c66ea6..74cf53ec 100644 --- a/src/__tests__/helpers/iframe.test.js +++ b/src/__tests__/helpers/iframe.test.js @@ -2,8 +2,10 @@ import 'jest-dom/extend-expect' import { createIframe } from '../../js/helpers/iframe' import styles from '../../css/styles.css' +import { updateState } from '../../js/helpers/state' test('iframe gets rendered to document', () => { + updateState({ config: {} }) createIframe(window.document, styles) const iframe = document.querySelector('iframe') diff --git a/src/css/dark-mode.css b/src/css/dark-mode.css new file mode 100644 index 00000000..919a5907 --- /dev/null +++ b/src/css/dark-mode.css @@ -0,0 +1,50 @@ +.bn-onboard-modal-shade { + background: rgba(237, 237, 237, 0.1); +} + +.bn-onboard-modal { + background: #152128; +} + +h1, +h2, +h3, +h4, +h5, +p { + color: #ededed; +} + +.bn-onboard-basic .bn-onboard-main { + background: #152128; +} + +.bn-onboard-basic .bn-onboard-sidebar { + background: #2c3943; +} + +.bn-active { + color: #ededed; +} + +.bn-notification { + background: #152128; + box-shadow: -1px -1px 8px 0px rgba(237, 237, 237, 0.1); + border: 1px 0px solid rgba(237, 237, 237, 0.1); +} + +.bn-status-icon { + transition: background-color 150ms ease-in-out; +} + +.bn-notification:hover .bn-status-icon { + background-position: -48px 1px !important; + background-color: #152128; +} + +.bn-notification.bn-failed .bn-status-icon:hover, +.bn-notification.bn-progress .bn-status-icon:hover, +.bn-notification.bn-complete .bn-status-icon:hover { + background-color: #ededed; + background-position: -48px 1px !important; +} diff --git a/src/css/styles.css b/src/css/styles.css index d88c2058..825adc7e 100644 --- a/src/css/styles.css +++ b/src/css/styles.css @@ -1,7 +1,6 @@ /* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 License: none (public domain) - 1. Reset 2. Fonts 3. Onboarding @@ -135,13 +134,10 @@ table { } /* Colors and Fonts - Red:#FF3F4A; Yellow:;#FFC137 Green:#7ED321; - Font sizes based on https://www.modularscale.com/?16&px&1.125 - */ h1, @@ -243,14 +239,13 @@ b { } .bn-onboard { - padding: 10px; + border-radius: 2px; + box-shadow: 0px 2px 15px rgba(0, 0, 0, 0.1); } .bn-onboard-modal { background: #fff; border-radius: 2px; - border: 1px solid #efefef; - box-shadow: 0px 2px 15px rgba(0, 0, 0, 0.1); box-sizing: border-box; position: relative; } @@ -272,6 +267,7 @@ b { background: #fff; -webkit-flex-grow: 1; flex-grow: 1; + border-radius: 0 2px 2px 0; } .bn-onboard-basic .bn-onboard-sidebar { @@ -280,6 +276,7 @@ b { background: #eeeeee; -webkit-flex-shrink: 0; flex-shrink: 0; + border-radius: 2px 0 0 2px; } .bn-onboard-advanced { padding: 15px 20px; @@ -329,6 +326,7 @@ img.bn-onboard-img { display: block; max-width: 100%; height: auto; + border-radius: 4px; } .bn-onboard-close { @@ -408,11 +406,8 @@ img.bn-onboard-img { #blocknative-notifications { position: fixed; - z-index: 999; - bottom: 10px; - right: 10px; opacity: 0; - transform: translateX(600px); + padding: 10px; transition: opacity 150ms ease-in-out, transform 300ms ease-in-out; } @@ -420,12 +415,6 @@ img.bn-onboard-img { display: none; } -.bn-notifications-scroll { - overflow-y: scroll; - overflow-x: hidden; - padding-top: 3rem; -} - .bn-notification { background: #fff; border-left: 2px solid transparent; @@ -436,21 +425,38 @@ img.bn-onboard-img { 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.*/ - transform: translateX(600px); opacity: 0; transition: transform 350ms ease-in-out, opacity 300ms linear; font-family: 'Source Sans Pro', 'Open Sans', 'Helvetica Neue', Arial, sans-serif; } +ul.bn-notifications { + list-style-type: none; +} + .bn-notification.bn-progress { - border-left: 2px solid #ffc137; + border-color: #ffc137; + border-width: 0px 0px 0px 2px; + border-style: solid; } .bn-notification.bn-complete { - border-left: 2px solid #7ed321; + border-color: #7ed321; + border-width: 0px 0px 0px 2px; + border-style: solid; } .bn-notification.bn-failed { - border-left: 2px solid #ff3f4a; + border-color: #ff3f4a; + border-width: 0px 0px 0px 2px; + border-style: solid; +} + +li.bn-notification.bn-right-border { + border-width: 0px 2px 0px 0px; +} + +li.bn-notification.bn-right-border .bn-notification-info { + margin: 0px 10px 0px 5px; } .bn-status-icon { @@ -458,6 +464,11 @@ img.bn-onboard-img { width: 18px; height: 18px; background-image: url('https://assist.blocknative.com/images/jJu8b0B.png'); + border-radius: 50%; +} + +.bn-float-right { + float: right; } .bn-progress .bn-status-icon { @@ -509,14 +520,14 @@ img.bn-onboard-img { } a#bn-transaction-branding { - float: right; - margin-right: 10px; + margin: 0 10px; padding-top: 10px; display: inline-block; width: 18px; height: 25px; - background: transparent url('https://assist.blocknative.com/images/fJxOtIj.png') no-repeat - center left; + background: transparent + url('https://assist.blocknative.com/images/fJxOtIj.png') no-repeat center + left; -webkit-transition: width 0.2s ease-out; -moz-transition: width 0.2s ease-out; -o-transition: width 0.2s ease-out; @@ -590,6 +601,10 @@ a#bn-transaction-branding:hover { transition: opacity 0.25s ease-out 100ms; } +div.progress-tooltip.bn-left { + left: -180; +} + .progress-tooltip-inner { max-width: 200px; padding: 3px 8px; @@ -614,6 +629,11 @@ a#bn-transaction-branding:hover { content: ''; } +div.progress-tooltip.bn-left::after { + left: initial; + right: 4px; +} + .bn-status-icon:hover .progress-tooltip { opacity: 1; filter: alpha(opacity=1); @@ -737,26 +757,26 @@ fieldset[disabled] .bn-btn-default.focus { } .bn-btn-primary { color: #ffffff; - background-color: #337ab7; - border-color: #2e6da4; + background-color: #26c6da; + border-color: #26c6da; } .bn-btn-primary:focus, .bn-btn-primary.focus { color: #ffffff; - background-color: #286090; + background-color: #26c6da; border-color: #122b40; } .bn-btn-primary:hover { color: #ffffff; - background-color: #286090; - border-color: #204d74; + background-color: #26c6da; + border-color: #26c6da; } .bn-btn-primary:active, .bn-btn-primary.active, .open > .dropdown-toggle.bn-btn-primary { color: #ffffff; background-color: #286090; - border-color: #204d74; + border-color: #26c6da; } .bn-btn-primary:active:hover, .bn-btn-primary.active:hover, @@ -768,7 +788,7 @@ fieldset[disabled] .bn-btn-default.focus { .bn-btn-primary.active.focus, .open > .dropdown-toggle.bn-btn-primary.focus { color: #ffffff; - background-color: #204d74; + background-color: #26c6da; border-color: #122b40; } .bn-btn-primary:active, @@ -1085,13 +1105,17 @@ input[type='button'].bn-btn-block { } .bn-btn-outline { - background-color: transparent; + background-color: white; color: inherit; transition: all 0.5s; } .bn-btn-primary.bn-btn-outline { - color: #428bca; + color: #26c6da; +} + +.bn-btn-primary.bn-btn-outline:focus { + color: white; } .bn-btn-success.bn-btn-outline { diff --git a/src/js/helpers/iframe.js b/src/js/helpers/iframe.js index b94ecd43..dae76192 100644 --- a/src/js/helpers/iframe.js +++ b/src/js/helpers/iframe.js @@ -1,28 +1,34 @@ import { updateState, state } from './state' -import { getById } from '../views/dom' +import { positionElement } from '../views/dom' + +import darkModeStyles from '../../css/dark-mode.css' + +export function createIframe(browserDocument, assistStyles, style = {}) { + const { darkMode } = style -export function createIframe(browserDocument, style) { const initialIframeContent = ` + ` - const iframe = browserDocument.createElement('iframe') + const iframe = positionElement(browserDocument.createElement('iframe')) browserDocument.body.appendChild(iframe) iframe.style.position = 'fixed' - iframe.style.top = '0' - iframe.style.left = '0' - iframe.style.height = '100vh' - iframe.style.width = '100%' + iframe.style.height = '0' + iframe.style.width = '0' iframe.style.border = 'none' iframe.style.pointerEvents = 'none' - iframe.style['z-index'] = 999 + iframe.style.overflow = 'hidden' + iframe.style.transition = 'height 250ms ease-in-out' const iWindow = iframe.contentWindow const iDocument = iWindow.document iDocument.open() @@ -32,39 +38,17 @@ export function createIframe(browserDocument, style) { updateState({ iframe, iframeDocument: iDocument, iframeWindow: iWindow }) } -export function setupIframe(notificationsList) { - state.iframe.style.top = '' - state.iframe.style.left = '' - state.iframe.style.bottom = '0px' - state.iframe.style.right = '0px' - state.iframe.style.height = `${ - Number(notificationsList.clientHeight) + - Number(getById('bn-transaction-branding').clientHeight) + - 7 + // for some reason the bn-logo clips if this is not included - 40 // this is the height of the progress-tooltip - }px` - state.iframe.style.width = `${ - notificationsList.clientWidth + 17 // 17px is the width of the scrollbar - }px` +export function showIframe() { + state.iframe.style['z-index'] = 999 state.iframe.style.pointerEvents = 'all' } -// Set the iframe to the size of the notification list -export function resizeIframe() { - const notificationsContainer = getById('blocknative-notifications') - if (notificationsContainer) { - state.iframe.style.height = `${notificationsContainer.clientHeight}px` - state.iframe.style.width = `${notificationsContainer.clientWidth}px` - state.iframe.style.pointerEvents = 'all' - } +export function hideIframe() { + state.iframe.style['z-index'] = 'initial' + state.iframe.style.pointerEvents = 'none' } -export function resetIframe() { - state.iframe.style.top = '0px' - state.iframe.style.left = '0px' - state.iframe.style.bottom = '' - state.iframe.style.right = '' - state.iframe.style.height = '100vh' - state.iframe.style.width = '100%' - state.iframe.style.pointerEvents = 'none' +export function resizeIframe({ height, width }) { + state.iframe.style.height = `${height}px` + state.iframe.style.width = `${width}px` } diff --git a/src/js/helpers/utilities.js b/src/js/helpers/utilities.js index ad4cf13f..6c2dc194 100644 --- a/src/js/helpers/utilities.js +++ b/src/js/helpers/utilities.js @@ -1,3 +1,4 @@ +import uuid from 'uuid/v4' import { state } from './state' import { handleEvent } from './events' @@ -29,13 +30,12 @@ export function nowInTxPool(txHash) { txObj.transaction.inTxPool = true } -export function isDuplicateTransaction(txObject) { +export function isDuplicateTransaction({ value, to }) { const { transactionQueue } = state - return transactionQueue.filter( - txObj => - txObj.transaction.value === txObject.value && - txObj.transaction.to === txObject.to - )[0] + + return transactionQueue.find( + txObj => txObj.transaction.value === value && txObj.transaction.to === to + ) } // Nice time format @@ -53,25 +53,49 @@ export function timeString(time) { return seconds >= 60 ? `${Math.floor(seconds / 60)} min` : `${seconds} sec` } +function last(arr) { + return [...arr].reverse()[0] +} + +function takeLast(arr) { + // mutates original array + return arr.splice(arr.length - 1, 1)[0] +} + +export function first(arr) { + return arr[0] +} + +function takeFirst(arr) { + // mutates original arr + return arr.splice(0, 1)[0] +} + export function separateArgs(allArgs, argsLength) { const allArgsCopy = [...allArgs] const args = argsLength ? allArgsCopy.splice(0, argsLength) : [] + + const inlineCustomMsgs = + typeof last(allArgsCopy) === 'object' && + last(allArgsCopy).messages && + takeLast(allArgsCopy).messages + const callback = - typeof allArgsCopy[allArgsCopy.length - 1] === 'function' && - allArgsCopy.splice(allArgsCopy.length - 1, 1)[0] + typeof last(allArgsCopy) === 'function' && takeLast(allArgsCopy) const txObject = - typeof allArgsCopy[0] === 'object' && allArgsCopy[0] !== null - ? allArgsCopy.splice(0, 1)[0] + typeof first(allArgsCopy) === 'object' && first(allArgsCopy) !== null + ? takeFirst(allArgsCopy) : {} - const defaultBlock = allArgsCopy[0] && allArgsCopy[0] + const defaultBlock = first(allArgsCopy) return { callback, args, txObject, - defaultBlock + defaultBlock, + inlineCustomMsgs } } @@ -96,6 +120,10 @@ export function handleError(categoryCode, propagateError) { } } +export function createTransactionId() { + return uuid() +} + export function capitalize(str) { const first = str.slice(0, 1) const rest = str.slice(1) diff --git a/src/js/helpers/web3.js b/src/js/helpers/web3.js index 3467d08d..22c75b20 100644 --- a/src/js/helpers/web3.js +++ b/src/js/helpers/web3.js @@ -89,7 +89,9 @@ export const web3Functions = { default: return () => Promise.reject(errorObj) } - } + }, + txReceipt: txHash => + promisify(state.web3Instance.eth.getTransactionReceipt)(txHash) } export function configureWeb3(web3) { @@ -167,10 +169,10 @@ export function getNonce(address) { return web3Functions.nonce(version)(address) } -export function hasSufficientBalance( +export function getTransactionParams( txObject = {}, contractMethod, - methodArgs + contractEventObj ) { return new Promise(async (resolve, reject) => { const version = state.web3Version && state.web3Version.slice(0, 3) @@ -180,7 +182,7 @@ export function hasSufficientBalance( txObject.value = formatNumber(txObject.value) } - const transactionValue = txObject.value + const value = txObject.value ? await web3Functions .bigNumber(version)(txObject.value) .catch(handleError('web3', reject)) @@ -206,7 +208,7 @@ export function hasSufficientBalance( await web3Functions .contractGas(version)( contractMethod, - methodArgs.parameters, + contractEventObj.parameters, txObject ) .catch(handleError('web3', reject)) @@ -220,33 +222,32 @@ export function hasSufficientBalance( ) .catch(handleError('web3', reject)) - const transactionFee = gas.mul(gasPrice) + resolve({ value, gasPrice, gas }) + }) +} + +export function hasSufficientBalance({ value, gas, gasPrice }) { + return new Promise(async (resolve, reject) => { + const version = state.web3Version && state.web3Version.slice(0, 3) - const buffer = transactionFee.div( + const gasCost = gas.mul(gasPrice) + + const buffer = gasCost.div( await web3Functions .bigNumber(version)('10') .catch(handleError('web3', reject)) ) - const totalTransactionCost = transactionFee - .add(transactionValue) - .add(buffer) + const transactionCost = gasCost.add(value).add(buffer) const balance = await getAccountBalance().catch(handleError('web3', reject)) const accountBalance = await web3Functions .bigNumber(version)(balance) .catch(handleError('web3', reject)) - const sufficientBalance = accountBalance.gt(totalTransactionCost) - - const transactionParams = { - value: transactionValue.toString(), - gas: gas.toString(), - gasPrice: gasPrice.toString(), - to: txObject.to - } + const sufficientBalance = accountBalance.gt(transactionCost) - resolve({ transactionParams, sufficientBalance }) + resolve(sufficientBalance) }) } @@ -256,7 +257,7 @@ export function getAccountBalance() { updateState({ accountAddress: accounts[0] }) const version = state.web3Version && state.web3Version.slice(0, 3) - const balance = web3Functions + const balance = await web3Functions .balance(version)(accounts[0]) .catch(handleError('web3', reject)) @@ -318,19 +319,20 @@ export function getCurrentProvider() { // Poll for a tx receipt export function waitForTransactionReceipt(txHash) { - const web3 = state.web3Instance || window.web3 return new Promise((resolve, reject) => { + return checkForReceipt() + function checkForReceipt() { - web3.eth.getTransactionReceipt(txHash, (err, res) => { - if (err) { - return reject(err) - } - if (res === null) { - return setTimeout(() => checkForReceipt(), timeouts.pollForReceipt) - } - return resolve(res) - }) + return web3Functions + .txReceipt(txHash) + .then(txReceipt => { + if (!txReceipt) { + return setTimeout(() => checkForReceipt(), timeouts.pollForReceipt) + } + + return resolve(txReceipt) + }) + .catch(reject) } - checkForReceipt() }) } diff --git a/src/js/index.js b/src/js/index.js index 20768793..7c39f6a1 100644 --- a/src/js/index.js +++ b/src/js/index.js @@ -22,10 +22,10 @@ import { removeItem, getItem } from './helpers/storage' -import styles from '../css/styles.css' +import assistStyles from '../css/styles.css' // Library Version - if changing, also need to change in package.json -const version = '0.5.0' +const version = '0.5.1' function init(config) { updateState({ version }) @@ -49,7 +49,7 @@ function init(config) { updateState({ config }) } - const { web3, dappId, mobileBlocked, headlessMode } = config + const { web3, dappId, mobileBlocked, headlessMode, style } = config // Check that an api key has been provided to the config object if (!dappId) { @@ -77,7 +77,7 @@ function init(config) { // Commit a cardinal sin and create an iframe (to isolate the CSS) if (!state.iframe && !headlessMode) { - createIframe(document, styles) + createIframe(document, assistStyles, style) } // Check if on mobile and mobile is blocked @@ -405,7 +405,7 @@ function init(config) { // TRANSACTION FUNCTION // - function Transaction(txObject, callback) { + function Transaction(txObject, callback, inlineCustomMsgs) { if (!state.validApiKey) { const errorObj = new Error('Your api key is not valid') errorObj.eventCode = 'initFail' @@ -437,7 +437,8 @@ function init(config) { 'activeTransaction', txObject, sendMethod, - callback + callback, + inlineCustomMsgs ).catch(errorObj => { reject(errorObj) callback && callback(errorObj) @@ -452,7 +453,35 @@ function init(config) { function getState() { return new Promise(async (resolve, reject) => { await checkUserEnvironment().catch(reject) - resolve(state) + const { + mobileDevice, + validBrowser, + currentProvider, + web3Wallet, + accessToAccounts, + walletLoggedIn, + walletEnabled, + accountAddress, + accountBalance, + minimumBalance, + userCurrentNetworkId, + correctNetwork + } = state + + resolve({ + mobileDevice, + validBrowser, + currentProvider, + web3Wallet, + accessToAccounts, + walletLoggedIn, + walletEnabled, + accountAddress, + accountBalance, + minimumBalance, + userCurrentNetworkId, + correctNetwork + }) }) } diff --git a/src/js/logic/contract-methods.js b/src/js/logic/contract-methods.js index dbaa6f27..8477a88c 100644 --- a/src/js/logic/contract-methods.js +++ b/src/js/logic/contract-methods.js @@ -69,13 +69,17 @@ export function modernSend(method, name, args) { returnObject.send = (...innerArgs) => new Promise(async (resolve, reject) => { - const { callback, txObject } = separateArgs(innerArgs, 0) + const { callback, txObject, inlineCustomMsgs } = separateArgs( + innerArgs, + 0 + ) const txPromiseObj = sendTransaction( 'activeContract', txObject, innerMethod, callback, + inlineCustomMsgs, method, { methodName: name, parameters: args } ).catch(error => { @@ -135,13 +139,17 @@ export async function legacyCall(method, name, allArgs, argsLength) { } export async function legacySend(method, name, allArgs, argsLength) { - const { callback, txObject, args } = separateArgs(allArgs, argsLength) + const { callback, txObject, args, inlineCustomMsgs } = separateArgs( + allArgs, + argsLength + ) await sendTransaction( 'activeContract', txObject, promisify(method.sendTransaction), callback, + inlineCustomMsgs, method, { methodName: name, diff --git a/src/js/logic/send-transaction.js b/src/js/logic/send-transaction.js index a748dee2..40cc0f0d 100644 --- a/src/js/logic/send-transaction.js +++ b/src/js/logic/send-transaction.js @@ -4,7 +4,8 @@ import { prepareForTransaction } from './user' import { hasSufficientBalance, getNonce, - waitForTransactionReceipt + waitForTransactionReceipt, + getTransactionParams } from '../helpers/web3' import { isDuplicateTransaction, @@ -14,7 +15,8 @@ import { addTransactionToQueue, timeouts, extractMessageFromError, - handleError + handleError, + createTransactionId } from '../helpers/utilities' function inferNonce() { @@ -33,35 +35,47 @@ function sendTransaction( txObject = {}, sendTransactionMethod, callback, + inlineCustomMsgs, contractMethod, contractEventObj ) { return new Promise(async (resolve, reject) => { - // Prepare for transaction - try { - await prepareForTransaction('activePreflight') - } catch (errorObj) { - reject(errorObj) - return - } + // Make sure user is onboarded and ready to transact + await prepareForTransaction('activePreflight').catch(reject) // make sure that we have from address in txObject if (!txObject.from) { txObject.from = state.accountAddress } - // Get the total transaction cost and see if there is enough balance - const { sufficientBalance, transactionParams } = await hasSufficientBalance( + const transactionId = createTransactionId() + + const transactionParams = await getTransactionParams( txObject, contractMethod, contractEventObj ).catch(reject) + const transactionEventObj = { + id: transactionId, + gas: transactionParams.gas.toString(), + gasPrice: transactionParams.gasPrice.toString(), + value: transactionParams.value.toString(), + to: txObject.to, + from: txObject.from + } + + const sufficientBalance = await hasSufficientBalance( + transactionParams + ).catch(reject) + if (!sufficientBalance) { handleEvent({ eventCode: 'nsfFail', categoryCode: 'activePreflight', - transaction: transactionParams, + transaction: transactionEventObj, + contract: contractEventObj, + inlineCustomMsgs, wallet: { provider: state.currentProvider, address: state.accountAddress, @@ -77,43 +91,38 @@ function sendTransaction( return } - const duplicateTransaction = isDuplicateTransaction(transactionParams) + const duplicateTransaction = isDuplicateTransaction(transactionEventObj) + if (duplicateTransaction) { - handleEvent( - addContractEventObj( - { - eventCode: 'txRepeat', - categoryCode: 'activePreflight', - transaction: transactionParams, - wallet: { - provider: state.currentProvider, - address: state.accountAddress, - balance: state.accountBalance, - minimum: state.config.minimumBalance - } - }, - contractEventObj - ) - ) + handleEvent({ + eventCode: 'txRepeat', + categoryCode: 'activePreflight', + transaction: transactionEventObj, + contract: contractEventObj, + inlineCustomMsgs, + wallet: { + provider: state.currentProvider, + address: state.accountAddress, + balance: state.accountBalance, + minimum: state.config.minimumBalance + } + }) } if (state.transactionAwaitingApproval) { - handleEvent( - addContractEventObj( - { - eventCode: 'txAwaitingApproval', - categoryCode: 'activePreflight', - transaction: transactionParams, - wallet: { - provider: state.currentProvider, - address: state.accountAddress, - balance: state.accountBalance, - minimum: state.config.minimumBalance - } - }, - contractEventObj - ) - ) + handleEvent({ + eventCode: 'txAwaitingApproval', + categoryCode: 'activePreflight', + transaction: transactionEventObj, + contract: contractEventObj, + inlineCustomMsgs, + wallet: { + provider: state.currentProvider, + address: state.accountAddress, + balance: state.accountBalance, + minimum: state.config.minimumBalance + } + }) } let txPromise @@ -133,22 +142,19 @@ function sendTransaction( resolve(txPromise) - handleEvent( - addContractEventObj( - { - eventCode: 'txRequest', - categoryCode, - transaction: transactionParams, - wallet: { - provider: state.currentProvider, - address: state.accountAddress, - balance: state.accountBalance, - minimum: state.config.minimumBalance - } - }, - contractEventObj - ) - ) + handleEvent({ + eventCode: 'txRequest', + categoryCode, + transaction: transactionEventObj, + contract: contractEventObj, + inlineCustomMsgs, + wallet: { + provider: state.currentProvider, + address: state.accountAddress, + balance: state.accountBalance, + minimum: state.config.minimumBalance + } + }) updateState({ transactionAwaitingApproval: true }) @@ -158,41 +164,51 @@ function sendTransaction( // Check if user has confirmed transaction after 20 seconds setTimeout(async () => { const nonce = await inferNonce().catch(reject) + if (!checkTransactionQueue(nonce) && !rejected && !confirmed) { - handleEvent( - addContractEventObj( - { - eventCode: 'txConfirmReminder', - categoryCode, - transaction: transactionParams, - wallet: { - provider: state.currentProvider, - address: state.accountAddress, - balance: state.accountBalance, - minimum: state.config.minimumBalance - } - }, - contractEventObj - ) - ) + handleEvent({ + eventCode: 'txConfirmReminder', + categoryCode, + transaction: transactionEventObj, + contract: contractEventObj, + inlineCustomMsgs, + wallet: { + provider: state.currentProvider, + address: state.accountAddress, + balance: state.accountBalance, + minimum: state.config.minimumBalance + } + }) } }, timeouts.txConfirmReminder) if (state.legacyWeb3) { txPromise - .then(async txHash => { - confirmed = handleTxHash( + .then(txHash => { + confirmed = true + + handleTxHash( txHash, - { transactionParams, categoryCode, contractEventObj }, + { + transactionEventObj, + categoryCode, + contractEventObj, + inlineCustomMsgs + }, reject ) callback && callback(null, txHash) - waitForTransactionReceipt(txHash).then(receipt => { + return waitForTransactionReceipt(txHash).then(receipt => { handleTxReceipt( receipt, - { transactionParams, categoryCode, contractEventObj }, + { + transactionEventObj, + categoryCode, + contractEventObj, + inlineCustomMsgs + }, reject ) }) @@ -200,7 +216,12 @@ function sendTransaction( .catch(async errorObj => { rejected = handleTxError( errorObj, - { transactionParams, categoryCode, contractEventObj }, + { + transactionEventObj, + categoryCode, + contractEventObj, + inlineCustomMsgs + }, reject ) callback && callback(errorObj) @@ -208,9 +229,16 @@ function sendTransaction( } else { txPromise .on('transactionHash', async txHash => { - confirmed = handleTxHash( + confirmed = true + + handleTxHash( txHash, - { transactionParams, categoryCode, contractEventObj }, + { + transactionEventObj, + categoryCode, + contractEventObj, + inlineCustomMsgs + }, reject ) callback && callback(null, txHash) @@ -218,14 +246,26 @@ function sendTransaction( .on('receipt', async receipt => { handleTxReceipt( receipt, - { transactionParams, categoryCode, contractEventObj }, + { + transactionEventObj, + categoryCode, + contractEventObj, + inlineCustomMsgs + }, reject ) }) .on('error', async errorObj => { - rejected = handleTxError( + rejected = true + + handleTxError( errorObj, - { transactionParams, categoryCode, contractEventObj }, + { + transactionEventObj, + categoryCode, + contractEventObj, + inlineCustomMsgs + }, reject ) callback && callback(errorObj) @@ -236,40 +276,52 @@ function sendTransaction( async function handleTxHash(txHash, meta, reject) { const nonce = await inferNonce().catch(reject) - const { transactionParams, categoryCode, contractEventObj } = meta - - onResult(transactionParams, nonce, categoryCode, contractEventObj, txHash) - - return true // confirmed + const { + transactionEventObj, + categoryCode, + contractEventObj, + inlineCustomMsgs + } = meta + + onResult( + transactionEventObj, + nonce, + categoryCode, + contractEventObj, + txHash, + inlineCustomMsgs + ) } async function handleTxReceipt(receipt, meta, reject) { const { transactionHash } = receipt const txObj = getTransactionObj(transactionHash) const nonce = await inferNonce().catch(reject) - const { transactionParams, categoryCode, contractEventObj } = meta - - handleEvent( - addContractEventObj( - { - eventCode: 'txConfirmedClient', - categoryCode, - transaction: - (txObj && txObj.transaction) || - Object.assign({}, transactionParams, { - hash: transactionHash, - nonce - }), - wallet: { - provider: state.currentProvider, - address: state.accountAddress, - balance: state.accountBalance, - minimum: state.config.minimumBalance - } - }, - contractEventObj - ) - ) + const { + transactionEventObj, + categoryCode, + contractEventObj, + inlineCustomMsgs + } = meta + + handleEvent({ + eventCode: 'txConfirmedClient', + categoryCode, + transaction: + (txObj && txObj.transaction) || + Object.assign({}, transactionEventObj, { + hash: transactionHash, + nonce + }), + contract: contractEventObj, + inlineCustomMsgs, + wallet: { + provider: state.currentProvider, + address: state.accountAddress, + balance: state.accountBalance, + minimum: state.config.minimumBalance + } + }) updateState({ transactionQueue: removeTransactionFromQueue( @@ -288,30 +340,30 @@ async function handleTxError(error, meta, reject) { } const nonce = await inferNonce().catch(reject) - const { transactionParams, categoryCode, contractEventObj } = meta - - handleEvent( - addContractEventObj( - { - eventCode: - errorMsg === 'transaction underpriced' - ? 'txUnderpriced' - : 'txSendFail', - categoryCode, - transaction: Object.assign({}, transactionParams, { - nonce - }), - reason: 'User denied transaction signature', - wallet: { - provider: state.currentProvider, - address: state.accountAddress, - balance: state.accountBalance, - minimum: state.config.minimumBalance - } - }, - contractEventObj - ) - ) + const { + transactionEventObj, + categoryCode, + contractEventObj, + inlineCustomMsgs + } = meta + + handleEvent({ + eventCode: + errorMsg === 'transaction underpriced' ? 'txUnderpriced' : 'txSendFail', + categoryCode, + transaction: Object.assign({}, transactionEventObj, { + nonce + }), + inlineCustomMsgs, + contract: contractEventObj, + reason: 'User denied transaction signature', + wallet: { + provider: state.currentProvider, + address: state.accountAddress, + balance: state.accountBalance, + minimum: state.config.minimumBalance + } + }) updateState({ transactionAwaitingApproval: false }) @@ -328,22 +380,16 @@ async function handleTxError(error, meta, reject) { return true // rejected } -function addContractEventObj(eventObj, contractEventObj) { - if (contractEventObj) { - return Object.assign({}, eventObj, { contract: contractEventObj }) - } - return eventObj -} - // On result handler function onResult( - transactionParams, + transactionEventObj, nonce, categoryCode, contractEventObj, - hash + hash, + inlineCustomMsgs ) { - const transaction = Object.assign({}, transactionParams, { + const transaction = Object.assign({}, transactionEventObj, { hash, nonce, startTime: Date.now(), @@ -351,22 +397,19 @@ function onResult( inTxPool: false }) - handleEvent( - addContractEventObj( - { - eventCode: 'txSent', - categoryCode, - transaction, - wallet: { - provider: state.currentProvider, - address: state.accountAddress, - balance: state.accountBalance, - minimum: state.config.minimumBalance - } - }, - contractEventObj - ) - ) + handleEvent({ + eventCode: 'txSent', + categoryCode, + transaction, + contract: contractEventObj, + inlineCustomMsgs, + wallet: { + provider: state.currentProvider, + address: state.accountAddress, + balance: state.accountBalance, + minimum: state.config.minimumBalance + } + }) updateState({ transactionQueue: addTransactionToQueue({ @@ -384,22 +427,19 @@ function onResult( !transactionObj.transaction.inTxPool && state.socketConnection ) { - handleEvent( - addContractEventObj( - { - eventCode: 'txStall', - categoryCode, - transaction, - wallet: { - provider: state.currentProvider, - address: state.accountAddress, - balance: state.accountBalance, - minimum: state.config.minimumBalance - } - }, - contractEventObj - ) - ) + handleEvent({ + eventCode: 'txStall', + categoryCode, + transaction, + contract: contractEventObj, + inlineCustomMsgs, + wallet: { + provider: state.currentProvider, + address: state.accountAddress, + balance: state.accountBalance, + minimum: state.config.minimumBalance + } + }) } }, timeouts.txStall) } diff --git a/src/js/views/content.js b/src/js/views/content.js index 69f16d08..2d4de2e1 100644 --- a/src/js/views/content.js +++ b/src/js/views/content.js @@ -25,12 +25,21 @@ import complete2x from '../../../lib/images/elR9xQ8.jpg' import blockNativeLogo from '../../../lib/images/fJxOtIj.png' import blockNativeLogo2x from '../../../lib/images/UhcCuKF.png' +import blockNativeLogoLight from '../../../lib/images/bn-branding-white.png' +import blockNativeLogoLight2x from '../../../lib/images/bn-branding-white@2x.png' + import mobile from '../../../lib/images/EcUxQVJ.jpg' import mobile2x from '../../../lib/images/GS6owd9.jpg' +import mobileLight from '../../../lib/images/mobile-not-supported-white.png' +import mobileLight2x from '../../../lib/images/mobile-not-supported-white@2x.png' + import browserFail from '../../../lib/images/riXzN0X.jpg' import browserFail2x from '../../../lib/images/xpWtOVX.jpg' +import browserFailLight from '../../../lib/images/browser-not-supported-white.png' +import browserFailLight2x from '../../../lib/images/browser-not-supported-white@2x.png' + import chromeLogo from '../../../lib/images/XAwAAmL.png' import chromeLogo2x from '../../../lib/images/maxXVIH.png' @@ -176,6 +185,10 @@ export const imageSrc = { src: blockNativeLogo, srcset: blockNativeLogo2x }, + blockNativeLogoLight: { + src: blockNativeLogoLight, + srcset: blockNativeLogoLight2x + }, mobile: { src: mobile, srcset: mobile2x @@ -184,6 +197,14 @@ export const imageSrc = { src: browserFail, srcset: browserFail2x }, + mobileLight: { + src: mobileLight, + srcset: mobileLight2x + }, + browserLight: { + src: browserFailLight, + srcset: browserFailLight2x + }, chromeLogo: { src: chromeLogo, srcset: chromeLogo2x diff --git a/src/js/views/dom.js b/src/js/views/dom.js index b57ae55f..f742f712 100644 --- a/src/js/views/dom.js +++ b/src/js/views/dom.js @@ -3,7 +3,8 @@ import { capitalize, timeString, timeouts, - stepToImageKey + stepToImageKey, + first } from '../helpers/utilities' import { onboardHeading, @@ -13,7 +14,7 @@ import { notSupported, onboardWarningMsg } from './content' -import { resizeIframe, resetIframe, setupIframe } from '../helpers/iframe' +import { showIframe, hideIframe, resizeIframe } from '../helpers/iframe' export function createElementString(type, className, innerHTML) { return ` @@ -45,21 +46,27 @@ export function createElement(el, className, children, id) { export function closeModal() { const modal = state.iframeDocument.querySelector('.bn-onboard-modal-shade') modal.style.opacity = '0' - state.iframe.style.pointerEvents = 'none' + + const notifications = getById('blocknative-notifications') + if (notifications) { + resizeIframe({ + height: notifications.clientHeight, + width: notifications.clientWidth + }) + } else { + hideIframe() + } + setTimeout(() => { state.iframeDocument.body.removeChild(modal) - const notificationsList = getByQuery('.bn-notifications') - if (notificationsList) { - setupIframe(notificationsList) - } }, timeouts.removeElement) } export function openModal(modal, handlers = {}) { const { onClick, onClose } = handlers - resetIframe() - state.iframe.style.pointerEvents = 'all' state.iframeDocument.body.appendChild(modal) + showIframe() + resizeIframe({ height: window.innerHeight, width: window.innerWidth }) const closeButton = state.iframeDocument.querySelector('.bn-onboard-close') closeButton.onclick = () => { @@ -105,7 +112,7 @@ export function browserLogos() { Firefox Logo + srcset="${firefoxLogo.srcset} 2x" />
Firefox @@ -114,15 +121,18 @@ export function browserLogos() { } export function onboardBranding() { - const { blockNativeLogo } = imageSrc + const { blockNativeLogo, blockNativeLogoLight } = imageSrc + const { darkMode } = state.config.style return ` @@ -131,13 +141,15 @@ export function onboardBranding() { export function notSupportedModal(type) { const info = notSupported[`${type}NotSupported`] + const { darkMode } = state.config.style + return `
- ${notSupportedImage(type)} + ${notSupportedImage(`${type}${darkMode ? 'Light' : ''}`)}

${info.heading}

${info.description()}

@@ -287,6 +299,10 @@ export function getByQuery(query) { return state.iframeDocument.querySelector(query) } +export function getAllByQuery(query) { + return Array.from(state.iframeDocument.querySelectorAll(query)) +} + export function createTransactionBranding() { const blockNativeBrand = createElement( 'a', @@ -296,18 +312,28 @@ 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' + return blockNativeBrand } export function notificationContent(type, message, time = {}) { const { showTime, startTime, timeStamp } = time const elapsedTime = timeString(Date.now() - startTime) + const position = + (state.config.style && state.config.style.notificationsPosition) || '' return ` - - ${ + + ${ type === 'progress' - ? `
+ ? `
You will be notified when this transaction is completed.
@@ -329,8 +355,13 @@ export function notificationContent(type, message, time = {}) { } // Start clock -export function startTimerInterval(notificationId, startTime) { - const notification = getById(notificationId) +export function startTimerInterval(notificationId, eventCode, startTime) { + const notification = first( + getAllByQuery(`.bn-${notificationId}`).filter(n => + n.classList.contains(`bn-${eventCode}`) + ) + ) + if (!notification) { return null } @@ -358,7 +389,7 @@ export function showElement(element, timeout) { export function hideElement(element) { setTimeout(() => { - element.style.transform = 'translateX(600px)' + element.style.transform = `translateX(${getPolarity()}600px)` element.style.opacity = '0' }, timeouts.hideElement) } @@ -367,7 +398,6 @@ export function removeElement(parent, element) { setTimeout(() => { if (parent.contains(element)) { parent.removeChild(element) - resizeIframe() if (parent !== state.iframeDocument.body) { checkIfNotifications() } @@ -375,6 +405,30 @@ export function removeElement(parent, element) { }, timeouts.removeElement) } +function getPolarity() { + const position = + (state.config.style && state.config.style.notificationsPosition) || '' + + return position.includes('Left') ? '-' : '' +} + +export function offsetElement(el) { + el.style.transform = `translate(${getPolarity()}600px)` + return el +} + +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.bottom = position.includes('bottom') || !position ? '0px' : 'initial' + el.style.top = position.includes('top') ? '0px' : 'initial' + + return el +} + // Remove notification from DOM export function removeNotification(notification) { const notificationsList = getByQuery('.bn-notifications') @@ -382,25 +436,12 @@ export function removeNotification(notification) { removeElement(notificationsList, notification) } -export function removeAllNotifications(queries) { - const notificationsToRemove = queries.reduce((allNotifications, query) => { - if (query) { - const newNotifications = Array.from( - state.iframeDocument.querySelectorAll(query) - ) - return [...allNotifications, ...newNotifications] +export function removeAllNotifications(notifications) { + notifications.forEach(notification => { + if (notification) { + removeNotification(notification) } - return allNotifications - }, []) - if (notificationsToRemove.length > 0) { - notificationsToRemove.forEach(notification => { - if (notification) { - setTimeout(() => { - removeNotification(notification) - }, timeouts.removeElement) - } - }) - } + }) } export function checkIfNotifications() { @@ -414,8 +455,6 @@ export function checkIfNotifications() { if (visibleNotifications.length === 0) { removeContainer() - } else { - setContainerHeight() } } @@ -424,21 +463,36 @@ export function removeContainer() { const notificationsContainer = getById('blocknative-notifications') hideElement(notificationsContainer) removeElement(state.iframeDocument.body, notificationsContainer) - resetIframe() + resizeIframe({ height: 0, width: 0 }) + hideIframe() } -export function setContainerHeight() { +export function setNotificationsHeight() { const scrollContainer = getByQuery('.bn-notifications-scroll') - - const maxHeight = - Number(window.innerHeight) - - Number(getById('bn-transaction-branding').clientHeight) - - 13 // needed to have some padding on the top - const listHeight = Number(getByQuery('.bn-notifications').clientHeight) - - if (listHeight > maxHeight) { - scrollContainer.style.height = maxHeight + const maxHeight = window.innerHeight + const widgetHeight = + scrollContainer.scrollHeight + + getById('bn-transaction-branding').clientHeight + + 16 + + const tooBig = widgetHeight > maxHeight + + if (tooBig) { + scrollContainer.style['overflow-y'] = 'scroll' + scrollContainer.style['overflow-x'] = 'hidden' + scrollContainer.style.height = + maxHeight - (getById('bn-transaction-branding').clientHeight + 26) } else { + scrollContainer.style['overflow-y'] = 'initial' + scrollContainer.style['overflow-x'] = 'initial' scrollContainer.style.height = 'auto' } + + const notificationsContainer = getById('blocknative-notifications') + const toolTipBuffer = !tooBig ? 50 : 0 + + resizeIframe({ + height: notificationsContainer.clientHeight + toolTipBuffer, + width: 371 + }) } diff --git a/src/js/views/event-to-ui.js b/src/js/views/event-to-ui.js index 649a899c..1f0c3d9b 100644 --- a/src/js/views/event-to-ui.js +++ b/src/js/views/event-to-ui.js @@ -7,16 +7,19 @@ import { onboardModal, getById, getByQuery, + getAllByQuery, + offsetElement, removeNotification, createTransactionBranding, notificationContent, showElement, - setContainerHeight, + setNotificationsHeight, startTimerInterval, - removeAllNotifications + removeAllNotifications, + positionElement } from './dom' -import { setupIframe } from '../helpers/iframe' +import { showIframe } from '../helpers/iframe' import { transactionMsgs } from './content' import { @@ -120,20 +123,13 @@ function onboardingUI(eventObj, handlers) { openModal(modal, handlers) } -const notificationsNoRepeat = [ - 'nsfFail', - 'txSendFail', - 'txStall', - 'txRepeat', - 'txAwaitingApproval', - 'txConfirmReminder' -] - -function getCustomTxMsg(eventCode, data) { +function getCustomTxMsg(eventCode, data, inlineCustomMsgs = {}) { const msgFunc = - state.config.messages && - typeof state.config.messages[eventCode] === 'function' && - state.config.messages[eventCode] + typeof inlineCustomMsgs[eventCode] === 'function' + ? inlineCustomMsgs[eventCode] + : state.config.messages && + typeof state.config.messages[eventCode] === 'function' && + state.config.messages[eventCode] if (!msgFunc) return undefined @@ -155,22 +151,21 @@ function getCustomTxMsg(eventCode, data) { } } -function notificationsUI(eventObj) { - const { transaction = {}, contract = {} } = eventObj - let { eventCode } = eventObj +const eventCodesNoRepeat = ['nsfFail', 'txSendFail', 'txUnderPriced'] - // replace eventCode to get the same messages if it's a client confirm - if (eventCode === 'txConfirmedClient') { - eventCode = 'txConfirmed' - } +function notificationsUI({ + transaction = {}, + contract = {}, + inlineCustomMsgs, + eventCode +}) { + const { id, startTime } = transaction const type = eventCodeToType(eventCode) - const id = (transaction && transaction.hash) || eventCode const timeStamp = formatTime(Date.now()) const message = - getCustomTxMsg(eventCode, { transaction, contract }) || + getCustomTxMsg(eventCode, { transaction, contract }, inlineCustomMsgs) || transactionMsgs[eventCode]({ transaction, contract }) - const startTime = transaction && transaction.startTime const hasTimer = eventCode === 'txPending' || eventCode === 'txSent' || @@ -179,68 +174,97 @@ function notificationsUI(eventObj) { hasTimer || eventCode === 'txConfirmed' || eventCode === 'txFailed' let blockNativeBrand - let existingNotifications - let notificationsList - let notificationsScroll let notificationsContainer = getById('blocknative-notifications') + const position = + (state.config.style && state.config.style.notificationsPosition) || '' + if (notificationsContainer) { existingNotifications = true notificationsList = getByQuery('.bn-notifications') - const pendingNotificationToRemove = - getById(`${id}-progress`) || getById('txRequest-progress') + const notificationsNoRepeat = eventCodesNoRepeat.reduce( + (acc, eventCode) => [ + ...acc, + ...Array.from(getAllByQuery(`.bn-${eventCode}`)) + ], + [] + ) + + // remove all notifications we don't want to repeat + removeAllNotifications(notificationsNoRepeat) - // if pending notification with the same id, we can remove it to be replaced with new status - if (pendingNotificationToRemove) { - removeNotification(pendingNotificationToRemove) + // due to delay in removing many notifications, need to make sure container size is right + if (notificationsNoRepeat.length > 4) { + setTimeout(setNotificationsHeight, timeouts.changeUI) } - // remove all notifications we don't want to repeat - removeAllNotifications( - notificationsNoRepeat.map(eventCode => `.bn-${eventCode}`) - ) + // We want to keep the txRepeat notification if the new notification is a txRequest or txConfirmReminder + const keepTxRepeatNotification = + eventCode === 'txRequest' || eventCode === 'txConfirmReminder' + + const notificationsWithSameId = keepTxRepeatNotification + ? Array.from(getAllByQuery(`.bn-${id}`)).filter( + n => !n.classList.contains('bn-txRepeat') + ) + : Array.from(getAllByQuery(`.bn-${id}`)) + + // if notification with the same id we can remove it to be replaced with new status + removeAllNotifications(notificationsWithSameId) } else { existingNotifications = false - notificationsContainer = createElement( - 'div', - null, - null, - 'blocknative-notifications' + notificationsContainer = positionElement( + offsetElement( + createElement('div', null, null, 'blocknative-notifications') + ) ) + blockNativeBrand = createTransactionBranding() notificationsList = createElement('ul', 'bn-notifications') notificationsScroll = createElement('div', 'bn-notifications-scroll') + if (position === 'topRight') { + notificationsScroll.style.float = 'right' + } + showIframe() } - const notification = createElement( - 'li', - `bn-notification bn-${type} bn-${eventCode}`, - notificationContent(type, message, { startTime, showTime, timeStamp }), - `${id}-${type}` + const notification = offsetElement( + createElement( + 'li', + `bn-notification bn-${type} bn-${eventCode} bn-${id} ${ + position.includes('Left') ? 'bn-right-border' : '' + }`, + notificationContent(type, message, { startTime, showTime, timeStamp }) + ) ) notificationsList.appendChild(notification) if (!existingNotifications) { notificationsScroll.appendChild(notificationsList) - notificationsContainer.appendChild(notificationsScroll) - notificationsContainer.appendChild(blockNativeBrand) + + if (position.includes('top')) { + notificationsContainer.appendChild(blockNativeBrand) + notificationsContainer.appendChild(notificationsScroll) + } else { + notificationsContainer.appendChild(notificationsScroll) + notificationsContainer.appendChild(blockNativeBrand) + } state.iframeDocument.body.appendChild(notificationsContainer) showElement(notificationsContainer, timeouts.showElement) } - setupIframe(notificationsList) - setContainerHeight() showElement(notification, timeouts.showElement) + setNotificationsHeight() + let intervalId if (hasTimer) { setTimeout(() => { - intervalId = startTimerInterval(`${id}-${type}`, startTime) + intervalId = startTimerInterval(id, eventCode, startTime) }, timeouts.changeUI) } @@ -248,13 +272,14 @@ function notificationsUI(eventObj) { dismissButton.onclick = () => { intervalId && clearInterval(intervalId) removeNotification(notification) + setNotificationsHeight() } if (type === 'complete') { - setTimeout( - () => removeNotification(notification), - timeouts.autoRemoveNotification - ) + setTimeout(() => { + removeNotification(notification) + setNotificationsHeight() + }, timeouts.autoRemoveNotification) } }