19.09.2014

wormhole.js — общение между табами/окнами браузера

Задача

Нужно общаться между вкладками/окнами браузера.

А ещё различать, кто сейчас master.

Технологии

Технологии

SharedWorker

SharedWorker

			#!+ // tab.js
			#!- var worker = new SharedWorker("shared-resource.js");
			#!+ worker.port.addEventListener("message", function (evt) {
				console.log(evt.data);
			#!- });

			#! worker.port.start();
			#!+ // shared-resource.js
			var ports = [];
			window.onconnect = function (evt) {
				var port = evt.ports[0];
				ports.push(port); // добавляем port
				#!+ port.onmessage = function (evt) {
					#! broadcast(evt.data); // рассылаем всем табам
				#!- };
			#!- };
		

SharedWorker

			// shared-resource.js
			function broadcast(data) {
				ports.forEach(function (port) {
					port.postMessage(data);
				});
			}
		

SharedWorker

			// tab.js
			// ... создаем воркер
			// Отправляем сообщение через воркер
			worker.port.postMessage("any data");
			// теперь все табы получат переданные данные
		

localStorage

localStorage

			// tab.js
			#!+ // Индекс последнего события
			#!- var lastIndex = localStorage.getItem("index")|0;

			#!+ function emit(data) {
				#! var events = localStorage.getItem("events") || [];
				#! var nextIndex = (localStorage.getItem("index")|0) + 1;
				#!+ events.push({
					index: nextIndex,
					data: data
				#!- });
				#! localStorage.setItem("index", nextIndex);
				#! localStorage.setItem("events", JSON.stringify(events));
			#!- }
		

localStorage

			// tab.js
			// Подписываемся на изменение хранилища
			window.onstorage = function (evt) {
				#!+ if (evt.key === "events") {
					#! var events = JSON.parse(localStorage.getItem(evt.key));
					#!+ events.forEach(function (evt) {
						#!+ if (evt.idx > lastIndex) {
							// Актуальное для нас событие
							#! lastIndex = evt.idx;
							#! console.log(evt.data);
						#!- }
					#!- });
				#!- }
			};
		

document.cookie

document.cookie

Куки, как куки...

Какие решения уже есть?

Какие решения уже есть?

Intercom.js

Стандартное решение решение на основе localStorage.

BNC Connector

BNC Connector

BNC Connector

"Once upon a time at home ..."

Once upon a time at evening I have decided to properly brake the famous browsers communication problem and as a result you have landed in here. I have created implementation of BNC networks model with simple TCP/IP layer, that as transport packet it will use browser's cookie object.

И всё.

Что делать?

Сделать самому,
что же ещё.

wormhole.js

			// X.js
			#!+ wormhole().on("message", function (data) {
				console.log(data);
			#!- });

			#!+ wormhole().on("peers", function (peers) {
				console.log(peers); // ["X"], а потом ["X", "Y"]
			#!- });
		

wormhole.js

			// Y.js
			wormhole().emit("message", "any data");

			#!+ wormhole().on("master", function () {
				console.log("Wow! I'm master!");
			#!- });
		

Master/Slave

Master/Slave

			#!+ // регистрируем команду `foo`
			wormhole()["foo"] = function (array, next) {
				#! next(null, array.reverse());
			#!- };
			#!+ // Выполняем команду (на мастере)
			wormhole().call("foo", [1, 2, 3], function (err, result) {
				console.log(result);
			#!- });
		

wormhole.js

DEMO

Как это работает?

SharedWorker
+
localStorage

wormhole: localStorage

В хранилище хранится:
			// `meta` — мета информация
			{
				#! id: null, // идентификатор мастера
				#! ts: 0, // время последнего обновления
				#! peers: {..} // список табов и время обновления (id => ts)
			}
		

wormhole: localStorage

В хранилище хранится:
			// `queue` — очередь событий
			{
				#! idx: 0, // текущий индекс события
				#! items: [] // массив событий
			}
		

wormhole: localStorage

			function emit(type, args) {
				var queue = this._store("queue");
				#!+ queue.items.push({
					#! idx: ++queue.idx, // увеличим индекс
					#! type: type,
					#! args: args,
					#! source: this.id // идентификатор «дырки»
				#!- });
				#! this._store("queue", queue);
				#! this._processingQueue(queue.items);
			}
		

wormhole: localStorage

			// Обработка изменения в хранилище
			#!+ store.on("change", function (key, data) {
				#!+ if (key === "queue") {
					this._processingQueue(queue.items);
				#!- }
				#!+ else if (key === "meta") {
					this._checkMeta();
				#!- }
			#!- }.bind(this));
		

wormhole: localStorage

			function _processingQueue(queue) {
				for (var i = 0, n = queue.length, evt; i < n; i++) {
					evt = queue[i];
					#!+ if (this._idx < evt.idx) {
						#! this._idx = evt.idx;
						#! _emitterEmit.call(this, evt.type, evt.args);
					#!- }
				}
			}
		

The End