Cross domain local storage, with permissions

Overview

cross-storage

Cross domain local storage, with permissions. Enables multiple browser windows/tabs, across a variety of domains, to share a single localStorage. Features an API using ES6 promises.

Build Status

Overview

The library is a convenient alternative to sharing a root domain cookie. Unlike cookies, your client-side data isn't limited to a few kilobytes - you get up to 2.49M chars. For a client-heavy application, you can potentially shave a few KB off your request headers by avoiding cookies. This is all thanks to LocalStorage, which is available in IE 8+, FF 3.5+, Chrome 4+, as well as a majority of mobile browsers. For a list of compatible browsers, refer to caniuse.

How does it work? The library is divided into two types of components: hubs and clients. The hubs reside on a host of choice and interact directly with the LocalStorage API. The clients then load said hub over an embedded iframe and post messages, requesting data to be stored, retrieved, and deleted. This allows multiple clients to access and share the data located in a single store.

Care should be made to limit the origins of the bidirectional communication. As such, when initializing the hub, an array of permissions objects is passed. Any messages from clients whose origin does not match the pattern are ignored, as well as those not within the allowed set of methods. The set of permissions are enforced thanks to the same-origin policy. However, keep in mind that any user has full control of their local storage data - it's still client data. This only restricts access on a per-domain or web app level.

Hub

// Config s.t. subdomains can get, but only the root domain can set and del
CrossStorageHub.init([
  {origin: /\.example.com$/,            allow: ['get']},
  {origin: /:\/\/(www\.)?example.com$/, allow: ['get', 'set', 'del']}
]);

Note the $ for matching the end of the string. The RegExps in the above example will match origins such as valid.example.com, but not invalid.example.com.malicious.com.

Client

var storage = new CrossStorageClient('https://store.example.com/hub.html');

storage.onConnect().then(function() {
  return storage.set('newKey', 'foobar');
}).then(function() {
  return storage.get('existingKey', 'newKey');
}).then(function(res) {
  console.log(res.length); // 2
}).catch(function(err) {
  // Handle error
});

Installation

The library can be installed via bower:

bower install cross-storage

Or using npm:

npm install cross-storage

along with browserify:

var CrossStorageClient = require('cross-storage').CrossStorageClient;
var CrossStorageHub    = require('cross-storage').CrossStorageHub;

When serving the hub, you may want to set the CORS and CSP headers for your server depending on client/hub location. For example:

{
  'Access-Control-Allow-Origin':  '*',
  'Access-Control-Allow-Methods': 'GET,PUT,POST,DELETE',
  'Access-Control-Allow-Headers': 'X-Requested-With',
  'Content-Security-Policy':      "default-src 'unsafe-inline' *",
  'X-Content-Security-Policy':    "default-src 'unsafe-inline' *",
  'X-WebKit-CSP':                 "default-src 'unsafe-inline' *",
}

If using inline JS to create the hub, you'll need to specify unsafe-inline for the CSP headers. Otherwise, it can be left out if simply including the init code via another resource.

API

CrossStorageHub.init(permissions)

Accepts an array of objects with two keys: origin and allow. The value of origin is expected to be a RegExp, and allow, an array of strings. The cross storage hub is then initialized to accept requests from any of the matching origins, allowing access to the associated lists of methods. Methods may include any of: get, set, del, getKeys and clear. A 'ready' message is sent to the parent window once complete.

CrossStorageHub.init([
  {origin: /localhost:3000$/, allow: ['get', 'set', 'del', 'getKeys', 'clear']}
]);

new CrossStorageClient(url, [opts])

Constructs a new cross storage client given the url to a hub. By default, an iframe is created within the document body that points to the url. It also accepts an options object, which may include a timeout, frameId, and promise. The timeout, in milliseconds, is applied to each request and defaults to 5000ms. The options object may also include a frameId, identifying an existing frame on which to install its listeners. If the promise key is supplied the constructor for a Promise, that Promise library will be used instead of the default window.Promise.

