Adds Wyre Widget (#6434)

* Adds Wyre widget to the deposit modal.

* Move wyre widget code to vendor directory

* Get Wyre widget working without metamask connect/sign steps

* Code cleanup for wyre changes

* Change wyre widget to using prod environment

* Remove code allowing signing of wyre messages without confirmations

* Update wyre vendor code for wyre 2.0

* Remove unnecessary changes to provider approval constructor, triggerUI and openPopup

* Fix Wyre translation message

* Delete no longer used signature-request-modal

* Fix documentation of matches function in utils/util.js

* Code cleanup on wyre branch

* Remove front end code changes not needed to support wyre v2
feature/default_network_editable
Dan J Miller 5 years ago committed by GitHub
parent 57a29668f3
commit 6a4df0dc3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      app/_locales/en/messages.json
  2. 16
      ui/app/components/app/modals/deposit-ether-modal.js
  3. 2
      ui/app/helpers/constants/routes.js
  4. 22
      ui/app/pages/routes/index.js
  5. 87
      ui/vendor/wyre.js

@ -214,7 +214,7 @@
"message": "Buy ETH with Wyre" "message": "Buy ETH with Wyre"
}, },
"buyWithWyreDescription": { "buyWithWyreDescription": {
"message": "Wyre lets you use a credit card to deposit ETH right in to your MetaMask account." "message": "Wyre lets you use a credit card to deposit ETH right in to your MetaMask account. Open Wyre's widget here to get started."
}, },
"buyCoinSwitch": { "buyCoinSwitch": {
"message": "Buy on CoinSwitch" "message": "Buy on CoinSwitch"

@ -5,6 +5,10 @@ const inherits = require('util').inherits
const connect = require('react-redux').connect const connect = require('react-redux').connect
const actions = require('../../../store/actions') const actions = require('../../../store/actions')
const { getNetworkDisplayName } = require('../../../../../app/scripts/controllers/network/util') const { getNetworkDisplayName } = require('../../../../../app/scripts/controllers/network/util')
const openWyre = require('../../../../vendor/wyre')
const { DEPOSIT_ROUTE } = require('../../../helpers/constants/routes')
const { ENVIRONMENT_TYPE_POPUP } = require('../../../../../app/scripts/lib/enums')
const { getEnvironmentType } = require('../../../../../app/scripts/lib/util')
import Button from '../../ui/button' import Button from '../../ui/button'
@ -121,7 +125,7 @@ DepositEtherModal.prototype.renderRow = function ({
} }
DepositEtherModal.prototype.render = function () { DepositEtherModal.prototype.render = function () {
const { network, toWyre, toCoinSwitch, address, toFaucet } = this.props const { network, address, toFaucet, toCoinSwitch } = this.props
const isTestNetwork = ['3', '4', '5', '42'].find(n => n === network) const isTestNetwork = ['3', '4', '5', '42'].find(n => n === network)
const networkName = getNetworkDisplayName(network) const networkName = getNetworkDisplayName(network)
@ -182,8 +186,14 @@ DepositEtherModal.prototype.render = function () {
title: WYRE_ROW_TITLE, title: WYRE_ROW_TITLE,
text: WYRE_ROW_TEXT, text: WYRE_ROW_TEXT,
buttonLabel: this.context.t('continueToWyre'), buttonLabel: this.context.t('continueToWyre'),
onButtonClick: () => toWyre(address), onButtonClick: () => {
hide: isTestNetwork, if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) {
global.platform.openExtensionInBrowser(DEPOSIT_ROUTE)
} else {
openWyre(address)
}
},
hide: isTestNetwork && !network === '42',
}), }),
this.renderRow({ this.renderRow({

@ -25,6 +25,7 @@ const NEW_ACCOUNT_ROUTE = '/new-account'
const IMPORT_ACCOUNT_ROUTE = '/new-account/import' const IMPORT_ACCOUNT_ROUTE = '/new-account/import'
const CONNECT_HARDWARE_ROUTE = '/new-account/connect' const CONNECT_HARDWARE_ROUTE = '/new-account/connect'
const SEND_ROUTE = '/send' const SEND_ROUTE = '/send'
const DEPOSIT_ROUTE = '/deposit'
const INITIALIZE_ROUTE = '/initialize' const INITIALIZE_ROUTE = '/initialize'
const INITIALIZE_WELCOME_ROUTE = '/initialize/welcome' const INITIALIZE_WELCOME_ROUTE = '/initialize/welcome'
@ -94,4 +95,5 @@ module.exports = {
CONTACT_MY_ACCOUNTS_EDIT_ROUTE, CONTACT_MY_ACCOUNTS_EDIT_ROUTE,
NETWORKS_ROUTE, NETWORKS_ROUTE,
INITIALIZE_BACKUP_SEED_PHRASE_ROUTE, INITIALIZE_BACKUP_SEED_PHRASE_ROUTE,
DEPOSIT_ROUTE,
} }

@ -8,6 +8,7 @@ import log from 'loglevel'
import IdleTimer from 'react-idle-timer' import IdleTimer from 'react-idle-timer'
import {getNetworkIdentifier, preferencesSelector} from '../../selectors/selectors' import {getNetworkIdentifier, preferencesSelector} from '../../selectors/selectors'
import classnames from 'classnames' import classnames from 'classnames'
import openWyre from '../../../vendor/wyre'
// init // init
import FirstTimeFlow from '../first-time-flow' import FirstTimeFlow from '../first-time-flow'
@ -67,6 +68,7 @@ import {
CONFIRM_TRANSACTION_ROUTE, CONFIRM_TRANSACTION_ROUTE,
INITIALIZE_ROUTE, INITIALIZE_ROUTE,
INITIALIZE_UNLOCK_ROUTE, INITIALIZE_UNLOCK_ROUTE,
DEPOSIT_ROUTE,
} from '../../helpers/constants/routes' } from '../../helpers/constants/routes'
// enums // enums
@ -98,6 +100,20 @@ class Routes extends Component {
}) })
} }
componentDidMount () {
const {
location,
modal,
showDepositModal,
selectedAddress,
} = this.props
if (location.pathname === DEPOSIT_ROUTE && (!modal || !modal.open) && selectedAddress) {
showDepositModal()
openWyre(selectedAddress)
}
}
renderRoutes () { renderRoutes () {
const { autoLogoutTimeLimit, setLastActiveTime } = this.props const { autoLogoutTimeLimit, setLastActiveTime } = this.props
@ -116,6 +132,7 @@ class Routes extends Component {
<Authenticated path={CONFIRM_ADD_TOKEN_ROUTE} component={ConfirmAddTokenPage} exact /> <Authenticated path={CONFIRM_ADD_TOKEN_ROUTE} component={ConfirmAddTokenPage} exact />
<Authenticated path={CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE} component={ConfirmAddSuggestedTokenPage} exact /> <Authenticated path={CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE} component={ConfirmAddSuggestedTokenPage} exact />
<Authenticated path={NEW_ACCOUNT_ROUTE} component={CreateAccountPage} /> <Authenticated path={NEW_ACCOUNT_ROUTE} component={CreateAccountPage} />
<Authenticated path={DEPOSIT_ROUTE} component={Home} exact />
<Authenticated path={DEFAULT_ROUTE} component={Home} exact /> <Authenticated path={DEFAULT_ROUTE} component={Home} exact />
</Switch> </Switch>
) )
@ -348,6 +365,9 @@ Routes.propTypes = {
providerId: PropTypes.string, providerId: PropTypes.string,
providerRequests: PropTypes.array, providerRequests: PropTypes.array,
autoLogoutTimeLimit: PropTypes.number, autoLogoutTimeLimit: PropTypes.number,
showDepositModal: PropTypes.func,
modal: PropTypes.object,
selectedAddress: PropTypes.string,
} }
function mapStateToProps (state) { function mapStateToProps (state) {
@ -381,6 +401,7 @@ function mapStateToProps (state) {
providerId: getNetworkIdentifier(state), providerId: getNetworkIdentifier(state),
autoLogoutTimeLimit, autoLogoutTimeLimit,
providerRequests: metamask.providerRequests, providerRequests: metamask.providerRequests,
selectedAddress: metamask.selectedAddress,
} }
} }
@ -391,6 +412,7 @@ function mapDispatchToProps (dispatch) {
setCurrentCurrencyToUSD: () => dispatch(actions.setCurrentCurrency('usd')), setCurrentCurrencyToUSD: () => dispatch(actions.setCurrentCurrency('usd')),
setMouseUserState: (isMouseUser) => dispatch(actions.setMouseUserState(isMouseUser)), setMouseUserState: (isMouseUser) => dispatch(actions.setMouseUserState(isMouseUser)),
setLastActiveTime: () => dispatch(actions.setLastActiveTime()), setLastActiveTime: () => dispatch(actions.setLastActiveTime()),
showDepositModal: () => dispatch(actions.showModal({ name: 'DEPOSIT_ETHER' })),
} }
} }

