14

I need to store some data client side and this data is too large to store it in a cookie. LocalStorage seemed like the perfect way of doing this but the thing is that the website that i will be using this has some parts that work on https and others with just http and as local storage can't access data from https that you set with http this doesn't seem like a viable solution anymore.

Any idea if there is any solution to this? Any other alternatives?

daniels
  • 18,416
  • 31
  • 103
  • 173
  • Related (1) [same localStorage for http and https?](http://stackoverflow.com/questions/5144471/same-localstorage-for-http-and-https), (2) [Can HTML5 databases and localStorage be shared across subdomains?](http://stackoverflow.com/questions/4177803/can-html5-databases-and-localstorage-be-shared-across-subdomains). –  Aug 13 '13 at 19:41

1 Answers1

23

Store all data on one domain, e.g. https://my.domain.org/.

  • On https protocols, simply use localStorage.setItem('key', 'value') to save the data.
  • On http protocols, embed a https frame, and use postMessage to save the data:

Demo: http://jsfiddle.net/gK7ce/4/ (with the helper page being located at http://jsfiddle.net/gK7ce/3/).

// Script at https://my.domain.org/postMessage
window.addEventListener('message', function(event) {
    // Domain restriction (to not leak variables to any page..)
    if (event.origin == 'http://my.domain.org' ||
        event.origin == 'https://my.domain.org') {
        var data = JSON.parse(event.data);
        if ('setItem' in data) {
            localStorage.setItem(data.setItem, data.value);
        } else if ('getItem' in data) {
            var gotItem = localStorage.getItem(data.getItem);
            // See below
            event.source.postMessage(
                '#localStorage#' + data.identifier + 
                (gotItem === null ? 'null#' : '#' + gotItem),
                event.origin
            );
        } else if ('removeItem' in data) {
            localStorage.removeItem(data.removeItem);
        }
    }
}, false);

On the http(s) page, the frame can be embedded as follows (replace https://my.mydomain.com with the actual URL. Note that you can simply get a reference to the frame, and use the src attribute):

<iframe name="myPostMessage" src="https://my.domain.org/postMessage" style="display:none;"></iframe>
// Example: Set the data
function LSsetItem(key, value) {
    var obj = {
        setItem: key,
        value: value
    };
    frames['myPostMessage'].postMessage(JSON.stringify(obj), 'https://my.domain.com');
}
LSsetItem('key', 'value');

Note that the method is asynchronous, because of postMessage. An implementation of the getItem method has to be implemented differently:

var callbacks = {};
window.addEventListener('message', function(event) {
    if (event.source === frames['myPostMessage']) {
        var data = /^#localStorage#(\d+)(null)?#([\S\s]*)/.exec(event.data);
        if (data) {
            if (callbacks[data[1]]) {
                // null and "null" are distinguished by our pattern
                callbacks[data[1]](data[2] === 'null' ? null : data[3]);
            }
            delete callbacks[data[1]];
        }
    }
}, false);
function LSgetItem(key, callback) {
    var identifier = new Date().getTime();
    var obj = {
        identifier: identifier,
        getItem: key
    };
    callbacks[identifier] = callback;
    frames['myPostMessage'].postMessage(JSON.stringify(obj), 'https://my.domain.com');
}
// Usage:
LSgetItem('key', function(value) {
    console.log('Value: ' + value);
});

Note that each callback is stored in a hash. Each message also contains an identifier, so that the window which receives the message calls the correct corresponding callback.

For completeness, here's the LSremoveItem method:

function LSremoveItem(key) {
    var obj = {
        removeItem: key
    };
    frames['myPostMessage'].postMessage(JSON.stringify(obj), 'https://my.domain.com');
}
Rob W
  • 341,306
  • 83
  • 791
  • 678
  • I tried this but when the https iframe tries to postMessage back to http parent window i get an error "Unsafe JavaScript attempt to access frame with URL... Domains, protocols and ports must match." Any idea? – daniels May 17 '12 at 20:34
  • I'm using this: https://github.com/mozilla/jschannel and this sample https://github.com/mozilla/jschannel/tree/master/example – daniels May 17 '12 at 22:01
  • Yea, did some more tests, and it seems that it does work. My issue was caused by another script that got injected. Thanks for the help. – daniels May 17 '12 at 23:01
  • 1
    This is a clever solution, but it's stopped working on iOS 7/Safari. Anyone else having issues with this now? The local storage seems to be using the parent window's protocol instead of the iframe. Anyone have a new answer to contribute? – marcc Feb 22 '14 at 03:20
  • Yes having same problem with Safari... http://stackoverflow.com/questions/20401751/iframe-localstorage-on-safari-and-safari-mobile – robC Mar 17 '14 at 09:48
  • 2
    @robC The issue with Safari is caused by the "block cookies and other website data" preference. When it it set to "From third parties and advertisers", localStorage is not shared any more. It must be set to "Never", as seen in the following picture: http://i.stack.imgur.com/AP4ed.png. – Rob W Mar 17 '14 at 15:06