BroadcastChannel Polyfill
(function(global) {
var channels = [];
function BroadcastChannel(channel) {
var $this = this;
channel = String(channel);
var id = '$BroadcastChannel$' + channel + '$';
channels[id] = channels[id] || [];
channels[id].push(this);
this._name = channel;
this._id = id;
this._closed = false;
this._mc = new MessageChannel();
this._mc.port1.start();
this._mc.port2.start();
global.addEventListener('storage', function(e) {
if (e.storageArea !== global.localStorage) return;
if (e.newValue === null) return;
if (e.key.substring(0, id.length) !== id) return;
var data = JSON.parse(e.newValue);
$this._mc.port2.postMessage(data);
});
}
BroadcastChannel.prototype = {
// BroadcastChannel API
get name() { return this._name; },
postMessage: function(message) {
var $this = this;
if (this._closed) {
var e = new Error();
e.name = 'InvalidStateError';
throw e;
}
var value = JSON.stringify(message);
// Broadcast to other contexts via storage events...
var key = this._id + String(Date.now()) + '$' + String(Math.random());
global.localStorage.setItem(key, value);
setTimeout(function() { global.localStorage.removeItem(key); }, 500);
// Broadcast to current context via ports
channels[this._id].forEach(function(bc) {
if (bc === $this) return;
bc._mc.port2.postMessage(JSON.parse(value));
});
},
close: function() {
if (this._closed) return;
this._closed = true;
this._mc.port1.close();
this._mc.port2.close();
var index = channels[this._id].indexOf(this);
channels[this._id].splice(index, 1);
},
// EventTarget API
get onmessage() { return this._mc.port1.onmessage; },
set onmessage(value) { this._mc.port1.onmessage = value; },
addEventListener: function(type, listener /*, useCapture*/) {
return this._mc.port1.addEventListener.apply(this._mc.port1, arguments);
},
removeEventListener: function(type, listener /*, useCapture*/) {
return this._mc.port1.removeEventListener.apply(this._mc.port1, arguments);
},
dispatchEvent: function(event) {
return this._mc.port1.dispatchEvent.apply(this._mc.port1, arguments);
}
};
global.BroadcastChannel = global.BroadcastChannel || BroadcastChannel;
}(self));
BroadcastChannel is a new communication API proposed in the HTML Standard but not yet widely implemented. It allows messages to be sent to all other BroadcastChannel
instances sharing the same channel name within the same browsing context and origin.
var bc = new BroadcastChannel('name');
bc.postMessage(payload);
bc.onmessage = function(e) {
console.log('Received: ' + e.data);
};
Included in this gist is a polyfill for the API.
There is native BroadcastChannel support is in:
The polyfill requires Message Channel support, so should work in:
Does not work in:
JSON.stringify()
/JSON.parse()
so it is much more limited.localStorage
) and storage
events. DOM Storage is a synchronous API and so may cause performance issues in pages. In addition, it is not exposed to Workers. Therefore, the polyfill will not function in Workers.Here's a sample inter-tab chat app. Note that a BroadcastChannel
should broadcast to other BroadcastChannel
instances within the same page but not to itself, so there is no local echo of the messages.
<!DOCTYPE html>
<script src="broadcastchannel.js"></script>
<textarea id="out" readonly rows=30 col=80></textarea><br>
<form><input id="in"><input type="submit" id="go" value="Send"></form>
<script>
var $ = document.querySelector.bind(document);
var bc = new BroadcastChannel('chat');
bc.addEventListener('message', function(e) {
$('#out').value += e.data.message + '\r\n';
});
$('#go').addEventListener('click', function(e) {
e.preventDefault();
bc.postMessage({message: $('#in').value});
$('#in').value = '';
});
</script>