Automail
Automail copied to clipboard
Session Keepalive?
Since i occasionally use AL in a way that involves a bunch of tabs which don't get attention for some time i regularly run into the wellknown "Session expired, please refresh" issue.
So i've been thinking: Would adding "automatic session token refreshing" be a possible feature for the script?
I haven't been digging too deep into this but i found out it was stored in window.al_token
.
So in theory you could create a hidden iframe, load AL in there and then update the token through window.al_token = iframe.contentWindow.al_token;
.
I haven't tested this though.
Do you figure this workaround would be worth looking into or should we just wait until AL takes care of this issue themselves?
This sounds interesting, as it's an annoying part of the UX.
Definitely worth looking into, but a lot of stuff has to be tested first.. Loading an iframe sounds like it may have excessive performance implications. Perhaps a cheaper way of getting those tokens exists? Maybe sending postmessage messages with updated tokens to old tabs will work?
It's not even certain that just updating the token will fix the issue, as the native javascript may be keeping track of it in some other ways.
Experimental results will be highly appreciated!
Posting a couple of example tokens may have a small probability of making this trivial. They are most likely random, but in the off chance they encode a timestamp or something, the solution will be so much easier.
I don't think there are any security implications, since the auth system is separate.
Ultimately the new token will be present in the result HTML in the form of a <script>window.al_token = "token";</script>
.
So there should be plenty of alternatives to iframes, like making the request to AL through the Fetch API and then extracting the token through string operations, however i don't know what the site will do if you do that without a valid token as it also likes to autorefresh, whatever the trigger for this may be.
Questions that need answers:
- When are new tokens created?
- How long does it take for a token to be expired?
- What happens when one tampers with
window.al_token
? - Where can tokens be harvested from?
Experiments:
a. Setting window.al_token
to a fresh token in an expired tab.
b. Setting window.al_token
to an invalid value.
Ultimately the new token will be present in the result HTML in the form of a
<script>window.al_token = "token";</script>
. So there should be plenty of alternatives to iframes, like making the request to AL through the Fetch API and then extracting the token through string operations, however i don't know what the site will do if you do that without a valid token as it also likes to autorefresh, whatever the trigger for this may be.
This seems possible. Should perform much better than rendering the page and executing all the native javascript.
b) Makes native use of the API fail until something is refreshed again.
But at least the site does not autorefresh instantly when the token is changed to something invalid.
That confirms that anilist is checking window.al_token
after creation though! Very nice.
I imagine two types of script functionality are possible:
-
Broadcast session tokens from newly refreshed tabs to old stale tabs. Will solve some of the issue, but will not prevent the session expiring if you go away from the computer for a while.
-
Regular fetching of new session tokens in the background.
another thing i noticed is that you don't get a new token on every refresh so in theory you should be able to do the same thing in every tab and always get the current, valid token instead of developing any cross-tab-communication shenanigans.
I already have cross-tabl-communication shenanigans :3 The fewer requests sent the better.
But yeah, as it's just requesting a couple of hundred bytes of HTML it should be fine. Currently trying to time how long a token lasts. Do you have any numbers?
As expected, different browsers open have different access tokens. Tokens appear to be random in content. I still have the same token as 40min ago.
And now I got a new one.
Updating a stale tab with a new access token, and then crossing out the "session expired" message appears to work fine.
Do you prefer to be named or not in a shout-out post?
feel free to, i don't mind <3 and thanks for the hard work
If tokens can't be issued until the previous one expires, it's inevitable to have those "session expired" messages from time to time during regular browsing. (Unless one frequently polls, and I prefer to not do that).
But the old stale tabs problem, or leaving the computer for a while issue could be solved this way.
can you maybe listen for the event that triggers the display of the message? and uh... just hide it with CSS? :P
That may actually work very well. Detect it, hide it, and resolve the problem silently.
Now I just have to wait until another token expires, see where the message is occurring, and then make a mutationObserver for that.
Then do some testing until I'm confident it works, and then make a css rule to just hide it :)
Experimental implementation: https://github.com/hohMiyazawa/Automail/commit/015d9534b33a2d177937c64f7f229f7ca6fe2620
Issue: Anilist seems really keen on autorefreshing. That has to be stopped, somehow.
To get back on this... the autorefresh get's triggered by the 403.
In the main.js you can find the following (formatted through chrome dev tools)
This may look confusing at first sight but it's essentially an
if
that checks wether the last time the refresh of the al_token
happened and if it's been over a minute ago, the page refreshes.
Which means... we'd need to localStorage.setItem("session-reload", Date.now());
before the 403 happens...uh...
Okay this is silly but i guess we have no other choice than doing the "You have unsaved changes" route by doing something like
addEventListener('beforeunload', function(e){
e.preventDefault();
// Get the new al_token
// Revert the damage caused by the 403
// Return something because browser demand it despite no longer showing the string.
return e.returnValue = "we don't do that here";
});
What i mean by "damage" is things like reverting eternal loading animations back into "Load More" buttons in case of infinity scrolls. Not sure if any other things on the page need to get fixed in case of a 403.
If you want to analyze that you can just invalidate the al_token
and localStorage.setItem("session-reload", aTimestampWayIntotheFuture);
, then cause the 403.
In what other cases would a page reload be triggered? Do we want to intercept all those?
Do you think something along these lines is appropriate?
// Revert the damage caused by the 403
new MutationObserver(function(){
let messages = Array.from(document.querySelectorAll(".el-message--error.is-closable"));
if(messages.some(message => message.textContent === "Session expired, please refresh")){
message.querySelector(".el-message__closeBtn").click()
}
}).observe(
document.body,
{attributes: false, childList: true, subtree: false}
)
addEventListener("beforeunload", function(e){
e.preventDefault();
let oldSessionReload = localStorage.getItem("session-reload");
// set timestamp immediately, so Anilist doesn't reload the page
localStorage.setItem("session-reload", Date.now());
// Get the new al_token
fetch("index.html").then(function(response){
return response.text()
}).then(function(html){
let token = html.match(/window\.al_token\ =\ "([a-zA-Z0-9]+)";/);
console.log("token",token);
if(!token){
//idk, stuff changed, better clean up after the failed attempt
localStorage.setItem("session-reload", oldSessionReload);
return
}
window.al_token = token;
//alert the other tabs so they don't have to do the same
aniCast.postMessage({type:"sessionToken",value:token});
}).catch(function(){
//fail silently, but clean up, trust Anilist to do the right thing by default
localStorage.setItem("session-reload", oldSessionReload)
})
// Return something because browser demand it despite no longer showing the string.
return e.returnValue = "we don't do that here";
});
Reload interception issues:
https://github.com/hohMiyazawa/Automail/blob/master/src/modules/settingsPage.js#L583
https://github.com/hohMiyazawa/Automail/blob/master/src/modules/ALbuttonReload.js#L6
Or maybe:
addEventListener("beforeunload", function(e){
let messages = Array.from(document.querySelectorAll(".el-message--error.is-closable"));
if(messages.some(message => message.textContent === "Session expired, please refresh")){
// Revert the damage caused by the 403
message.querySelector(".el-message__closeBtn").click()
}
else{
// not the reload we are looking for
return
}
e.preventDefault();
let oldSessionReload = localStorage.getItem("session-reload");
// set timestamp immediately, so Anilist doesn't reload the page
localStorage.setItem("session-reload", Date.now());
// Get the new al_token
fetch("index.html").then(function(response){
return response.text()
}).then(function(html){
let token = html.match(/window\.al_token\ =\ "([a-zA-Z0-9]+)";/);
console.log("token",token);
if(!token){
//idk, stuff changed, better clean up after the failed attempt
localStorage.setItem("session-reload", oldSessionReload);
return
}
window.al_token = token;
//alert the other tabs so they don't have to do the same
aniCast.postMessage({type:"sessionToken",value:token});
}).catch(function(){
//fail silently, but clean up, trust Anilist to do the right thing by default
localStorage.setItem("session-reload", oldSessionReload)
})
// Return something because browser demand it despite no longer showing the string.
return e.returnValue = "we don't do that here";
});
Hmm... i'm not sure if we can actually do much in the beforeunload
listener, especially the asynchronous nature of the fetch will cause issues.
I think a better approach would be introducing a global variable with which we can either deny or allow the refresh.
new MutationObserver(function(){
let messages = Array.from(document.querySelectorAll(".el-message--error.is-closable"));
if(messages.some(message => message.textContent === "Session expired, please refresh")){
message.querySelector(".el-message__closeBtn").click()
// update timestamp and al_token
// Revert the damage caused by the 403
}
}).observe(
document.body,
{attributes: false, childList: true, subtree: false}
)
let allowRefresh = false;
addEventListener("beforeunload", function(e){
e.preventDefault();
if (allowRefresh) {
return;
}
// Return something because browser demand it despite no longer showing the string.
return e.returnValue = "we don't do that here";
});
// Add eventlistener for Keypresses of F5 or CTRL + R which set allowRefresh to true
// Set allowRefresh to true in other parts of Automail that do window.location.reload()
I was counting on using the "session expired" message to detect if this is a refresh to be blocked.
From your disassembly, it looks like the message is displayed before the refresh is initiated.
For the asynchronous complication, doesn't it make sense to just keep blocking the refresh until the fetch resolves?