SyncXHR in Page Dismissal Alternative - xmlhttprequest

Since google has declared to disallow sync XHR in page dismissal, i havent found the decent replacement to this feature. I've tried sendBeacon, but the 64KB payload limit makes it useless for my use case. At this point, i found the workaround by configuring the chromium flag directly (#allow-sync-xhr-in-page-dismissal). But this is clearly not the final solution. It's not user friendly to force your user to tweak their own browser in order to use our app.
Is there any syncXHR in page dismissal alternative?

var xhr;
function saveChanges(){
xhr = new XMLHttpRequest();
xhr.open('POST',url,false)
xhr.send(post)
}
window.addEventListener('beforeunload', (event) =>{
saveChanges();
if(xhr.readyState == 4) return;
event.preventDefault();
event.returnValue = '';
})
Credit to : https://groups.google.com/a/chromium.org/g/blink-dev/c/LnqwTCiT9Gs/m/wM0yAjcfAAAJ

Related

script tag XHR event listener for monitoring cart activities does not work anymore

I have an app which will add this script tag into the store.
In the past I use script tag with this script to monitor customer's cart activities.
When the script tag detect a XHR, it will fire some data to my backend.
var oldXHR = window.XMLHttpRequest;
function newXHR() {
console.log('XHR detected!')
var realXHR = new oldXHR();
realXHR.addEventListener(
"load",
function () {
if (realXHR.readyState == 4 && realXHR.status == 200) {
if (realXHR._url === "/cart.js" || realXHR._url === "/cart/change.js") {
// do something....
}
}
},
false
);
return realXHR;
}
window.XMLHttpRequest = newXHR;
But today I don't know why the action of changing the cart and adding item into cart cannot trigger the XHR listener anymore.
However, this script tag is still working in my old store. But if I install it to a new store, it does not trigger anything. I check the script tag is normally running in that new store, but the problem is the XHR listener did not trigger.
Anyone have some ideas?
I configured a new Dev store on Shopify and tried your code as well as similar code from my other answer that listens to XHR calls. But it did not work. On debugging a bit, I found that it listens to calls made using jQuery or XHR but not for Shopify Cart. This led me to find out that cart updates were done using Fetch API. So, we also need to listen to all fetch calls. It can be done so using
(function(ns, fetch) {
if (typeof fetch !== 'function') return;
ns.fetch = function() {
const response = fetch.apply(this, arguments);
response.then(res => {
if ([
`${window.location.origin}/cart/add.js`,
`${window.location.origin}/cart/update.js`,
`${window.location.origin}/cart/change.js`,
`${window.location.origin}/cart/clear.js`,
].includes(res.url)) {
res.clone().json().then(data => console.log(data));
}
});
return response;
}
}(window, window.fetch))
If it matches the URL, you can call your function with custom data.
The URL logic matching is not tested thoroughly.
The above code is working for me on latest Shopify Debut theme.
Fetch override code from Yury Tarabanko

How to open popup with dropbox-api dropbox-js V2

I'm trying to migrate to dropbox-api v2 in my web application.
Currently I have implementation of opening popup window where user connects to his/her dropbox and I get token. Which I use to get access to files user selected in Dropbox.chooser in later steps.
But I'm having hard time to find the solution for this. I have link to all migration documents dropbox has, but there is not any word about what is the equivalent of client.authenticate() and Dropbox.AuthDriver.Popup() ?
Common Dropbox!!! I just found this issue posted in GitHub for dropbox-sdk-js, and the answer that they don't have this functionality in V2 :( it is really disappointing, i need to implement all staff myself:
https://github.com/dropbox/dropbox-sdk-js/issues/73#issuecomment-247382634
Updated
I have implemented my solution and would like to share if someone will need.
To open a popup window I use following code:
window.open(dropbox.getAuthenticationUrl("MY REDIRECT URL"), 'DropboxAuthPopup', 'dialog=yes,dependent=yes,scrollbars=yes,location=yes')
window.addEventListener('message',function(e) {
if (window.location.origin !== e.origin) {
// Throw error
} else {
// e.data Is what was sent from redirectUrl
// e.data.access_token is the token I needed from dropbox
}
},false);
Then on my page, which I specify dropbox to redirect, i put:
window.addEventListener('load', function() {
var message = parseQueryString(window.location.hash)
window.location.hash = '';
opener = window.opener
if (window.parent != window.top) {
opener = opener || window.parent
}
opener.postMessage(message, window.location.origin);
window.close();
})
Example of parseQueryString can be found from dropbox-sdk-js examples

Phantomjs dump dom object