87
ui/vendor/wyre.js vendored

@ -0,0 +1,87 @@
/* eslint-disable */
// code taken from https://verify.sendwyre.com/js/verify-module-init-beta.js
'use strict'
function _classCallCheck (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError('Cannot call a class as a function')
}
}
function _defineProperties (target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i]
descriptor.enumerable = descriptor.enumerable || false
descriptor.configurable = true
if ('value' in descriptor) descriptor.writable = true
Object.defineProperty(target, descriptor.key, descriptor)
}
}
function _createClass (Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps)
if (staticProps) _defineProperties(Constructor, staticProps)
return Constructor
}
function createWyreWidget () {
var ga = null
var Widget = function () {
function a (b) {
var c = !!(1 < arguments.length && void 0 !== arguments[1]) && arguments[1];
_classCallCheck(this,a), this.debug = c, this.operationHostedWidget="debitcard-hosted", this.operationHostedDialogWidget="debitcard-hosted-dialog", this.operationWidgetLite="debitcard-whitelabel",this.operationDigitalWallet="debitcard",this.operationAch="onramp",this.validOperations=[this.operationWidgetLite,this.operationDigitalWallet,this.operationHostedWidget,this.operationAch,this.operationHostedDialogWidget],this.injectedWidgetOperations=[this.operationWidgetLite,this.operationDigitalWallet],this.iframeWidgetOperations=[this.operationAch],this.queue=[],this.ready=!1,this.eventRegistrations=new Map,null==b.env&&(b.env="production"),this.initParams=b,this.init=this.processClassicInit(b);
var d=b;
console.log("init",b,d),this.getNewWidgetScriptLocation(),this.operationType=d.operation&&d.operation.type||"",this.validateOperationType(),this.injectedWidgetOperations.includes(this.operationType)?(console.log("inject run"),this.attachEvents(),this.createInjectedWidget()):this.iframeWidgetOperations.includes(this.operationType)?(console.log("iframe run"),this.validateInit(),this.attachEvents(),this.createIframe()):this.operationHostedWidget===this.operationType?this.handleHostedWidget():this.operationHostedDialogWidget===this.operationType&&this.emit("ready");
}
return _createClass(
a,
[
{key:"validateOperationType",value:function a(){if(!this.validOperations.includes(this.operationType)){var b="supplied operation type >>"+this.operationType+"<< is invalid, valid types are:"+this.validOperations.join(",").toString();throw this.emit("close",{error:b}),this.removeClass(),new Error(b)}}},
{key:"removeListener",value:function d(a,b){var c=this.eventRegistrations.get(a)||[];c=c.filter(function(a){return a!==b}),this.eventRegistrations.set(a,c);}},
{key:"removeAllListeners",value:function b(a){a?this.eventRegistrations.set(a,[]):this.eventRegistrations=new Map;}},
{key:"on",value:function d(a,b){if(!a)throw new Error("must supply an event!");var c=this.eventRegistrations.get(a)||[];c.push(b),this.eventRegistrations.set(a,c);}},
{key:"open",value:function a(){return this.send("open",{}),console.log(this.operationType),this.operationType==this.operationHostedDialogWidget?void this.handleHostedDialogWidget():void(this.iframe?this.iframe.style.display="block":this.injectedWidget&&(this.injectedWidget.style.display="block"))}},
{key:"emit",value:function d(a,b){var c=this.eventRegistrations.get(a)||[];c.forEach(function(a){try{a(b||{});}catch(a){console.warn("subscribed widget event handler failure: ",a);}});}},
{key:"validateInit",value:function a(){switch(this.init.auth.type){case"secretKey":if(this.init.error)return;var b=this.init.auth.secretKey;if(25>b.length)return console.error("Diligently refusing to accept a secret key with length < 25"),this.emit("close",{error:"supplied secretKey is too short"}),void this.removeClass();}}},
{key:"send",value:function c(a,b){this.queue.push({type:a,payload:b}),this.flush();}},
{key:"flush",value:function b(){var a=this;this.ready&&(this.queue.forEach(function(b){return a.iframe.contentWindow.postMessage(JSON.stringify(b),"*")}),this.queue=[]);}},
{key:"attachEvents",value:function d(){var a=this,b=window.addEventListener?"addEventListener":"attachEvent",c="attachEvent"==b?"onmessage":"message";window[b](c,function(b){if("string"==typeof b.data&&0==b.data.indexOf("{")){var c=JSON.parse(b.data);if(console.log("frame",c),!!c.type)switch(c.type){case"ready":a.ready=!0,a.init.web3PresentInParentButNotChild=c.payload&&!c.payload.web3Enabled&&"undefined"!=typeof web3,"function"==typeof ga&&c.payload&&c.payload.gaTrackingCode?(ga("create",c.payload.gaTrackingCode,"auto"),ga(function(b){var c=b.get("clientId");a.send("init",a.init),a.emitReady();})):(a.send("init",a.init),a.emitReady());break;case"close":case"complete":a.close(),a.removeClass(),a.emit(c.type,c.payload);break;case"sign-request":var d=c.payload,e=new Web3(web3.currentProvider);e.personal.sign(e.fromUtf8(d.message),d.address,function(b,c){a.send("sign-response",{signature:c,error:b});});break;case"provider-name":var h=a.getNameOfProvider();a.send("provider-name",h);break;case"accounts":var f=new Web3(web3.currentProvider),g=f.eth.accounts;a.send("accounts-response",{accounts:g});break;default:}}},!1);}},
{key:"close",value:function a(){this.removeClass(),this.iframe&&document.body.removeChild(this.iframe),this.injectedWidget&&document.body.removeChild(this.injectedWidget),this.injectedWidget=null,this.iframe=null,this.queue=[],this.ready=!1;}},
{key:"createIframe",value:function b(){var a=Math.round;this.iframe=document.createElement("iframe"),this.iframe.setAttribute("allow","camera;"),this.iframe.style.display="none",this.iframe.style.border="none",this.iframe.style.width="100%",this.iframe.style.height="100%",this.iframe.style.position="fixed",this.iframe.style.zIndex="999999",this.iframe.style.top="0",this.iframe.style.left="0",this.iframe.style.bottom="0",this.iframe.style.right="0",this.iframe.style.backgroundColor="transparent",this.iframe.src=this.getBaseUrl()+"/loader?_cb="+a(new Date().getTime()/1e3),document.body.appendChild(this.iframe);}},
{key:"getBaseUrl",value:function a(){switch(this.init.env){case"test":return "https://verify.testwyre.com";case"staging":return "https://verify-staging.i.sendwyre.com";case"local":return "http://localhost:8890";case"local_https":return "https://localhost:8890";case"android_emulator":return "http://10.0.2.2:8890";case"production":default:return "https://verify.sendwyre.com";}}},
{key:"processClassicInit",value:function d(a){if(a.auth)return a;var b=a,c={env:b.env,auth:{type:"metamask"},operation:{type:"onramp",destCurrency:b.destCurrency},apiKey:b.apiKey,web3PresentInParentButNotChild:!1};return b.onExit&&this.on("close",function(a){a.error?(b.onExit(a.error),this.removeClass()):(this.removeClass(),b.onExit(null));}),b.onSuccess&&(this.on("complete",function(){b.onSuccess();}),this.removeClass()),console.debug("converted v1 config to v2, please use this instead: ",c),c}},
{key:"getNameOfProvider",value:function a(){return web3.currentProvider.isTrust?"trust":"undefined"==typeof __CIPHER__?"undefined"==typeof SOFA?web3.currentProvider.isDDEXWallet?"ddex":"metamask":"coinbase":"cipher"}},
{key:"getOperationParametersAsQueryString",value:function b(){var a=this;return this.initParams.operation?Object.keys(this.initParams.operation).map(function(b){return b+"="+encodeURIComponent(a.initParams.operation[b])}).join("&"):""}},
{key:"handleHostedWidget",value:function a(){location.href=this.getPayWidgetLocation()+"/purchase?"+this.getOperationParametersAsQueryString();}},
{key:"handleHostedDialogWidget",value:function f(){var a=this,b=this.getPayWidgetLocation()+"/purchase?"+this.getOperationParametersAsQueryString(),c=this.openAndCenterDialog(b,360,650),d=window.addEventListener?"addEventListener":"attachEvent",e="attachEvent"===d?"onmessage":"message";window[d](e,function(b){"paymentSuccess"===b.data.type&&a.emit("paymentSuccess",{data:b.data});},!1);}},
{key:"openAndCenterDialog",value:function o(a,b,c){var d=navigator.userAgent,e=function(){return /\b(iPhone|iP[ao]d)/.test(d)||/\b(iP[ao]d)/.test(d)||/Android/i.test(d)||/Mobile/i.test(d)},f="undefined"==typeof window.screenX?window.screenLeft:window.screenX,g="undefined"==typeof window.screenY?window.screenTop:window.screenY,h="undefined"==typeof window.outerWidth?document.documentElement.clientWidth:window.outerWidth,i="undefined"==typeof window.outerHeight?document.documentElement.clientHeight-22:window.outerHeight,j=e()?null:b,k=e()?null:c,l=0>f?window.screen.width+f:f,m=[];null!==j&&m.push("width="+j),null!==k&&m.push("height="+k),m.push("left="+(l+(h-j)/2)),m.push("top="+(g+(i-k)/2.5)),m.push("scrollbars=1");var n=window.open(a,"Wyre",m.join(","));return window.focus&&n.focus(),n}},
{key:"getPayWidgetLocation",value:function a(){switch(this.initParams.env){case"test":return "https://pay.testwyre.com";case"staging":return "https://pay-staging.i.sendwyre.com";case"local":return "http://localhost:3000";case"production":default:return "https://pay.sendwyre.com";}}},
{key:"getNewWidgetScriptLocation",value:function a(){return this.operationType===this.operationWidgetLite?this.getPayWidgetLocation()+"/widget-lite/static/js/widget-lite.js":this.getPayWidgetLocation()+"/digital-wallet-embed.js"}},
{key:"createInjectedWidget",value:function a(){switch(this.operationType){case"debitcard":{if(this.injectedWidget=document.getElementById("wyre-dropin-widget-container"),!this.injectedWidget){throw this.emit("close",{error:"unable to mount the widget did with id `wyre-dropin-widget-container` not found"}),this.removeClass(),new Error("unable to mount the widget did with id `wyre-dropin-widget-container` not found")}this.injectedWidget.style.display="none",this.handleDebitcardInjection();break}case"debitcard-whitelabel":{if(this.injectedWidget=document.getElementById("wyre-dropin-widget-container"),!this.injectedWidget){throw this.emit("close",{error:"unable to mount the widget did with id `wyre-dropin-widget-container` not found"}),this.removeClass(),new Error("unable to mount the widget did with id `wyre-dropin-widget-container` not found")}this.injectedWidget.style.display="none",this.handleDebitcardWhiteLabelInjection();break}}}},
{key:"loadJSFile",value:function b(a){return new Promise(function(b,c){var d=document.createElement("script");d.type="text/javascript",d.src=a,d.onload=b,d.onerror=c,d.async=!0,document.getElementsByTagName("head")[0].appendChild(d);})}},
{key:"handleDebitcardInjection",value:function b(){var a=this;this.loadJSFile(this.getNewWidgetScriptLocation()).then(function(){DigitalWallet.init("wyre-dropin-widget-container",{type:a.operationType,dest:a.initParams.operation.dest,destCurrency:a.initParams.operation.destCurrency,sourceAmount:a.initParams.operation.sourceAmount,paymentMethod:a.initParams.operation.paymentMethod,accountId:a.initParams.accountId},function(b){a.emit("complete",b),a.removeClass();},function(b){a.emit("close",{error:b}),a.removeClass();}),a.send("init",a.init),a.emitReady();}).catch(function(b){throw a.emit("close",{error:b}),a.removeClass(),new Error(b)});}},
{key:"handleDebitcardWhiteLabelInjection",value:function b(){var a=this;this.loadJSFile(this.getNewWidgetScriptLocation()).then(function(){Wyre2.setData&&Wyre2.widgetLite.appLoaded&&(Wyre2.setData(a.initParams.operation.paymentMethod,a.initParams.operation.sourceAmount,"USD",a.initParams.operation.destCurrency,a.initParams.operation.dest),Wyre2.registerCallback(function(a){this.send("paymentAuthorized",a);},function(a){this.send("error",a),this.removeClass(),console.log(JSON.stringify(a));}),a.send("init",a.init),a.emitReady());}).catch(function(b){throw a.emit("close",{error:b}),a.removeClass(),new Error(b)});}},
{key:"updateData",value:function e(a,b,c,d){if(this.operationType==this.operationWidgetLite)Wyre2.setData&&Wyre2.widgetLite.appLoaded&&(Wyre2.setData(a,b,"USD",c,d),this.emitReady());else throw this.removeClass(),new Error("this can only be called for operation type "+this.operationWidgetLite)}},
{key:"emitReady",value:function a(){this.setClassName(),this.emit("ready");}},{key:"removeComponent",value:function a(){}},{key:"setClassName",value:function d(){var a,b,c;a=document.getElementById("wyre-dropin-widget-container"),b="wyre-dropin-widget-container",c=a.className.split(" "),-1==c.indexOf(b)&&(a.className+=" "+b);}},{key:"removeClass",value:function b(){document.getElementById("wyre-dropin-widget-container").style.display="none";var a=document.getElementById("wyre-dropin-widget-container");a.className=a.className.replace(/\bwyre-dropin-widget-container\b/g,"");}}
]
),a
}()
var widget=Object.assign(Widget,{Widget:Widget});
return widget;
}
function openWyre(address) {
const Wyre = createWyreWidget()
const widget = new Wyre({
env: 'prod',
operation: {
type: 'debitcard-hosted-dialog',
destCurrency: 'ETH',
dest: `ethereum:${address}`,
},
})
widget.open()
}
module.exports = openWyre
Loading…
Cancel
Save