The script is not working in Mozilla Firefox - javascript

I need to send some data to the server when the page is closed or refreshed. Therefore, I created a simple script that can handle my needs. The problem is that this script is not working in Mozilla firefox.
The script is working in many other browsers like chrome, chromium, brave, opera, falkon, epiphany, qutebroser, Midori, safari, edge. The problem is only with firefox.
var timeLog = {
start: null,
end: null,
init: function () {
this.start = new Date().getTime();
},
sendResults: function () {
this.end = new Date().getTime();
var url = "tracker";
url += "?" + "start=" + this.start;
url += "&" + "end=" + this.end;
url += "&" + "User-Agent-JS=" + navigator.userAgent;
url += "&" + "url=" + window.location.toString();
fetch(url, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
keepalive: true
});
}
};
window.onbeforeunload = function () {
timeLog.sendResults();
};
timeLog.init();
The error message is:
Uncaught (in promise) TypeError: NetworkError when attempting to fetch resource.
EDIT:
if the event for onbeforeunload is registred as here:
window.onbeforeunload = async function(event){
event.preventDefault();
timeLog.sendResults();
};
it is working, but I need to confirm that I want to leave the page.
As I found on the internet, the problem arises because firefox uses its own implementation of fetch.
-----------------------SOLUTION [still not working in firefox correctly]-------------------------
window.onbeforeunload = function (event) {
event.preventDefault();
timeLog.sendResults();
delete event['returnValue'];
};
-----------------------SOLUTION-------------------------
I used sendBeacon instead of fetch
so the final code is following:
/* ----REPLACED----
fetch(url, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
keepalive: true
});
*/
navigator.sendBeacon(url);

Let's add a bit more code to see what is going on, allow the fetch to complete then process any error (pause to see it) then proceed to the unload if no error happened - our desired case.
var timeLog = {
start: null,
end: null,
init: function() {
this.start = new Date().getTime();
},
sendResults: function() {
this.end = new Date().getTime();
var url = "tracker";
url += "?" + "start=" + this.start;
url += "&" + "end=" + this.end;
url += "&" + "User-Agent-JS=" + navigator.userAgent;
url += "&" + "url=" + window.location.toString();
return fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
keepalive: true
});
}
};
window.addEventListener('beforeunload', function(e) {
// Cancel the event
// e.preventDefault(); // If you prevent default behavior in Mozilla Firefox prompt will always be shown
// Chrome requires returnValue to be set
// e.returnValue = '';
let myfetch = timeLog.sendResults();
myfetch
// borrowed code https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
.then(response => {
//do something with response
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
throw new TypeError("Oops, we haven't got JSON!");
}
return response.json();
})
.then(data => {
/* process your data further */
})
.catch(error => {
console.error(error);
e.preventDefault(); // pause to see the error in console
});
// the absence of a returnValue property on the event will guarantee the browser unload happens
delete e['returnValue'];
});
timeLog.init();

Related

How do I capture an aborted call or is setting the timeout to 0 correct?

