ANTON072
5/1/2014 - 1:52 PM

My fundamental cookie manager

My fundamental cookie manager

'use strict';

var _ = require('underscore');
var dotProp = require('dot-prop');

var global = global || window, doc = global.document, nav = global.navigator;
var RE = global.RegExp, J = global.JSON, decode = global.decodeURIComponent, encode = global.encodeURIComponent;

var escapedKey = function (key) {
  return encode(key).replace(/[\-\.\+\*]/g, '\\$&');
};

var reKey = function (_escapedKey) {
  return new RE('(?:(?:^|.*;)\\s*' + _escapedKey + '\\s*\\=\\s*([^;]*).*$)|^.*$');
};


/**
 * Fundamental cookie reader and writer
 * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie
 * @see https://gist.github.com/cou929/7869555
 */
exports.cookie = {
  /**
   * Get the value of specified key
   * @param {String} key Key string
   * @returns {String|Null} Value string for specified key
   */
  getItem: function (key) {
    if (!_.isString(key)) return null;
    return decode(doc.cookie.replace(reKey(escapedKey(key)), "$1")) || null;
  },
  /**
   * Create or update the pair of key and value
   * @param {String} key Key string
   * @param {String} val Value string
   * @param {Number|String|Date} end Seconds for `max-age` or date string/object for `expires`
   * @param {String} path Target path affected by this cookie
   * @param {String} domain Target domain name affected by this cookie
   * @param {Boolean} secure Flag to transmit this cookie only by HTTPS
   * @returns {Boolean} Flag whether the process completes successfully or not
   */
  setItem: function (key, val, end, path, domain, secure) {
    if (!key || /^(?:expires|max\-age|path|domain|secure)$/i.test(key)) return false;
    var expires = '';
    if (end) {
      switch (end.constructor) {
        case Number:
          expires = (Infinity === end) ? '; expires=Tue, 19 Jan 2038 03:14:07 GMT' : '; max-age=' + end;
          break;
        case String:
          expires = '; expires=' + end;
          break;
        case Date:
          expires = '; expires=' + end.toUTCString();
          break;
      }
    }
    doc.cookie = encode(key) + '=' + encode(val) + expires + (_.isString(domain) ? '; domain=' + domain : '') + (_.isString(path) ? '; path=' + path : '') + (secure ? '; secure' : '');
    return true;
  },
  /**
   * Check whether the key exists or not
   * @param {String} key Key string
   * @returns {Boolean} Flag for the existence
   */
  hasItem: function (key) {
    if (!_.isString(key)) return false;
    return reKey(escapedKey(key)).test(doc.cookie);
  },
  /**
   * Remove the pair of key and value by making them expire
   * @param {String} key Key string
   * @param {String} path Target path affected by this cookie
   * @param {String} domain Target domain name affected by this cookie
   * @returns {Boolean} Flag whether the process completes successfully or not
   */
  removeItem: function (key, path, domain) {
    if (!this.hasItem(key)) return false;
    doc.cookie = encode(key) + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT' + (_.isString(domain) ? '; domain=' + domain : '') + (_.isString(path) ? '; path=' + path : '');
    return true;
  },
  /**
   * Return an array of existing keys
   * @returns {Array} List of existing keys
   */
  keys: function () {
    var keys = doc.cookie.replace(/((?:^|\s*;)[^\=]+)(?=;|$)|^\s*|\s*(?:\=[^;]*)?(?:\1|$)/g, '').split(/\s*(?:\=[^;]*)?;\s*/);
    return _.map(keys, decode);
  },
  /**
   * Clear all the cookie
   */
  clear: function () {
    _.each(this.keys(), function (key) {
      this.removeItem(key);
    });
  },
  /**
   * Return the total length of cookie
   * @returns {Number} Total length (bytes)
   */
  size: function () {
    return doc.cookie.length;
  }
};


/**
 * Namespace based cookie manager
 * @class
 * @see https://gist.github.com/cou929/7869555
 */
exports.CookieManager = (function () {

  function CookieManager (rootKey, opts_) {
    this.maxSize = 4000;
    this.rootKey = rootKey;

    var opts = _.isObject(opts_) ? opts_ : {};
    this.end = opts.end || undefined;
    this.path = opts.path || undefined;
    this.domain = opts.domain || undefined;
    this.secure = opts.secure || undefined;

    this.exceeded = _.bind(this.exceeded, this);
    this.getRoot = _.bind(this.getRoot, this);
    this.setRoot = _.bind(this.setRoot, this);
    this.getItem = _.bind(this.getItem, this);
    this.setItem = _.bind(this.setItem, this);
    this.removeItem = _.bind(this.removeItem, this);
    this.clear = _.bind(this.clear, this);
  }

  var proto = CookieManager.prototype;

  proto.exceeded = function (value) {
    var stringifiedValue = _.isObject(value) ? J.stringify(value) : value;
    var currentSize = exports.cookie.size() + stringifiedValue.length;
    if (this.maxSize > currentSize) return false;
    return true;
  };

  proto.getRoot = function () {
    var data = J.parse(exports.cookie.getItem(this.rootKey));
    return _.isObject(data) ? data : {};
  };

  proto.setRoot = function (data) {
    return exports.cookie.setItem(this.rootKey, J.stringify(data), this.end, this.path, this.domain, this.secure);
  };

  proto.getItem = function (path) {
    var stringifiedData = exports.cookie.getItem(this.rootKey);
    var data = J.parse(stringifiedData);
    return dotProp.get(data, path);
  };

  proto.setItem = function (path, value) {
    if (this.exceeded(value)) throw new Error('Exceeded the limit of cookie size.');
    var data = this.getRoot();
    dotProp.set(data, path, value);
    return this.setRoot(data);
  };

  proto.removeItem = function (path) {
    var data = this.getRoot();
    dotProp.set(data, path, null);
    return this.setRoot(data);
  };

  proto.clear = function () {
    return exports.cookie.removeItem(this.rootKey);
  };

  return CookieManager;

})();