Skip to content

Latest commit

 

History

History
682 lines (513 loc) · 22.2 KB

README.md

File metadata and controls

682 lines (513 loc) · 22.2 KB

Proxy Storage

This library manages an adapter that implements an interface similar to Web Storage to normalize the API for document.cookie to be as window.localStorage and window.sessionStorage.

One of the advantages of this library is that the adapter stores the data as JSON, allowing to save Object and Array<Any> values, which is not the default behavior when using the native window.localStorage, window.sessionStorage or document.cookie storages.

It also provides a new mechanism -- memoryStorage, that persists the data in memory (for current browser-tab), even if a forced refresh is done on the page. It is a mimic of sessionStorage and it could be used as fallback when the other storage mechanisms are not available, for example, some browsers navigating in private mode. Read more about window.sessionStorage.

Another advantage with proxy-storage is that you can register interceptors as functions for the prototype methods of WebStorage class: setItem, getItem, removeItem, and clear, giving you the ability to intercept and modify the values to read, write, or delete.

Content

  1. Installing the library
  2. Including the library
  3. API: storage/default
  4. API: WebStorage
    1. Handling cookies
    2. Looping the storage
    3. Clearing data
    4. Interceptors
  5. API: configStorage
  6. API: isAvailable
  7. Polyfills

Installing the library

To include this library into your package manager with npm or yarn, run:

# with npm
$ npm install proxy-storage --save

# with yarn
$ yarn add proxy-storage

Including the library

proxy-storage can be included directly from a CDN in your page:

<!-- from unpkg.com -->
<script src="https://unpkg.com/proxy-storage/dist/proxy-storage.min.js"></script>

<!-- or from rawgit.com -->
<script src="https://cdn.rawgit.com/jherax/proxy-storage/2.3.2/dist/proxy-storage.min.js"></script>

In the above case, proxyStorage is included as a global object in the browser, and you can use it like this:

Browser

// gets the default storage mechanism (usually localStorage)
var storage = proxyStorage.default;

// or get an specific storage mechanism
var cookieStore = new proxyStorage.WebStorage('cookieStorage');

As proxy-storage is built as UMD (Universal Module Definition), it can be included from module loaders such as CommonJS, ES2015 Imports or AMD RequireJS.

CommonJS

// gets the default storage mechanism (usually localStorage)
var storage = require('proxy-storage').default;

// or get an specific storage mechanism
var WebStorage = require('proxy-storage').WebStorage;
var cookieStore = new WebStorage('cookieStorage');

ES2015 Imports

// gets the default storage mechanism (usually localStorage)
import storage from 'proxy-storage';

// or get some API members
import storage, { WebStorage, configStorage } from 'proxy-storage';
const cookieStore = new WebStorage('cookieStorage');

AMD

// using RequireJS
requirejs.config({
  paths: {
    // remove the extension .js
    'proxy-storage': '<PATH>/proxy-storage.min'
  }
});
require(['proxy-storage'], function(proxyStorage) {
  // gets the default storage mechanism (usually localStorage)
  var storage = proxyStorage.default;
  // or get an specific storage mechanism
  var sessionStore = new proxyStorage.WebStorage('sessionStorage');
});

See an example with RequireJS here: http://jsfiddle.net/FdKTn/77/

☗ Back to Index

API

The exposed API manages an adapter that stores the data as JSON, allowing to save and retrieve primitive, Object and Array<Any> values, thanks to JSON.stringify.

It also provides a new storage mechanism called memoryStorage which persists the data in memory (current tab in the browser), even if a forced refresh is done on the page, as sessionStorage does.

The WebStorage class has a static member called interceptors which lets you to register callback functions upon the prototype methods setItem, getItem, removeItem, and clear, giving you the ability to intercept and modify the values to read, write, or delete.

This library is exported as UMD (Universal Module Definition) and the API contains the following members:

storage (or default)

@type Object

This is the default member of the library and is an instance of WebStorage. It inherits the following members from the prototype:

  • setItem(key, value [,options]): stores a value given a key name.
    The options parameter is used only with instances of cookieStorage. Read more details here.
  • getItem(key [, noParse]): retrieves a value by its key name.
    If noParse is true then the value retrieved is not parsed with JSON.parse.
  • removeItem(key [,options]): deletes an item from the storage.
    The options parameter is used only with instances of cookieStorage. Read more details here.
  • clear(): removes all items from the storage instance.
  • length: gets the number of items stored in the instance.