I have a JavaScript client that works in Chrome and Firefox, but fails in IE. Looking at the network trace in the IE debugger it shows that multiple of the AJAX calls have been aborted.
I've been able to get around it by setting the timeout to 0. I'd like to know if this is the correct way to handle my requests being aborted? Basically what could go wrong?
My initial thought was that I should capture and resend on error, and if multiple resubmits do not result in a completed request, finally alert the user. I'd still like to know how to do this even if the setTimeout is the proper way to address my immediate issue.
Also the application will process an excel workbook of addresses, call a web service to add some data to them and then allow the user to download the enhanced file.
This is what I have so far, first in the app.js
var requestWithFeedback = function (args) {
$(".loader").removeClass('hidden');
var oldConfig = args.config || function () { };
args.config = function (xhr) {
xhr.setRequestHeader("Authorization", "Bearer " + localStorage.token);
oldConfig(xhr);
extract: extract;
};
var deferred = m.deferred();
setTimeout(function () { // <== This solved in IE, but is this the way to handle this?
m.request(args).then(deferred.resolve, function(err){
if (err === "Invalid token!"){
m.route('/');
}
})}, 0);
$(".loader").addClass('hidden');
return deferred.promise;
}
From the model.js
app.MarkedAddresses.ProcessAddressBatch = function () {
var requestData = {
Addresses: app.MarkedAddresses.vm.addresses
}
return requestWithFeedback({
method: "POST"
, url: "API/server.ashx"
, data: requestData
, deserialize: function (value) { return value; }
})
.then(function (value) {
var responseJSON = $.parseJSON(value);
$.merge(app.MarkedAddresses.vm.results, responseJSON)
app.MarkedAddresses.vm.currentRecord(app.MarkedAddresses.vm.results.length);
app.MarkedAddresses.vm.progress(Math.max(app.MarkedAddresses.vm.progress(), ~~(app.MarkedAddresses.vm.currentRecord() / app.MarkedAddresses.vm.totalRecords() * 100)));
m.redraw(); //Force redraw for progress bar
return value;
},
function (error) { console.log(error) } // <== I thought error would show up here, but I never hit a breakpoint here.
);
}
Added loops
function process_wb(wb) {
app.MarkedAddresses.vm.results.length = 0;
$('.descending').removeClass("descending");
$('.ascending').removeClass("ascending");
app.MarkedAddresses.vm.progress(.1);
m.redraw();
var header = mapHeader(wb);
var addressJSON = to_json(wb, header);
app.MarkedAddresses.vm.totalRecords(addressJSON.length);
for (var i = 0; (i < addressJSON.length + 1) ; i += 1000) {
app.MarkedAddresses.vm.addresses = addressJSON.slice(i, Math.min(((i) + 1000), addressJSON.length));
app.MarkedAddresses.vm.response(new app.MarkedAddresses.vm.processAddressBatch());
}
}
Why isn't the error triggered in the section of the code?
It seems like I should add a deferred section here, but anything I've tried has been a syntax error.

Angular not working in Chrome

I have an app that works fine in Firefox, IE, Safari and Chrome. But in Chrome it stops working when the contoller below runs. By not working I mean that after $auth.login(user_info) executes the code inside the promise fails. For example if the call returns succesfully the app stops working. Nothing happens when you click on any links. $location.path(lpath) should go to the home page but it doesn't and toastr.success("Login Success"); should display a message but just shows a white popup.
If I run Chrome using Chrome.exe --disable-web-security then everything works. Does anyone have any suggestions?
angular.module("MyApp")
.controller("LoginCtrl", function($rootScope, $scope, $location, $auth, toastr) {
var lpath = "/home";
$scope.login = function() {
var user_info = { email:$scope.user.email, password:hex_sha512($scope.user.password)} ;
$auth.login(user_info)
.then(function(response)
{
if(response.data.rtn == "true")
{
$location.path(lpath);
toastr.success("Login Success");
}
else
{
toastr.error(response.data.msg);
}
})
.catch(function(response)
{
toastr.error("Host Login Error!");
});
};
});
$auth.login = function(user, opts) {
opts = opts || {};
opts.url = config.baseUrl ? utils.joinUrl(config.baseUrl, config.loginUrl) : config.loginUrl;
opts.data = user || opts.data;
opts.method = opts.method || 'POST';
return $http(opts).then(function(response) {
shared.setToken(response);
return response;
});
};
Shared.setToken = function(response) {
var accessToken = response && response.access_token;
var token;
if (accessToken) {
if (angular.isObject(accessToken) && angular.isObject(accessToken.data)) {
response = accessToken;
} else if (angular.isString(accessToken)) {
token = accessToken;
}
}
if (!token && response) {
var tokenRootData = config.tokenRoot && config.tokenRoot.split('.').reduce(function(o, x) { return o[x]; }, response.data);
token = tokenRootData ? tokenRootData[config.tokenName] : response.data[config.tokenName];
}
if (!token) {
var tokenPath = config.tokenRoot ? config.tokenRoot + '.' + config.tokenName : config.tokenName;
throw new Error('Expecting a token named "' + tokenPath + '" but instead got: ' + JSON.stringify(response.data));
}
storage.set(tokenName, token);
storage.set(emailName, response.data.email);
storage.set(permsName, response.data.permissions);
};
Posting as an answer because I don't have the rep.
I would say it is not in this code but in your controller for /home. Did you try removing code until it works?
Are you sure it did pass and tokens are valid? Try clearing the cache CTRL-SHIFT-END. You probably hit some code that wants a valid token and can't continue because you didn't catch failed case.
ANSWER (from Tony): Seems that the periods in the string cause the problem. If I remove the periods it works fine.

Uncaught ReferenceError: error is not defined in Ajax Callback

