Detecting Unique Browser Tabs - javascript

On every page of my sites, I am using AJAX to poll the server and retrieve a list of messages. The server maintains a list of messages and the SessionId (I'm in an ASP.NET environment, but I feel like this question is applicable to any server side technology) that the message is intended for. If a message is found for the particular SessionId, it is returned to the client side script. I use a JavaScript library to create a notification (using noty, a Jquery Notification Plugin). Once it returns a particular message, the server discards that message.
This works well if the user only has a single tab/window open for a particular site. However, let's say they have two open and they do something that causes a warning message to be generated. I have no control over which tab the notification goes to, so the user may not end up seeing the warning message.
Is there a way of uniquely identifying a browser tab? Then I could pass this as one of the parameters in my AJAX call.

Firstly, polling doesn't seem good mechanism. It might hit your server down when you have large number of active users. Ideally you should return a message in the response to the request that was result of invalid action.
Still below solution might work for you. It is inspired by the reply of #SergioGarcia.
Keep a hidden input just before the end of your form tag, which stores a unique ID for identifying a tab uniquely. You will store the messages on server session against unique tabID,
<input type="hidden" id="hiddenInputTabId" value="<%=getValue()%>" />
and then define getValue.
function string getValue() {
var v = getValueFormBodyOrAccessValueDirectlyByMakingInput_a_ServerSideControl();
if (string.IsNullOrWhiteSpace(v)) {
return Guid.NewId();
} else {
return v;
}
}
Because it is a hidden input you should get it's value in the POSTed form body, and for ajax requests below snippet should take care of sending that value in header which you can access on server side.
$.ajaxSetup({
beforeSend: function(xhr) {
xhr.setRequestHeader("tabId", $('#hiddenInputTabId').val());
},
});
Same header can be check while returning the response to your polling requests and only respond message available against the provided tabId should be responded.

You can add a query string parameter called tabId and control it's binding to tab using javascript.
There is a functional prototype below:
$(function () {
// from: https://developer.mozilla.org/en-US/docs/Web/API/Window.location
function getQueryStringParameter (sVar) {
return decodeURI(window.location.search.replace(new RegExp("^(?:.*[&\\?]" + encodeURI(sVar).replace(/[\.\+\*]/g, "\\$&") + "(?:\\=([^&]*))?)?.*$", "i"), "$1"));
}
// from: http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
function newGuid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
});
}
window.tabId = getQueryStringParameter('tabId');
// tabId not defined, reload the page setting it
if (!window.tabId) {
window.tabId = newGuid();
}
// on click set the tabId of each link to current page
$(document).on('click', 'a', function (e) {
var $this = $(this);
var newLocation = $(this).attr("href");
// In page links
if (newLocation.match(/^#.+$/)) {
return;
}
// Outbound links
if (newLocation.match(new RegExp("^https?")) && !newLocation.match(new RegExp("^https?://" + window.location.host))) {
return;
}
// Insert tab id
if (newLocation.match(/(\?|&)tabId=[0-9a-f-]+/)) {
newLocation.replace(/(\?|&)tabId=[0-9a-f-]+/, (newLocation.indexOf('?') == -1 ? "?" : "&") + "tabId=" + window.tabId);
} else {
newLocation += (newLocation.indexOf('?') == -1 ? "?" : "&") + "tabId=" + window.tabId;
}
window.location.href = newLocation;
e.preventDefault();
});
});
If you enter a page in your application without setting the tabId parameter on query string, it will be set to a new UUID (Guid).
When the page has a tabId parameter on query string, it defines the window.tabId variable inside your page and you can use that in your application.
When the user click on any link in your page, a javascript event will be triggered and the link url will be redirected to match the current tabId. An right click to open in new tab or a middle click will not trigger that event and the user will be sent to a new tab without the query string parameters, so the new page will create a new tabId in that page.
You can see it working here: http://codepen.io/anon/pen/sCcvK

