Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

frontend: use postmessage to communicate with the iframe #3146

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions backend/arguments/arguments.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@ type Arguments struct {
regtest bool

// devservers stores wether the app should connect to the dev servers.
// This also applies to the Pocket widget environment: if devserver is true, the widget
// will be loaded from the staging environment, otherwise from production.
// This also applies to the Pocket and BTCDirect widget environments:
// if devserver is true, the widgets will be loaded from the staging environment,
// otherwise from production.
// The devservers configuration is not persisted when switching back to production.
devservers bool

Expand Down
35 changes: 35 additions & 0 deletions backend/exchanges/btcdirect.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,29 @@ package exchanges
import (
"slices"

"github.com/BitBoxSwiss/bitbox-wallet-app/backend/accounts"
"github.com/BitBoxSwiss/bitbox-wallet-app/backend/coins/coin"
)

const (
// BTCDirectName is the name of the exchange, it is unique among all the supported exchanges.
BTCDirectName = "btcdirect"

btcDirectTestApiKey = "6ed4d42bd02eeac1776a6bb54fa3126f779c04d5c228fe5128bb74e89ef61f83"

btcDirectProdAPiKey = "7d71f633626901d5c4d06d91f7d0db2c15cdf524ddd0ebcd36f4d9c4e04694cd"

btcDirectUrlTesting = "/btcdirect/fiat-to-coin.html"

btcDirectUrlProd = "https://bitboxapp.shiftcrypto.io/widgets/btcdirect/v1/fiat-to-coin.html"
)

type btcDirectInfo struct {
Url string
ApiKey string
Address *string
}

var regions = []string{
"AT", "BE", "BG", "CH", "CY", "CZ", "DE", "DK", "EE", "ES", "FI", "FR", "GR",
"HR", "HU", "IE", "IS", "IT", "LI", "LT", "LU", "LV", "MC", "MT", "NL", "NO",
Expand Down Expand Up @@ -76,3 +91,23 @@ func BtcDirectDeals() *ExchangeDealsList {
},
}
}

