I have a userscript that refreshes the page every second, but sometimes the website it's trying to refresh runs into the status 503 error, and that stops the script from running anymore. That means that script will no longer try refresh the page every second. How do I keep the script running after the page runs into the status 503 error? The error looks like this in the console:
Failed to load resource: the server responded with a status of 503 (Service Unavailable)
// ==UserScript==
// #name script
// #namespace name
// #description example
// #match *^https://example.com/$*
// #version 1
// #require https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js
// #grant GM_xmlhttpRequest
// #run-at document-end
// ==/UserScript==
//*****************************************START OF SET_TIMEOUT
var timeOne = 1000;
var theTime = timeOne;
var timeout = setTimeout("location.reload(true);", theTime);
function resetTimeout() {
clearTimeout(timeout);
timeout = setTimeout("location.reload(true);", theTime);
} //end of function resetTimeout()
//*****************************************END OF SET_TIMEOUT
The user-script run at page load, they won't run if page does not load at all for any status code other than 200. You can use <iframe> as #Hemanth suggested but you have to break the infinite loop as the <iframe> will also load the user-script and so on. To break it just check if the user-script is loaded on the top window.
if (window == window.top) {
// Remove everything from the page
// Add an iframe with current page URL as it's source
// Add an event to reload the iframe few seconds after it is loaded
}
Complete Code:
(function ($) {
'use strict';
var interval = 5000;
if (window == window.top) {
var body = $('body').empty();
var myframe = $('<iframe>')
.attr({ src: location.href })
.css({ height: '95vh', width: '100%' })
.appendTo(body)
.on('load', function () {
setTimeout(function () {
myframe.attr({ src: location.href });
}, interval);
});
}
})(jQuery);
Simple solution I could think of is to move the actual webpage that need to be refreshed every second to another frame or some HTML container and using your user script you could refresh or reload that container with the HTML page you want to refresh.
Unless you are making a http request to load webpage from your script it may not be possible to handle the http errors (I might be wrong!). Your user script should be at a superset level where it will have control or scope even after any 503 or any similar HTTP errors.
You could use Page state change events to ensure or to check to refresh the page in the frame/container has loaded or not to avoid reloading the webpage before it loads. Unless if you want to reload irrespective of webpage successfully loaded or not.
Hope this helps..
The problem is that location.reload attempts to completely replace the page with a reloaded version, but if the browser fails to connect to the server, the page will not be loaded, and any userscripts for that page will fail to run, because there's no page for them to run on.
A workaround would be to use a Javascript network request to fetch the text of the new page, and then replace the existing content with the new content with Javascript - if the response is a 503 error (or some other error), you can simply ignore it and try again. This ensures that you'll always stay on the same (loaded) page, so the userscript will continue running indefinitely, even if the responses sometimes fail.
Here's an example of a userscript that updates StackOverflow's homepage every 10 seconds with new HTML. You can use DOMParser to transform response text into a document that can be navigated with querySelector, among other things:
// ==UserScript==
// #name Update SO
// #namespace CertainPerformance
// #version 1
// #match https://stackoverflow.com/
// #grant none
// ==/UserScript==
const container = document.querySelector('.container');
function getNewData() {
console.log('getting new data');
fetch('https://stackoverflow.com',
{
// The following is needed to include your existing cookies with the request, which may be needed:
credentials: "same-origin"
})
.then((resp) => {
// If there are errors, such as a 503 error, proceed directly to the `finally` to try again:
if (!resp.ok) return;
return resp.text();
})
.then((text) => {
const doc = new DOMParser().parseFromString(text, 'text/html');
container.innerHTML = doc.querySelector('.container').innerHTML;
})
.finally(() => {
// Whether there was an error or not, try to refresh again in a few seconds:
setTimeout(getNewData, 10000);
});
}
setTimeout(getNewData, 10000);
Related
I am using below code to open a link on button click. The link is pointing to a Controller method responsible for downloading some Excel file.
// Button to download table data
$("#btnDownloadCIRResults").click(function (e) {
var All_Recs = $("#cbShowAllRecords").prop("checked") ? "YES" : "NO";
DisplayStatusMessageWind("Downloading report, please wait...", MessageType.Info, true, false);
// DownloadCIRemediationTable(string AllRecords)
window.location = '/AskNow/DownloadCIRemediationTable?AllRecords=' + All_Recs;
DisplayStatusMessageWind("Report downloaded successfully.", MessageType.Success, false, true, 1000);
e.preventDefault();
});
The Controller method queries a DB table, converts it to an Excel workbook and returns a file as download result. All is working fine and as expected, except, since this is a time consuming process, I just want to improve on user experience and update this code to show some wait message while the file is being downloaded.
The DisplayStatusMessageWind() method shows a wait message. However, it doesn't know or care about the load complete event of the window.location = '/AskNow/DownloadCIRemediationTable?AllRecords=' + All_Recs; code.
How can I make the completion message appear only after the file download is completed:
DisplayStatusMessageWind("Report downloaded successfully.", MessageType.Success, false, true, 1000);
By assigning a new location with window.location = "<NEWURL>"; you're requesting asynchronously to replace the current page. What will happen, is that the next line is immediately executed (DisplayStatusMessage()). When all events are handled, the page will finally be replaced. The new page (URL) will load and you'll have no control whatsoever about how or what will happen next.
What you should do is use window.open("<NEWURL>", '_blank') MOZ and then on the new page send a signal via localStorage, which can be read and written by all pages of the same domain. These are some hints, to write the actual code is your job.
On this page, in on("click") event:
// local scope
var ukey;
// polling function
function waitOtherIsReady()
{
if (localStorage.getItem(ukey) === true)
{
// other page experienced ready event
localStorage.removeItem(ukey); // clean-up
// TODO: do your stuff
} else {
setTimeout(waitOtherIsReady, 500);
}
}
// create unique key and deposit it in localStorage
ukey = "report_" + Math.random().toString(16);
localStorage.setItem(ukey, false);
// pass key to other page
window.open("URL?ukey=" + ukey, "_blank");
// start polling until flag is flipped to true
setTimeout(waitOtherIsReady, 500);
On the other page:
$(() => {
// get ukey from URL
var ukey = new URL(window.location.href).searchParams.get("ukey");
// page is now ready, flip flag to signal ready event
localStorage.setItem(ukey, true);
});
I'm writing a Tampermonkey script that I want to use to redirect from youtube.com/* to a YouTube channel address.
window.addEventListener ("load", LocalMain, false);
function LocalMain () {
location.replace("https://www.youtube.com/channel/*");
}
When the script is running it redirects to the channel URL but then keeps running and continuously redirects.
The standard, much more efficient, and much faster performing way to do this kind of redirect is to tune the script's metadata to not even run on the redirected-to page.
Also, use // #run-at document-start for even better response.
So your script would become something like:
// ==UserScript==
// #name YouTube, Redirect to my channel
// #match https://www.youtube.com/*
// #exclude https://www.youtube.com/channel/*
// #run-at document-start
// ==/UserScript==
location.replace("https://www.youtube.com/channel/UC8VkNBOwvsTlFjoSnNSMmxw");
Also, for such "broad spectrum" redirect scripts, consider using location.assign() so that you or your user can recover the original URL in the history in case of an overzealous redirect.
You could check to see if the location contains 'channel', then return before you change the location.
window.addEventListener ("load", LocalMain, false);
function LocalMain () {
if(location.href.indexOf('channel') === -1) return;
location.replace("https://www.youtube.com/channel/*");
}
You need to check if the current pathname includes channel before reassigning a new href
function LocalMain () {
if(!location.pathname.includes('/channel/')) {
location.replace("https://www.youtube.com/channel/*");
}
}
Also note you don't need to window load event to complete to do this
Is tampermonkey configured to run the script only when not on your channel URL?
If not... then your script might ironically be doing exactly what you want: running once each page load. And the script loads a new page.
You can fix this within the script, like this:
window.addEventListener ('load', localMain, false);
function localMain () {
if (location.href.startsWith('https://www.youtube.com/channel/')) {
return; // exit
}
location.replace('https://www.youtube.com/channel/*');
}
I have two scripts. Each runs on a different subdomain of our company "Example.com".
Script #1 -- house.example.com
Script #2 -- bob.fred.example.com
Same domain, different subdomains.
When a particular element appears on house.example.com, I need to send a message over to the script running on bob.fred.example.com
Since Google extensions can exchange messages between extensions, there must be a way with TamperMonkey to exchange messages within the same extension, between scripts -- especially if they run on the same second-level domain.
Can anyone point me in the right direction? An example or two would be worth their weight in gold.
Update: Although Gothdo referenced Javascript communication between browser tabs/windows as containing an answer to this question, he failed to take into consideration the cross-origin policies involved. None of the answers in that referenced question provide a clear answer for cross-origin browser tab communications, which was the main point of this question. I have now researched and solved this problem, getting ideas from a number of SO and non-SO sources. If this question is re-opened, I will post my solution.
You can use GM_getValue, GM_setValue & GM_addValueChangeListener to achieve cross-tab user script communication.
Add the following lines in your user script header.
// #grant GM_setValue
// #grant GM_getValue
// #grant GM_addValueChangeListener
The following lines of rough code will simplify the cross-tab user script communication.
function GM_onMessage(label, callback) {
GM_addValueChangeListener(label, function() {
callback.apply(undefined, arguments[2]);
});
}
function GM_sendMessage(label) {
GM_setValue(label, Array.from(arguments).slice(1));
}
So all you'll need to do is the following to send and receive messages.
GM_onMessage('_.unique.name.greetings', function(src, message) {
console.log('[onMessage]', src, '=>', message);
});
GM_sendMessage('_.unique.name.greetings', 'hello', window.location.href);
NOTE Sending messages may not trigger your callback if the message sent is the same as before. This is due to GM_addValueChangeListener not firing because the value has not changed, i.e. same value as before even though GM_setValue is called.
Using #grant enables the sandbox, which can sometimes result in difficulties when trying to interact with complicated page objects on Greasemonkey.
If you do not want to enable the sandbox with #grant, another option is to have the userscript create an iframe to the other domain, and then post a message to it. On the other domain, in the iframe, listen for messages. When a message is received, use BroadcastChannel to send the message to every other tab on that other domain, and your other tabs with the userscript running can have the same BroadcastChannel open and listen for messages.
For example, to create a userscript on stackoverflow.com that can send a message to a userscript running in a different tab on example.com:
// ==UserScript==
// #name 0 Cross-tab example
// #include /^https://example\.com\/$/
// #include /^https://stackoverflow\.com\/$/
// #grant none
// ==/UserScript==
if (window.location.href === 'https://example.com/') {
const broadcastChannel = new BroadcastChannel('exampleUserscript');
if (window.top !== window) {
// We're in the iframe:
window.addEventListener('message', (e) => {
if (e.origin === 'https://stackoverflow.com') {
broadcastChannel.postMessage(e.data);
}
});
} else {
// We're on a top-level tab:
broadcastChannel.addEventListener('message', (e) => {
console.log('Got message', e.data);
});
}
} else {
// We're on Stack Overflow:
const iframe = document.body.appendChild(document.createElement('iframe'));
iframe.style.display = 'none';
iframe.src = 'https://example.com';
setTimeout(() => {
iframe.contentWindow.postMessage('Sending message from Stack Overflow', '*');
}, 2000);
}
This results in:
If you want two-way communication, not just one-way communication, have both parent pages create a child iframe to a single target domain (say, to example.com). To communicate to other tabs, post a message to the child iframe. Have the child iframe listen for messages, and when seen, post a BroadcastChannel message to communicate with all other iframes. When an iframe receives a BroadcastChannel message, relay it to the parent window with postMessage.
// ==UserScript==
// #name 0 Cross-tab example
// #include /^https://example\.com\/$/
// #include /^https://(?:stackoverflow|stackexchange)\.com\/$/
// #grant none
// ==/UserScript==
if (window.location.href === 'https://example.com/') {
const broadcastChannel = new BroadcastChannel('exampleUserscript');
if (window.top !== window) {
// We're in an iframe:
window.addEventListener('message', (e) => {
console.log('iframe received message from top window');
if (e.origin === 'https://stackoverflow.com' || e.origin === 'https://stackexchange.com') {
broadcastChannel.postMessage(e.data);
}
});
broadcastChannel.addEventListener('message', (e) => {
console.log('iframe received message from BroadcastChannel');
window.top.postMessage(e.data, '*');
});
}
} else {
// We're on Stack Overflow or Stack Exchange
const iframe = document.body.appendChild(document.createElement('iframe'));
iframe.style.display = 'none';
iframe.src = 'https://example.com';
window.addEventListener('message', (e) => {
if (e.origin === 'https://example.com') {
console.log(`Top window ${window.origin} received message from iframe:`, e.data);
}
});
if (window.location.href === 'https://stackoverflow.com/') {
setTimeout(() => {
console.log('stackoverflow posting message to iframe');
iframe.contentWindow.postMessage('Message from stackoverflow', '*');
}, 2000);
}
}
In the above code, a tab on Stack Overflow sends a message to a tab on Stack Exchange. Result screenshot:
The method I ended up using for tab-to-tab communication between subdomains on the same domain was to pass information via javascript cookies. (I also tried using localStorage, but that didn't work between subdomains.)
Scenario: Tab A on SubDomain A will send messages to Tab B on SubDomain B:
Code looked like this:
function getCookie(cooVal) {
var cname = cooVal+ '=';
var ca = document.cookie.split(';');
for (var i=0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0)==' ') c = c.substring(1,c.length);
if (c.indexOf(cname) === 0) {
return c.substring(cname.length, c.length);
}
}
return null;
} //END getcookie()
Tab B on subDomainB would RECEIVE messages from TabA on subDomain A:
function checkIncomingCIQ(){
var acciq = getCookie('acciq');
var jdlwc = getCookie('jdlwc');
}
TabA would SEND messages to Tab B like this:
document.cookie="acciq=5; domain=.example.com; path=/";
document.cookie="jdlwc=fubar; domain=.example.com; path=/";
For anyone wondering, yes, the subdomains can send messages to one another - it is not only a one-way communication. Just duplicate the same scenario in the other direction as well.
Of course, on both tabs the messaging system would be inside a javascript loop, like this:
(function foreverloop(i) {
//Do all my stuff - send/receive the cookies, do stuff with the values, etc
setTimeout(function() {
foreverloop(++i);
},2000);
}(0)); //END foreverloop
The TM headers on both tabs look like this:
// ==UserScript==
// #namespace abcd.tops.example.com
// #match *://abcd.tops.example.*/*
// #grant none
// #require http://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js
// ==/UserScript==
and
// ==UserScript==
// #namespace http://mysubdomain.example.com/callcenter/
// #match *://*.example.com/callcenter/
// #grant none
// #require http://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js
// ==/UserScript==
Apologies to all for the delay posting this solution. It took so long for the question to be re-opened after it was wrongly marked as duplicate that life went on . . .
I have an angularjs application running on tomcat, and behind a loadbalancer
If the app is requested via the loadbalancer with https, the balancer still requests the application internally via http, of course.
Problem: I'd like to hide one tab that shows mixed content in this case (because I have to embed external pdf links which do not support https, thus I'd like to hide them).
I cannot use $location.protocol() because the app is behind the loadbalancer and always only gets http.
Question: is there a chance I could detect if the browser is actually showing mixed content?
You can't detect it in the simple way. You can try to listen load event on iframe and set timeout, and when timeout triggered, block iframe because iframe didn't loaded like this (jsfiddle example):
checkMixedContent(urlToCheck, function(urlToCheck) {
// For example, change location
alert('ok');
// load iframe
}, function() {
alert('Error: resource timed out');
// hide iframe / show message
}, checkDelay);
function checkMixedContent(urlToCheck, successCallback, errorCallback, checkDelay, dontCheckOnError) {
checkDelay = checkDelay || 10000;
// 1. Create invisible iframe and append it to body
var iframeHelper = document.createElement("iframe");
iframeHelper.src = urlToCheck;
iframeHelper.height = 0;
iframeHelper.width = 0;
iframeHelper.style.visibility = 'hidden';
document.body.appendChild(iframeHelper);
// 2. Set time out and while content on iframeHelper.src should be definitely loaded
var checkTimeout = window.setTimeout(function() {
errorCallback(urlToCheck);
}, checkDelay);
var onLoad = function() {
window.clearTimeout(checkTimeout); // if OK - not show error => clearTimeout
iframeHelper.removeEventListener('load', onLoad);
iframeHelper.removeEventListener('error', onError);
document.body.removeChild(iframeHelper);
successCallback(urlToCheck);
};
var onError = function() {
window.clearTimeout(checkTimeout); // if OK - not show error => clearTimeout
iframeHelper.removeEventListener('load', onLoad);
iframeHelper.removeEventListener('error', onError);
document.body.removeChild(iframeHelper);
errorCallback(urlToCheck);
};
// 3. If everything is fine - "load" should be triggered
iframeHelper.addEventListener('load', onLoad);
// Turn "true" in case of "X-Frame-Options: SAMEORIGIN"
if (!dontCheckOnError) {
iframeHelper.addEventListener('error', onError);
}
}
For some reason my ISP is blocking the following URL:
http://assets.tumblr.com/javascript/prototype_and_effects.js
Chrome console states:
Failed to load resource: the server responded with a status of 403 (URLBlocked)
The result is I can't use Tumblr properly because many functions depend on this script. I have already contacted my ISP and asked them to stop blocking this URL, but meanwhile I would like to do something about it.
How can I load this resource externally? It could be a bookmarklet solution, userscript/Greasemonkey or any other thing you can think of.
A 403 status means that the server (assets.tumblr.com) blocked the request, not your ISP. The most common reasons a server does this are (a) because you've not logged in with sufficient access, and/or (b) The server didn't receive the referrer header and/or cookies it wanted, and/or (c) the request came from an IP address that the server has blacklisted. Using a proxy server can trigger any or all of these for some sites.
This means that if you, or your proxy server, are being blocked from that file, then the standard methods of injecting remote javascript, that a userscript would use, will also be blocked.
To get around this Greasemonkey can backfill the javascript file from a local copy. To do this:
Create the file, Insure Tumbler has prototype.user.js, as shown below. Place it in a directory that is not in a temp folder on your machine.
Download the prototype_and_effects.js file you referenced and place it in the same folder. You may have to use a different (or no proxy), or a different browser profile, or whatever. (It downloads just fine, for me, just by right-clicking the immediately-preceding link.)
Install the script with Greasemonkey. (Firefox: File -> Open (CtrlO) will work.)
The script tests for the Prototype and Effect libraries and loads them locally if one is missing. There may be more required to get Tumblr working again but, if so, that is beyond the scope of this question.
Works in Firefox+Greasemonkey. Should work in Chrome+Tampermonkey (not tested), Will not work where #resource is not supported, such as straight Chrome.
// ==UserScript==
// #name _Backfill Prototype and Effect libraries on Tumblr pages
// #match http://tumblr.com/*
// #match http://www.tumblr.com/*
// #match https://tumblr.com/*
// #match https://www.tumblr.com/*
// #resource PandE_src prototype_and_effects.js
// #grant GM_getResourceText
// ==/UserScript==
//-- Does this page load prototype_and_effects.js?
var protoScriptNode = document.querySelector ("script[src*='prototype_and_effects']");
if (protoScriptNode) {
//console.log ("Page uses prototype_and_effects.js.");
//-- Are Prototype and Effects loaded?
var P = unsafeWindow.Prototype;
var E = unsafeWindow.Effect;
if (P && P.Version && E && E.BlindDown) {
//console.log ("Everything's loaded, no action needed.");
}
else {
//console.log ("Loading prototype_and_effects.js");
var PandE_src = GM_getResourceText ("PandE_src");
var scriptNode = document.createElement ('script');
scriptNode.type = "text/javascript";
scriptNode.textContent = PandE_src;
var targ = document.getElementsByTagName ('head')[0];
targ.appendChild (scriptNode);
}
}
else {
//-- No action needed
//console.log ("Page doesn't use prototype_and_effects.js.");
}