var storage = new CrossStorageClient('http://localhost:3000/hub.html');

var storage = new CrossStorageClient('http://localhost:3000/hub.html', {
  timeout: 5000,
  frameId: 'storageFrame'
});

CrossStorageClient.prototype.onConnect()

Returns a promise that is fulfilled when a connection has been established with the cross storage hub. Its use is required to avoid sending any requests prior to initialization being complete.

storage.onConnect().then(function() {
  // ready!
});

CrossStorageClient.prototype.set(key, value)

Sets a key to the specified value. Returns a promise that is fulfilled on success, or rejected if any errors setting the key occurred, or the request timed out.

storage.onConnect().then(function() {
  return storage.set('key', JSON.stringify({foo: 'bar'}));
});

CrossStorageClient.prototype.get(key1, [key2], [...])

Accepts one or more keys for which to retrieve their values. Returns a promise that is settled on hub response or timeout. On success, it is fulfilled with the value of the key if only passed a single argument. Otherwise it's resolved with an array of values. On failure, it is rejected with the corresponding error message.

storage.onConnect().then(function() {
  return storage.get('key1');
}).then(function(res) {
  return storage.get('key1', 'key2', 'key3');
}).then(function(res) {
  // ...
});

CrossStorageClient.prototype.del(key1, [key2], [...])

Accepts one or more keys for deletion. Returns a promise that is settled on hub response or timeout.

storage.onConnect().then(function() {
  return storage.del('key1', 'key2');
});

CrossStorageClient.prototype.getKeys()

Returns a promise that, when resolved, passes an array of keys currently in storage.

storage.onConnect().then(function() {
  return storage.getKeys();
}).then(function(keys) {
  // ['key1', 'key2', ...]
});

CrossStorageClient.prototype.clear()

Returns a promise that, when resolved, indicates that all localStorage data has been cleared.

storage.onConnect().then(function() {
  return storage.clear();
});

CrossStorageClient.prototype.close()

Deletes the iframe and sets the connected state to false. The client can no longer be used after being invoked.

storage.onConnect().then(function() {
  return storage.set('key1', 'key2');
}).catch(function(err) {
  // Handle error
}).then(function() {
  storage.close();
});

Compatibility

For compatibility with older browsers, simply load a Promise polyfill such as es6-promise.

You can also use RSVP or any other ES6 compliant promise library. Supports IE8 and up using the above polyfill. A JSON polyfill is also required for IE8 in Compatibility View. Also note that catch is a reserved word in IE8, and so error handling with promises can be done as:

storage.onConnect().then(function() {
  return storage.get('key1');
}).then(function(res) {
  // ... on success
})['catch'](function(err) {
  // ... on error
});

Breaking Changes

API breaking changes were introduced in both 0.6 and 1.0. Refer to releases for details.

Notes on Safari 7+ (OSX, iOS)

All cross-domain local storage access is disabled by default with Safari 7+. This is a result of the "Block cookies and other website data" privacy setting being set to "From third parties and advertisers". Any cross-storage client code will not crash, however, it will only have access to a sandboxed, isolated local storage instance. As such, none of the data previously set by other origins will be accessible. If an option, one could fall back to using root cookies for those user agents, or requesting the data from a server-side store.

Compression

Most localStorage-compatible browsers offer at least ~5Mb of storage. But keys and values are defined as DOMStrings, which are UTF-8 encoded using single 16-bit sequences. That means a string of ~2.5 million ASCII characters will use up ~5Mb, since they're 2 bytes per char.

If you need to maximize your storage space, consider using lz-string. For smaller strings, it's not uncommon to see a 50% reduction in size when compressed, which will bring you a lot closer to 5 million characters. At that point, you're only limited by the average compression rate of your strings.

Building

