/* @source https://www.git.taler.net/?p=web-common.git;a=blob_plain;f=taler-wallet-lib.ts;hb=HEAD @license magnet:?xt=urn:btih:5de60da917303dbfad4f93fb1b985ced5a89eac2&dn=lgpl-2.1.txt LGPL v21 @licstart The following is the entire license notice for the JavaScript code in this page. Copyright (C) 2015, 2016 INRIA The JavaScript code in this page is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License (GNU LGPL) as published by the Free Software Foundation, either version 2.1 of the License, or (at your option) any later version. The code is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU LGPL for more details. As additional permission under GNU LGPL version 2.1 section 7, you may distribute non-source (e.g., minimized or compacted) forms of that code without the copy of the GNU LGPL normally required by section 4, provided you include this license notice and a URL through which recipients can access the Corresponding Source. @licend The above is the entire license notice for the JavaScript code in this page. @author Marcello Stanisci @author Florian Dold */ (function (factory) { if (typeof module === "object" && typeof module.exports === "object") { var v = factory(require, exports); if (v !== undefined) module.exports = v; } else if (typeof define === "function" && define.amd) { define(["require", "exports"], factory); } })(function (require, exports) { "use strict"; var taler; (function (taler) { "use strict"; var logVerbose = false; try { logVerbose = !!localStorage.getItem("taler-log-verbose"); } catch (e) { } var presentHandlers = []; var absentHandlers = []; // Are we running as the content script of an // extension (and not just from a normal page)? var runningInExtension = false; var callSeqId = 1; var installed = false; var probeExecuted = false; var pageLoaded = false; var errorHandler = undefined; var sheet; function onError(handler) { if (errorHandler) { console.warn("Overriding error handler"); } errorHandler = handler; } taler.onError = onError; /** * Error handler for things that go wrong in the merchant * frontend browser code. */ function raise_error(reason, detail) { if (errorHandler) { errorHandler(reason, detail); return; } alert("Failure: " + reason + ". No error handler installed. Open the developer console for more information."); console.error(reason, detail); console.warn("No custom error handler set."); } function callWallet(funcName, args, onResult) { var detail = JSON.parse(JSON.stringify(args || {})); var callId = callSeqId++; detail.callId = callId; var onTimeout = function () { console.warn("timeout for invocation of " + funcName); }; var timeoutHandle = setTimeout(onTimeout, 1000); var handler = function (evt) { if (evt.detail.callId !== callId) { return; } if (onResult) { onResult(evt.detail); } clearTimeout(timeoutHandle); document.removeEventListener(funcName + "-result", handler); }; document.addEventListener(funcName + "-result", handler); var evt = new CustomEvent(funcName, { detail: detail }); document.dispatchEvent(evt); } /** * Confirm that a reserve was created. * * Used by tightly integrated bank portals. */ function confirmReserve(reservePub) { if (!installed) { logVerbose && console.log("delaying confirmReserve"); taler.onPresent(function () { confirmReserve(reservePub); }); return; } callWallet("taler-confirm-reserve", { reserve_pub: reservePub }); } taler.confirmReserve = confirmReserve; function createReserve(callbackUrl, amount, wtTypes, suggestedExchangeUrl) { if (!installed) { logVerbose && console.log("delaying createReserve"); taler.onPresent(function () { createReserve(callbackUrl, amount, wtTypes, suggestedExchangeUrl); }); return; } var args = { callback_url: callbackUrl, amount: amount, wt_types: wtTypes, suggested_exchange_url: suggestedExchangeUrl }; callWallet("taler-create-reserve", args); } taler.createReserve = createReserve; function handlePaymentResponse(walletResp) { /** * Handle a failed payment. * * Try to notify the wallet first, before we show a potentially * synchronous error message (such as an alert) or leave the page. */ function handleFailedPayment(r) { var timeoutHandle = null; function err() { raise_error("pay-failed", { status: r.status, response: r.responseText }); } function onResp() { if (timeoutHandle != null) { clearTimeout(timeoutHandle); timeoutHandle = null; } err(); } function onTimeout() { timeoutHandle = null; err(); } callWallet("taler-payment-failed", { H_contract: walletResp.H_contract }, onResp); timeoutHandle = setTimeout(onTimeout, 200); } logVerbose && console.log("handling taler-notify-payment: ", walletResp); // Payment timeout in ms. var timeout_ms = 1000; // Current request. var r; var timeoutHandle = null; function sendPay() { r = new XMLHttpRequest(); r.open("post", walletResp.contract.pay_url); r.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); r.send(JSON.stringify(walletResp.payReq)); r.onload = function () { if (!r) { return; } switch (r.status) { case 200: var merchantResp = JSON.parse(r.responseText); logVerbose && console.log("got success from pay_url"); callWallet("taler-payment-succeeded", { H_contract: walletResp.H_contract, merchantSig: merchantResp.sig }, function () { var nextUrl = walletResp.contract.fulfillment_url; logVerbose && console.log("taler-payment-succeeded done, going to", nextUrl); window.location.href = nextUrl; window.location.reload(true); }); break; default: handleFailedPayment(r); break; } r = null; if (timeoutHandle != null) { clearTimeout(timeoutHandle); timeoutHandle = null; } }; function retry() { if (r) { r.abort(); r = null; } timeout_ms = Math.min(timeout_ms * 2, 10 * 1000); logVerbose && console.log("sendPay timed out, retrying in ", timeout_ms, "ms"); sendPay(); } timeoutHandle = setTimeout(retry, timeout_ms); } sendPay(); } function onPresent(f) { presentHandlers.push(f); } taler.onPresent = onPresent; function onAbsent(f) { absentHandlers.push(f); } taler.onAbsent = onAbsent; function internalPay(p) { // either the callback gets called, // or the wallet will redirect the browser callWallet("taler-pay", p, handlePaymentResponse); } taler.internalPay = internalPay; function pay(p) { if (!installed) { logVerbose && console.log("delaying call to 'pay' until GNU Taler wallet is present"); taler.onPresent(function () { pay(p); }); return; } internalPay(p); } taler.pay = pay; function internalAddAuditor(d) { // either the callback gets called, // or the wallet will redirect the browser callWallet("taler-add-auditor", d); } taler.internalAddAuditor = internalAddAuditor; function addAuditor(d) { if (!installed) { logVerbose && console.log("delaying call to 'addAuditor' until GNU Taler wallet is present"); taler.onPresent(function () { addAuditor(d); }); return; } internalAddAuditor(d); } taler.addAuditor = addAuditor; function internalCheckAuditor(url) { return new Promise(function (resolve, reject) { callWallet("taler-check-auditor", url, function (x) { return resolve(x); }); }); } taler.internalCheckAuditor = internalCheckAuditor; /** * Check if an auditor is already added to the wallet. * * Same-origin restrictions apply. */ function checkAuditor(url) { if (!installed) { logVerbose && console.log("delaying call to 'checkAuditor' until GNU Taler wallet is present"); return new Promise(function (resolve, reject) { taler.onPresent(function () { resolve(checkAuditor(url)); }); }); } return internalCheckAuditor(url); } taler.checkAuditor = checkAuditor; function initTaler() { function handleUninstall() { installed = false; // not really true, but we want "uninstalled" to be shown firstTimeoutCalled = true; announce(); } function handleProbe() { probeExecuted = true; if (!installed) { logVerbose && console.log("taler install detected"); installed = true; announce(); } } function probeTaler() { probeExecuted = false; var eve = new Event("taler-probe"); document.dispatchEvent(eve); } var firstTimeoutCalled = false; function onProbeTimeout() { if (!probeExecuted) { if (installed || !firstTimeoutCalled) { installed = false; firstTimeoutCalled = true; logVerbose && console.log("taler uninstall detected"); announce(); } } // try again, maybe it'll be installed ... probeTaler(); } /** * Announce presence/absence and update stylesheets. * * Only called after document.readyState is at least "interactive". */ function announce() { if (!pageLoaded) { logVerbose && console.log("page not loaded yet, announcing later"); return; } setStyles(); if (installed) { logVerbose && console.log("announcing installed"); for (var i = 0; i < presentHandlers.length; i++) { presentHandlers[i](); } } else { if (firstTimeoutCalled) { logVerbose && console.log("announcing uninstalled"); for (var i = 0; i < absentHandlers.length; i++) { absentHandlers[i](); } } else { logVerbose && console.log("announcing nothing"); } } } function setStyles() { if (!sheet || !sheet.cssRules) { return; } while (sheet.cssRules.length > 0) { sheet.deleteRule(0); } if (installed) { sheet.insertRule(".taler-installed-hide { display: none; }", 0); sheet.insertRule(".taler-probed-hide { display: none; }", 0); } else { sheet.insertRule(".taler-installed-show { display: none; }", 0); if (firstTimeoutCalled) { sheet.insertRule(".taler-probed-hide { display: none; }", 0); } else { // We're still doing the detection sheet.insertRule(".taler-installed-hide { display: none; }", 0); } } } function initStyle() { logVerbose && console.log("taking over styles"); var name = "taler-presence-stylesheet"; var content = "/* Taler stylesheet controlled by JS */"; var style = document.getElementById(name); if (!style) { style = document.createElement("style"); // Needed by WebKit style.appendChild(document.createTextNode(content)); style.id = name; document.head.appendChild(style); sheet = style.sheet; } else { // We've taken over the stylesheet now, // make it clear by clearing all the rules in it // and making it obvious in the DOM. if (style.tagName.toLowerCase() === "style") { style.innerText = content; } if (!style.sheet) { throw Error("taler-presence-stylesheet should be a style sheet ( or