/* @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 */ "use strict"; /** * The precision of the amount's fraction, when * it comes objectified. */ var FRACTION = 100000000; /** * Default 'start' URL argument used when querying for /history. */ var START = 0; /** * Fixed 'delta' URL argument used when querying for /history. */ var DELTA = 5 /** * Row id of the _oldest_ /history record that's shown on the * screen; will be used as the next 'delta' argument if scrolling * down occurs. */ var LAST = 0; /** * Flag controlling whether the scrolling of the page is enabled * or not; for example, after /track/transfer results are shown * on page, the semantics of "scrolling down towards older records" * disappears because former results aren't arranged regarding of * time. */ var SCROLL = true; /** * Maps error codes from https://git.taler.net/exchange.git/tree/src/include/taler_error_codes.h * to friendlier messages. */ function error_map(code){ switch (code){ /* /history errors. */ case "2200": return "The timestamp used to query the history overflowed."; case "2201": return "Merchant had serious problems in its history database"; case "2202": return "The instance used to query the history is unknown"; /* /track/order errors. */ case "2307": /* Such errors should provide full proof to the user. */ return "One of the coin was untraceable"; case "2308": case "2408": return "Conflicting reports from the Exchange"; case "2301": return "Instance is unknown to track this order"; case "2303": case "2304": case "2305": case "1801": return "Merchant database failed, code:" + code; case "2306": return "One of the coin failed at getting its WTID"; /* /track/transfer errors. */ case "2410": return "Exchange charged a higher wire fee than what " + " it was originally advertised."; case "2405": return "Error from the exchange, no proof received!"; case "2404": case "2402": return "Database failure, could not store results: " + code; case "2406": return "Database failure, could not retrieve previous results"; case "2407": return "Database failure, internal logic error"; case "2409": return "Merchant could not pack the JSON response"; case "2403": return "Merchant failed to request /track/transfer to \ the exchange"; case "2400": return "Exchange timed out.."; default: return "Error code not given."; } } /** * Convert Taler-compliant amount to human-friendly string. * The amount may be both a string or object type. */ function amount_to_string(amount){ if (typeof amount === 'string' || amount instanceof String) { var split = amount.match(/([A-Z]+):([0-9]+)\.?([0-9]+)?/); if (!split) return "Bad amount given: " + amount; if (split[3]) return `${split[2]}.${split[3]} ${split[1]}`; return `${split[2]}.00 ${split[1]}`; } var number = Number(amount.value) + (Number(amount.fraction)/FRACTION); return `${number.toFixed(2)} ${amount.currency}`; } /** * Close the center-box that shows results from /track/transaction * Note that this function first *empties* the results, and then * closes the box. */ function close_popup(){ var ctx = document.getElementsByClassName("track-content")[0]; var tbody = xpath_get("table/tbody", ctx).snapshotItem(0); var tbody_children = xpath_get("table/tbody/tr", ctx); for(var i=1; i${msg} ${hint}

`; info_bar.style.visibility = "visible"; } /** * Call /track/order API offered by the frontend. It will make * results shown in a centered box that overlays the page. */ var track_order = function(order_id, cb){ /* Remove potential information bar. */ var info_bar = document.getElementById("information-bar"); info_bar.style.visibility = "hidden"; toggle_loader(); var req = new XMLHttpRequest(); var url = `/track/order?` + `order_id=${order_id}&` + `instance=${get_instance()}`; req.open("GET", url, true); req.onload = function(){ if (4 == req.readyState){ if ((200 == req.status) || (202 == req.status)) cb(JSON.parse(req.responseText), req.status); else show_error(req.responseText, true); return; } } req.send(); } /** * Fill the screen-centered box with data from /track/order. * This box will overlay the ordinary page, and disappear when * the user clicks the close button or Esc key. */ function fill_box(tracks, http_code) { toggle_loader(); if (http_code == 404){ alert("No tracks for that order."); return; } if (http_code == 202){ console.log("Pending order."); toggle_loader(); show_warning("This order is still waiting to be paid back"); return; } if(!tracks) show_error("/track/transfer returned EMPTY! Very bad."); console.log("Got invalid JSON"); if(0 == tracks.length || !tracks.length){ console.log(`Got no tracks and status == ${http_code}. ` + `Should not be here.`); return; } for(var i=0; i` + `${subject.substring(0, 20)}...` + `` + `` + `` + `${amount_to_string(entry.amount)}` + `` + `` + `${parse_date(entry.execution_time)}` + ``; table.appendChild(row); toggle_overlay(); } } /** * Helper function which abstracts the hard-to-remember * API offered by document.evaluate(). */ function xpath_get(xpath, ctx){ var ret = document.evaluate (xpath, ctx, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null); return ret; } /** * Fill the page with orders rows. */ function fill_table(data, execution_time, wtid_marker){ var table = document.getElementById("history"); var tbody = xpath_get("tbody", table).snapshotItem(0); /* Make table visible, if it's hidden. */ xpath_get("tr[@class='headers']", tbody) .snapshotItem(0).style.visibility = "visible"; table.style.visibility = "visible"; for (var i=0; i ${entry.order_id}`; td_summary.className = "summary"; td_summary.innerHTML = entry.summary || "deposited"; td_amount.innerHTML = amount_to_string( entry.amount || entry.deposit_value); td_date.innerHTML = parse_date (entry.timestamp || execution_time); row.appendChild(td_order_id); row.appendChild(td_summary); row.appendChild(td_amount); row.appendChild(td_date); row_summary.appendChild(td_summary); tbody.appendChild(row); tbody.appendChild(row_summary); } if (wtid_marker){ // close popup showing wire transfer information close_popup(); // draw a line at the bottom, mentioning the WTID. var marker = make_marker(wtid_marker); tbody.appendChild(marker); } toggle_loader(); } /** * Make the spinning wheel appear/disappear. */ function toggle_loader(){ var loader = document.getElementsByClassName("loader")[0]; if (loader.style.visibility != "hidden") { loader.style.visibility = "hidden"; console.log("toggle_loader: visible>hidden"); } else { loader.style.visibility = "visible"; console.log("toggle_loader: hidden>visible"); } } /** * Issue a /track/order (/track/transfer) depending on * whether the user selected "order" ("transfer") on the * dedicated form. */ function track_cherry_pick(form){ var types = xpath_get("input[@type='radio']", form); for(var i in [0, 1]){ if (!types.snapshotItem(i).checked) continue; var type = types.snapshotItem(i).value; if ("order" == type){ var order_id = xpath_get("input[@class='order']", form) .snapshotItem(0); track_order(order_id.value, fill_box); } else{ var data = xpath_get("input[@class='transfer']", form); var wtid = data.snapshotItem(0); var exchange = data.snapshotItem(1); track_transfer(exchange.value, wtid.value, fill_table); } } } /** * Make the /track/order form visible. */ function cherry_pick_form_order(form){ var input_order = xpath_get("input[@class='order']", form) .snapshotItem(0); input_order.style.visibility = ""; var input_transfer = xpath_get("input[@class='transfer']", form); for(var i in [0, 1]) input_transfer.snapshotItem(i).style.visibility = "hidden"; } /** * Make the /track/transaction form visible. */ function cherry_pick_form_transfer(form){ var input_order = xpath_get("input[@class='order']", form) .snapshotItem(0); input_order.style.visibility = "hidden"; var input_transfer = xpath_get("input[@class='transfer']", form); for(var i in [0, 1]) input_transfer.snapshotItem(i).style.visibility = ""; } /** * Retrieve the selected instance. */ function get_instance(){ var select = document.getElementById("instance"); return select.value; } /** * Remove tracks from the main page table, but * do NOT remove the table headers; it hides them though. */ function clear_results(){ var table = document.getElementById("history"); var tbody = xpath_get("tbody", table).snapshotItem(0); var tbody_children = xpath_get("tbody/*[position() > 1]", table); for(var i=0; i= document.body.offsetHeight) && SCROLL) get_history(true, fill_table); }); /** * Close the centered box that (typically) shows /track/transfer * results. */ document.onkeydown = function(e) { if(!e) e = event; if(e.keyCode == 27){ close_popup(); } } /** * export functions to be tested by AVA. */ if (typeof(module) != "undefined"){ module.exports.track_transfer = track_transfer; module.exports.track_order = track_order; }