The minified, production JavaScript can be generated with gulp by running gulp dist. If not already on your system, gulp can be installed using npm install -g gulp

Tests

Tests can be ran locally using npm test. Tests are ran using Zuul, and the Travis CI build uses Sauce Labs for multi-browser testing as well.

Copyright and license

Copyright 2016 Zendesk

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Comments
  • getOrigin returns :// in IE9

    getOrigin returns :// in IE9

    _getOrigin method of CrossStorageClient return :// in IE9 in the following lineorigin = uri.protocol + '//' + uri.host; because uri.protocol is : and uri.host empty string. Why not to use origin and host of window.location if it's available, and as a fallback, use current implementation?

    opened by bolshchikov 14
  • Uncaught TypeError: Cannot read property 'appendChild' of null

    Uncaught TypeError: Cannot read property 'appendChild' of null

    I dont know what I'm doing wrong I followed the example and I'm getting :

    Uncaught TypeError: Cannot read property 'appendChild' of null
    

    On the Hub I put :

    CrossStorageHub.init([
       {origin: /:\/\/(www\.)?mysite.com\/something\/myapp?     my.tabName=01rf0000000D4nF#!\/dashboard$/, allow: ['get', 'set', 'del']}
    ]);
    

    And for the Client I put:

    var storage = new CrossStorageClient('https://mysite.com/something/myapp?my.tabName=01rf0000000D4nF#!/dashboard');
        var setKeys = function () {
          return storage.set('newUserData', 'foo', 9000)
          .then(function() {
            return storage.get('key2', 'bar');
          });
        };
        storage.onConnect()
        .then(setKeys)
        .then(function() {
          return storage.get('key1');
        }).then(function(res) {
          console.log(res); // 'foo'
        })['catch'](function(err) {
          console.log(err);
        });
    

    Its not showing the last bit of code but I got it right from the example

    opened by nousacademy 9
  • Add localstorage event handler

    Add localstorage event handler

    We needed to be updated when another window changed a value, so this PR implements forwarding storage events from the hub to the client.

    Resolves initial request in https://github.com/zendesk/cross-storage/issues/19. It tries to behave like the storage event as much as possible, rather than implementing own event for different types of requests. This also means that no events will be sent from hubs that actually make the changes.

    Feedback appreciated, I'm not sure if I've followed all conventions of the library or if the API is the best available.

    opened by alvinlindstam 7
  • Option or methods to skip serialization

    Option or methods to skip serialization

    We're trying to use this over at NPR as a drop-in solution to access and set localstorage variables used for analytics from the sub-domains we use for special projects. I love the overall design of the library and the high quality of the code, especially the use of ES6 promises.

    However, it doesn't work as a drop-in solution because of the choice to serialize the data in and out of localstorage. Because my team and our projects don't have control over how those variables are set on our parent website and nobody thought to adopt cross-storage when they originally decided to use LocalStorage to stash this information, we can't use the library without modification.

    For now, we're going to maintain a fork which skips the json serialization and directly sets/gets the values out of LocalStorage on the hub. But I'm curious if you'd be open to a initialization option that skips the serialization or alternate get/set methods (getDirect?) that accomplish the same. Since we're forking anyway, we could easily make such a thing and submit a pull request.

    I suspect this would help spur adoption, because we can't be the only ones who need to retrofit a cross domain storage solution into an existing ecosystem of sites where the choice of storage format is out of their control.

    opened by eads 6
  • Use as a node\browserify npm module

    Use as a node\browserify npm module

    There's an option to install as an npm-module, but did anyone actually tried to use it from as a node-module from browserify? It's not possible since it's not exporting anything from the root index.js

    opened by gothy 6
  • Updating storage

    Updating storage

    Is there anyway i can update the storage?

    As of now we call below method initially and when value updated

    menuService.prototype.setMenuStructureInLocalStorage = function (menuStructureKey, menuStructure) {

        var crossDomainStorage = new CrossStorageClient('localstoragehub.html');
        var setCrossDomainStorageKeys = function () {
            return crossDomainStorage.set(that.constants.MENU_STORAGE_KEY.COMBINED_MENU_STRUCTURE_XT, menuStructure);
        };
        crossDomainStorage.onConnect()
        .then(setCrossDomainStorageKeys);
    };
    

    But it does not update it.. It always has old values

    opened by bhuranideepak82 5
  • Adds `rawMode` to disable serializing localStorage values

    Adds `rawMode` to disable serializing localStorage values

    We've been integrating cross-storage with a localStorage ecosystem that does not serialize its values; thus, we needed a way to use cross-storage that did not depend on serialization. This PR adds a configuration option to the client init called rawMode that will make getting/setting values not rely on serialization.

    Another way to do this would be to use native ES6 classes and create a rawCrossStorageClient subclass.

    See #21 for more.

    opened by TylerFisher 5
  • Storage update event

    Storage update event

    Send an update message an let the application create a trigger (so when another tab changes it the other can detect this)

    window.addEventListener('storage', function(e) {
      window.parent.postMessage('cross-storage:updated', '*');
    });
    
    opened by TNAJanssen 5
  • Error handling onConnect()

    Error handling onConnect()

    Hi,

    First of all thanks for this really useful library. It should solve the problem I've been trying to solve for days, if only I can get it to work!

    I'm new to promises and perhaps I'm missing something. I can't seem to gets a successful connection to the hub and can't tell why since there's no connection error callback. Is there a way to handle connection errors to the hub using promises?

    In the example code below the catch never gets called because it can't connect for some reason.

    var storage = new CrossStorageClient('https://store.example.com/hub.html');

    storage.onConnect().then(function() { // Set a key with a TTL of 90 seconds return storage.set('newKey', 'foobar', 90000); }).then(function() { return storage.get('existingKey', 'newKey'); }).then(function(res) { console.log(res.length); // 2 }).catch(function(err) { // Handle error });

    Any guidance would be extremely appreciated.

    Thank you.

    opened by siulca 5
  • Main file as client.js

    Main file as client.js

    Would it be possible to set the main file as client.js? Currently running into issues on our suite of projects that use Wiredep that are unable to load it in without setting an override for the main file in our bower.json's.

    opened by JasonLi914 4
  • Prevent memory leaks when calling _request()

    Prevent memory leaks when calling _request()

    Whenever the _request() function gets called, if the request callback gets properly called (i.e. it is executed before timeout), the callback function is never removed from the _requests object. We need to remove the callbacks when they are executed, otherwise they will keep accumulating in memory indefinitely.

    opened by frederick-loranger 4
  • Requesting session storage option

    Requesting session storage option

    Greetings. I love the code and was wondering if cross-storage would support session storage in the future? Noticed an older pull request from @cyrilchapon.

    opened by jonathanwalton720 0
  • Not working with the modern browsers if third-party cookies are blocked

    Not working with the modern browsers if third-party cookies are blocked

    When I tried running the example given within the repo it is giving me this error.

    Error: Closing client. Could not access localStorage in hub.

    Google Chrome Version - 90.0.4430.212 (Official Build) (x86_64)

    Also, this seems legit to me as soul purpose of third-party cookie blocking is it should block any kind of data access in cross origins. There can't be any loophole to that.

    opened by hetpatel33 1
  • CrossStorageClient could not connect in IE 11

    CrossStorageClient could not connect in IE 11

    I'm debugging a website on my machine with Windows and Windows Subsystem for Linux.

    I use cross-storage in my website. I can launch the website https://localhost:3000/sign under Chrome, whereas in IE it raises an error CrossStorageClient could not connect, which blocks the page.

    No error in Chrome: [enter image description here]1

    CrossStorageClient could not connect in IE [enter image description here]2

    In the client side, it seems that it is new CrossStorageClient(...) that raises the error.

    const storage = new CrossStorageClient(`https://localhost:3000/htmls/hub.html`, {}); 
    
    export async function getSignToken() {
      await storage.onConnect();
    

    If I open https://localhost:3000/htmls/hub.html in IE in a tab, it shows a problem of security certificate, I could click on Go on to the webpage (not recommended) to continue. (The CrossStorageClient could not connect error of the client side is still raised regardless of this). [enter image description here]3

    Here is hub.html on the server side:

    <!doctype html>
    <head>
      <title>Production Cross Storage Hub</title>
    </head>
    <body>
      <script type="text/javascript" src="/javascripts/hub.js"></script>
      <script>
        CrossStorageHub.init([
            {origin: /:\/\/localhost(:[0-9]*)?$/, allow: ['get', 'set', 'del']},
            {origin: /\.10studio.tech$/, allow: ['get', 'set', 'del']},
            {origin: /:\/\/(www\.)?10studio.tech$/, allow: ['get', 'set', 'del']}
        ]);
      </script>
    </body>
    </html>
    

    So, does anyone know how to fix this CrossStorageClient could not connect error?

    opened by chengtie 1
  • Add storage opt in hub. Can now configure localStorage or sessionStorage

    Add storage opt in hub. Can now configure localStorage or sessionStorage

    Hello.

    I made this because we needed support for sessionStorage option.

    Changes :

    • Hub() constructor now accepts a single opts arguments
      • opts.permissions behaves like previously permissions
      • opts.storage is added. Can be one of window.localStorage and window.sessionStorage (or any object with the same API, I guess). It defaults to window.localStorage

    Compat checks with window.localStorage are now made to opts.storage

    TODO : Some tests should be added to ensure

    • Chosen storage is respected
    • Unprovided storage defaults to localStorage
    • Everything works like excepted
    opened by cyrilchapon 0
  • Feature Request: storageEvent listener

    Feature Request: storageEvent listener

    It'd be great if we can listen any change event from other origin. It could be achieved via HTML5 storageEvent

    • https://developer.mozilla.org/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API#Responding_to_storage_changes_with_the_StorageEvent
    • https://html5demos.com/storage-events
    opened by rokoroku 0
  • Error: CrossStorageClient could not connect  at client.js:152

    Error: CrossStorageClient could not connect at client.js:152

    Hello I am getting an error "Error: CrossStorageClient could not connect at client.js:152". I have two sub domains auth-example.com and expert-example.com. I am declaring hub in auth-example.com like this. "CrossStorageHub.init([{origin: /://(www.)?auth-example.com$/, allow: ['get', 'set', 'del']}]);" and client side is expert-example.com " var storage = new CrossStorageClient('https://auth.example.com');", everything works perfect only on localhosts but error is occurring on live. and I can console in auth-example.com(in hub) but getting error on sub domains side (client side) Anyone can help me?

    opened by mihirsuchak11 9
Releases(1.0.0)
Owner
Zendesk
Zendesk
An AngularJS module that gives you access to the browsers local storage with cookie fallback

angular-local-storage An Angular module that gives you access to the browsers local storage Table of contents: Get Started Video Tutorial Development

Gregory Pike 2.9k Dec 25, 2022
local storage wrapper for both react-native and browser. Support size controlling, auto expiring, remote data auto syncing and getting batch data in one query.

react-native-storage This is a local storage wrapper for both react native apps (using AsyncStorage) and web apps (using localStorage). ES6 syntax, pr

Sunny Luo 2.9k Dec 16, 2022
A enhanced web storage with env support, expire time control, change callback and LRU storage clear strategy.

enhanced-web-storage A enhanced web storage with env support, expire time control, change callback and LRU storage clear strategy. How to Start import

Ziwen Mei 15 Sep 10, 2021
Cross-browser storage for all use cases, used across the web.

Store.js Cross-browser storage for all use cases, used across the web. Store.js has been around since 2010 (first commit, v1 release). It is used in p

Marcus Westin 13.9k Dec 29, 2022
The perfect combination: local business shopping and crypto.

The perfect combination: local business shopping and crypto. Get passive income and support local businesses.

Mauricio Figueiredo 4 Mar 19, 2022
💾 Offline storage, improved. Wraps IndexedDB, WebSQL, or localStorage using a simple but powerful API.

localForage localForage is a fast and simple storage library for JavaScript. localForage improves the offline experience of your web app by using asyn

localForage 21.5k Jan 4, 2023
JS / CSS / files loader + key/value storage

bag.js - JS / CSS loader + KV storage bag.js is loader for .js / .css and other files, that uses IndexedDB/ WebSQL / localStorage for caching. Conside

Nodeca 86 Nov 28, 2022
:sunglasses: Everything you need to know about Client-side Storage.

awesome-web-storage Everything you need to know about Client-side Storage. Table of Contents Introduction Browser Support Cookies Pros Cons API Useful

Varun Malhotra 420 Dec 12, 2022
💾 Offline storage, improved. Wraps IndexedDB, WebSQL, or localStorage using a simple but powerful API.

localForage localForage is a fast and simple storage library for JavaScript. localForage improves the offline experience of your web app by using asyn

localForage 21.5k Jan 1, 2023
⁂ The simple file storage service for IPFS & Filecoin

⁂ web3.storage The simple file storage service for IPFS & Filecoin. Getting started This project uses node v16 and npm v7. It's a monorepo that use np

Web3 Storage 423 Dec 25, 2022
This is an upload script which allows you to upload to web3 storage using JS.

This is an upload script which allows you to upload to web3 storage using JS. first make sure to run npm install on the directory run script using nod

null 1 Dec 24, 2021
A javascript based module to access and perform operations on Linode object storage via code.

Linode Object Storage JS Module A javascript based module to access and perform operations on Linode object storage via code. Code Guardian Installing

Core.ai 3 Jan 11, 2022
Dustbin - Just Another Text Storage Service

Dustbin It's just another text storage service built in fastify. API Ofcouse we

Dustbin Server 25 Dec 3, 2022
Store your data in the world's fastest and most secure storage, powered by the blockchain technology⚡️

Store your data in the world's fastest and most secure storage, powered by the blockchain technology.

BlockDB 3 Mar 5, 2022
Expirable data storage based on localStorage and sessionStorage.

Expirable storage About The Project Expirable data storage based on localStorage and sessionStorage. Getting Started To get a local copy up and runnin

Wayfair Tech – Incubator 5 Oct 31, 2022
Browser storage interface for IndexedDB, WebSQL, LocalStorage, and in memory data with Schema and data validator.

Client Web Storage Browser storage interface for IndexedDB, WebSQL, LocalStorage, and in memory data with basic Schema and data validation. Installati

Before Semicolon 19 Sep 30, 2022
Authentication, Permissions and Payload Rules with Nextjs using ReactJ with Typescript

Auth with Next.js ?? About Authentication, Permissions and Payload Rules with Nextjs using ReactJS with Typescript ?? Status Finished project ✅ ✅ Feat

César Augusto Polidorio Machado 7 Dec 7, 2022
A GitHub Action to generate reports that contain all the SSH keys, personal access tokens, GitHub App installations, deploy keys and their respective permissions authorized against a GitHub organization.

A GitHub Action to generate reports that contain all the SSH keys, personal access tokens, GitHub App installations, deploy keys and their respective permissions authorized against a GitHub organization.

Nick Nagel 5 Dec 13, 2022
A high-resolution local database that uses precise algorithms to easily record data in local files within a project with persistent JSON and YAML support designed to be easy to set up and use

About A high-resolution local database that uses precise algorithms to easily record data in local files within a project with persistent JSON and YML

Shuruhatik 5 Dec 28, 2022