The storage object is a proxy for the first storage mechanism available, usually localStorage, which is established when the library is initialized. The availability of the storage mechanisms is determined in the following order:

  1. localStorage: adapter of the window.localStorage object.
  2. cookieStorage: adapter of the document.cookie object, and normalized with the WebStorage interface.
  3. sessionStorage: adapter of the window.sessionStorage object.
  4. memoryStorage: internal storage mechanism that can be used as fallback when none of the above mechanisms are available. The behavior of memoryStorage is similar to sessionStorage, which let you to persist data in the current session (browser tab)

Important: As the storage object is a proxy for the first storage mechanism available, that means if localStorage is available to read and write data, it will be used, otherwise, if localStorage is not available, then cookieStorage will be used, and finally if none of the above are available, memoryStorage will be the fallback mechanism.

Example

import storage from 'proxy-storage';
// for browser: storage = proxyStorage.default;

// use the default storage mechanism, usually localStorage
storage.setItem('qwerty', [{ garbage: true, some: 'object' }]);
console.log(storage.getItem('qwerty'));
// [{ garbage: true, some: 'object' }]

console.log(storage.getItem('qwerty', true));
// '[{ "garbage": true, "some": "object" }]'

storage.setItem('persisted', true);
storage.setItem('o-really', { status: 'saved' });
console.log(`items: ${storage.length}`);

storage.removeItem('qwerty');
console.log(storage.getItem('qwerty'));
// null

// removes all data in the current storage
storage.clear();
console.log(`items: ${storage.length}`);
// items: 0

ProTip: you can override the default storage mechanism by calling the method configStorage.set()

☗ Back to Index

WebStorage

@type Class

This constructor mimics the Web Storage interface and manages an adapter that allows saving Object and Array<Any> values as JSON. It also lets you register interceptors for the methods setItem, getItem, removeItem and clear.

This is the usage:

var instance = new WebStorage(storageType)

Where storageType is a string that describes the type of storage to manage. It can be one of the following values:

  • "localStorage"
  • "cookieStorage"
  • "sessionStorage"
  • "memoryStorage"

Each instance inherits the following properties:

  • setItem(key, value [,options]): stores a value given a key name.
    The options parameter is used only with instances of cookieStorage. Read more details here.
  • getItem(key [, noParse]): retrieves a value by its key name.
    If noParse is true then the value retrieved is not parsed with JSON.parse.
  • removeItem(key [,options]): deletes an item from the storage.
    The options parameter is used only with instances of cookieStorage. Read more details here.
  • clear(): removes all items from the storage instance.
  • length: gets the number of items stored in the instance.

You can create multiple instances of WebStorage to handle different storage mechanisms. For example, to store data in cookies and also in sessionStorage, you can do as follow:

import storage, { WebStorage } from 'proxy-storage';
// for browser:
// var storage = proxyStorage.default;
// var WebStorage = proxyStorage.WebStorage;

// use the default storage mechanism, usually localStorage
storage.setItem('tv-show', { name: 'Regular Show' });

// store in sessionStorage
const sessionStore = new WebStorage('sessionStorage');
sessionStore.setItem('character', { name: 'Mordecai' });

// store in cookies
const options = { expires: {days: 1} };
const cookieStore = new WebStorage('cookieStorage');
cookieStore.setItem('character', { name: 'Rigby' }, options);

Important: If you request an instance of a storage mechanism that is not available, you will get an instance of the first storage mechanism available, so you can continue storing data. It is useful when you rely on a specific storage mechanism. Let's see an example:

import { WebStorage, isAvailable } from 'proxy-storage';
// for browser:
// var WebStorage = proxyStorage.WebStorage;
// var isAvailable = proxyStorage.isAvailable;

 // let's suppose the following storage is not available
 isAvailable.sessionStorage = false;

 const sessionStore = new WebStorage('sessionStorage');
 // sessionStorage is not available. Falling back to memoryStorage
 sessionStore.setItem('ulugrun', 3.1415926);

 // as sessionStorage is not available, the instance
 // obtained is the fallback mechanism: memoryStorage
 console.dir(sessionStore);

☗ Back to Index

Handling cookies

When you create an instance of WebStorage with cookieStorage, the method setItem() receives an optional argument as the last parameter that configures the way how the cookie is stored.

Signature of setItem:

instance.setItem(key: String, value: Any, options: Object) : void