In my App, I get a list of Events from a Sharepoint Calendar List. That part works perfectly.
However after I get the collection of results, for each item I need to get the Display Form Url, which is another REST Call with the ListItem ID.
However I get the error below, but I still dont know what the problem might be
Uncaught ReferenceError: error is not defined App.js:87(anonymous function) App.js:87$.ajax.error App.js:40c jquery-1.9.1.min.js:22p.fireWith jquery-1.9.1.min.js:22k jquery-1.9.1.min.js:24send.r
I based my code on this answer:
https://sharepoint.stackexchange.com/questions/119236/how-to-get-the-display-form-url-using-rest
My adapted code is like this:
var SPHostUrl;
var SPAppWebUrl;
var ready = false;
// this function is executed when the page has finished loading. It performs two tasks:
// 1. It extracts the parameters from the url
// 2. It loads the request executor script from the host web
$(document).ready(function () {
var params = document.URL.split("?")[1].split("&");
for (var i = 0; i < params.length; i = i + 1) {
var param = params[i].split("=");
switch (param[0]) {
case "SPAppWebUrl":
SPAppWebUrl = decodeURIComponent(param[1]);
break;
case "SPHostUrl":
SPHostUrl = decodeURIComponent(param[1]);
break;
}
}
// load the executor script, once completed set the ready variable to true so that
// we can easily identify if the script has been loaded
$.getScript(SPHostUrl + "/_Layouts/15/SP.RequestExecutor.js", function (data) {
ready = true;
getItems();
});
});
function getListItemFormUrl(webUrl, listName, listItemId, formTypeId, complete, failure) {
$.ajax({
url: webUrl + "/_api/web/lists/GetByTitle('" + listName + "')/Forms?$select=ServerRelativeUrl&$filter=FormType eq " + formTypeId,
method: "GET",
headers: { "Accept": "application/json; odata=verbose" },
success: function (data) {
var url = data.d.results[0].ServerRelativeUrl + '?ID=' + listItemId
complete(url);
},
error: function (data) {
failure(data);
}
});
}
// this function retrieves the items within a list which is contained within the parent web
function getItems() {
// only execute this function if the script has been loaded
if (ready) {
// the name of the list to interact with
var listName = "Events";
// the url to use for the REST call.
var url = SPAppWebUrl + "/_api/SP.AppContextSite(#target)" +
// this is the location of the item in the parent web. This is the line
// you would need to change to add filters, query the site etc
// "/web/lists/getbytitle('" + listName + "')/items?" +
"/web/lists/getbytitle('" + listName + "')/items?$select=Title,Category,EventDate,Description,EncodedAbsUrl,ID" +
"&#target='" + SPHostUrl + "'";
// create new executor passing it the url created previously
var executor = new SP.RequestExecutor(SPAppWebUrl);
// execute the request, this is similar although not the same as a standard AJAX request
executor.executeAsync(
{
url: url,
method: "GET",
headers: { "Accept": "application/json; odata=verbose" },
success: function (data) {
// parse the results into an object that you can use within javascript
var results = JSON.parse(data.body);
var events = [];
$.each(results.d.results, function (i, obj) {
//Usage
getListItemFormUrl(SPAppWebUrl, 'Calendar', obj.ID, 4,
function (url) {
console.log('Display from url for list item: ' + url);
},
function (sender, args) {
console.log(JSON.stringify(error));
})
//use obj.id and obj.name here, for example:
var event = {
date: Date.parse(obj.EventDate).toString(),
type: obj.Category,
title: obj.Title,
description: obj.Description,
url: obj.EncodedAbsUrl + 'DispForm.aspx?ID=' + obj.ID
}
events.push(event);
});
var myJsonString = JSON.stringify(events);
$("#eventCalendarInline").eventCalendar({
jsonData: events,
openEventInNewWindow: true,
showDescription: true,
txt_GoToEventUrl: "Go to event"
});
Communica.Part.init();
},
error: function (data) {
// an error occured, the details can be found in the data object.
alert("Ooops an error occured");
}
});
}
}
Under //Usage:
function (sender, args) {
console.log(JSON.stringify(error));
})
error does not seem to be defined

Delete post with Blogger API

