UNPKG

cookie-events

Version:

Lightweight, event-driven cookie manager for parsing, setting, and tracking cookies with ease.

396 lines (338 loc) 12.9 kB
/** * Cookie JavaScript Library v1.0.0 * A lightweight utility to parse, retrieve, and set browser cookies using JavaScript. * https://github.com/jsvibe/cookie-events * * @license MIT License * @author Indian Modassir * * Date: 29 May 2025 05:55 GMT+0530 */ (function(window) { // Catches errors and disallows unsafe actions "use strict"; var hasOwn = ({}).hasOwnProperty; var document = window.document; // Used for storing event handlers var memory = {}; // Initialize cookie object to access existing cookies var cookies = new Cookie(); /** * Cookie constructor function to create a cookie object that parses the current document's cookies * into a JavaScript object. * * This function can be called with or without the `new` keyword. * If called without `new`, it automatically returns a new instance of Cookie. */ function Cookie() { // Allow instantiation without the 'new' keyword if (!(this instanceof Cookie)) { return new Cookie(); } var cookie, key, value, cookies = document.cookie.split("; "), cookieStore = {}; // Define a method on the prototype to retrieve all stored cookies Cookie.prototype.getAll = function() { return cookieStore; }; // If the first item in the cookies array is an empty string, if (cookies[0] === "") return; // Loop through each individual cookie string for(cookie of cookies) { // Split the cookie into key and value using "=" as the delimiter cookie = cookie.split("="); key = decodeURIComponent(cookie.shift()); value = decodeURIComponent(cookie.join("=")); // Try to parse the value as JSON try {value = JSON.parse(value)} catch(e) {} // Store the parsed key-value pair in the cookieStore object cookieStore[key] = value; } // Copy all key-value pairs from cookieStore to the current instance Object.assign(this, cookieStore); return this; } // Define methods on Cookie's prototype Cookie.prototype = { // Expose the internal cookie store (optional) store: cookieStore, /** * Converts cookies to a URL query-style string * Useful for debugging or building query parameters * Example: "name=John; age=30" => "name=John&age=30" */ param: function() { return document.cookie.replace(/;\s/g, "&"); }, /** * Sets a new cookie in the browser with optional attributes for customization and security. * * This method allows the storage of structured data (objects, arrays, primitives) * by JSON-stringifying and URI-encoding the value. * Additional cookie attributes like `expires`, `path`, `domain`, `secure`, and `SameSite` * help control cookie scope and behavior. * * @param {String} key The name of the cookie. * @param {*} value The value to be stored * @param {String|Number} expires Expiry date in Custom or UTC format * @param {String} path Path where the cookie is accessible (default is root). * @param {String} domain Domain where the cookie is valid. * @param {Boolean} secure If true, cookie will be sent over HTTPS only. * @param {String} SameSite Controls cross-site request behavior */ set: function(key, value, expires, path, domain, secure, SameSite) { var entries, group, expkey = +expires ? "max-age" : "expires", cookie = [], params = { [key]: encodeURIComponent(value), // Safely encode the value [expkey]: expires, // Expiration date path: path || "/", // Path defaults to root domain, // Domain scope secure, // Secure flag SameSite // SameSite policy }; // Convert the object into an array of key-value pairs entries = Object.entries(params); // Iterate over the key-value pairs to build the cookie string for(group of entries) { if (group[1] != null) { cookie.push(group.join("=")); // Only include non-null values } } // Join parts with '; ' and assign or set to document.cookie document.cookie = cookie.join("; "); }, /** * Convert current cookie object to a JSON string Useful for debugging or storage * @returns Cookies in json format */ json: function() { return JSON.stringify(new Cookie); }, /** * Parses all available cookies into an array of [key, value] pairs. * * @returns {Array[]} An array of [key, value] pairs representing cookies. */ parse: function() { return Object.entries(this.getAll()); }, /** * Checks whether a cookie with the specified key exists in the current instance. * * This method verifies if the given cookie key is present in the internal * cookie storage. It's a safe way to determine existence without directly accessing properties. * * It uses `Object.prototype.hasOwnProperty` to avoid false positives * from inherited properties or prototype pollution. * * @param {String} key The name of the cookie to check. * @returns {Boolean} Returns `true` if the cookie exists, otherwise `false`. */ has: function(key) { return hasOwn.call(this.getAll(), key); }, /** * Retrieves the value of a specific cookie by its key. * The value returned is already decoded and, if applicable, parsed from JSON. * * If the specified key exists, the associated value is returned. * If not found, the method returns `null` instead of `undefined` * to clearly indicate the absence of the cookie. * * @param {String} key The name of the cookie to retrieve. * @returns The cookie value, or null if it doesn't exist */ get: function(key) { var cookies = this.getAll(); return hasOwn.call(cookies, key) ? cookies[key] : null; }, /** * Updates the value of an existing cookie. * * @param {String} key The name of the cookie. * @param {*} value The value to be stored * @param {String|Number} expires Expiry date in Custom or UTC format * @param {String} path Path where the cookie is accessible (default is root). * @param {String} domain Domain where the cookie is valid. * @param {Boolean} secure If true, cookie will be sent over HTTPS only. * @param {String} SameSite Controls cross-site request behavior */ update: function(key, value, expires, path, domain, secure, SameSite) { if (!this.has(key)) { throw new Error('Cookie [' + key + '] not found. Update failed!'); } this.set(key, value, expires, path, domain, secure, SameSite); }, /** * Converts a date string with optional time to a UTC-formatted string. * @param {String} date A string representing the date and/or time. * @returns {String} The corresponding UTC date string in standard format */ UTC: function(date) { var name, value, expires, _arguments, /* * Regular expression to parse the date and time components from the input string. * It captures: * day, month, and year (optional) * hour, minute, and second (required) */ rdateTime = /(?:(?:(?<day>\d+)([\/-])(?<month>\d+)\2(?<year>\d+)\s)?(?:(?<hour>\d+):(?<minute>\d+):(?<second>\d+)))/, // Extract named capture groups (if any) from the input string using the regex groups = ((rdateTime.exec(date) || {}).groups || {}), // Create a base Date object using the current date and time date = new Date, // Create a dateTime object initialized with current local date and time // Note: month is zero-based in JavaScript Date (0 = January, 11 = December) dateTime = { year: date.getFullYear(), month: date.getMonth(), day: date.getDate(), hour: date.getHours(), minute: date.getMinutes(), second: date.getSeconds() }; // Override the default dateTime values with parsed values from the input, if available for(name in groups) { value = groups[name]; if (value != null) { dateTime[name] = +value; } } // Convert the dateTime object to an array of arguments in the correct order for Date.UTC // Order: year, month (0-based), day, hour, minute, second _arguments = Object.values(dateTime); // Create a UTC date using Date.UTC and convert it to a UTC string expires = new Date(Date.UTC.apply(null, _arguments)).toUTCString(); // Return the formatted UTC string return expires; }, /** * Removes a cookie by setting its expiry date to the past. * @param {String} key The name of the cookie to remove. * @param {String} path The path the cookie was set on. Default is "/". * @param {String} domain The domain the cookie was set on. */ remove: function(key, path, domain) { // Construct the base cookie deletion string with key and expired date var expr = key + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=" + (path || "/") + ";"; // If a domain is provided, append it to the cookie deletion string if (domain) { expr += " domain=" + domain + ";"; } // Set the cookie with expired date to delete it document.cookie = expr; }, /** * Asynchronously clears (removes) all cookies available using the Cookie Store API. * * Note: * - This function is asynchronous and should be awaited when called. * - The `cookieStore` API is modern and works in secure contexts (HTTPS) and supported browsers. */ clear: async function() { var cookie, cookies = await cookieStore.getAll(); // Loop through each cookie object for(cookie of cookies) { this.remove(cookie.name, cookie.path, cookie.domain); } }, /** * Converts all available cookies into a FormData object. * * This method is useful when you want to send cookies as form-encoded data * (e.g., in an AJAX request using fetch or XMLHttpRequest). * * @returns {FormData} A FormData object containing all cookies as key-value pairs. */ createFormData: function() { var key, value, fd = new FormData(), entries = this.parse(); for(value of entries) { key = value.shift(); fd.append(key, value.join("")); } return fd; } }; /** * @internal * Utility function to trigger events and call associated callbacks * @param {Object} event The original event object * @param {String} type Type of cookie event */ function fireWith(event, type) { var fn, handlers = memory[type], newEvent = { changed: event.changed, currentTarget: event.currentTarget, deleted: event.deleted, srcElement: event.srcElement, CookieStore: event.CookieStore, origEvent: event, timeStamp: event.timeStamp, type }; // If handlers exist for this event type, invoke them if (handlers) { for(fn of handlers) { fn.call(this, newEvent, type); } } } /** * Register event listeners for cookie changes * @param {String} types Space-separated list of event types * @param {Function} callback Callback function to invoke on events */ Cookie.prototype.on = function(types, callback) { var type; types = types.split(" "); for(type of types) { memory[type] = memory[type] || [], memory[type].push(callback); } // Allows method chaining return this; }; /** * onchange handler for cookieStore * Detects changes and fires corresponding cookie events */ cookieStore.onchange = function(e) { var curCookieLen = Object.values(new Cookie()).length, changed = e.changed, {name, value} = changed[0] || {}; // If cookie already exists and value changed => update if (hasOwn.call(cookies, name) && (cookies[name] + "") !== value) { fireWith.call(this, e, "update"); } // If new cookie added => insert else if (!hasOwn.call(cookies, name) && changed.length) { fireWith.call(this, e, "insert"); } // If cookies were deleted or no changes => delete or clear else if (e.deleted.length || !changed.length) { !curCookieLen ? fireWith.call(this, e, "clear") : fireWith.call(this, e, "delete"); } // Always fire a generic change event fireWith(this, e, "change"); cookies = new Cookie(); return this; }; // Set the Symbol.toStringTag to "Cookie" for better introspection Object.defineProperty( Cookie.prototype, Symbol.toStringTag, {value: Cookie.name} ); /* EXPOSE */ // Register as named AMD module, // since Cookie can be concatenated with other files that may use define typeof define === 'function' && define.amd ? define(function() { return Cookie; // Expose Cookie identifiers, Even in AMD and CommonJS for browser emulators }) : (typeof module === "object" ? module.exports = Cookie : window.Cookie = Cookie); /* EXPOSE */ })(window);