Where the options parameter is an object with the following properties:

  • domain{string}: the domain or subdomain where the cookie will be valid.
  • path{string}: relative path where the cookie is valid. Default "/"
  • secure{boolean}: if provided, creates a secure cookie over HTTPS.
  • expires{Date, object}: the cookie expiration date. You can pass an object describing the expiration:
    • date{Date}: if provided, this date will be applied, otherwise the current date is used.
    • minutes{number}: minutes to add / subtract
    • hours{number}: hours to add / subtract
    • days{number}: days to add / subtract
    • months{number}: months to add / subtract
    • years{number}: years to add / subtract

Example

import { WebStorage } from 'proxy-storage';
// for browser: WebStorage = proxyStorage.WebStorage;

const cookieStore = new WebStorage('cookieStorage');

let data = {
  start: new Date().toISOString(),
  sessionId: 'J34H5609-SG7ND98W3',
  platform: 'Linux x86_64',
};

cookieStore.setItem('activity', data, {
  expires: { minutes: 30 },
});

cookieStore.setItem('testing1', true, {
  secure: true,
  path: '/jherax',
  expires: new Date('2017/12/31'),
});

cookieStore.setItem('testing2', [1,4,7], {
  domain: '.github.com',
  expires: { days: 1 },
});

cookieStore.setItem('testing3', 3, {
  expires: {
    date: new Date('2018/03/06'),
    hours: -6,
  },
});

Important: Take into account that if you want to modify or remove a cookie that was created under specific path or domain (subdomain), you need to specify the domain or path attributes in the options parameter when calling setItem(key, value, options) or removeItem(key, options).

If you have created the cookie with proxyStorage, it will handle the metadata internally, so that you can call removeItem(key) with no more arguments. Otherwise you will need to provide the metadata path or domain:

cookies-metadata

// change the value of an external cookie in /answers
cookieStore.setItem('landedAnswers', 999, {
  path: '/answers',
});

// remove an external cookie in a subdomain
cookieStore.removeItem('optimizelyEndUserId', {
  domain: '.healthcare.org',
});

☗ Back to Index

Looping the storage

You can loop over the items in the storage instance, but it is not recommended to rely on it because navigable items in the storage instance are not synchronized with external changes, for example, a cookie has expired, or a cookie/localStorage element was created/deleted from another page with the same domain/subdomain.

const sessionStore = new WebStorage('sessionStorage');

sessionStore.setItem('test1', 1);
sessionStore.setItem('test2', 2);

// loop over the storage object (not recommended)
Object.keys(sessionStore).forEach((key) => {
  console.log(key, sessionStore[key]);
});

// or this way (not recommended either)
for (let key in sessionStore) {
  console.log(key, sessionStore[key]);
}

It is also applied not only when reading, but also when writing to storage:

// not recommended: not synchronized with the real storage
var title = cookieStorage['title'];
var session = cookieStorage.sessionId;
cookieStorage['sessionId'] = 'E6URTG5';

// good practice: it is synchronized for external changes
var title = cookieStorage.getItem('title');
var session = cookieStorage.getItem('sessionId');
cookieStorage.setItem('sessionId', 'E6URTG5');

☗ Back to Index

Clearing data

You can use the removeItem(key) method to delete a specific item in the storage instance, or use the clear() method to remove all items in the storage instance, e.g.

import { WebStorage } from 'proxy-storage';
// for browser: WebStorage = proxyStorage.WebStorage;

function clearAllStorages() {
  new WebStorage('localStorage').clear();
  new WebStorage('sessionStorage').clear();
  new WebStorage('cookieStorage').clear();
}

Important: When handling cookieStorage, the method clear() only will remove the cookies with no metadata or those created through proxyStorage. Take into account that if you want to remove a cookie that was created from another page, you need to set the domain or path attributes in the options parameter when calling removeItem(key, options).
See handling-cookies.

☗ Back to Index

Interceptors

The WebStorage class exposes the static member interceptors which lets you to register callback functions upon the prototype methods setItem, getItem, removeItem, and clear. It is very useful when you need to perform an action to intercept the value to read, write, or delete.

  • WebStorage.interceptors(command, action): adds an interceptor to the API method.
    • command{string}: name of the API method to intercept. It can be setItem, getItem, removeItem, clear.
    • action{function}: callback executed when the API method is called. It must return the value in order to be processed by the API method.

ProTip: interceptors are registered in chain, allowing you to transform the value passed and returned in each callback.

import storage, { WebStorage } from 'proxy-storage';
// for browser:
// var storage = proxyStorage.default;
// var WebStorage = proxyStorage.WebStorage;

