基于 postMessage 和 localStorage 的跨域本地存储方案

HTML5 的 postMessage 为解决跨域页面通信提供了一套可控的机制, 而 localStorage 则提供了易用简洁的本地存储方案? 这两者结合起来,能否实现跨域本地存储呢 ?

答案是可以的。假设有 a.com 和 b.com 两个页面。我们想通过 a 页面去修改 b 页面的本地数据。 我们需要做如下步奏:

  • 在 a 页面创建一个 iframe ,嵌入 b 页面
  • a 页面通过 postMessage 传递指定格式的消息给 b 页面
  • b 页面解析 a 页面传递过来的消息内容,调用localStorage API 操作本地数据
  • b 页面包装 localStorage 的操作结果,并通过 postMessage 传递给 a 页面
  • a 页面解析 b 页面传递回来的消息内容,得到 localStorage 的操作结果

整个过程如下图:

OK,清楚了整个过程,我们就可以封装出相应的组件:CSClient.js 和 csHub.js,来完成这个过程,下面我们来简单实现一下。

先来看CSClient.js,作用于 a 页面,用于创建跨域存储实例,它提供了 get、set、del 这三个方法来操作跨域的数据:

function CSClient(url) {
 this.id = this._getId();
 this._init(url);
 this._origin = this._getOrigin(url);
 this._callbacks = {
 _get: {},
 _set: {},
 _del: {}
 }
 this._bindEvent();
}
CSClient.prototype._getId = function () {
 id = 0;
 return function () {
 return ++id;
 }
}();
CSClient.prototype._init = function (url) {
 var frame = document.createElement('iframe');
 frame.style.display = 'none';
 frame.src = url;
 document.body.appendChild(frame);
 this._hub = frame.contentWindow;
}
CSClient.prototype._getOrigin = function(url) {
 var uri, origin;
 uri = document.createElement('a');
 uri.href = url;
 origin = uri.protocol + '//' + uri.host;
 return origin;
};
CSClient.prototype._parseMessage = function (method, key, value) {
 return JSON.stringify({
 method: method,
 key: key,
 value: value
 });
}
CSClient.prototype._bindEvent = function () {
 var _this = this;
 window.addEventListener('message', function (event) {
 var data = JSON.parse(event.data);
 var error = data.error;
 var result = data.result && JSON.parse(data.result) || null;
 try {
 _this._callbacks['_' + data.method][data.key](error, result);
 }
 catch (e){
 console.log(e);
 }
 }, false);
}
CSClient.prototype.get = function (key, callback) {
 this._hub.postMessage(this._parseMessage('get', key), this._origin);
 this._callbacks._get[key] = callback;
}
CSClient.prototype.set = function (key, value, callback) {
 this._hub.postMessage(this._parseMessage('set', key, value), this._origin);
 this._callbacks._set[key] = callback;
}
CSClient.prototype.del = function (key, callback) {
 this._hub.postMessage(this._parseMessage('del', key), this._origin);
 this._callbacks._del[key] = callback;
}

使用方法如下:

var key = document.querySelector('#key');
var value = document.querySelector('#value');
var btn = document.querySelectorAll('button');
var storage = new CSClient('http://b.com');
btn[0].onclick = function (e) {
 storage.get(key.value, function (err, result) {
 if (err) {
 console.log(err);
 return;
 }
 console.log(result);
 })
}
btn[1].onclick = function (e) {
 storage.set(key.value, value.value, function (err) {
 if (err) {
 console.log(err);
 return;
 }
 console.log('set ok');
 })
}
btn[2].onclick = function (e) {
 storage.del(key.value, function (err) {
 if (err) {
 console.log(err);
 return;
 }
 console.log('del ok');
 })
}

接着看 csHub.js,作用于 b 页面,用于设置跨域存储的权限、接受 client 的消息、操作本地数据。只有拥有权限的 origin ,才能操作本页面的本地数据。

var csHub = window.csHub = {
 init: function (origin) {
 this.originRule = origin;
 },
 get: function (key) {
 return JSON.stringify(window.localStorage.getItem(key));
 },
 set: function (key, value) {
 window.localStorage.setItem(key, JSON.stringify(value));
 },
 del: function (key) {
 window.localStorage.removeItem(key);
 }
};
window.addEventListener('message', function (event) {
 var message = JSON.parse(event.data), result, err = null;
 if (csHub.originRule.test(event.origin)) {
 try {
 result = csHub[message.method](message.key, message.value);
 }
 catch (e) {
 err = {
 message: e.message,
 stack: e.stack
 };
 }
 window.parent.postMessage(JSON.stringify({
 error: err,
 method: message.method,
 key: message.key,
 result: result
 }), event.origin);
 }
}, false);

使用方法如下:

csHub.init(/a.com$/);

以上代码都比较简单, 主要为了演示如何实现跨域存储。如果你想在项目中使用此方案。推荐使用https://github.com/zendesk/cross-storage这个库。 这个库实现了更灵活的权限机制,使用 ES6 的 promises 规范简化了操作接口, 并对浏览器的差异化做了处理,兼容到了 ie8+ 与其他现代浏览器。

转自: http://cssha.com/cross-domain-local-storage/

w3ctech微信

扫码关注w3ctech微信公众号

共收到1条回复

  • 能跨浏览器就好了

    回复此楼

AltStyle によって変換されたページ (->オリジナル) /