pm100
pm100

Reputation: 50180

Can I be notified of cookie changes in client side JavaScript?

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

Answers (8)

lc2k
lc2k

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

fuweichin
fuweichin

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

  1. not for IE
  2. require BroadcastChannel polyfill for Safari

Conclusion

| Metric \ Method  | Periodic Polling            | API Interception |
| ---------------- | --------------------------- | ---------------- |
| delay            | depends on polling interval | instant          |
| scope            | same-domain                 | same-origin      |

Upvotes: 36

Alexander Finkbeiner
Alexander Finkbeiner

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

Richie Bendall
Richie Bendall

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

iamousseni
iamousseni

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

Moshe L
Moshe L

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

wessel
wessel

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

calebds
calebds

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
  • This example uses a closure for persistent memory. The outer function is executed immediately, returning the inner function, and creating a private scope.
  • window.setInterval

Upvotes: 38

Related Questions