1 /* This Source Code Form is subject to the terms of the Mozilla Public
 
   2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 
   3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
   5 // Get the best-available objects for indexedDB and requestAnimationFrame.
 
   6 window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
 
   7 window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
 
  10 var gAppInitDone = false;
 
  12 var gUIHideCountdown = 0;
 
  14 var gTrackUpdateInterval;
 
  15 var gAction, gActionLabel;
 
  16 var authData = null, userData = null;
 
  17 var gBackendURL = "https://backend.lantea.kairo.at/";
 
  18 var gAuthClientID = "lantea";
 
  20 window.onload = function() {
 
  21   // Assign click functions to buttons.
 
  22   document.getElementById("zoomInButton").onclick = gMap.zoomIn;
 
  23   document.getElementById("zoomOutButton").onclick = gMap.zoomOut;
 
  25   gAction = document.getElementById("action");
 
  26   gActionLabel = document.getElementById("actionlabel");
 
  28   var mSel = document.getElementById("mapSelector");
 
  29   for (var mapStyle in gMapStyles) {
 
  30     var opt = document.createElement("option");
 
  32     opt.text = gMapStyles[mapStyle].name;
 
  36   var areas = document.getElementsByClassName("autoFade");
 
  37   for (var i = 0; i <= areas.length - 1; i++) {
 
  38     areas[i].addEventListener("mouseup", uiEvHandler, false);
 
  39     areas[i].addEventListener("mousemove", uiEvHandler, false);
 
  40     areas[i].addEventListener("mousedown", uiEvHandler, false);
 
  41     areas[i].addEventListener("mouseout", uiEvHandler, false);
 
  43     areas[i].addEventListener("touchstart", uiEvHandler, false);
 
  44     areas[i].addEventListener("touchmove", uiEvHandler, false);
 
  45     areas[i].addEventListener("touchend", uiEvHandler, false);
 
  46     areas[i].addEventListener("touchcancel", uiEvHandler, false);
 
  47     areas[i].addEventListener("touchleave", uiEvHandler, false);
 
  50   document.getElementById("body").addEventListener("keydown", uiEvHandler, false);
 
  52   if (navigator.platform.length == "") {
 
  53     // For Firefox OS, don't display the "save" button.
 
  54     // Do this by setting the debugHide class for testing in debug mode.
 
  55     document.getElementById("saveTrackButton").classList.add("debugHide");
 
  58   // Set backend URL in a way that it works for testing on localhost as well as
 
  59   // both the lantea.kairo.at and lantea-dev.kairo.at deployments.
 
  60   if (window.location.host == "localhost") {
 
  61     gBackendURL = window.location.protocol + '//' + window.location.host + "/lantea-backend/";
 
  64     gBackendURL = window.location.protocol + '//' + "backend." + window.location.host + "/";
 
  66   // Make sure to use a different login client ID for the -dev setup.
 
  67   if (/\-dev\./.test(window.location.host)) {
 
  68     gAuthClientID += "-dev";
 
  71   document.getElementById("libCloseButton").onclick = hideLibrary;
 
  73   // Set up the login area.
 
  74   document.getElementById("loginbtn").onclick = startLogin;
 
  75   document.getElementById("logoutbtn").onclick = doLogout;
 
  76   // Put in a logged-out state by default.
 
  77   // Opening the track drawer will update this correctly.
 
  80   gAction.addEventListener("dbinit-done", initMap, false);
 
  81   gAction.addEventListener("mapinit-done", postInit, false);
 
  82   console.log("starting DB init...");
 
  86 function postInit(aEvent) {
 
  87   gAction.removeEventListener(aEvent.type, postInit, false);
 
  88   console.log("init done, draw map.");
 
  89   gMapPrefsLoaded = true;
 
  91   //gMap.resizeAndDraw();  <-- HACK: This triggers bug 1001853, work around with a delay.
 
  92   window.setTimeout(gMap.resizeAndDraw, 100);
 
  93   gActionLabel.textContent = "";
 
  94   gAction.style.display = "none";
 
  95   setTracking(document.getElementById("trackCheckbox"));
 
  96   gPrefs.get("devicename", function(aValue) {
 
  98       document.getElementById("uploadDevName").value = aValue;
 
 102     showFirstRunDialog();
 
 105     gPrefs.get("lastInfoShown", function(aValue) {
 
 106       if (!aValue || !parseInt(aValue) || parseInt(aValue) < 1) {
 
 111   gPrefs.set("lastInfoShown", 1);
 
 114 window.onresize = function() {
 
 115   gMap.resizeAndDraw();
 
 118 function startLogin() {
 
 119   var logerr = document.getElementById("loginerror");
 
 120   logerr.classList.add("hidden");
 
 122   if (!authData || !authData["state"]) {
 
 123     // We have no oAuth state, try to fetch it and call ourselves again if it worked.
 
 124     prepareLoginButton(function() {
 
 125       if (authData && authData["state"]) {
 
 128       else if (!userData) {
 
 129         // Only warn if we didn't actually end up being logged in.
 
 130         console.log("No OAuth state and fetching fails, client or server may be offline.");
 
 131         logerr.classList.remove("hidden");
 
 132         logerr.title = "Client or server may be offline.";
 
 137   var authURL = authData["url"] + "authorize?response_type=code&client_id=" + gAuthClientID + "&scope=email" +
 
 138                 "&state=" + authData["state"] + "&redirect_uri=" + encodeURIComponent(getRedirectURI());
 
 139   if (window.open(authURL, "KaiRoAuth", 'height=450,width=600')) {
 
 140     console.log("Sign In window open.");
 
 143     console.log("Opening Sign In window failed.");
 
 144     logerr.classList.remove("hidden");
 
 145     logerr.title = "Opening Sign-In window failed.";
 
 149 function getRedirectURI() {
 
 150   return window.location.protocol + '//' + window.location.host + window.location.pathname.replace("index.html", "") + "login.html";
 
 153 function doLogout() {
 
 154   fetchBackend("logout", "GET", null,
 
 155      function(aResult, aStatus) {
 
 157           prepareLoginButton();
 
 160           console.log("Backend issue trying to log out.");
 
 167 function prepareLoginButton(aCallback) {
 
 168   fetchBackend("oauth_state", "GET", null,
 
 169       function(aResult, aStatus) {
 
 170         if (aStatus == 200) {
 
 171           if (aResult["logged_in"]) {
 
 173               "email": aResult["email"],
 
 174               "permissions": aResult["permissions"],
 
 180             authData = {"state": aResult["state"], "url": aResult["url"]};
 
 186           console.log("Backend error " + aStatus + " fetching OAuth state: " + aResult["message"]);
 
 188         if (aCallback) { aCallback(); }
 
 194 function finishLogin(aCode, aState) {
 
 195   if (aState == authData["state"]) {
 
 196     fetchBackend("login?code=" + aCode + "&state=" + aState + "&redirect_uri=" + encodeURIComponent(getRedirectURI()), "GET", null,
 
 197         function(aResult, aStatus) {
 
 198           if (aStatus == 200) {
 
 200               "email": aResult["email"],
 
 201               "permissions": aResult["permissions"],
 
 206             console.log("Login error " + aStatus + ": " + aResult["message"]);
 
 207             prepareLoginButton();
 
 214     console.log("Login state did not match, not continuing with login.");
 
 218 function displayLogin() {
 
 219   document.getElementById("loginbtn").classList.add("hidden");
 
 220   document.getElementById("logindesc").classList.add("hidden");
 
 221   document.getElementById("username").classList.remove("hidden");
 
 222   document.getElementById("username").textContent = userData.email;
 
 223   document.getElementById("uploadTrackButton").disabled = false;
 
 224   document.getElementById("libraryShowLine").classList.remove("hidden");
 
 225   document.getElementById("logoutbtn").classList.remove("hidden");
 
 228 function displayLogout() {
 
 229   document.getElementById("logoutbtn").classList.add("hidden");
 
 230   document.getElementById("username").classList.add("hidden");
 
 231   document.getElementById("username").textContent = "";
 
 232   document.getElementById("uploadTrackButton").disabled = true;
 
 233   document.getElementById("libraryShowLine").classList.add("hidden");
 
 234   document.getElementById("loginbtn").classList.remove("hidden");
 
 235   document.getElementById("logindesc").classList.remove("hidden");
 
 238 function initDB(aEvent) {
 
 241     gAction.removeEventListener(aEvent.type, initDB, false);
 
 242   var request = window.indexedDB.open("MainDB-lantea", 2);
 
 243   request.onerror = function(event) {
 
 244     // Errors can be handled here. Error codes explain in:
 
 245     // https://developer.mozilla.org/en/IndexedDB/IDBDatabaseException#Constants
 
 246     console.log("error opening mainDB: " + event.target.error);
 
 249       console.log("error code: " + event.target.error.code +
 
 250                   " - name: " + event.target.error.name);
 
 253   request.onsuccess = function(event) {
 
 254     mainDB = event.target.result;
 
 255     var throwEv = new CustomEvent("dbinit-done");
 
 256     gAction.dispatchEvent(throwEv);
 
 258   request.onupgradeneeded = function(event) {
 
 259     mainDB = request.result;
 
 260     var ver = mainDB.version || 0; // version is empty string for a new DB
 
 262       console.log("mainDB has version " + ver + ", upgrade needed.");
 
 263     if (!mainDB.objectStoreNames.contains("prefs")) {
 
 264       // Create a "prefs" objectStore.
 
 265       var prefsStore = mainDB.createObjectStore("prefs");
 
 268     if (!mainDB.objectStoreNames.contains("track")) {
 
 269       // Create a "track" objectStore.
 
 270       var trackStore = mainDB.createObjectStore("track", {autoIncrement: true});
 
 272     if (!mainDB.objectStoreNames.contains("tilecache")) {
 
 273       // Create a "tilecache" objectStore.
 
 274       var tilecacheStore = mainDB.createObjectStore("tilecache");
 
 276     mainDB.onversionchange = function(event) {
 
 285   if (gUIHideCountdown <= 0) {
 
 286     var areas = document.getElementsByClassName('autoFade');
 
 287     for (var i = 0; i <= areas.length - 1; i++) {
 
 288       areas[i].classList.remove("hidden");
 
 290     setTimeout(maybeHideUI, 1000);
 
 292   gUIHideCountdown = 5;
 
 295 function maybeHideUI() {
 
 297   if (gUIHideCountdown <= 0) {
 
 298     var areas = document.getElementsByClassName('autoFade');
 
 299     for (var i = 0; i <= areas.length - 1; i++) {
 
 300       areas[i].classList.add("hidden");
 
 304     setTimeout(maybeHideUI, 1000);
 
 308 function updateTrackInfo() {
 
 309   document.getElementById("trackLengthNum").textContent = calcTrackLength().toFixed(1);
 
 310   var duration = calcTrackDuration();
 
 311   var durationM = Math.round(duration/60);
 
 312   var durationH = Math.floor(durationM/60); durationM = durationM - durationH * 60;
 
 313   document.getElementById("trackDurationH").style.display = durationH ? "inline" : "none";
 
 314   document.getElementById("trackDurationHNum").textContent = durationH;
 
 315   document.getElementById("trackDurationMNum").textContent = durationM;
 
 318 function toggleTrackArea() {
 
 319   var fs = document.getElementById("trackArea");
 
 320   if (fs.classList.contains("hidden")) {
 
 321     prepareLoginButton();
 
 322     fs.classList.remove("hidden");
 
 324     gTrackUpdateInterval = setInterval(updateTrackInfo, 1000);
 
 327     clearInterval(gTrackUpdateInterval);
 
 328     fs.classList.add("hidden");
 
 332 function toggleSettings() {
 
 333   var fs = document.getElementById("settingsArea");
 
 334   if (fs.classList.contains("hidden")) {
 
 335     fs.classList.remove("hidden");
 
 339     fs.classList.add("hidden");
 
 343 function toggleFullscreen() {
 
 344   if ((document.fullScreenElement && document.fullScreenElement !== null) ||
 
 345       (document.mozFullScreenElement && document.mozFullScreenElement !== null) ||
 
 346       (document.webkitFullScreenElement && document.webkitFullScreenElement !== null)) {
 
 347     if (document.cancelFullScreen) {
 
 348       document.cancelFullScreen();
 
 349     } else if (document.mozCancelFullScreen) {
 
 350       document.mozCancelFullScreen();
 
 351     } else if (document.webkitCancelFullScreen) {
 
 352       document.webkitCancelFullScreen();
 
 356     var elem = document.getElementById("body");
 
 357     if (elem.requestFullScreen) {
 
 358       elem.requestFullScreen();
 
 359     } else if (elem.mozRequestFullScreen) {
 
 360       elem.mozRequestFullScreen();
 
 361     } else if (elem.webkitRequestFullScreen) {
 
 362       elem.webkitRequestFullScreen();
 
 367 function showUploadDialog() {
 
 368   var dia = document.getElementById("trackDialogArea");
 
 369   var areas = dia.children;
 
 370   for (var i = 0; i <= areas.length - 1; i++) {
 
 371     areas[i].style.display = "none";
 
 373   document.getElementById("uploadDialog").style.display = "block";
 
 374   document.getElementById("uploadTrackButton").disabled = true;
 
 375   dia.classList.remove("hidden");
 
 378 function cancelTrackDialog() {
 
 379   document.getElementById("trackDialogArea").classList.add("hidden");
 
 380   document.getElementById("uploadTrackButton").disabled = false;
 
 383 function showGLWarningDialog() {
 
 384   var dia = document.getElementById("dialogArea");
 
 385   var areas = dia.children;
 
 386   for (var i = 0; i <= areas.length - 1; i++) {
 
 387     areas[i].style.display = "none";
 
 389   document.getElementById("noGLwarning").style.display = "block";
 
 390   dia.classList.remove("hidden");
 
 393 function showDBErrorDialog() {
 
 394   var dia = document.getElementById("dialogArea");
 
 395   var areas = dia.children;
 
 396   for (var i = 0; i <= areas.length - 1; i++) {
 
 397     areas[i].style.display = "none";
 
 399   document.getElementById("DBError").style.display = "block";
 
 400   dia.classList.remove("hidden");
 
 403 function showFirstRunDialog() {
 
 404   var dia = document.getElementById("dialogArea");
 
 405   var areas = dia.children;
 
 406   for (var i = 0; i <= areas.length - 1; i++) {
 
 407     areas[i].style.display = "none";
 
 409   document.getElementById("firstRunIntro").style.display = "block";
 
 410   dia.classList.remove("hidden");
 
 413 function closeDialog() {
 
 414   document.getElementById("dialogArea").classList.add("hidden");
 
 417 function showInfoDialog() {
 
 418   var dia = document.getElementById("dialogArea");
 
 419   var areas = dia.children;
 
 420   for (var i = 0; i <= areas.length - 1; i++) {
 
 421     areas[i].style.display = "none";
 
 423   document.getElementById("infoDialog").style.display = "block";
 
 424   dia.classList.remove("hidden");
 
 428   handleEvent: function(aEvent) {
 
 429     var touchEvent = aEvent.type.indexOf('touch') != -1;
 
 431     switch (aEvent.type) {
 
 445 function setUploadField(aField) {
 
 447     case "uploadDevName":
 
 448       gPrefs.set("devicename", aField.value);
 
 453 function makeISOString(aTimestamp) {
 
 454   // ISO time format is YYYY-MM-DDTHH:mm:ssZ
 
 455   var tsDate = new Date(aTimestamp);
 
 456   // Note that .getUTCMonth() returns a number between 0 and 11 (0 for January)!
 
 457   return tsDate.getUTCFullYear() + "-" +
 
 458          (tsDate.getUTCMonth() < 9 ? "0" : "") + (tsDate.getUTCMonth() + 1 ) + "-" +
 
 459          (tsDate.getUTCDate() < 10 ? "0" : "") + tsDate.getUTCDate() + "T" +
 
 460          (tsDate.getUTCHours() < 10 ? "0" : "") + tsDate.getUTCHours() + ":" +
 
 461          (tsDate.getUTCMinutes() < 10 ? "0" : "") + tsDate.getUTCMinutes() + ":" +
 
 462          (tsDate.getUTCSeconds() < 10 ? "0" : "") + tsDate.getUTCSeconds() + "Z";
 
 465 function convertTrack(aTargetFormat) {
 
 467   switch (aTargetFormat) {
 
 469       out += '<?xml version="1.0" encoding="UTF-8" ?>' + "\n\n";
 
 470       out += '<gpx version="1.0" creator="Lantea" xmlns="http://www.topografix.com/GPX/1/0">' + "\n";
 
 472         out += '  <trk>' + "\n";
 
 473         out += '    <trkseg>' + "\n";
 
 474         for (var i = 0; i < gTrack.length; i++) {
 
 475           if (gTrack[i].beginSegment && i > 0) {
 
 476             out += '    </trkseg>' + "\n";
 
 477             out += '    <trkseg>' + "\n";
 
 479           out += '      <trkpt lat="' + gTrack[i].coords.latitude + '" lon="' +
 
 480                                         gTrack[i].coords.longitude + '">' + "\n";
 
 481           if (gTrack[i].coords.altitude) {
 
 482             out += '        <ele>' + gTrack[i].coords.altitude + '</ele>' + "\n";
 
 484           out += '        <time>' + makeISOString(gTrack[i].time) + '</time>' + "\n";
 
 485           out += '      </trkpt>' + "\n";
 
 487         out += '    </trkseg>' + "\n";
 
 488         out += '  </trk>' + "\n";
 
 490       out += '</gpx>' + "\n";
 
 493       out = JSON.stringify(gTrack);
 
 501 function saveTrack() {
 
 503     var outDataURI = "data:application/gpx+xml," +
 
 504                      encodeURIComponent(convertTrack("gpx"));
 
 505     window.open(outDataURI, 'GPX Track');
 
 509 function saveTrackDump() {
 
 511     var outDataURI = "data:application/json," +
 
 512                      encodeURIComponent(convertTrack("json"));
 
 513     window.open(outDataURI, 'JSON dump');
 
 517 function uploadTrack() {
 
 518   // Hide all areas in the dialog.
 
 519   var dia = document.getElementById("trackDialogArea");
 
 520   var areas = dia.children;
 
 521   for (var i = 0; i <= areas.length - 1; i++) {
 
 522     areas[i].style.display = "none";
 
 524   // Reset all the fields in the status area.
 
 525   document.getElementById("uploadStatusCloseButton").disabled = true;
 
 526   document.getElementById("uploadInProgress").style.display = "block";
 
 527   document.getElementById("uploadSuccess").style.display = "none";
 
 528   document.getElementById("uploadFailed").style.display = "none";
 
 529   document.getElementById("uploadError").style.display = "none";
 
 530   document.getElementById("uploadErrorMsg").textContent = "";
 
 531   // Now show the status area.
 
 532   document.getElementById("uploadStatus").style.display = "block";
 
 534   // Assemble field to post to the backend.
 
 535   var formData = new FormData();
 
 536   formData.append("jsondata", convertTrack("json"));
 
 537   var desc = document.getElementById("uploadDesc").value;
 
 538   formData.append("comment",
 
 539                   desc.length ? desc : "Track recorded via Lantea Maps");
 
 540   formData.append("devicename",
 
 541                   document.getElementById("uploadDevName").value);
 
 542   formData.append("public",
 
 543                   document.getElementById("uploadPublic").value);
 
 545   fetchBackend("save_track", "POST", formData,
 
 546     function(aResult, aStatusCode) {
 
 547       if (aStatusCode >= 400) {
 
 548         reportUploadStatus(false, aResult);
 
 550       else if (aResult["id"]) {
 
 551         reportUploadStatus(true);
 
 553       else { // If no ID is returned, we assume a general error.
 
 554         reportUploadStatus(false);
 
 560 function reportUploadStatus(aSuccess, aResponse) {
 
 561   document.getElementById("uploadStatusCloseButton").disabled = false;
 
 562   document.getElementById("uploadInProgress").style.display = "none";
 
 564     document.getElementById("uploadSuccess").style.display = "block";
 
 566   else if (aResponse && aResponse["message"]) {
 
 567     document.getElementById("uploadErrorMsg").textContent = aResponse["message"];
 
 568     if (aResponse["errortype"]) {
 
 569       document.getElementById("uploadErrorMsg").textContent += " (" + aResponse["errortype"] + ")";
 
 571     document.getElementById("uploadError").style.display = "block";
 
 573   else if (aResponse) {
 
 574     document.getElementById("uploadErrorMsg").textContent = aResponse;
 
 575     document.getElementById("uploadError").style.display = "block";
 
 578     document.getElementById("uploadFailed").style.display = "block";
 
 582 function setMapStyle() {
 
 583   var mapSel = document.getElementById("mapSelector");
 
 584   if (mapSel.selectedIndex >= 0 && gMap.activeMap != mapSel.value) {
 
 585     gMap.setActiveMap(mapSel.value);
 
 592   get: function(aKey, aCallback) {
 
 595     var transaction = mainDB.transaction([this.objStore]);
 
 596     var request = transaction.objectStore(this.objStore).get(aKey);
 
 597     request.onsuccess = function(event) {
 
 598       aCallback(request.result, event);
 
 600     request.onerror = function(event) {
 
 601       // Errors can be handled here.
 
 602       aCallback(undefined, event);
 
 606   set: function(aKey, aValue, aCallback) {
 
 610     var transaction = mainDB.transaction([this.objStore], "readwrite");
 
 611     var objStore = transaction.objectStore(this.objStore);
 
 612     var request = objStore.put(aValue, aKey);
 
 613     request.onsuccess = function(event) {
 
 616         aCallback(success, event);
 
 618     request.onerror = function(event) {
 
 619       // Errors can be handled here.
 
 621         aCallback(success, event);
 
 625   unset: function(aKey, aCallback) {
 
 629     var transaction = mainDB.transaction([this.objStore], "readwrite");
 
 630     var request = transaction.objectStore(this.objStore).delete(aKey);
 
 631     request.onsuccess = function(event) {
 
 634         aCallback(success, event);
 
 636     request.onerror = function(event) {
 
 637       // Errors can be handled here.
 
 639         aCallback(success, event);
 
 647   getList: function(aCallback) {
 
 650     var transaction = mainDB.transaction([this.objStore]);
 
 651     var objStore = transaction.objectStore(this.objStore);
 
 652     if (objStore.getAll) { // currently Mozilla-specific
 
 653       objStore.getAll().onsuccess = function(event) {
 
 654         aCallback(event.target.result);
 
 657     else { // Use cursor (standard method).
 
 659       objStore.openCursor().onsuccess = function(event) {
 
 660         var cursor = event.target.result;
 
 662           tPoints.push(cursor.value);
 
 672   getListStepped: function(aCallback) {
 
 675     var transaction = mainDB.transaction([this.objStore]);
 
 676     var objStore = transaction.objectStore(this.objStore);
 
 677     // Use cursor in reverse direction (so we get the most recent position first)
 
 678     objStore.openCursor(null, "prev").onsuccess = function(event) {
 
 679       var cursor = event.target.result;
 
 681         aCallback(cursor.value);
 
 690   push: function(aValue, aCallback) {
 
 693     var transaction = mainDB.transaction([this.objStore], "readwrite");
 
 694     var objStore = transaction.objectStore(this.objStore);
 
 695     var request = objStore.add(aValue);
 
 696     request.onsuccess = function(event) {
 
 698         aCallback(request.result, event);
 
 700     request.onerror = function(event) {
 
 701       // Errors can be handled here.
 
 703         aCallback(false, event);
 
 707   clear: function(aCallback) {
 
 711     var transaction = mainDB.transaction([this.objStore], "readwrite");
 
 712     var request = transaction.objectStore(this.objStore).clear();
 
 713     request.onsuccess = function(event) {
 
 716         aCallback(success, event);
 
 718     request.onerror = function(event) {
 
 719       // Errors can be handled here.
 
 721         aCallback(success, event);
 
 726 function fetchBackend(aEndpoint, aMethod, aSendData, aCallback, aCallbackForwards) {
 
 727   var XHR = new XMLHttpRequest();
 
 728   XHR.onreadystatechange = function() {
 
 729     if (XHR.readyState == 4) {
 
 730       // State says we are fully loaded.
 
 732       if (XHR.getResponseHeader("Content-Type") == "application/json") {
 
 733         // Got a JSON object, see if we have success.
 
 735           result = JSON.parse(XHR.responseText);
 
 739           result = {"error": e,
 
 740                     "message": XHR.responseText};
 
 744         result = XHR.responseText;
 
 746       aCallback(result, XHR.status, aCallbackForwards);
 
 749   XHR.open(aMethod, gBackendURL + aEndpoint, true);
 
 750   XHR.withCredentials = "true";
 
 751   //XHR.setRequestHeader("Accept", "application/json");
 
 753     XHR.send(aSendData); // Send actual form data.
 
 756     aCallback(e, 500, aCallbackForwards);