// adds first interceptor for 'setItem'
WebStorage.interceptors('setItem', (key, value/*, options*/) => {
  if (key === 'storage-test') {
    // encodes the 'id' to base64
    value.id = btoa(value.id);
    return value;
  }
});

// adds second interceptor for 'setItem'
WebStorage.interceptors('setItem', (key, value) => {
  // does not apply any transformation
  console.info('setItem: See the application storage in your browser');
  console.log(`${key}: ${JSON.stringify(value)}`);
});

// adds first interceptor for 'getItem'
WebStorage.interceptors('getItem', (key, value) => {
  if (key === 'storage-test') {
    // decodes from base64
    value.id = +atob(value.id);
  }
  return value;
});

WebStorage.interceptors('removeItem', (key/*, options*/) => {
  console.log(`removeItem: ${key}`);
});

// uses the default storage mechanism (usually localStorage)
storage.setItem('storage-test', {id: 1040, text: 'it works!'});
let data = storage.getItem('storage-test');
console.log(data);

// storage.removeItem('storage-test');

☗ Back to Index

configStorage

@type Object

Sets the default storage mechanism, or get the current default storage mechanism. This object contains the following methods:

  • get(): returns a string with the name of the current storage mechanism.
  • set(storageType): sets the default storage mechanism. storageType must be one of the following strings: "localStorage", "sessionStorage", "cookieStorage", or "memoryStorage". If the storage type provided is not valid, it will throw an exception.

Example

import storage, { configStorage } from 'proxy-storage';
// for browser:
// var storage = proxyStorage.default;
// var configStorage = proxyStorage.configStorage;

// gets the default storage mechanism
let storageName = configStorage.get();
console.log('Default:', storageName);

storage.setItem('defaultStorage', storageName);

// sets the new default storage mechanism
configStorage.set('cookieStorage');
storageName = configStorage.get();
console.log('Current:', storageName);

// if you are running in the browser,
// you MUST update the alias of the storage:
// storage = proxyStorage.default;
storage.setItem('currentStorage', storageName);

☗ Back to Index

isAvailable

@type Object

Determines which storage mechanisms are available to read, write, or delete data.

It contains the following flags:

  • localStorage: is set to true if the local storage is available.
  • cookieStorage: is set to true if the document cookies are available.
  • sessionStorage: is set to true if the session storage is available.
  • memoryStorage: always is set to true.

Example

import storage, * as proxyStorage from 'proxy-storage';
// * imports the entire module's members into proxyStorage.

const flags = proxyStorage.isAvailable;

if (!flags.sessionStorage) {
  // forces the storage mechanism to memoryStorage
  proxyStorage.configStorage.set('memoryStorage');
}

let data = storage.getItem('hidden-data');

if (!data) {
  storage.setItem('hidden-data', {
    mechanism: 'memoryStorage',
    availability: 'Current page: you can refresh the page, data still remain'
  });
}

console.log('in memoryStorage', data);

☗ Back to Index

Polyfills

This library is written using some of the new ES5/ES6 features. If you have to support Non-standard-compliant browsers like Internet Explorer, you can polyfill some of the missing features with the following alternatives:

Using es6-shim

<!-- put this script FIRST, before all other scripts -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.35.3/es6-shim.min.js"></script>

Using polyfill.io

<!-- put this script FIRST, before all other scripts -->
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=default-3.3"></script>

Polyfill.io reads the User-Agent header of each request and returns the polyfills that are suitable for the requesting browser.

If you want to request specific polyfills, you can pass a query parameter to the url, for example:

<!--[if IE]>
<script src="https://polyfill.io/v2/polyfill.min.js?features=default-3.3&flags=always"></script>
<![endif]-->

Read the list of available features: Features and Browsers Supported.

☗ Back to Index

Versioning

This projects adopts the Semantic Versioning (SemVer) guidelines:

<MAJOR>.<MINOR>.<PATCH>

Given a version number MAJOR.MINOR.PATCH, increment the:

  1. MAJOR version when you make incompatible API changes.
  2. MINOR version when you add functionality in a backwards-compatible manner.
  3. PATCH version when you make backwards-compatible bug fixes.

☗ Back to Index

Issues

To report an issue and keep traceability of bug-fixes, please report to:

Changelog

The change history for each version is documented here.

License

This project is released under the MIT license. This license applies ONLY to the source of this repository and doesn't extend to any other distribution, or any other 3rd party libraries used in a repository. See LICENSE file for more information.