You can do it by generating a unique tab id with javascript by loading your client.
I strongly recommend you to use something for intertab communication, like intercom.js, which can broadcast the messages from a single tab with a single connection to every other tabs. Intertab works with socket.io, which has long polling fallback, so it may be good in your current system as well. I agree that polling is a poor choice, and you should use websockets instead.
If you use ZMQ on the server, then in the browser you can use NullMQ either (for websockets ofc). I think it does not have intertab support, so you should make your own intertab solution to make it work. It is not so hard to write such a system, you need only a common storage, for example localStorage, but it can be even cookie... If you don't have a storage event, you have to ping that storage for changes with setInterval. You have to store there the messages, and which tab broadcasts them (probably in a semaphore) and when was the last time it pinged the storage. After that you can keep each tab in sync with the others, or by using a unique tab id, you can send customized messages to any of the tabs. If the broadcast tab has a storage timeout (it did not ping the storage for a long while), then it is probably closed, so you should assign the broadcast service to another tab.

So what I ended up doing was changing how my notification framework functioned in order to prevent the need for identifying unique tabs. It's just too hard to layer information on the stateless web.
Instead of using Ajax to pump messages out to the client instantly, I build them up on each page into a List<Message> property. On PreRender I render them to the client with ClientScript.RegisterStartupScript(). But if I need to send the user to another page, I started using Server.Transfer() instead of Response.Redirect() instead so that it will preserve the message queue. The new page checks the old page to see if it exists and if is the correct Type. If it is the correct type, I cast it and retrieve the message queue from the old page and add them to the new page's queue. And since Server.Transfer() doesn't update the URL on the client, I also added a JavaScript function to manually push the state to the URL in supported browsers.
I know I took this in a little different direction than I did on the question, but I think I had been approaching it wrong in the beginning.

Related

Refresh page on new user sign in when using Google oAuth 2.0

I'm trying to make a website using Google Sign For Websites. Mostly just for the sake of learning about how it works.
I've followed the steps outlined in that tutorial Google Provides which works fine. Users can successfully sign into my site, it is able to pass the users ID to the backend and then verify the ID server side using a php script.
Php then creates a session for the user on the website. What I can't figure out is how would I refresh the page when a user clicks the Google Sign in button and the sign in is successful. Refreshing the page would allow the home page to be reloaded with the new php session data.
<div class="g-signin2 signin-button" data-onsuccess="onSignIn" data-theme="dark"></div>
function onSignIn(googleUser){
// The ID token you need to pass to your backend:
var id_token = googleUser.getAuthResponse().id_token;
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://mywebsite.com/tokensignin.php');
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onload = function() {
console.log('Signed in as: ' + xhr.responseText);
};
xhr.send('idtoken=' + id_token);
};
I've tried using simply location.reload() inside of the onload = function() portion of the code. This causes the page to just infinately refresh every time it is loaded however since Google verifys that the user is signed in through this xhr variable every time.
I've tried looking through their reference to use GoogleAuth.isSignedIn.listen(listener) to monitor any changes but it doesn't seem to fullfull what I want it to or I'm not using it correctly since I don't exactly know what the listener should be.
The other option might be to use their GoogleAuth.attachClickHandler(container, options, onsuccess, onfailure) function but I'm not entirely sure how the properly configure the options field/variable.
If someone could provide some insight as to how this world work I would greatly appreciate it.
To summarize if the user is already signed into my website using Google I want the page to do nothing, if they click the signin button, after the sign in is successful I want to refresh the page they are on.
You could add a listener to xhr with a callback function.
xhr.addEventListener("load", loginComplete);
And then create a function:
function loginComplete(evt) {
console.log("Login Complete");
window.location.reload();
}
EDIT:
Ok. Since the listener doesn't help. You will need to check if the user is already logged in. To save that information one thing I could think of would be using cookies.
So you could store the Auth Token you receive from Google and set a cookie for it and check everytime before you make your POST.
Here is a nice js cookie library: https://github.com/js-cookie/js-cookie
Then onload you save the id:
xhr.onload = function() {
Cookie.set('google_token', id_token);
window.location.reload();
};
And the onSignIn function would be like:
function onSignIn(googleUser){
var cookie = Cookie.get('google_token');
if(cookie != undefined){
//cookie is set
return;
}
...
//rest of the function
}
Of course you need to improve this code, for example, check if the token stored in the cookies is still valid, some cases you can just refresh it instead of making the user log in again (the oAuth API provide such things).
Make some security measures to be sure the token is not being injected and etc..
My approach was the same as yours and I indeed ran into the same problem with constant refreshing.
I tried a couple of solutions, the best working one was as follows:
User signs in using GAPI2 triggering callback onGoogleSignIn
Backend checks ID token validity, and signs user in through session on my webapp
If successful, I log the user out using GAPI2, preventing infinite reload, because onGoogleSignIn is never called again
Then I refresh the page and replace the sign-in button with a logout button for my webapp
This solution is of course pretty much useless if you want to manipulate some data on user's Google account, but I found no use for this functionality.
Code for signing someone out of their google account.
function signOut() {
var auth2 = gapi.auth2.getAuthInstance();
auth2.signOut().then(function () {
console.log('User signed out.');
});
}
Why not have your back end redirect (or send info so the page so it can redirect on success)?
What is the need for reloading the login screen?
You could also check out the listener documentation it seems like this may solve what you want. ie. listen for user change and trigger a function or redirect.
https://developers.google.com/identity/sign-in/web/listeners