// BtcDirectInfo returns the information needed to interact with BtcDirect.
// If `devServers` is true, it returns testing URL and ApiKey.
func BtcDirectInfo(action ExchangeAction, acct accounts.Interface, devServers bool) btcDirectInfo {
res := btcDirectInfo{
Url: btcDirectUrlProd,
ApiKey: btcDirectProdAPiKey,
}

if devServers {
res.Url = btcDirectUrlTesting
res.ApiKey = btcDirectTestApiKey
}

if action == BuyAction {
addr := acct.GetUnusedReceiveAddresses()[0].Addresses[0].EncodeForHumans()
res.Address = &addr
}
return res
}
29 changes: 28 additions & 1 deletion backend/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,8 @@ func NewHandlers(
getAPIRouterNoError(apiRouter)("/exchange/by-region/{code}", handlers.getExchangesByRegion).Methods("GET")
getAPIRouterNoError(apiRouter)("/exchange/deals/{action}/{code}", handlers.getExchangeDeals).Methods("GET")
getAPIRouterNoError(apiRouter)("/exchange/supported/{code}", handlers.getExchangeSupported).Methods("GET")
getAPIRouterNoError(apiRouter)("/exchange/btcdirect-otc/supported/{code}", handlers.getExchangeBtcDirectOTCSupported).Methods("GET")
getAPIRouterNoError(apiRouter)("/exchange/btcdirect/supported/{code}", handlers.getExchangeBtcDirectOTCSupported).Methods("GET")
getAPIRouterNoError(apiRouter)("/exchange/btcdirect/info/{action}/{code}", handlers.getExchangeBtcDirectInfo).Methods("GET")
getAPIRouter(apiRouter)("/exchange/moonpay/buy-info/{code}", handlers.getExchangeMoonpayBuyInfo).Methods("GET")
getAPIRouterNoError(apiRouter)("/exchange/pocket/api-url/{action}", handlers.getExchangePocketURL).Methods("GET")
getAPIRouterNoError(apiRouter)("/exchange/pocket/verify-address", handlers.postPocketWidgetVerifyAddress).Methods("POST")
Expand Down Expand Up @@ -1438,6 +1439,32 @@ func (handlers *Handlers) getExchangeMoonpayBuyInfo(r *http.Request) (interface{
return resp, nil
}

func (handlers *Handlers) getExchangeBtcDirectInfo(r *http.Request) interface{} {
type result struct {
Success bool `json:"success"`
Url string `json:"url"`
ApiKey string `json:"apiKey"`
Address *string `json:"address"`
}

code := accountsTypes.Code(mux.Vars(r)["code"])
acct, err := handlers.backend.GetAccountFromCode(code)
accountValid := acct != nil && acct.Offline() == nil && !acct.FatalError()
if err != nil || !accountValid {
return result{Success: false}
}

action := exchanges.ExchangeAction(mux.Vars(r)["action"])
btcInfo := exchanges.BtcDirectInfo(action, acct, handlers.backend.DevServers())

return result{
Success: true,
Url: btcInfo.Url,
ApiKey: btcInfo.ApiKey,
Address: btcInfo.Address,
}
}

func (handlers *Handlers) getExchangePocketURL(r *http.Request) interface{} {
lang := handlers.backend.Config().AppConfig().Backend.UserLanguage
if len(lang) == 0 {
Expand Down
195 changes: 107 additions & 88 deletions frontends/web/public/btcdirect/fiat-to-coin.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,104 +10,123 @@

<script lang="js">
;(() => {
const {
address,
apiKey,
baseCurrency,
mode,
quoteCurrency,
} = window.frameElement?.dataset;

if (mode === 'debug') {
console.info(window.frameElement?.dataset);

if (window.top === window) {
showError('Unexpected error');
return;
}

if ( // this should never happen, but if it does we stop here
!address
|| !baseCurrency
|| !quoteCurrency
) {
document.body.append(
Object.assign(document.createElement('h1'), {
style: 'color: red; padding: 1rem;',
textContent: `Unexpected error:
const onMessage = (event) => {
switch (event.data?.action) {
case 'configuration':
const {
address,
apiKey,
baseCurrency,
mode,
quoteCurrency,
} = event.data || {};

if ( // this should never happen, but if it does we stop here
!address
|| !baseCurrency
|| !quoteCurrency
) {
showError(`Unexpected error:
${!address ? 'Address missing' : ''}
${!baseCurrency ? 'BaseCurrency missing' : ''}
${!quoteCurrency ? 'QuoteCurrency missing' : ''}
`
})
);
return;
}
`);
return;
}
const currency = baseCurrency.toUpperCase();

const currency = baseCurrency.toUpperCase();
// add the btcdirect CSS
document.head.appendChild(
Object.assign(document.createElement('link'), {
href: (
mode === 'production'
? 'https://cdn.btcdirect.eu/fiat-to-coin/fiat-to-coin.css'
: 'https://cdn-sandbox.btcdirect.eu/fiat-to-coin/fiat-to-coin.css'
),
rel: 'stylesheet',
})
);

// add the btcdirect CSS
document.head.appendChild(
Object.assign(document.createElement('link'), {
href: (
// add the btcdirect script
(function (btc, d, i, r, e, c, t) {
btc[r] = btc[r] || function () {
(btc[r].q = btc[r].q || []).push(arguments)
};
c = d.createElement(i);
c.id = r; c.src = e; c.async = true;
c.type = 'module'; c.dataset.btcdirect = '';
t = d.getElementsByTagName(i)[0];
t.parentNode.insertBefore(c, t);
})(window, document, 'script', 'btcdirect',
mode === 'production'
? 'https://cdn.btcdirect.eu/fiat-to-coin/fiat-to-coin.css'
: 'https://cdn-sandbox.btcdirect.eu/fiat-to-coin/fiat-to-coin.css'
),
rel: 'stylesheet',
})
);

// add the btcdirect script
(function (btc, d, i, r, e, c, t) {
btc[r] = btc[r] || function () {
(btc[r].q = btc[r].q || []).push(arguments)
};
c = d.createElement(i);
c.id = r; c.src = e; c.async = true;
c.type = 'module'; c.dataset.btcdirect = '';
t = d.getElementsByTagName(i)[0];
t.parentNode.insertBefore(c, t);
})(window, document, 'script', 'btcdirect',
mode === 'production'
? 'https://cdn.btcdirect.eu/fiat-to-coin/fiat-to-coin.js'
: 'https://cdn-sandbox.btcdirect.eu/fiat-to-coin/fiat-to-coin.js'
);

btcdirect('init', {
token: apiKey,
debug: mode === 'debug',
locale: window.frameElement?.dataset.locale || 'en-US',
theme: window.frameElement?.dataset.theme || 'light',
});

// fiat to coin order
btcdirect('wallet-addresses', {
addresses: {
address,
currency,
id: 'BitBox',
walletName: 'BitBox'
}
});

btcdirect('set-parameters',
mode === 'production' ? {
baseCurrency: currency,
fixedCurrency: true,
quoteCurrency,
// paymentMethod: any of 'bancontact', 'bankTransfer', 'creditCard', 'giropay', 'iDeal', 'sofort', 'applepay'
showWalletAddress: false,
} : {
baseCurrency: currency,
fixedCurrency: true,
paymentMethod: 'sofort', // sandbox currently only supports sofort payment method
quoteCurrency,
showWalletAddress: false,
? 'https://cdn.btcdirect.eu/fiat-to-coin/fiat-to-coin.js'
: 'https://cdn-sandbox.btcdirect.eu/fiat-to-coin/fiat-to-coin.js'
);

btcdirect('init', {
token: apiKey,
debug: mode === 'debug',
locale: window.frameElement?.dataset.locale || 'en-GB',
theme: window.frameElement?.dataset.theme || 'light',
Copy link
Collaborator Author

@thisconnect thisconnect Feb 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: take locale and theme from onMessage "configuration" / event.data

Copy link
Collaborator Author

@thisconnect thisconnect Feb 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rebased this branch without the backend commit and fixed "locale" and "theme" in #3152

});

// fiat to coin order
btcdirect('wallet-addresses', {
addresses: {
address,
currency,
id: 'BitBox',
walletName: 'BitBox'
}
});

btcdirect('set-parameters',
mode === 'production' ? {
baseCurrency: currency,
fixedCurrency: true,
quoteCurrency,
// paymentMethod: any of 'bancontact', 'bankTransfer', 'creditCard', 'giropay', 'iDeal', 'sofort', 'applepay'
showWalletAddress: false,
} : {
baseCurrency: currency,
fixedCurrency: true,
paymentMethod: 'sofort', // sandbox currently only supports sofort payment method
quoteCurrency,
showWalletAddress: false,
}
);

window.addEventListener('btcdirect-embeddable-fiat-to-coin-order-confirmed', (event) => {
console.log('btcdirect-embeddable-fiat-to-coin-order-confirmed', event);
// Handle the event
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you could directly post a message to pass the order confirmation, maybe? And leave the TODO in the handler inside btcdirect.tsx. No strong opinion, tho

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only tested once, this event is fired after the user clicked proceed to payment and not really useful this way, I'll just remove.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed in #3152

// Note that the sent information from the widget is found inside event.detail
});

break;
}
);
};

window.addEventListener('message', onMessage);

// Request the parent to send attributes
window.parent.postMessage({
action: 'request-configuration'
}, '*');

window.addEventListener('btcdirect-embeddable-fiat-to-coin-order-confirmed', (event) => {
console.log('btcdirect-embeddable-fiat-to-coin-order-confirmed', event);
// Handle the event
// Note that the sent information from the widget is found inside event.detail
});
function showError(message) {
document.body.append(
Object.assign(document.createElement('h1'), {
style: 'color: red; padding: 1rem;',
textContent: message,
})
);
}

})();
</script>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the file we host on shiftcrypto.io and is already deployed bitboxapp.shiftcrypto.io/widgets/btcdirect/v1/fiat-to-coin.html

but in order for it to communicate with the react app we need the changes in this PR in btcdirect.tsx

13 changes: 4 additions & 9 deletions frontends/web/src/api/exchanges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,22 +103,17 @@ export type TBTCDirectInfoResponse = {
success: true;
url: string;
apiKey: string;
address?: string;
} | {
success: false;
errorMessage: string;
};

export const getBTCDirectInfo = (
action: TExchangeAction,
code: string,
): Promise<TBTCDirectInfoResponse> => {
console.log(action);
// TODO: change to return apiGet(`exchange/btc-direct/info/${action}`); or similar
return Promise.resolve({
success: true,
url: '/btcdirect/fiat-to-coin.html', // local static file for testing
apiKey: '6ed4d42bd02eeac1776a6bb54fa3126f779c04d5c228fe5128bb74e89ef61f83', // debug
// apiKey: '7d71f633626901d5c4d06d91f7d0db2c15cdf524ddd0ebcd36f4d9c4e04694cd', // prod
});
return apiGet(`exchange/btcdirect/info/${action}/${code}`);
};

export type SupportedExchanges= {
Expand All @@ -140,6 +135,6 @@ export type TBtcDirectResponse = {

export const getBtcDirectOTCSupported = (code: AccountCode, region: ExchangeRegion['code']) => {
return (): Promise<TBtcDirectResponse> => {
return apiGet(`exchange/btcdirect-otc/supported/${code}?region=${region}`);
return apiGet(`exchange/btcdirect/supported/${code}?region=${region}`);
};
};
Loading