Skip to content

Modernized version of evercookie.jsΒ #143

@adarshmadrecha

Description

@adarshmadrecha

This modernized version of evercookie.js provides a robust foundation for persistent client-side storage using contemporary web APIs.​

πŸ§ͺ Notes

  • Removed Flash (swfobject): All Flash-related code has been eliminated, as Flash is obsolete and unsupported in modern browsers.​
  • Modern JavaScript: Utilizes ES6+ features such as classes, async/await, and arrow functions for cleaner and more maintainable code.
  • IndexedDB Support: Implements IndexedDB with proper error handling and fallbacks, ensuring compatibility across browsers.​
  • Storage Mechanisms: Includes cookies, localStorage, sessionStorage, window.name, and IndexedDB.​
  • Value Retrieval: When retrieving a value, the most frequently occurring value across all storage mechanisms is returned, enhancing resilience.​
class Evercookie {
  constructor() {
    this.dbName = 'evercookie_db';
    this.storeName = 'evercookie_store';
    this.idbSupported = 'indexedDB' in window;
  }

  // Set value in all available storage mechanisms
  async set(key, value) {
    this.setCookie(key, value);
    this.setLocalStorage(key, value);
    this.setSessionStorage(key, value);
    this.setWindowName(key, value);
    if (this.idbSupported) {
      await this.setIndexedDB(key, value);
    }
  }

  // Retrieve value from available storage mechanisms
  async get(key) {
    const values = [];

    const cookieVal = this.getCookie(key);
    if (cookieVal) values.push(cookieVal);

    const localVal = this.getLocalStorage(key);
    if (localVal) values.push(localVal);

    const sessionVal = this.getSessionStorage(key);
    if (sessionVal) values.push(sessionVal);

    const windowVal = this.getWindowName(key);
    if (windowVal) values.push(windowVal);

    if (this.idbSupported) {
      const idbVal = await this.getIndexedDB(key);
      if (idbVal) values.push(idbVal);
    }

    // Return the most frequent value
    return this.getMostFrequentValue(values);
  }

  // Helper to determine the most frequent value
  getMostFrequentValue(arr) {
    const freqMap = {};
    let maxFreq = 0;
    let mostFreqVal = null;

    for (const val of arr) {
      freqMap[val] = (freqMap[val] || 0) + 1;
      if (freqMap[val] > maxFreq) {
        maxFreq = freqMap[val];
        mostFreqVal = val;
      }
    }

    return mostFreqVal;
  }

  // Cookie methods
  setCookie(key, value) {
    document.cookie = `${encodeURIComponent(key)}=${encodeURIComponent(value)}; path=/; SameSite=Lax`;
  }

  getCookie(key) {
    const cookies = document.cookie.split('; ');
    for (const cookie of cookies) {
      const [k, v] = cookie.split('=');
      if (decodeURIComponent(k) === key) {
        return decodeURIComponent(v);
      }
    }
    return null;
  }

  // localStorage methods
  setLocalStorage(key, value) {
    try {
      localStorage.setItem(key, value);
    } catch (e) {
      // Handle quota exceeded or disabled storage
    }
  }

  getLocalStorage(key) {
    try {
      return localStorage.getItem(key);
    } catch (e) {
      return null;
    }
  }

  // sessionStorage methods
  setSessionStorage(key, value) {
    try {
      sessionStorage.setItem(key, value);
    } catch (e) {
      // Handle quota exceeded or disabled storage
    }
  }

  getSessionStorage(key) {
    try {
      return sessionStorage.getItem(key);
    } catch (e) {
      return null;
    }
  }

  // window.name methods
  setWindowName(key, value) {
    try {
      const data = JSON.parse(window.name || '{}');
      data[key] = value;
      window.name = JSON.stringify(data);
    } catch (e) {
      // Handle JSON parse/stringify errors
    }
  }

  getWindowName(key) {
    try {
      const data = JSON.parse(window.name || '{}');
      return data[key] || null;
    } catch (e) {
      return null;
    }
  }

  // IndexedDB methods
  async setIndexedDB(key, value) {
    try {
      const db = await this.openIndexedDB();
      const tx = db.transaction(this.storeName, 'readwrite');
      const store = tx.objectStore(this.storeName);
      store.put(value, key);
      await tx.complete;
      db.close();
    } catch (e) {
      // Handle errors
    }
  }

  async getIndexedDB(key) {
    try {
      const db = await this.openIndexedDB();
      const tx = db.transaction(this.storeName, 'readonly');
      const store = tx.objectStore(this.storeName);
      const request = store.get(key);
      return new Promise((resolve) => {
        request.onsuccess = () => {
          resolve(request.result || null);
          db.close();
        };
        request.onerror = () => {
          resolve(null);
          db.close();
        };
      });
    } catch (e) {
      return null;
    }
  }

  openIndexedDB() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(this.dbName, 1);
      request.onupgradeneeded = () => {
        const db = request.result;
        if (!db.objectStoreNames.contains(this.storeName)) {
          db.createObjectStore(this.storeName);
        }
      };
      request.onsuccess = () => {
        resolve(request.result);
      };
      request.onerror = () => {
        reject(request.error);
      };
    });
  }
}
// Example usage:
const ec = new Evercookie();
ec.set('user_id', '12345');
ec.get('user_id').then((value) => {
  console.log('Retrieved value:', value);
});

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions