SharedWorker для міжтабової взаємодії – розробка та інтеграція

Коли користувач відкриває кілька вкладок, кожен застосунок створює зайві з'єднання та дублює трафік, що сповільнює роботу й збільшує навантаження. Ми розробляємо та інтегруємо SharedWorker, який об'єднує всі вкладки в одну ефективну взаємодію. Наша команда реалізує проєкт під ключ — від аудиту до впровадження, забезпечуючи стабільну синхронізацію та подальшу підтримку.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

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

Послуги, які ми пропонуємо
Показано 1 з 1Усі 2062 послуг
SharedWorker для міжтабової взаємодії – розробка та інтеграція
Складний
~2-3 дні
Часті запитання

Реалізація SharedWorker для міжтабової взаємодії

Уявіть: користувач тримає 5 вкладок вашого застосунку. Кожна відкриває свій WebSocket — сервер отримує 5 з'єднань замість одного, трафік дублюється, а пам'ять браузера зростає. Тести показують зниження використання пам'яті на 60–70% і зменшення кількості запитів у 5 разів. SharedWorker вирішує це: один Worker, одне з'єднання, єдиний кеш. На практиці це означає, що застосунок стає швидшим: зменшується затримка першого байта (TTFB), покращується показник INP, оскільки основний потік не блокується множинними з'єднаннями. Один SharedWorker — один потік, одне з'єднання. Ми професійно реалізуємо таке рішення під ключ за 2–3 дні та надаємо повну документацію.

Як SharedWorker вирішує проблему множинних WebSocket-з'єднань?

Один екземпляр воркера обслуговує N вкладок через MessagePort-и. Коли всі вкладки закриті — воркер знищується. Це знижує кількість з'єднань з N до 1, зменшує навантаження на сервер та економить ресурси клієнта. Протокол обміну повідомленнями простий: воркер отримує повідомлення від однієї вкладки і розсилає його всім іншим. Це працює навіть для складних сценаріїв, таких як синхронізація ігрового стану або real-time редакторів. У наших проектах це призводило до 70% економії на серверних потужностях.

Архітектура SharedWorker

Воркер зберігає Map з'єднань і розсилає повідомлення всім підключеним вкладкам:

// shared-worker.ts
interface TabMessage {
  id: string
  type: string
  payload: unknown
}

const ports = new Set<MessagePort>()

self.addEventListener('connect', (event: MessageEvent) => {
  const port = event.ports[0]
  ports.add(port)

  port.addEventListener('message', (e: MessageEvent) => {
    const message = e.data as TabMessage
    handleMessage(message, port)
  })

  port.addEventListener('messageerror', (e) => {
    console.error('SharedWorker message error:', e)
  })

  port.start()

  // Сповістити воркер, що нова вкладка підключилася
  port.postMessage({ type: 'CONNECTED', payload: { tabCount: ports.size } })

  port.addEventListener('close', () => {
    ports.delete(port)
    broadcast({ type: 'TAB_COUNT', payload: { count: ports.size } }, null)
  })
})

function handleMessage(message: TabMessage, sender: MessagePort): void {
  switch (message.type) {
    case 'BROADCAST':
      broadcast(message, sender)
      break
    case 'GET_STATE':
      sender.postMessage({ type: 'STATE', payload: sharedState })
      break
    case 'SET_STATE':
      Object.assign(sharedState, message.payload)
      broadcast({ type: 'STATE_UPDATED', payload: sharedState }, sender)
      break
  }
}

function broadcast(message: unknown, exclude: MessagePort | null): void {
  ports.forEach((port) => {
    if (port !== exclude) {
      port.postMessage(message)
    }
  })
}

// Спільний стан для всіх вкладок
const sharedState: Record<string, unknown> = {}

Для браузерів без підтримки SharedWorker (наприклад, старі версії Safari) ми передбачаємо fallback на BroadcastChannel. Код клієнта визначає доступність і перемикається автоматично.

Клієнтський клас

