Могу ли я получать уведомления об изменениях файлов cookie в JavaScript на стороне клиента?

Могу ли я каким-то образом следить за изменениями файлов cookie (для моего домена) в моем клиентском JavaScript. Например, функция, которая вызывается в случае изменения, удаления или добавления файла cookie.

В порядке предпочтения

  • стандартный кросс-браузер
  • кросс-браузер
  • специфичный для браузера
  • расширение / плагин

Зачем? потому что файлы cookie, от которых я зависел в окне / вкладке № 1, могут быть изменены в окне / вкладке № 2.

Я обнаружил, что Chrome позволяет уведомлять расширения об изменениях файлов cookie. Но это мой наименее любимый вариант

7 ответов

Решение

Один из вариантов - написать функцию, которая периодически проверяет cookie на наличие изменений:

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
  • В этом примере используется закрытие для постоянной памяти. Внешняя функция выполняется немедленно, возвращая внутреннюю функцию и создавая частную область видимости.
  • window.setInterval

Метод 1: периодический опрос

Голосование 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);
}

Применение

listenCookieChange(({oldValue, newValue})=> {
  console.log(`Cookie changed from "${oldValue}" to "${newValue}"`);
}, 1000);

document.cookie = 'a=1';

Метод 2: перехват API

Перехватить document.cookie

(()=> {
  const channel = new BroadcastChannel('cookie-channel');
  channel.onmessage = (e)=> { // get notification from other same-origin tabs/frames
    document.dispatchEvent(new CustomEvent('cookiechange', {
      detail: e.data
    }));
  };

  let expando = '_cookie';
  let lastCookie = document.cookie;
  let checkCookieChange = ()=> {
    let cookie = document[expando];
    if (cookie !== lastCookie) {
      try {
        let detail = {oldValue: lastCookie, newValue: cookie};
        document.dispatchEvent(new CustomEvent('cookiechange', {
          detail: detail
        }));
        channel.postMessage(detail); // notify other same-origin tabs/frames
      } finally {
        lastCookie = cookie;
      }
    }
  };

  let nativeCookieDesc = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie');
  Object.defineProperty(Document.prototype, expando, nativeCookieDesc);
  Object.defineProperty(Document.prototype, 'cookie', { // redefine document.cookie
    enumerable: true,
    configurable: true,
    get() {
      return this[expando];
    },
    set(value) {
      this[expando] = value;
      checkCookieChange();
    }
  });
})();

Применение

document.addEventListener('cookiechange', ({detail: {oldValue, newValue}})=> {
  console.log(`Cookie changed from "${oldValue}" to "${newValue}"`);
});

document.cookie = 'a=1';

Заключение

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

Мы можем преодолеть document.cookie и следите за изменениями файлов cookie:

const parseCookies = (cookies = document.cookie) => cookies.split(/; (.*)/).slice(0, -1).map(cookie => {
    const [name, value] = cookie.split("=")
    return [name, decodeURIComponent(value)]
})

const COOKIE = Symbol("Cookie")
let lastCookies = document.cookie
let lastCookiesParsed = new Map(parseCookies(lastCookies))

Object.defineProperty(Document.prototype, COOKIE, Object.getOwnPropertyDescriptor(Document.prototype, "cookie"))

Object.defineProperty(Document.prototype, "cookie", {
    enumerable: true,
    configurable: true,
    get() {
        return this[COOKIE]
    },
    set(value) {
        this[COOKIE] = value

        if (value === lastCookies) {
            return
        }

        for (const [name, cookieValue] of parseCookies(value).filter(([name, cookieValue]) => lastCookiesParsed.get(name) === cookieValue)) {
            document.dispatchEvent(new CustomEvent("cookiechange", {
                detail: {
                    name,
                    value: cookieValue
                }
            }));
        }

        lastCookies = value
        lastCookiesParsed = new Map(parseCookies(lastCookies))
    }
})

Применение:

document.addEventListener("cookiechange", ({detail}) => {
    const {name, value} = detail
    console.log(`${name} was set to ${value}`)
})

Я думаю, что мой путь лучше. Я написал пользовательское событие для определения вероятности получения cookie:

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 */
  }
});

Если код, который управлял файлами cookie, принадлежит вам, вы можете использовать localStorage для отслеживания изменилось с событиями. например, вы можете сохранить ненужную информацию в localStorage, чтобы вызвать событие на других вкладках.

например

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.

Немного улучшено (показывает файл console.log для каждого измененного файла 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);

Если вы хотите использовать новый CookieStore и вам нужна поддержка всех браузеров, вы можете установить (спекулятивный) полифилл, как показано ниже: https://github.com/markcellus/cookie-store

Другие вопросы по тегам