diff options
author | MS <ms@taler.net> | 2020-07-22 14:53:45 +0200 |
---|---|---|
committer | MS <ms@taler.net> | 2020-07-22 14:53:45 +0200 |
commit | 2d97ecc2c1ac605ca49e8a866b309daaeb7a831c (patch) | |
tree | 173f7917c5d0af822d2d51ed491c3cf2d8eaf23f /talermerchantdemos/blog/static/web-common/taler-wallet-lib.ts | |
download | taler-merchant-demos-2d97ecc2c1ac605ca49e8a866b309daaeb7a831c.tar.gz taler-merchant-demos-2d97ecc2c1ac605ca49e8a866b309daaeb7a831c.tar.bz2 taler-merchant-demos-2d97ecc2c1ac605ca49e8a866b309daaeb7a831c.zip |
Installing the Blog
Diffstat (limited to 'talermerchantdemos/blog/static/web-common/taler-wallet-lib.ts')
-rw-r--r-- | talermerchantdemos/blog/static/web-common/taler-wallet-lib.ts | 488 |
1 files changed, 488 insertions, 0 deletions
diff --git a/talermerchantdemos/blog/static/web-common/taler-wallet-lib.ts b/talermerchantdemos/blog/static/web-common/taler-wallet-lib.ts new file mode 100644 index 0000000..cbf32e8 --- /dev/null +++ b/talermerchantdemos/blog/static/web-common/taler-wallet-lib.ts @@ -0,0 +1,488 @@ +/* + @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 +*/ + + +export namespace taler { + "use strict"; + + let logVerbose: boolean = false; + try { + logVerbose = !!localStorage.getItem("taler-log-verbose"); + } catch (e) { + // can't read from local storage + } + + const presentHandlers: any[] = []; + const absentHandlers: any[] = []; + + // Are we running as the content script of an + // extension (and not just from a normal page)? + let runningInExtension = false; + + let callSeqId = 1; + + let installed = false; + let probeExecuted = false; + let pageLoaded = false; + + let errorHandler: any = undefined; + + let sheet: CSSStyleSheet; + + export function onError(handler: any) { + if (errorHandler) { + console.warn("Overriding error handler"); + } + errorHandler = handler; + } + + + /** + * Error handler for things that go wrong in the merchant + * frontend browser code. + */ + function raise_error(reason: string, detail: any) { + 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: string, args: any, onResult?: any): void { + const detail = JSON.parse(JSON.stringify(args || {})); + const callId = callSeqId++; + detail.callId = callId; + let onTimeout = () => { + console.warn("timeout for invocation of " + funcName); + } + const timeoutHandle: number = setTimeout(onTimeout, 1000); + let handler = (evt: CustomEvent) => { + if (evt.detail.callId !== callId) { + return; + } + if (onResult) { + onResult(evt.detail); + } + clearTimeout(timeoutHandle); + document.removeEventListener(funcName + "-result", handler); + }; + document.addEventListener(funcName + "-result", handler); + const evt = new CustomEvent(funcName, {detail}); + document.dispatchEvent(evt) + } + + + /** + * Confirm that a reserve was created. + * + * Used by tightly integrated bank portals. + */ + export function confirmReserve(reservePub: string) { + if (!installed) { + logVerbose && console.log("delaying confirmReserve"); + taler.onPresent(() => { + confirmReserve(reservePub); + }); + return; + } + callWallet("taler-confirm-reserve", {reserve_pub: reservePub}); + } + + + export function createReserve(callbackUrl: string, amount: any, wtTypes: string[], suggestedExchangeUrl?: string) { + if (!installed) { + logVerbose && console.log("delaying createReserve"); + taler.onPresent(() => { + createReserve(callbackUrl, amount, wtTypes, suggestedExchangeUrl); + }); + return; + } + let args = { + callback_url: callbackUrl, + amount: amount, + wt_types: wtTypes, + suggested_exchange_url: suggestedExchangeUrl + }; + callWallet("taler-create-reserve", args); + } + + + function handlePaymentResponse(walletResp: any) { + /** + * 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: XMLHttpRequest) { + let timeoutHandle: number|null = 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. + let timeout_ms = 1000; + // Current request. + let r: XMLHttpRequest|null; + let timeoutHandle: number|null = 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: + const 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}, () => { + let 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(); + } + + + export function onPresent(f: any) { + presentHandlers.push(f); + } + + export function onAbsent(f: any) { + absentHandlers.push(f); + } + + interface FulfillmentQuery { + type: "fulfillment_url"; + } + + interface OrderIdQuery { + type: "order_id"; + order_id: string; + } + + + interface PayDetail { + contract_url?: string; + offer_url?: string; + } + + export function internalPay(p: PayDetail) { + // either the callback gets called, + // or the wallet will redirect the browser + callWallet("taler-pay", p, handlePaymentResponse); + } + + export function pay(p: PayDetail) { + if (!installed) { + logVerbose && console.log("delaying call to 'pay' until GNU Taler wallet is present"); + taler.onPresent(() => { + pay(p); + }); + return; + } + internalPay(p); + } + + export interface AuditorDetail { + currency: string; + url: string; + auditorPub: string; + expirationStamp: number; + } + + + export function internalAddAuditor(d: AuditorDetail) { + // either the callback gets called, + // or the wallet will redirect the browser + callWallet("taler-add-auditor", d); + } + + + export function addAuditor(d: AuditorDetail) { + if (!installed) { + logVerbose && console.log("delaying call to 'addAuditor' until GNU Taler wallet is present"); + taler.onPresent(() => { + addAuditor(d); + }); + return; + } + internalAddAuditor(d); + } + + + export function internalCheckAuditor(url: string): Promise<AuditorDetail|undefined> { + return new Promise<AuditorDetail|undefined>((resolve, reject) => { + callWallet("taler-check-auditor", url, (x: any) => resolve(x as AuditorDetail)); + }); + } + + + /** + * Check if an auditor is already added to the wallet. + * + * Same-origin restrictions apply. + */ + export function checkAuditor(url: string): Promise<AuditorDetail|undefined> { + if (!installed) { + logVerbose && console.log("delaying call to 'checkAuditor' until GNU Taler wallet is present"); + return new Promise<AuditorDetail|undefined>((resolve, reject) => { + taler.onPresent(() => { + resolve(checkAuditor(url)); + }); + }); + } + return internalCheckAuditor(url); + } + + + 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); + } + + let 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"); + const name = "taler-presence-stylesheet"; + const content = "/* Taler stylesheet controlled by JS */"; + let style = document.getElementById(name) as HTMLStyleElement|null; + if (!style) { + style = document.createElement("style"); + // Needed by WebKit + style.appendChild(document.createTextNode(content)); + style.id = name; + document.head.appendChild(style); + sheet = style.sheet as CSSStyleSheet; + } 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 (<link> or <style>)"); + } + sheet = style.sheet as CSSStyleSheet; + while (sheet.cssRules.length > 0) { + sheet.deleteRule(0); + } + } + } + + function onPageLoad() { + pageLoaded = true; + if (document.readyState == "complete") { + initStyle(); + } else { + let listener = () => { + initStyle(); + setStyles(); + }; + window.addEventListener("load", listener); + } + + // We only start the timeout after the page is interactive. + window.setInterval(onProbeTimeout, 300); + + announce(); + } + + probeTaler(); + document.addEventListener("taler-probe-result", handleProbe, false); + document.addEventListener("taler-uninstall", handleUninstall, false); + // Handle the case where the JavaScript is loaded after the page + // has been loaded for the first time. + if (document.readyState == "loading") { + document.addEventListener("DOMContentLoaded", onPageLoad, false); + } else { + onPageLoad(); + } + } + + function onPageLoadInExtension() { + if (document.documentElement.getAttribute("data-taler-nojs")) { + logVerbose && console.log("doing taler initialization from extension (nojs)"); + initTaler(); + } + } + + let caught = false; + try { + (chrome as any).runtime.sendMessage({type: "ping"}); + } catch (e) { + caught = true; + } + + if (caught) { + logVerbose && console.log("running taler-wallet-lib from page"); + initTaler(); + } else { + logVerbose && console.log("running taler-wallet-lib from extension"); + runningInExtension = true; + // Wait for even style sheets to load + if (document.readyState != "complete") { + window.addEventListener("load", () => onPageLoadInExtension()); + } else { + onPageLoadInExtension(); + } + } +} +// @license-end |