I'm using the Blogger Protocol API and I'm having trouble deleting posts. I'm working on a webOS device and so I can't send DELETE directly; instead I use Google's workaround to use POST:
deletePostList: function(event)
{
var deletePostID = event.item.id.split('.').pop().split('-').pop();
var deleteRequest = new Ajax.Request("http://www.blogger.com/feeds/" + activeBlogID + "/posts/default/" + deletePostID,
{
method: 'post',
requestHeaders:
{
Authorization: 'GoogleLogin auth=' + authCode,
"X-HTTP-Method-Override": "DELETE",
"If-Match": "*"
},
onSuccess: this.deletePostRequestSuccess.bind(this),
onFailure: this.deletePostRequestFailure.bind(this)
});
},
This seems to work, i.e. deletePostRequestSuccess is called after this processes and all the headers and response text look like I think they should when deleting a post, but the reality is that the post remains in the feed. I tried adding the "If-Match" header to make sure it wasn't the GData conditional delete holding me up (even though I haven't changed anything in the post at this time), but that doesn't seem to help.
Any ideas on how to make this work? I'd like to stick with Protocol since it's native on webOS, whereas jQuery, etc. is not.
From what I can tell, your issue with the HTTP methods is not webOS, but in Prototype according to the source.
I would suggest creating a subclass:
<script type="text/javascript">
var MyAjaxRequest = Class.create(Ajax.Request, {
request: function(url) {
this.url = url;
this.method = this.options.method;
var params = Object.isString(this.options.parameters) ?
this.options.parameters :
Object.toQueryString(this.options.parameters);
/* comment out this stuff that prevents you from using the DELETE method
if (!['get', 'post'].include(this.method)) {
// simulate other verbs over post
params += (params ? '&' : '') + "_method=" + this.method;
this.method = 'post';
}
*/
if (params && this.method === 'get') {
// when GET, append parameters to URL
this.url += (this.url.include('?') ? '&' : '?') + params;
}
this.parameters = params.toQueryParams();
try {
var response = new Ajax.Response(this);
if (this.options.onCreate) this.options.onCreate(response);
Ajax.Responders.dispatch('onCreate', this, response);
this.transport.open(this.method.toUpperCase(), this.url,
this.options.asynchronous);
if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
this.transport.onreadystatechange = this.onStateChange.bind(this);
this.setRequestHeaders();
this.body = this.method == 'post' ? (this.options.postBody || params) : null;
this.transport.send(this.body);
/* Force Firefox to handle ready state 4 for synchronous requests */
if (!this.options.asynchronous && this.transport.overrideMimeType)
this.onStateChange();
}
catch (e) {
this.dispatchException(e);
}
});
</script>
That way you can use method: 'DELETE'

I can't figure out why the URL won't change for my GM_xmlhttpRequest in my Greasemonkey script

I'm having a really frustrating problem I hope someone can help me with. Here is a piece of my Greasemonkey script, I can't figure out why the asynchronous requests are always sent to the same URL.
function parse(details) {
var element = $(details);
var coll = element.find("#my valid selector");
$.each(coll, function(index, href) {
SendData(href);
});
}
function SendData(url) {
GM_xmlhttpRequest ({
method: 'GET',
url: url,
headers: {
'User-agent': 'Mozilla/4.0 (compatible) Greasemonkey',
'Accept': 'application/atom+xml,application/xml,text/xml',
},
onload: function(responseDetails) {
doSomething(responseDetails.responseText);
}
});
}
When I fire up Fiddler, I can see that it makes the same request no matter how many items are in my collection. Whatever the first link is, all requests are made to the that link. I have verified that the parse method successfully passes a different link to the SendData function every time, but the requests are always made to the first URL in the collection.
I thought what I had was similar to what I found here, but maybe I'm missing something. Any help would be appreciated.
It seems as though url is not getting captured in a closure, so it's undefined for all but the first GM_xmlhttpRequest run.
Modifying SendData(), like so:
function SendData(url)
{
var moreSubstantial = url + " ";
GM_xmlhttpRequest(
{
method: 'GET',
url: moreSubstantial,
should be enough.
Or, you can get the pages sequentially. Change parse() to something like:
function parse (details)
{
var element = $(details);
var coll = element.find("#my valid selector");
var TargetPages = coll.map (function() {return this.href;} );
(function getNextPage (J)
{
var PageURL = TargetPages[J];
GM_xmlhttpRequest
( {
method: "GET",
url: PageURL,
headers: {
'User-agent': 'Mozilla/4.0 (compatible) Greasemonkey',
'Accept': 'application/atom+xml,application/xml,text/xml',
},
onload: function (responseDetails)
{
doSomething (responseDetails.responseText);
if (--J >= 0)
getNextPage (J);
}
} );
} ) (TargetPages.length - 1);
}

Categories