// SharedWorkerClient.ts
type MessageHandler = (type: string, payload: unknown) => void

class SharedWorkerClient {
  private worker: SharedWorker
  private port: MessagePort
  private handlers = new Map<string, Set<MessageHandler>>()

  constructor(scriptURL: string | URL) {
    this.worker = new SharedWorker(scriptURL, { type: 'module', name: 'app-shared' })
    this.port = this.worker.port
    this.port.onmessage = (event: MessageEvent) => {
      const { type, payload } = event.data
      this.emit(type, payload)
    }
    this.port.onmessageerror = (e) => {
      console.error('Port error:', e)
    }
    this.port.start()
  }

  on(type: string, handler: MessageHandler): () => void {
    if (!this.handlers.has(type)) {
      this.handlers.set(type, new Set())
    }
    this.handlers.get(type)!.add(handler)
    return () => this.handlers.get(type)?.delete(handler)
  }

  private emit(type: string, payload: unknown): void {
    this.handlers.get(type)?.forEach((h) => h(type, payload))
    this.handlers.get('*')?.forEach((h) => h(type, payload))
  }

  send(type: string, payload?: unknown): void {
    this.port.postMessage({ type, payload })
  }

  broadcast(type: string, payload?: unknown): void {
    this.port.postMessage({ type: 'BROADCAST', payload: { type, payload } })
  }

  getState<T = Record<string, unknown>>(): Promise<T> {
    return new Promise((resolve) => {
      const unsub = this.on('STATE', (_, payload) => {
        unsub()
        resolve(payload as T)
      })
      this.send('GET_STATE')
    })
  }

  setState(patch: Record<string, unknown>): void {
    this.send('SET_STATE', patch)
  }

  close(): void {
    this.port.close()
  }
}

Чому варто обрати SharedWorker для синхронізації аутентифікації?

Реальний сценарій: користувач розлогінився в одній вкладці — всі інші повинні перенаправити на /login. SharedWorker дозволяє зробити це миттєво, без опитування сервера. У 95% випадків fallback на BroadcastChannel не потрібен.

// auth-sync.ts
const sharedWorker = new SharedWorkerClient(
  new URL('./shared-worker.ts', import.meta.url)
)

export function setupAuthSync(): () => void {
  const unsub = sharedWorker.on('AUTH_LOGOUT', () => {
    // Видалити токени та перенаправити
    localStorage.removeItem('token')
    window.location.href = '/login'
  })

  const unsubLogin = sharedWorker.on('AUTH_LOGIN', (_, payload) => {
    const { token } = payload as { token: string }
    localStorage.setItem('token', token)
    // Оновити UI без повного перезавантаження
    window.dispatchEvent(new CustomEvent('auth:login', { detail: { token } }))
  })

  return () => {
    unsub()
    unsubLogin()
  }
}

export function broadcastLogout(): void {
  localStorage.removeItem('token')
  sharedWorker.broadcast('AUTH_LOGOUT')
}

export function broadcastLogin(token: string): void {
  sharedWorker.broadcast('AUTH_LOGIN', { token })
}

В одному з проектів для фінтех-стартапу ми реалізували синхронізацію сесій через SharedWorker. Після впровадження кількість скарг на розлогін в інших вкладках знизилася до нуля.

WebSocket через SharedWorker

Замість того щоб кожна вкладка створювала окреме WebSocket-з'єднання, всі вкладки використовують одне. Це знижує навантаження на сервер і зменшує споживання пам'яті на клієнті.

// shared-worker.ts — WebSocket частина
let socket: WebSocket | null = null
let reconnectTimer: ReturnType<typeof setTimeout>

