Skip to main content

A tiny (1.24 KB gzipped), stand-alone JavaScript utility for managing cookies in the browser.

/*!
 * Cookie.js, v1.4.1
 *
 * A tiny (1.24 KB gzipped), stand-alone JavaScript utility for
 * managing cookies in the browser.
 *
 * Copyright (c) 2012-2017 Jon LaBelle
 * Licensed under MIT (http://creativecommons.org/licenses/MIT/)
 * https://github.com/jonlabelle/cookie-js
 */
(function (window, undefined) {
    "use strict";

    var encode = encodeURIComponent,
        decode = decodeURIComponent;

    /**
     * Determines if the value's type is a number.
     *
     * @param  {Any}
     *
     * @return {Boolean}
     */
    function isNumeric(val) {
        return !isNaN(parseFloat(val)) && isFinite(val);
    }

    /**
     * Merges two objects.
     *
     * @param  {Object} objOne
     * @param  {Object} objTwo
     *
     * @return {Object}
     */
    function mergeObjects(objOne, objTwo) {
        if (objOne instanceof Array) {
            return objOne.concat(objTwo);
        }

        var merged = {},
            property;

        for (property in objOne) {
            if (objOne.hasOwnProperty(property)) {
                merged[property] = objOne[property];
            }
        }

        for (property in objTwo) {
            if (objTwo.hasOwnProperty(property)) {
                merged[property] = objTwo[property];
            }
        }

        return merged;
    }

    /**
     * Iterates over an object's properties and applies a callback
     * function.
     *
     * @param  {Object}   obj The object to iterate over.
     * @param  {function} callback The callback function to
     */
    function objectForEach(obj, callback) {
        for (var property in obj) {
            if (obj.hasOwnProperty(property)) {
                callback(property, obj[property]);
            }
        }
    }

    var Cookie = {

        /**
         * Creates a cookie string that can be assigned into document.cookie.
         *
         * @param  {String}  name The name of the cookie.
         * @param  {String}  value The value of the cookie.
         * @param  {Boolean} encodeValue True to encode the value, false to
         *     leave as-is.
         * @param  {Object}  options (Optional) Options for the cookie.
         *
         * @return {String} The formatted cookie string.
         */
        createCookieString: function (name, value, encodeValue, options) {
            options = options || {};

            var text = encode(name) + "=" + (encodeValue ? encode(value) : value),
                expires = options.expires,
                path = options.path || "/",
                domain = options.domain;

            if (typeof options === "object") {
                // expire date
                if (expires instanceof Date) {
                    text += "; expires=" + expires.toUTCString();
                } else if (isNumeric(expires)) {
                    // expire days (int)
                    var when = new Date();
                    when.setDate(when.getDate() + expires);
                    text += "; expires=" + when.toUTCString();
                }

                // path
                if (typeof path === "string" && path !== "") {
                    text += "; path=" + path;
                }

                // domain
                if (typeof domain === "string" && domain !== "") {
                    text += "; domain=" + domain;
                }

                // secure
                if (options.secure === true) {
                    text += "; secure";
                }
            }

            return text;
        },

        /**
         * Creates a string from a hash/object.
         *
         * @param  {Object} hash An object of key-value pairs to create a string
         *     for.
         *
         * @return {String} A string suitable for use as a cookie value.
         */
        createCookieHashString: function (hash) {
            if (typeof hash !== "object") {
                return "";
            }

            var text = [];

            objectForEach(hash, function (key, value) {
                if (typeof value !== "function" && typeof value !== "undefined") {
                    text.push(encode(key) + "=" + encode(String(value)));
                }
            });

            return text.join("&");
        },

        /**
         * Parses a cookie hash string into an object.
         *
         * @param {String} text The cookie hash string to parse (format:
         *     n1=v1&n2=v2).
         *
         * @return {Object} An object containing entries for each cookie value.
         */
        parseCookieHash: function (text) {
            var hashParts = text.split("&"),
                hashPart = null,
                hash = {};

            if (text.length) {
                for (var i = 0, len = hashParts.length; i < len; i++) {
                    hashPart = hashParts[i].split("=");
                    hash[decode(hashPart[0])] = decode(hashPart[1]);
                }
            }

            return hash;
        },

        /**
         * Parses a cookie string into an object representing all accessible
         * cookies.
         *
         * @param {String} text The cookie string to parse.
         * @param {Boolean} shouldDecode (Optional) Indicates if the cookie
         *     values should be decoded or not. Default is true.
         * @param {Object} options (Optional) Contains settings for loading the
         *     cookie.
         *
         * @return {Object}
         */
        parseCookieString: function (text, shouldDecode, options) {
            var cookies = {};

            if (typeof text === "string" && text.length > 0) {
                var decodeValue = (shouldDecode === false ? function (s) {
                        return s;
                    } : decode),
                    cookieParts = text.split(/;\s/g),
                    cookieName = null,
                    cookieValue = null,
                    cookieNameValue = null;

                for (var i = 0, len = cookieParts.length; i < len; i++) {
                    cookieNameValue = cookieParts[i].match(/([^=]+)=/i);
                    if (cookieNameValue instanceof Array) {
                        try {
                            cookieName = decode(cookieNameValue[1]);
                            cookieValue = decodeValue(cookieParts[i].substring(
                                cookieNameValue[1].length + 1));
                        } catch (ex) {
                            // ignore... encoding is wrong.
                        }
                    } else {
                        // means the cookie does not have an "=", so treat it as a
                        // boolean flag
                        cookieName = decode(cookieParts[i]);
                        cookieValue = "";
                    }
                    // don't overwrite an already loaded cookie if set by option
                    if (typeof options !== "undefined" && options.reverseCookieLoading) {
                        if (typeof cookies[cookieName] === "undefined") {
                            cookies[cookieName] = cookieValue;
                        }
                    } else {
                        cookies[cookieName] = cookieValue;
                    }
                }
            }

            return cookies;
        },

        /**
         * Determines if the cookie with the given name exists. This is useful
         * for Boolean cookies (those that do not follow the name=value
         * convention).
         *
         * @param {String} name The name of the cookie to check.
         *
         * @return {Boolean} True if the cookie exists, false if not.
         */
        exists: function (name) {
            if (typeof name !== "string" || name === "") {
                return false;
            }

            var cookies = this.parseCookieString(document.cookie, true);

            return cookies.hasOwnProperty(name);
        },

        /**
         * Returns the cookie value for the given name.
         *
         * @param {String} name The name of the cookie to retrieve.
         * @param {Function|Object} options (Optional) An object containing one
         *      or more cookie options: raw (true/false), reverseCookieLoading
         *      (true/false) and converter (a function). The converter function
         *      is run on the value before returning it. The function is not
         *      used if the cookie doesn't exist. The function can be passed
         *      instead of the options object for backwards compatibility. When
         *      raw is set to true, the cookie value is not URI decoded.
         *
         * @return {Any} If no converter is specified, returns a string or null
         *      if the cookie doesn't exist. If the converter is specified,
         *      returns the value returned from the converter or null if the
         *      cookie doesn't exist.
         */
        get: function (name, options) {
            var cookies,
                cookie,
                converter;

            // if options is a function, then it's the converter
            if (typeof options === "function") {
                converter = options;
                options = {};
            } else if (typeof options === "object") {
                converter = options.converter;
            } else {
                options = {};
            }

            cookies = this.parseCookieString(document.cookie, !options.raw, options);
            cookie = cookies[name];

            // should return null, not undefined if the cookie doesn't exist
            if (typeof cookie === "undefined") {
                return null;
            }

            if (typeof converter === "function") {
                return converter(cookie);
            }

            return cookie;
        },

        /**
         * Returns the value of a sub-cookie.
         *
         * @param {String} name The name of the cookie to retrieve.
         * @param {String} subName The name of the sub-cookie to retrieve.
         * @param {Function} converter (Optional) A function to run on the value
         *      before returning it. The function is not used if the cookie
         *      doesn't exist.
         * @param {Object} options (Optional) Containing one or more settings
         *     for cookie parsing.
         *
         * @return {Any} If the cookie doesn't exist, null is returned. If the
         *      sub-cookie doesn't exist, null if also returned. If no converter
         *      is specified and the sub-cookie exists, a string is returned. If
         *      a converter is specified and the sub-cookie exists, the value
         *      returned from the converter is returned.
         *
         * @return {Any}
         */
        getSub: function (name, subName, converter, options) {
            var hash = this.getSubs(name, options);

            if (hash === null) {
                return null;
            }

            if (typeof subName !== "string" || subName === "") {
                return null;
            }

            if (typeof hash[subName] === "undefined") {
                return null;
            }

            if (typeof converter === "function") {
                return converter(hash[subName]);
            }

            return hash[subName];
        },

        /**
         * Returns an object containing name-value pairs stored in the cookie
         * with the given name.
         *
         * @param {String} name The name of the cookie to retrieve.
         * @param {Object} options (Optional) Containing one or more settings
         *     for cookie parsing.
         * @return {Object} An object of name-value pairs if the cookie with the
         *      given name exists, null if it does not.
         *
         * @return {Object}
         */
        getSubs: function (name, options) {
            var cookies = this.parseCookieString(document.cookie, false, options);

            if (typeof cookies[name] === "string") {
                return this.parseCookieHash(cookies[name]);
            }

            return null;
        },

        /**
         * Removes a cookie from the machine by setting its expiration date to
         * sometime in the past.
         *
         * @param {String} name The name of the cookie to remove.
         * @param {Object} options (Optional) An object containing one or more
         *      cookie options: path (a string), domain (a string), and secure
         *      (true/false). The expires option will be overwritten by the
         *      method.
         *
         * @return {String} The created cookie string.
         *
         * @return {String} the created cookie string.
         */
        remove: function (name, options) {
            if (typeof name !== "string" || name === "") {
                return "";
            }

            options = mergeObjects(options || {}, {
                expires: new Date(0)
            });

            return this.set(name, "", options);
        },

        /**
         * Removes a sub cookie with a given name.
         *
         * @param {String} name The name of the cookie in which the sub-cookie
         *     exists.
         * @param {String} subName The name of the sub-cookie to remove.
         * @param {Object} options (Optional) An object containing one or more
         *      cookie options: path (a string), domain (a string), expires (a
         *      Date object), removeIfEmpty (true/false), and secure
         *      (true/false). This must be the same settings as the original
         *      sub-cookie.
         *
         * @return {String} The created cookie string.
         *
         * @return {String} the created cookie string.
         */
        removeSub: function (name, subName, options) {
            if (typeof name !== "string" || name === "") {
                return "";
            }

            if (typeof subName !== "string" || subName === "") {
                return "";
            }

            options = options || {};

            // get all sub-cookies for this cookie
            var subs = this.getSubs(name);

            // delete the indicated sub-cookie
            if (typeof subs === "object" && subs.hasOwnProperty(subName)) {
                delete subs[subName];
                if (!options.removeIfEmpty) {
                    return this.setSubs(name, subs, options); // reset the cookie
                } else {
                    // reset the cookie if there are sub-cookies left, else remove
                    for (var key in subs) {
                        if (subs.hasOwnProperty(key) && typeof subs[key] !== "function" &&
                            typeof subs[key] !== "undefined") {
                            return this.setSubs(name, subs, options);
                        }
                    }
                    return this.remove(name, options);
                }
            } else {
                return "";
            }
        },

        /**
         * Sets a cookie with a given name and value.
         *
         * @param {String} name The name of the cookie to set.
         * @param {Any} value The value to set for the cookie.
         * @param {Object} options (Optional) An object containing one or more
         *      cookie options: path (a string), domain (a string), expires (a
         *      Date object), secure (true/false), and raw (true/false). Setting
         *      raw to true indicates that the cookie should not be URI encoded
         *      before being set.
         *
         * @return {String} The created cookie string.
         */
        set: function (name, value, options) {
            if (typeof name !== "string" || name === "") {
                return null;
            }

            if (typeof value === "undefined") {
                return null;
            }

            options = options || {};

            var text = this.createCookieString(name, value, !options.raw, options);
            document.cookie = text;

            return text;
        },

        /**
         * Sets a sub cookie with a given name to a particular value.
         *
         * @param {String} name The name of the cookie to set.
         * @param {String} subName The name of the sub-cookie to set.
         * @param {Any} value The value to set.
         * @param {Object} options (Optional) An object containing one or more
         *      cookie options: path (a string), domain (a string), expires (a
         *      Date object), and secure (true/false).
         *
         * @return {String} The created cookie string.
         */
        setSub: function (name, subName, value, options) {
            if (typeof name !== "string" || name === "") {
                return "";
            }

            if (typeof subName !== "string" || subName === "") {
                return "";
            }

            if (typeof value === "undefined") {
                return "";
            }

            var hash = this.getSubs(name);
            if (!hash) {
                hash = {};
            }

            hash[subName] = value;

            return this.setSubs(name, hash, options);
        },

        /**
         * Sets a cookie with a given name to contain a hash of name-value
         * pairs.
         *
         * @param {String} name The name of the cookie to set.
         * @param {Object} value An object containing name-value pairs.
         * @param {Object} options (Optional) An object containing one or more
         *      cookie options: path (a string), domain (a string), expires (a
         *      Date object), and secure (true/false).
         *
         * @return {String} The created cookie string.
         */
        setSubs: function (name, value, options) {
            if (typeof name !== "string" || name === "") {
                return "";
            }

            if (typeof value !== "object") {
                return "";
            }

            var text = this.createCookieString(
                name,
                this.createCookieHashString(value),
                false,
                options
            );

            document.cookie = text;

            return text;
        },

        /**
         * Checks whether or not Cookies on the client browser.
         *
         * @return {Boolean} True if Cookies are enabled, otherwise False.
         */
        enabled: function() {
            if (navigator.cookieEnabled) {
                return true;
            }

            var key = "_",
                val = "_",
                cookieEnabled = false;

            this.set(key, val);
            if (this.get(key) === val) {
                cookieEnabled = true;
                this.remove(key);
            }

            return cookieEnabled;
        },

        /**
         * Clears all browser cookies.
         *
         * @return {Void}
         */
        clear: function () {
            var cookies = document.cookie.split(";");
            for (var i = 0; i < cookies.length; i++) {
                var cookie = cookies[i];
                var position = cookie.indexOf("=");
                var name = position > -1 ? cookie.substr(0, position) : cookie;
                document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT";
            }
        }
    };

    window.Cookie = Cookie;

}(window));