Reputation: 50180
Can I somehow follow changes to cookies (for my domain) in my client side JavaScript? For example, is there a function that gets called if a cookie gets changed, deleted or added?
In order of preference:
Why? Because cookies I depend on in window / tab #1 can get changed in window / tab #2.
I found out that Chrome allows extensions to be notified of cookie changes. But that's my least favorite option.
Upvotes: 73
Views: 78048
Reputation: 1
I also ran into this problem. I feel that other answers have pretty clearly covered what can be done in specifically detecting a cookie change, so I wanted to offer a solution from a different perspective for anyone else that happens upon this question as I did myself.
Upon closer inspection, it seems the underlying problem that we want to solve is enabling one tab to communicate with another tab, so that a state change in the first tab can be reflected in the second tab.
The solution that worked for me is to use the Broadcast Channel API defined in the MDN docs here. This API works for all modern browsers.
Upon instantiating each web page, you can create a connection to some broadcast channel you define.
const bc = new BroadcastChannel("session_change_channel");
Directly below this, you can add a function that acts on a received message. In your case, a login or logout message.
bc.onmessage = (event) => {
if (event.data === "logout") {
// Wipe state
} else if (event.data === "login") {
// Retrieve new state if the user has changed
}
};
The final step is to add a broadcast message to the areas where your token changes.
function doLogin() {
bc.postMessage("login");
// Do other login stuff
}
I believe that this method has performance and power consumption advantages over the polling methods outlined in other answers. It is also fully supported by all browsers unlike the cookieStore API.
Upvotes: 0
Reputation: 1900
Method 1: Periodic Polling
Poll document.cookie
function listenCookieChange(callback, interval = 1000) {
let lastCookie = document.cookie;
setInterval(()=> {
let cookie = document.cookie;
if (cookie !== lastCookie) {
try {
callback({oldValue: lastCookie, newValue: cookie});
} finally {
lastCookie = cookie;
}
}
}, interval);
}
Usage
listenCookieChange(({oldValue, newValue})=> {
console.log(`Cookie changed from "${oldValue}" to "${newValue}"`);
}, 1000);
document.cookie = 'a=1; Path=/';
Method 2: API Interception
Intercept document.cookie
(()=> {
let lastCookie = document.cookie;
// rename document.cookie to document._cookie, and redefine document.cookie
const expando = '_cookie';
let nativeCookieDesc = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie');
Object.defineProperty(Document.prototype, expando, nativeCookieDesc);
Object.defineProperty(Document.prototype, 'cookie', {
enumerable: true,
configurable: true,
get() {
return this[expando];
},
set(value) {
this[expando] = value;
// check cookie change
let cookie = this[expando];
if (cookie !== lastCookie) {
try {
// dispatch cookie-change messages to other same-origin tabs/frames
let detail = {oldValue: lastCookie, newValue: cookie};
this.dispatchEvent(new CustomEvent('cookiechange', {
detail: detail
}));
channel.postMessage(detail);
} finally {
lastCookie = cookie;
}
}
}
});
// listen cookie-change messages from other same-origin tabs/frames
const channel = new BroadcastChannel('cookie-channel');
channel.onmessage = (e)=> {
lastCookie = e.data.newValue;
document.dispatchEvent(new CustomEvent('cookiechange', {
detail: e.data
}));
};
})();
Usage
document.addEventListener('cookiechange', ({detail: {oldValue, newValue}})=> {
console.log(`Cookie changed from "${oldValue}" to "${newValue}"`);
});
document.cookie = 'a=1; Path=/';
Notes
| Metric \ Method | Periodic Polling | API Interception |
| ---------------- | --------------------------- | ---------------- |
| delay | depends on polling interval | instant |
| scope | same-domain | same-origin |
Upvotes: 36
Reputation: 31
If you want to use the new CookieStore and want the support of all browsers, you can install a (speculative) polyfill like the following: https://github.com/markcellus/cookie-store
Upvotes: 3
Reputation: 9172
We can use the CookieStore
API:
cookieStore.addEventListener('change', ({changed}) => {
for (const {name, value} of changed) {
console.log(`${name} was set to ${value}`);
}
});
Upvotes: 38
Reputation: 140
I think my way is better. I wrote a custom event for detect when cookie is chanced:
const cookieEvent = new CustomEvent("cookieChanged", {
bubbles: true,
detail: {
cookieValue: document.cookie,
checkChange: () => {
if (cookieEvent.detail.cookieValue != document.cookie) {
cookieEvent.detail.cookieValue = document.cookie;
return 1;
} else {
return 0;
}
},
listenCheckChange: () => {
setInterval(function () {
if (cookieEvent.detail.checkChange() == 1) {
cookieEvent.detail.changed = true;
//fire the event
cookieEvent.target.dispatchEvent(cookieEvent);
} else {
cookieEvent.detail.changed = false;
}
}, 1000);
},
changed: false
}
});
/*FIRE cookieEvent EVENT WHEN THE PAGE IS LOADED TO
CHECK IF USER CHANGED THE COOKIE VALUE */
document.addEventListener("DOMContentLoaded", function (e) {
e.target.dispatchEvent(cookieEvent);
});
document.addEventListener("cookieChanged", function (e) {
e.detail.listenCheckChange();
if(e.detail.changed === true ){
/*YOUR CODE HERE FOR DO SOMETHING
WHEN USER CHANGED THE COOKIE VALUE */
}
});
Upvotes: 9
Reputation: 1905
If the code that manipulated the cookies is yours, you can use localStorage
for tracking changed with events. for example, you can store a junk on the localStorage to trigger an event on the other tabs.
for example
var checkCookie = function() {
var lastCookies = document.cookie.split( ';' ).map( function( x ) { return x.trim().split( /(=)/ ); } ).reduce( function( a, b ) {
a[ b[ 0 ] ] = a[ b[ 0 ] ] ? a[ b[ 0 ] ] + ', ' + b.slice( 2 ).join( '' ) :
b.slice( 2 ).join( '' ); return a; }, {} );
return function() {
var currentCookies = document.cookie.split( ';' ).map( function( x ) { return x.trim().split( /(=)/ ); } ).reduce( function( a, b ) {
a[ b[ 0 ] ] = a[ b[ 0 ] ] ? a[ b[ 0 ] ] + ', ' + b.slice( 2 ).join( '' ) :
b.slice( 2 ).join( '' ); return a; }, {} );
for(cookie in currentCookies) {
if ( currentCookies[cookie] != lastCookies[cookie] ) {
console.log("--------")
console.log(cookie+"="+lastCookies[cookie])
console.log(cookie+"="+currentCookies[cookie])
}
}
lastCookies = currentCookies;
};
}();
$(window).on("storage",checkCookie); // via jQuery. can be used also with VanillaJS
// on the function changed the cookies
document.cookie = ....
window.localStorage["1"] = new Date().getTime(); // this will trigger the "storage" event in the other tabs.
Upvotes: 7
Reputation: 801
Slightly improved (shows a console.log for each changed cookie):
var checkCookie = function() {
var lastCookies = document.cookie.split( ';' ).map( function( x ) { return x.trim().split( /(=)/ ); } ).reduce( function( a, b ) {
a[ b[ 0 ] ] = a[ b[ 0 ] ] ? a[ b[ 0 ] ] + ', ' + b.slice( 2 ).join( '' ) :
b.slice( 2 ).join( '' ); return a; }, {} );
return function() {
var currentCookies = document.cookie.split( ';' ).map( function( x ) { return x.trim().split( /(=)/ ); } ).reduce( function( a, b ) {
a[ b[ 0 ] ] = a[ b[ 0 ] ] ? a[ b[ 0 ] ] + ', ' + b.slice( 2 ).join( '' ) :
b.slice( 2 ).join( '' ); return a; }, {} );
for(cookie in currentCookies) {
if ( currentCookies[cookie] != lastCookies[cookie] ) {
console.log("--------")
console.log(cookie+"="+lastCookies[cookie])
console.log(cookie+"="+currentCookies[cookie])
}
}
lastCookies = currentCookies;
};
}();
window.setInterval(checkCookie, 100);
Upvotes: 1
Reputation: 26228
One option is to write a function that periodically checks the cookie for changes:
var checkCookie = function() {
var lastCookie = document.cookie; // 'static' memory between function calls
return function() {
var currentCookie = document.cookie;
if (currentCookie != lastCookie) {
// something useful like parse cookie, run a callback fn, etc.
lastCookie = currentCookie; // store latest cookie
}
};
}();
window.setInterval(checkCookie, 100); // run every 100 ms
Upvotes: 38