summaryrefslogtreecommitdiff
path: root/talermerchantdemos/blog/static/web-common/taler-wallet-lib.ts
diff options
context:
space:
mode:
authorMS <ms@taler.net>2020-07-22 14:53:45 +0200
committerMS <ms@taler.net>2020-07-22 14:53:45 +0200
commit2d97ecc2c1ac605ca49e8a866b309daaeb7a831c (patch)
tree173f7917c5d0af822d2d51ed491c3cf2d8eaf23f /talermerchantdemos/blog/static/web-common/taler-wallet-lib.ts
downloadtaler-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.ts488
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