Uniquely identify a duplicated Chrome tab

I am currently using window.sessionStorage to store a uniquely generated session ID. This ID is passed to all ajax calls to the server and persists across page navigation and reloads.
This seems to work perfectly for my target browsers, with one caveat: the duplicate tab functionality in Chrome.
Duplicating the tab copies the session storage to the new tab. When the new tab loads and communicates with the server, it now has the same "unique" identifier as the duplicated target tab.
Are there any ways that I can distinguish between the two duplicated tabs?
Or are they truly duplicates and no information is different between the two?
Nothing persists after the tab/window is closed, so even a simple numeric tab id or anything would work as long as it is unique for that current instance.
A variant of this worked for me:
https://stackoverflow.com/a/60164111/13375384
The trick is to only put the ID in session storage for the brief period of time when the tab is refreshing
In my TypeScript project, the solution looked like this:
const tabIdKey = "tabIdStorageKey"
const initTabId = (): string => {
const id = sessionStorage.getItem(tabIdKey)
if (id) {
sessionStorage.removeItem(tabIdKey)
return id
}
return uuid()
}
const tabId = initTabId()
window.addEventListener("beforeunload", () => {
sessionStorage.setItem(tabIdKey, tabId)
})
Session storage is on the browser level, and not on the tab level. See https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage .
The only method I know of to identify a tab is to pass a paramater at the end of each URL (and even so you would need to use JavaScript to prevent open in new Tab).
Other methods would be unbearable, such as embedding an iframe in the app (and storing the identifier in the parent), but possible.
In short, you should rephrase you question to simply identifying a tab, and you will nevertheless not find an answer.
My eventual solution to this was to generate a unique ID on the server side which gets stored in the server session and passed back to be stored in sessionStorage. I also have another value (let's call it FOO) that is set on the client side and stored.
Pretend it looks like this in a map:
{
"unique_id" : "ABC123",
"FOO" : "some value"
}
I send the values up to the server with every ajax request.
On page load, I check to see if I already have the unique_id, FOO pair in sessionStorage and load them. Any time the value of FOO changes, it will get compared with the server pair. If it has a different value then I store the new value for FOO under a new unique ID and hand it back to the client.
This doesn't 100% solve the problem, but it does make sure that FOO, which represents some persistent state, is never used incorrectly after tab duplication.
The tab that originally had had the unique_id, FOO pair will continue to have that pair while the duplicated tab will have a new pair.
Technically, until the value of FOO changes the two tabs share the same unique_id, but as long as unique_id gets reassigned when FOO changes, they won't overwrite each other's state within the server session.

Selective Framebursting

i would like to implement selective Framebursting for my iframe application.
My iframe is available at www.mywebsite.con/iframe.aspx?lic=1234
When the third party website hosting my iframe is (PayedWebsited1.con OR PayedWebsited2.con) AND the lic=1234 option also exists, display the iframe. For any other cheaters, display bananas!
How can i do it?
The thing is, that licence number won't help in any way - whether you will use server-side solution or in javascript. Cheaters will be able to see that licence number in PayedWebsite1.com.
As was said, you cannot get the parent frame location, but you can get the referrer - it equals to the parent frame, if your page is loaded in iframe.
if (window.top.location !== document.location) { // only if we're in iframe
// we get host of our referrer
var host = document.referrer.match(new RegExp("(http|https)://(.*?)/.*$"))[2];
host = host.toLowerCase(); // convert to lower case
var myHost = document.location.host.toLowerCase();
if (
host !== myHost // so we can click on links in an iframe
&& host !== 'payedwebsite1.com'
&& host !== 'payedwebsite2.com'
) {
window.top.location.href = document.location.href;
}
}
Be awared, that this technique can be beaten. More info at http://javascript.info/tutorial/clickjacking
For newer browsers, you can send special header:
X-Frame-Options: DENY
The logic keeps the same, only in server-side. Check Referrer, if PayedDomain or your own domain, just keep going. Otherwise, send this header.
If it is possible for your third party users to include a javascript file, or ideally send a request in ASP prior to drawing the page, this is what I would do:
Javascript
Build a ASP (I do PHP, so my example is in PHP) page on your server that checks the referrer and the license number to match an account in your database. The ASP file should then output javascript functions that will replace or insert into the element your specified iframe with a "one-time-use" key that you generate. The file might look similar to this:
<?php
$lic = $_GET['lic']; // Do better validation (short for demo purposes)
if (valid_license($lic, $_SERVER['HTTP_REFERER'])) {
$one_time_key = get_access_key($lic);
?>
function drawIframe() {
document.getElementById('iframe_target').innerHTML = "<iframe src='mysite.php?key=<?php echo $one_time_key;?>'></iframe>";
}
<?php
}
else {
echo "You are not authorized to use this service.";
}
Have your customer include this javascript code as a replacement of your iframe, in a fashion similar to this:
<script src="http://www.yoursite.com/preauth.php?lic=1234"></script>
<script>drawIframe();</script>
<div id="iframe_target"></div>
On the page that is loaded by the iframe, immediately check the key that you generated against the value passed to the iframe. If it is valid, immediately delete or change the status of the key so that you know it's been used. Then display appropriate application.
This javascript method will be the least painful method for your third party users, although it can be beat (users could change the "referer" that is sent to your server, although it is unlikely.)
ASP
If you can get your users to make a request to your url within their server, you will eliminate exposing any risky information like the license to the user. They could call something like $key = file_get_contents("http://www.yoursite.com/preauth.asp?lic=1234"); Immediately after they can output the iframe with the one time use key that you just generated.
Due to security, your browser will not allow you to use javascript to detect the URL of the parent page (i.e. the page that contains the iframe that displays your page).
The only solutions I can think of are:
Insist that users of your iframe.aspx page, include an additional GET param that states the domain that they are using.
Use the Request.UrlReferrer to get the referrer
On the page which you render, you should have a literal that, should you want to prevent the person from framing your page, you can simply add the javascript required to force the frames.
Unfortunately if Javascript is disabled, this will render your code useless...
Hope this helps?
protected void page_load(object sender, EventArgs e)
{
bool killFrames = false;
if(Request.QueryString["lic"] == null)
killFrames = true;
if(!killFrames && Request.UrlReferrer != null)
{
// do some database check against the LIC and Referrer
// and set killFrames accordingly.
}
if(killFrames)
{
literalFrame.Text = "<script type=\"text/javascript\">if(top.location != location) { top.location.href = document.location.href; }</script>";
// or show the bananas
}
else
{
// render the page accordingly.
}
}
I will try to point a solution for your general problem and not this particular technical problem, which as far as i know is impossible for security precautions done by all web browsers.
You need some sort of hand-shake between their app and yours and that haves to be done server-side.
Every PayedWebsite should have a password (or if they hava a static IP you could use that). Internally on their server (using CURL may be) they shold send you -via POST- their password; then you return a token that is used in the iframe.
iframe.aspx?lic=1234&token=d16evg5tr44e0trty45xp6es5
And the token only works once; so the process haves to be repeated every time the iframe needs to be opened. And you refuse every connection that doesn't include a valid token.
I'm not a .NET expert, but it looks like your solution could be easily solved by tracking the referral header that the client sends to your page when loading the iframe content.
You may want to refer to another question regarding refer headers:
how should we validate http header referrer in aspx .net
Basically, you would do the following
Use the referral header to get the domain name
Look up the domain name in your database (to see if there was a license for that site)
Send the real page, or the bananas depending on the result of the match.
Global.asax did the trick!
Sub Application_BeginRequest(ByVal sender As Object, ByVal e As EventArgs)
Dim ref As String = HttpContext.Current.Request.ServerVariables("HTTP_REFERER")
If Not ref.Contains("PayedWebsited1") And Not ref.Contains("PayedWebsited2") Then
Response.Redirect("MYDOMAIN", True)
End If
End Sub
Thanks to all!

Show content based on user selection

Im building a site at the moment.
On the layout there are 3 flags for different countries.
Im wondering how i would go about displaying content based on what the user selects, and keeping that selection each time they come back to the site.
Obviously the first time they come to the site the default english will be shown, but once they click on a flag it would change.
Im taking im going to have to use javascript and cookies, i have been looking around and cant seem to find any examples, im wondering if someone could show me how to go about this.
Thanks
Cookies seems like the right idea:
http://www.google.co.uk/search?q=javascript+cookies
There are (probably) only two ways of doing this:
Cookies (i.e. mostly anonymous users)
Registration and getting users to login
For your needs, it sounds as though cookies would be enough.
http://www.quirksmode.org/js/cookies.html is absolutely worth reading from beginning to end.
I am giving a abstract idea of how i might do it..
In the html you write, put a javascript function which sets the flag's id in the browser cookie when you click the flag and then submit the request..
function setCookie(flag_id) //call this when the flag is clicked
{
var allcookies = document.cookie;
if(allcookies)
{
document.cookie += ';flagId=' + flag_id;
}
else
{
document.cookie='flagId=' + flag_id;
}
/*submit the form or whatever you would like to do when the flag is clicked*/
}
Thats it on the client part. You can specify the expiration time for the cookie as well.. for details you can refer the w3schools website. The cookie will stay in the browser and sent to the server on every request.
Now, on the server side, if you are using servlets, just use the following code to get the cookie in the doPost or doGet (in your case when the first request comes from the client).
.....
Cookies[] cookies = request.getCookies();
String flagId = null;
if(cookies != null)
{
for(String cookie:cookies)
{
if(cookie.getName().equals("flagId"))
{
flagId = cookie.getValue();
}
}
}
//use the flag id to decide your content here..
....
Hope this answers your question.

Facebook Connect - Single Sign On Causes Infinite Loop :(

I have a Facebook Connect (FBML) web application that has been live for a while now.
All is working well, however user's are reporting issues with the single sign on (and i have seen the issue on their computer, but have not been able to replicate locally).
With FBC, there is a "event" you can hook into to automatically determine if the user is authenticated to Facebook.
That is achieved via this:
FB.Connect.ifUserConnected(onUserConnected, null);
The onUserConnected function can then do whatever it needs to attempt a single sign on.
For me, what i do is a AJAX call to the server to see if the user has an active website account (based on the Facebook details - user id, which i store in my system when they connect).
If they do, i show a jQuery modal dialog ("Connecting with Facebook") and do a window.location.reload().
The server then kicks in, and does the Single Sign On. This code is executed on every page:
public static void SingleSignOn(string redirectUrl)
{
if (!HttpContext.Current.Request.IsAuthenticated) // If User is not logged in
{
// Does this user have an Account?
if (ActiveFacebookUser != null)
{
// Get the user.
var saUser = tblUserInfo.GetUserByUserId(ActiveFacebookUser.IdUserInfo);
if (saUser != null)
{
// Get the Membership user.
MembershipUser membershipUser = Membership.GetUser(saUser.UserID);
if (membershipUser != null && membershipUser.IsApproved)
{
// Log Them Automically.
FormsAuthentication.SetAuthCookie(membershipUser.UserName, true);
// At this point, the Forms Authentication Cookie is set in the HTTP Response Stream.
// But the current HTTP Context (IsAuthenticated) will read HTTP Request Cookies (which wont have the new cookie set).
// Therefore we need to terminate the execution of this current HTTP Request and refresh the page to reload the cookies.
HttpContext.Current.Response.Redirect(
!string.IsNullOrEmpty(redirectUrl) ? redirectUrl : HttpContext.Current.Request.RawUrl,
true);
}
else
{
HandleUnregisteredFacebookUser();
}
}
else
{
HandleUnregisteredFacebookUser();
}
}
else
{
HandleUnregisteredFacebookUser();
}
}
}
I have never experienced an issue with this, but user's are reporting an "infinite" loop where the dialog gets shown, the window is refreshed, dialog is shown, window is refreshed, etc.
Basically, my AJAX call is saying the user exists, but my single sign on isn't.
Which is hard to believe because the code is very similar:
This is the AJAX Web Service:
if (!HttpContext.Current.Request.IsAuthenticated) // If user is not yet authenticated
{
if (FacebookConnect.Authentication.IsConnected) // If user is authenticated to Facebook
{
long fbId;
Int64.TryParse(Authentication.UserId, out fbId);
if (fbId > 0)
{
tblFacebook activeUser = tblFacebook.Load(facebookUniqueId: fbId);
if (activeUser != null && activeUser.IsActive) // If user has an active account
{
return true;
}
}
}
}
So if the response of this WS is 'true', i do a window.location.reload();
So i have no idea what the issue is (as i cant replicate), sounds like the Single Sign On isn't adding the Forms Authentication cookie properly to the response stream, or the Response.Redirect(Request.RawUrl) isn't reloading the cookie properly.
How do other's handle this?
This is what should happen:
User logs into Facebook (on Facebook itself)
User comes to my site (which has been previously authorised)
My site compares the FBID with the FBID in my DB, and signs them in.
My site is an ASP.NET 4.0 Web Forms application, using the "old" Facebook Connect JavaScript API (FeatureLoader.js), and Forms Authentication.
The only other solution to an AJAX call/window.reload i can think of is an AJAX UpdatePanel.
Can anyone help me out?
EDIT
Also, i realise that i can also use 'reloadIfSessionStateChanged':true to do the reload (which stops the infinite loop), but the problem with this is i cannot show my nice fancy dialog.
So i found a couple of issues.
Firstly, i shouldn't be setting a persistent cookie:
FormsAuthentication.SetAuthCookie(membershipUser.UserName, true);
We should only do this when the user ticks a box such as "Remember Me".
Secondly, i was checking the FB Auth status on every page on the server, so this could have been getting out of sync in the client-side.
Here is my new solution - which is better, and has a failsafe for the dreaded 'infinite loop'.
I no longer check the FB Auth status on the server, i do on the client:
FB.Connect.ifUserConnected(onUserConnected, null); // runs on every page request, on client-side
In the onUserConnection function i do the following:
Call web service to see if user CAN be signed in automatically (same WS as above)
If ok, check a special "Single Sign On" cookie has not been set.
If it hasn't been set, redirect to FacebookSingleSignOn.aspx.
FacebookSingleSignOn.aspx does the following:
Signs the user in using Forms Authentication (same as above)
Creates a special "Single Sign On" cookie to signify a SSO has been attempted
Redirects to the homepage
So, at point 3 above - this is where the infinite loop "could" happen. (as the onUserConnected will run again)
But it is now impossible (i hope) for it to happen, as it will only attempt to do the SSO if they havent already tried.
I clear the SSO cookie on the Facebook Logout action.
Now works well, logic makes sense - and it's done on the client-side (where it should be, as this is where FB does it's magic).
Hope that helps someone else out.

Categories

Resources