Data Capsule

Sane key/value store access layer

Some more use cases

  • Local cache of blobs of data
  • Draft / Unsaved changes
  • Passing data between pages

How do we remember?

  • DOM Storage (aka sessionStorage / localStorage)
    • Easy, Fast, (Mostly) reliable
    • Will not persistent between machines
    • Might be cleared by user
    • Usually would be used where it is not a big deal if data is lost (or if it is short term storage)
  • Remote DB
    • Usually would be used where it is important to remember

DOM Storage

localStorage / sessionStorage

Session Storage is a lie!

Session cookie is even a worse lie

We never use session storage/cookie

DOM Storage is per origin

  • Storage is shared between all the pages that run on the same origin
  • Storage is shared between all the people who use the same browser (think internet cafe, universities)
  • Storage is shared between all the sites our same person builds
  • Storage is *not* shared between http://www.wix.com and https://www.wix.com
  • Storage is *not* shared between https://de.wix.com and https://fr.wix.com
  • Problems: global namespace, shared quota (5MB), privacy

DOM Storage doesn't always work

  • Incognito
  • Quota exceeded
  • Biggest problem: 3rd party storage policies

Conclusions

  • Access to local storage needs to be namespaced
  • Access to local storage needs to be scoped
  • Local storage quota needs to be managed
  • Access to local storage needs to be protected
  • Wanted: better abstraction!

Introducing

Data Capsule

(aka wixStorage/wixCache)

import {LocalStorageCapsule} from 'data-capsule';

const capsule = new LocalStorageCapsule({namespace: 'engage'});
await capsule.setItem('shahata', 123);
console.log(await capsule.getItem('shahata')); // logs 123
import {DataCapsule, LocalStorageStrategy} from 'data-capsule';

const capsule = new DataCapsule({
  strategy: new LocalStorageStrategy(),
  namespace: 'engage'
});
await capsule.setItem('shahata', 123);
console.log(await capsule.getItem('shahata')); // logs 123

How to use it

import {DataCapsule, LocalStorageStrategy} from 'data-capsule';
import secs from 'secs';

const capsule = new DataCapsule({
  strategy: new LocalStorageStrategy(),
  scope: {userId, siteId}, //optional
  namespace: 'engage' //required
});

await capsule.setItem('shahata', 123, {
  expiration: secs('2 days') //optional
});

console.log(await capsule.getItem('shahata')); // logs 123

Scope & Expiration

class BaseStrategy {
  setItem(key, value, {namespace, scope, expiration})
  getItem(key, {namespace, scope})
  removeItem(key, {namespace, scope})
  getAllItems({namespace, scope})
}

Strategies

class DataCapsule {
  constructor({strategy, namespace, scope})
  setItem(key, value, {namespace, scope, expiration})
  getItem(key, {namespace, scope})
  removeItem(key, {namespace, scope})
  getAllItems({namespace, scope})
}

Local Storage Strategy

const capsule = new DataCapsule({
  strategy: new LocalStorageStrategy()
});
  • Manages expiration
  • Auto cleans on quota exceeded
  • Maintains LRU
  • Consistent namespace & scope handling

Wix Storage Strategy

const capsule = new DataCapsule({
  strategy: new WixStorageStrategy()
});
  • Saves to remote DB using user preferences service
  • Scope are automatically per user, can be optionally per site
  • Expiration is rounded to days

Cached Storage Strategy

const capsule = new DataCapsule({
  strategy: new CachedStorageStrategy({
    remoteStrategy: new WixStorageStrategy(),
    localStrategy: new LocalStorageStrategy()
  })
});
  • Every value you get from server is cached locally (even if not found)
  • Every time you set or remove, it is cached locally
  • Expiration of local cache is up to 1 hour so it won't become stale

Frame Storage Strategy

const capsule = new DataCapsule({
  strategy: new FrameStorageStrategy(window.top, '*', 'secret'))
});
  • Commands are sent with postMessage to top window
  • Listener can get strategy in constructor (default is LocalStorageStrategy)
  • Listener can authenticate using callback
import {FrameStorageListener} from 'data-capsule';

const listener = new FrameStorageListener();
listener.start((source, origin, token) => token === 'secret');
//...
listener.stop();

Write your own strategy!

  • IndexDBStrategy
  • InMemoryStrategy
  • EncryptedStrategy
  • PigeonPostStrategy

Use it!

  • https://github.com/wix/data-capsule
  • Published to npm & bower
  • Can be used both in new and old stack
  • Test coverage 100%
  • Open source

Incidental open source

  • https://github.com/wix/greedy-split
  • https://github.com/wix/describe-jsdom
  • https://github.com/wix/secs

Questions???

Data Capsule

By Shahar Talmi

Data Capsule

  • 1,693