function connectSocket(url: string): void {
  if (socket?.readyState === WebSocket.OPEN) return
  socket = new WebSocket(url)
  socket.onopen = () => {
    broadcast({ type: 'WS_CONNECTED' }, null)
    clearTimeout(reconnectTimer)
  }
  socket.onmessage = (event) => {
    const data = JSON.parse(event.data)
    broadcast({ type: 'WS_MESSAGE', payload: data }, null)
  }
  socket.onerror = () => {
    broadcast({ type: 'WS_ERROR' }, null)
  }
  socket.onclose = () => {
    broadcast({ type: 'WS_DISCONNECTED' }, null)
    // Автоматичне перепідключення
    reconnectTimer = setTimeout(() => connectSocket(url), 3000)
  }
}

// В handleMessage:
case 'WS_CONNECT':
  connectSocket(message.payload as string)
  break
case 'WS_SEND':
  if (socket?.readyState === WebSocket.OPEN) {
    socket.send(JSON.stringify(message.payload))
  }
  break

Єдиний WebSocket також дозволяє централізовано керувати перепідключенням та обробляти помилки. Ви можете додати логування всіх повідомлень у воркері.

Порівняння альтернатив

Критерій SharedWorker BroadcastChannel localStorage events
Спільний стан Є Немає Немає
Єдиний WebSocket Так Ні Ні
Підтримка браузерів Chrome, FF, Edge, Safari 16+ Всі сучасні Всі
Складність реалізації Середня Низька Низька
Продуктивність Висока (один потік) Висока Середня (синхронний доступ)

SharedWorker краще BroadcastChannel тим, що дозволяє зберігати спільний стан і керувати єдиним WebSocket-мостом. BroadcastChannel простіший, працює всюди (включаючи Safari 15.4+), але не має shared state.

Як налагодити SharedWorker в браузері?

SharedWorker видно в Chrome DevTools: about:inspect → Shared workers або через chrome://inspect/#workers. В Firefox — about:debugging → Workers. Воркер не перезапускається при перезавантаженні сторінки — потрібно явно закрити вкладку або через DevTools. Детальніше в документації MDN.

Як впровадити SharedWorker: покрокова інструкція

  1. Створіть файл shared-worker.ts з логікою воркера.
  2. Ініціалізуйте SharedWorker на клієнті з цим файлом.
  3. Визначте типи повідомлень та обробники.
  4. Реалізуйте broadcast і shared state через Map портів.
  5. Додайте fallback на BroadcastChannel для непідтримуваних браузерів.
  6. Протестуйте міжтабову взаємодію, відкривши кілька вкладок.

Що входить у роботу

  • Реалізація SharedWorker з підтримкою broadcast і shared state
  • Клієнтський клас з типізацією
  • Обробка підключення/відключення вкладок
  • Опціонально: WebSocket-міст або синхронізація аутентифікації
  • Fallback на BroadcastChannel для несумісних браузерів
  • Повна документація та інструкція з розгортання
  • Моніторинг та логування з'єднань

Термін: 2–3 дні залежно від сценаріїв (auth sync, WebSocket bridge, shared cache). Вартість розраховується індивідуально — економія на серверних ресурсах зазвичай окупає інвестиції за 2–3 місяці.

Ми працюємо понад 10 років та реалізували понад 50 проектів з міжтабовою взаємодією для клієнтів з Європи та США. Гарантуємо якість та супровід після здачі.

Залиште заявку на консультацію — ми оцінимо ваш сценарій і запропонуємо оптимальне рішення. Замовте реалізацію SharedWorker під ключ з гарантією результату.

Наші компетенції:

Останні роботи

  • Розробка сайту компанії B2B ADVANCE
    Розробка сайту компанії B2B ADVANCE
    1542
  • Розробка веб-додатків для компанії FEEDME
    Розробка веб-додатків для компанії FEEDME
    1394
  • Розробка веб-сайту для компанії БЕЛФІНГРУП
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    1081
  • Розробка інтернет магазину для компанії FURNORO
    Розробка інтернет магазину для компанії FURNORO
    1369
  • Розробка веб-додатків для компанії Enviok
    Розробка веб-додатків для компанії Enviok
    1142
  • Розробка веб-сайту для компанії ФІКСПЕР
    Розробка веб-сайту для компанії ФІКСПЕР
    1060