多页签websocket 共享
网上搜索类似的问题方法讲的都很不错,以下是一个简答说明
问题来源
主要是看到atmosphere 的js client 看到支持多页签websocket 共享,比较好奇,顺带看了下实现机制,发现居然是基于了
localStorage的storage event 实现上还是比较巧妙,功能还是很强大的
参考处理
function _execute() {
// Shared across multiple tabs/windows.
if (_request.shared) {
// windows 以及tabs 共享
_localStorageService = _local(_request);
if (_localStorageService != null) {
if (_canLog('debug')) {
atmosphere.util.debug("Storage service available. All communication will be local");
}
if (_localStorageService.open(_request)) {
// Local connection.
return;
}
}
if (_canLog('debug')) {
atmosphere.util.debug("No Storage service available.");
}
// Wasn't local or an error occurred
_localStorageService = null;
}
_local 的处理
function _local(request) {
var trace, connector, orphan, name = "atmosphere-" + request.url, connectors = {
// 基于localstorage 的处理
storage: function () {
function onstorage(event) {
if (event.key === name && event.newValue) {
// 处理消息
listener(event.newValue);
}
}
if (!atmosphere.util.storage) {
return;
}
var storage = window.localStorage,
get = function (key) {
var item = storage.getItem(name + "-" + key);
return item === null ? [] : JSON.parse(item);
},
set = function (key, value) {
storage.setItem(name + "-" + key, JSON.stringify(value));
};
return {
init: function () {
set("children", get("children").concat([guid]));
atmosphere.util.on(window, "storage", onstorage);
return get("opened");
},
signal: function (type, data) {
storage.setItem(name, JSON.stringify({
target: "p",
type: type,
data: data
}));
},
close: function () {
var children = get("children");
atmosphere.util.off(window, "storage", onstorage);
if (children) {
if (removeFromArray(children, request.id)) {
set("children", children);
}
}
}
};
},
windowref: function () {
var win = window.open("", name.replace(/\W/g, ""));
if (!win || win.closed || !win.callbacks) {
return;
}
return {
init: function () {
win.callbacks.push(listener);
win.children.push(guid);
return win.opened;
},
signal: function (type, data) {
if (!win.closed && win.fire) {
win.fire(JSON.stringify({
target: "p",
type: type,
data: data
}));
}
},
close: function () {
// Removes traces only if the parent is alive
if (!orphan) {
removeFromArray(win.callbacks, listener);
removeFromArray(win.children, guid);
}
}
};
}
};
function removeFromArray(array, val) {
var i, length = array.length;
for (i = 0; i < length; i++) {
if (array[i] === val) {
array.splice(i, 1);
}
}
return length !== array.length;
}
// Receives open, close and message command from the parent
function listener(string) {
var command = JSON.parse(string), data = command.data;
// 基于协议处理消息
if (command.target === "c") {
switch (command.type) {
case "open":
_open("opening", 'local', _request);
break;
case "close":
if (!orphan) {
orphan = true;
if (data.reason === "aborted") {
_close();
} else {
// Gives the heir some time to reconnect
if (data.heir === guid) {
_execute();
} else {
setTimeout(function () {
_execute();
}, 100);
}
}
}
break;
// 消息处理
case "message":
// 消息处理
_prepareCallback(data, "messageReceived", 200, request.transport);
break;
// 本地消息处理
case "localMessage":
_localMessage(data);
break;
}
}
}
function findTrace() {
var matcher = new RegExp("(?:^|; )(" + encodeURIComponent(name) + ")=([^;]*)").exec(document.cookie);
if (matcher) {
return JSON.parse(decodeURIComponent(matcher[2]));
}
}
// Finds and validates the parent socket's trace from the cookie
trace = findTrace();
if (!trace || atmosphere.util.now() - trace.ts > 1000) {
return;
}
// Chooses a connector
connector = connectors.storage() || connectors.windowref();
if (!connector) {
return;
}
return {
open: function () {
var parentOpened;
// Checks the shared one is alive
_traceTimer = setInterval(function () {
var oldTrace = trace;
trace = findTrace();
if (!trace || oldTrace.ts === trace.ts) {
// Simulates a close signal
listener(JSON.stringify({
target: "c",
type: "close",
data: {
reason: "error",
heir: oldTrace.heir
}
}));
}
}, 1000);
parentOpened = connector.init();
if (parentOpened) {
// Firing the open event without delay robs the user of the opportunity to bind connecting event handlers
setTimeout(function () {
_open("opening", 'local', request);
}, 50);
}
return parentOpened;
},
send: function (event) {
connector.signal("send", event);
},
localSend: function (event) {
connector.signal("localSend", JSON.stringify({
id: guid,
event: event
}));
},
close: function () {
// Do not signal the parent if this method is executed by the unload event handler
if (!_abortingConnection) {
clearInterval(_traceTimer);
connector.signal("close");
connector.close();
}
}
};
}
说明:以上代码处理还是比较复杂的,核心上还是利用了localStorage的storage event,因为消息框架的复杂性,以上代码做了不少消息协议的处理
而且同时也包含了处理多windows的共享,很值得仔细研究下
参考资料
https://juejin.cn/post/6844904163533389837
https://juejin.cn/post/6844903811232825357
http://www.w3.org/TR/webstorage/#event-storage
https://github.com/Atmosphere/atmosphere-javascript
https://developer.mozilla.org/en-US/docs/web/api/window/open