CrazySquirrel/EverCookie

View on GitHub
lib/Storages/Cookies.ts

Summary

Maintainability
D
2 days
Test Coverage
B
80%
"use strict";
/**
 * Import the storage interface which must be implemented
 */
import IStorage from "../../interfaces/IStorage";

/**
 * The document cookies storage
 */
export default class Cookies implements IStorage {

  public regValidKey = new RegExp("([a-zA-Z0-9_-]{0,})", "i");

  /**
   * The hash needs for splitting scopes storage
   */
  private hash: string;

  /**
   * The constructor should accept a hash to separate the scopes of storage
   * @param hash {string}
   */
  constructor(hash?: string) {
    this.hash = hash || location.hostname;
  }

  /**
   * The method returns the flag whether supported this storage type or not
   * @returns {boolean}
   */
  public isSupported(): boolean {
    return (
        typeof document === "object" &&
        typeof document.cookie === "string"
    );
  }

  /**
   * The method sets the value and returns true if it has been set
   * @param checkSupport {boolean}
   * @param key {string}
   * @param value {string}
   * @param expires {number}
   * @param path {string}
   * @param domain {string}
   * @param secure {boolean}
   * @return {boolean}
   */
  public setItem(checkSupport: boolean = true,
                 key: string,
                 value: string,
                 expires: number = 30,
                 path: string = "/",
                 domain: string|undefined = undefined,
                 secure: boolean = (location.protocol === "https:")): boolean {
    try {
      if (typeof domain === "undefined") {
        const d = location.hostname.split(".");
        domain = (d.length > 2 ? "." : "") + d.join(".");
      }
      /**
       * Validate input data
       */
      if (
          typeof checkSupport === "boolean" &&
          (
              typeof key === "string" &&
              this.regValidKey.test(key)
          ) &&
          (
              typeof value === "string" &&
              (value === "" || this.regValidKey.test(value))
          ) &&
          (
              typeof expires === "number" &&
              expires < 365
          ) &&
          typeof path === "string" &&
          (
              typeof domain === "string" &&
              domain.indexOf(location.hostname) !== -1
          ) &&
          (
              typeof secure === "boolean" &&
              secure === (location.protocol === "https:")
          )
      ) {
        /**
         * Validate input data
         */
        const a: any = document.createElement("a");
        a.href = "http://" + domain + path;
        if (
            a.hostname === domain ||
            a.path === path
        ) {
          /**
           * If that store is supported
           */
          if (!checkSupport || this.isSupported()) {
            /**
             * The hash needs for splitting scopes storage
             * @type {string}
             */
            const localKey: string = this.hash + "_" + key;
            /**
             * Save cookies for 30 days
             * @type {Date}
             */
            const date: Date = new Date();
            date.setTime(date.getTime() + (expires * 24 * 60 * 60 * 1000));
            const exp: string = date.toUTCString();
            /**
             * Encode value for store
             * @type {string}
             */
            value = encodeURIComponent(value);
            /**
             * Writing value to the document cookie storage
             * @type {string}
             */
            document.cookie = (
                localKey + "=" +
                value +
                ((exp) ? "; expires=" + exp : "") +
                ((path) ? "; path=" + path : "") +
                ((domain) ? "; domain=" + domain : "") +
                ((secure) ? "; secure" : "")
            );
            /**
             * If all ok return true
             */
            return this.getItem(checkSupport, key) === decodeURIComponent(value);
          } else {
            /**
             * If cookie does not supported return false
             */
            return false;
          }
        } else {
          /**
           * If input data is not valid
           */
          return false;
        }
      } else {
        /**
         * If input data is not valid
         */
        return false;
      }
    } catch (e) {
      /**
       * If something goes wrong return false
       */
      return false;
    }
  }

