Реалізація обміну повідомленнями між вкладками браузера

Це перша стаття в нашому корпоративному блозі. І в цей раз я розповім про наше рішення задачі обміну повідомленнями між вкладками браузера.
Наприклад мені знадобилося вирішити цю задачу при реалізації JavaScript API до Comet сервісу. Ця задача зустрічається досить часто і її вже розглядали на хабре раніше тут і тут але я вирішив написати своє рішення цієї задачі виходячи з наступних вимог до коду:
  • Кросбраузерность
  • Відсутність залежностей
  • Мінімальний розмір коду
  • Простота і зручність

Я свою міні бібліотеку реалізував в стилі сигналів і слотів.
Ця дуже зручна модель і мені здається вона в даному прикладі як не можна краще підходить. Перевагою цього підходу є слабка зв'язок взаємодіючих між собою компонентів. Якщо коротко то модель сигналів і слотів нам дає наступні можливості:
  • Код який випромінює сигнал може нічого не знати про коді який цей сигнал обробляє. Він взагалі не знає, чи є цей код або він мовить у порожнечу.
  • Код приймає сигнал не знає чого про відправника.
  • Єдине, що є загальним це формат повідомлення.
Ось на приклад нам треба оповістити всі підписалися функції про якомусь подію.
Для цього виконуємо:
tabSignal().emitAll('ИмяСобытия', "Дані") // Для повідомлення всіх відкритих вкладок
tabSignal().emit( 'ИмяСобытия', "Дані" ) // Для роботи в межах однієї вкладки
Всі код відпрацював і якщо хто підписаний на цю подію він отримає дані.

Для підписки на подію треба передати ім'я події на яке підписуємося і callBack для виклику на той випадок якщо подія відбудеться.
tabSignal().connect('ИмяСобытия', function(param, signal_name){ });

Можна також передати ще й ім'я слота, воно може знадобиться якщо ви раптом вирішили відмовитися від повідомлень про подію.
tabSignal().connect("ИмяСобытия",'ИмяСлота', function(param, signal_name){} );
Тут param буде містити саме повідомлення
А signal_name ім'я сигналу, воно корисно на той випадок якщо ви підписали один callBack на кілька різних сигналів

Ось код на той випадок, якщо вам треба відписатися від події.
tabSignal().disconnect("ИмяСобытия", 'ИмяСлота');

Для передачі даних на іншу вкладом бібліотека просто пише їх у local storage браузера. А для того щоб отримувати дані бібліотека підписується на подію onstorage, воно відбувається у всіх вкладках коли хто ні будь пише щось в local storage.

Я не став обтяжувати саму бібліотеку функцією вибору майстер-вкладки з цього приведу її тут і за одне розберемо алгоритм її роботи. Але для початку розповім для чого взагалі знадобилося шукати майстер вкладку. Як я вже говорив я займався розробкою JavaScript API до comet сервісу.

Технологія Comet дозволяє відправляти повідомлення в браузер з ініціативи сервера. Це має безліч застосувань, найбільш очевидним є створення чату між користувачами або користувачем і техпідтримкою. Або наприклад динамічна подгруздка нових твітів в твіттері по мірі їх появи.

Для відправки push повідомлень в браузер, необхідно мати постійно відкрите з'єднання між браузером і комет сервером. Але багато людей відкривають сайт більш ніж в одній вкладці і було б корисно, якби тільки одна з відкритих вкладок тримала реальне з'єднання з комет сервером, а користувалися цим з'єднанням всі відриті вкладки. Цей підхід не просто економить ресурси сервера, а ще вирішує дуже важливу проблему — обмеження на кількість одночасно відкритих з'єднань.

Приміром chrome відкриває не більше 6 запитів до одного домену і не більше 255 запитів в сумі на всі відкриті вкладки не важливо до якого з доменів. Відповідно якщо підтримувати окреме з'єднання з комет сервером на кожній вкладці то зможете відкрити не більше 6 вкладок, а потім все.

Відповідно виходячи з цього завдання я вирішив що майстер вкладкою буде перша з відкритих вкладок, а якщо її закриють то майстром стане випадкова залишилися.
Для цього майстер вкладка надсилає повідомлення всім вкладкам кожні 150 мілісекунд про те що вона взагалі є.

При відкритті вкладки підписуємося на отримання повідомлень від майстер вкладки.
Потім ставимо таймер хоча б на 300мс і якщо за цей час не отримуємо повідомлення від майстра то вважаємо що майстри немає і ми за нього. У такому випадки починаємо розсилати повідомлення про те що ми майстер вкладка кожні 50мс, а якщо отримали повідомлення від майстер вкладки то скасовуємо поставлений таймер і відразу ставимо його назад і так до тих пір, поки майстер вкладка встигає нагадати про своє існування за час менше 300мс.

Ну а тепер реалізація в коді:
function tryStartMasterTab(masterCallback, slaveCallback)
{ 
var time_id = false;
var last_time_id = false;
var start_timer = 2000;

if( window.InTryStartMasterTab != undefined )
{
console.log("Уеже запущено");
return InTryStartMasterTab;
}
console.log("Запуск tryStartMasterTab");

InTryStartMasterTab = 0;

var setAsMaster = function(){
// Повідписуємося від повідомлень про наявність майстер вкладки
tabSignal().disconnect("comet_msg_connect", 'comet_msg_master_signal');

// Зробімо місце для радісного сигнал для попередження всіх інших вкладок про свою перевагу
tabSignal().emitAll('comet_msg_master_signal')

// Поставимо таймер для повідомлення всіх інших вкладок про свою перевагу
setInterval(function()
{
tabSignal().emitAll('comet_msg_master_signal')
console.log("Ми майстер!");
}, start_timer/8);

InTryStartMasterTab = 1;
if(masterCallback) masterCallback();
}


// Підключаємося на повідомлення від інших вкладок про те що вже є майстер вкладка,
// якщо за start_timer мілісекунд повідомлення станеться те скасуємо поставлений раніше таймер
tabSignal().connect("comet_msg_connect",'comet_msg_master_signal', function()
{
if(time_id !== false) // скасуємо поставленый раніше таймер якщо це ще не зроблено
{
console.log("Ми slave!, clearTimeout(time_id="+time_id+")");
clearTimeout( time_id );
time_id = setTimeout(setAsMaster, start_timer )
}

if(InTryStartMasterTab == 0)
{
if(slaveCallback) slaveCallback();
}
InTryStartMasterTab = -1;
})
// Створимо таймер, якщо цей таймер не буде скасований за start_timer мілісекунд то вважаємо себе майстер вкладкою
time_id = setTimeout(setAsMaster, start_timer )
}


В кінці наводжу Online demo
Репозиторій TabSignal.js

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.