I'm new to Phantomjs. For debugging on a remote server, I often want to dump a DOM object to look at the structure (similar to Data::Dumper in Perl). This currently is for scraping a couple of sites.
I've thought JSON.stringify may help with this, but it still displays an object name like "[object HTMLDocument]"
Edit: I have also looked at JavaScript: how to serialize a DOM element as a string to be used later? , but I can't seem to inject jquery in phantomjs (still looking for a solution to that, and would prefer no depencencies), and the other answer doesn't seem to work. As I assume it would be a common case for Phantom to analyse the DOM, I thought it would be common for phantom users to have a solution to this.
var page = require('webpage').create();
var system = require('system');
page.onConsoleMessage = function(msg) {
console.log( msg );
}
page.open('http://www.test.com', function(status) {
if(status !== "success") {
console.log( status );
} else {
page.evaluate(function() {
var headline = document.querySelectorAll('div');
console.log( JSON.stringify( headline ) ); // HERE???
});
}
phantom.exit();
});
Is there any way to do this, or am I approaching this wrong ?
in page.evaluate(), you can use XMLSerializer.serializeToString() to convert whatever DOM node you want to string.
page.evaluate(function() {
var s = new XMLSerializer();
return s.serializeToString(document.getElementById('div'));
});
I haven't tried it with "querySelectorAll", since it may return array instead of standalone DOM node, but it definitely works for DOM nodes.
MDN Link

How can I control PhantomJS to skip download some kind of resource?

phantomjs has config loadImage,
but I want more,
how can I control phantomjs to skip download some kind of resource,
such as css etc...
=====
good news:
this feature is added.
https://code.google.com/p/phantomjs/issues/detail?id=230
The gist:
page.onResourceRequested = function(requestData, request) {
if ((/http:\/\/.+?\.css/gi).test(requestData['url']) || requestData['Content-Type'] == 'text/css') {
console.log('The url of the request is matching. Aborting: ' + requestData['url']);
request.abort();
}
};
UPDATED, Working!
Since PhantomJS 1.9, the existing answer didn't work. You must use this code:
var webPage = require('webpage');
var page = webPage.create();
page.onResourceRequested = function(requestData, networkRequest) {
var match = requestData.url.match(/wordfamily.js/g);
if (match != null) {
console.log('Request (#' + requestData.id + '): ' + JSON.stringify(requestData));
networkRequest.cancel(); // or .abort()
}
};
If you use abort() instead of cancel(), it will trigger onResourceError.
You can look at the PhantomJS docs
So finally you can try this http://github.com/eugenehp/node-crawler
otherwise you can still try the below approach with PhantomJS
The easy way, is to load page -> parse page -> exclude unwanted resource -> load it into PhatomJS.
Another way is just simply block the hosts in the firewall.
Optionally you can use a proxy to block certain URL addresses and queries to them.
And additional one, load the page, and then remove the unwanted resources, but I think its not the right approach here.
Use page.onResourceRequested, as in example loadurlwithoutcss.js:
page.onResourceRequested = function(requestData, request) {
if ((/http:\/\/.+?\.css/gi).test(requestData['url']) ||
requestData.headers['Content-Type'] == 'text/css') {
console.log('The url of the request is matching. Aborting: ' + requestData['url']);
request.abort();
}
};
No way for now (phantomjs 1.7), it does NOT support that.
But a nasty solution is using a http proxy, so you can screen out some request that you don't need

Detecting browser print event

Is it possible to detect when a user is printing something from their browser?
To complicate matters, if we are presenting a user with a PDF document in a new window is it possible to detect the printing of that document ( assuming the user prints it from the browser window)?
The closest I've been able to find is if we implement custom print functionality (something like this) and track when that is invoked
I'm primarily interested in a solution that works for internet explorer (6 or later)
You can now detect a print request in IE 5+, Firefox 6+, Chrome 9+, and Safari 5+ using the following technique:
(function() {
var beforePrint = function() {
console.log('Functionality to run before printing.');
};
var afterPrint = function() {
console.log('Functionality to run after printing');
};
if (window.matchMedia) {
var mediaQueryList = window.matchMedia('print');
mediaQueryList.addListener(function(mql) {
if (mql.matches) {
beforePrint();
} else {
afterPrint();
}
});
}
window.onbeforeprint = beforePrint;
window.onafterprint = afterPrint;
}());
I go into more detail into what this is doing and what it can be used for at http://tjvantoll.com/2012/06/15/detecting-print-requests-with-javascript/.
For Internet Exploder, there are the events window.onbeforeprint and window.onafterprint but they don't work with any other browser and as a result they are usually useless.
They seem to work exactly the same for some reason, both executing their event handlers before the printing window opens.
But in case you want it anyway despite these caveats, here's an example:
window.onbeforeprint = function() {
alert("Printing shall commence!");
}
For anyone reading this on 2020.
The addListener function is mostly deprecated in favor of addEventListener except for Safari:
if (window.matchMedia) {
const media = window.matchMedia("print");
const myFunc = mediaQueryList => {
if (mediaQueryList.matches) {
doStuff();
}
};
try {
media.addEventListener("change", myFunc);
} catch (error) {
try {
media.addListener(myFunc);
} catch (error) {
console.debug('Error', error)
}
}
}
Reference: This other S.O question
If it's only for tracking purposes, perhaps you could set a background url in CSS print media to a server page (.aspx, .php, etc) and then do something on the server?
This guy claims it works.
This is not as versitile as TJ's solution, but it may be less buggy (see TJs blog post for issues he found) when only tracking is needed.