  /**
   * The method reads the value and returns it or returns false if the value does not exist
   * @param checkSupport {boolean}
   * @param key {string}
   * @returns {string|boolean}
   */
  public getItem(checkSupport: boolean = true,
                 key: string): string|boolean {
    try {
      /**
       * Validate input data
       */
      if (
          typeof checkSupport === "boolean" &&
          (
              typeof key === "string" &&
              this.regValidKey.test(key)
          )
      ) {
        /**
         * If that store is supported
         */
        if (!checkSupport || this.isSupported()) {
          /**
           * The hash needs for splitting scopes storage
           * @type {string}
           */
          key = this.hash + "_" + key;
          /**
           * Get the array from document cookie split by ;
           * @type {string[]}
           */
          const arrCookie: string[] = document.cookie.split(";");
          /**
           * Iterate through the cookies
           */
          for (let j = 0; j < arrCookie.length; j++) {
            const i = arrCookie[j];
            /**
             * Trim and split each cookie by = for key value pare
             * @type {string[]}
             */
            const v: string[] = i.trim().split("=", 2);
            /**
             * If it is correct cookie key return the value
             */
            if (v[0] === key) {
              /**
               * If the value was found return the value
               */
              return decodeURIComponent(v[1]);
            }
          }
          /**
           * If the value was not found return false
           */
          return false;
        } else {
          /**
           * If cookie does not supported return false
           */
          return false;
        }
      } else {
        /**
         * If input data is not valid
         */
        return false;
      }
    } catch (e) {
      /**
       * If something goes wrong return false
       */
      return false;
    }
  }

  /**
   * The method removes the value and return true if the value does not exist
   * @param checkSupport {boolean}
   * @param key {string}
   * @returns {boolean}
   */
  public removeItem(checkSupport: boolean = true,
                    key: string): boolean {
    try {
      /**
       * Validate input data
       */
      if (
          typeof checkSupport === "boolean" &&
          (
              typeof key === "string" &&
              this.regValidKey.test(key)
          )
      ) {
        /**
         * If that store is supported
         */
        if (!checkSupport || this.isSupported()) {
          /**
           * Set empty overdue value by key
           */
          this.setItem(checkSupport, key, "", -1 * 24 * 60 * 60);
          /**
           * If all ok return true
           */
          return (this.getItem(checkSupport, key) === false);
        } else {
          /**
           * If cookie does not supported return false
           */
          return false;
        }
      } else {
        /**
         * If input data is not valid
         */
        return false;
      }
    } catch (e) {
      /**
       * If something goes wrong return false
       */
      return false;
    }
  }

  /**
   * The method returns the array of string of available keys
   * @param checkSupport {boolean}
   * @returns {string[]}
   */
  public getKeys(checkSupport: boolean = true): string[] {
    try {
      /**
       * Validate input data
       */
      if (
          typeof checkSupport === "boolean"
      ) {
        /**
         * If that store is supported
         */
        if (!checkSupport || this.isSupported()) {
          /**
           * The array of available keys
           * @type {Array}
           */
          const arrKeys: string[] = [];
          /**
           * Get the array from document cookie split by ;
           * @type {string[]}
           */
          const arrCookie: string[] = document.cookie.split(";");
          /**
           * Iterate through the cookies
           */
          for (let j = 0; j < arrCookie.length; j++) {
            const i = arrCookie[j];
            /**
             * Trim and split each cookie by = for key value pare
             * @type {string[]}
             */
            const v: string[] = i.trim().split("=", 2);
            /**
             * If the key contains hash add it to the list
             */
            if (v[0].indexOf(this.hash) === 0) {
              /**
               * Add key to the list
               */
              arrKeys.push(v[0].substr(this.hash.length + 1));
            }
          }
          return arrKeys;
        } else {
          /**
           * If cookie does not supported return false
           */
          return [];
        }
      } else {
        /**
         * If input data is not valid
         */
        return [];
      }
    } catch (e) {
      /**
       * If something goes wrong return false
       */
      return [];
    }
  }

  /**
   * The method cleans the storage and return true if it is empty
   * @param checkSupport {boolean}
   * @returns {boolean}
   */
  public clear(checkSupport: boolean = true): boolean {
    try {
      /**
       * Validate input data
       */
      if (
          typeof checkSupport === "boolean"
      ) {
        /**
         * If that store is supported
         */
        if (!checkSupport || this.isSupported()) {
          const arrKeys = this.getKeys(checkSupport);
          if (arrKeys) {
            for (let j = 0; j < arrKeys.length; j++) {
              const i = arrKeys[j];
              this.removeItem(checkSupport, i);
            }
          }
          /**
           * If all ok return true
           */
          return (this.getKeys(checkSupport).length === 0);
        } else {
          /**
           * If cookie does not supported return false
           */
          return true;
        }
      } else {
        /**
         * If input data is not valid
         */
        return false;
      }
    } catch (e) {
      /**
       * If something goes wrong return false
       */
      return false;
    }
  }
}