import {Meta} from '@storybook/addon-docs/blocks'

<Meta title='@dxtmisha/functional/ru/Classes/BroadcastMessage'/>

# Класс BroadcastMessage

Класс для работы с сообщениями BroadcastChannel между вкладками/окнами браузера. Обеспечивает коммуникацию в реальном времени между различными контекстами одного источника с автоматическим именованием каналов и обработкой сообщений.

## Основные возможности

- **Межвкладочная коммуникация** — отправка и получение сообщений между вкладками и окнами браузера
- **Автоматическое именование каналов** — уникальные имена каналов для каждого экземпляра браузера, сохраняемые в localStorage
- **Типобезопасная передача сообщений** — полная поддержка TypeScript с генерик-типами сообщений
- **Обработка через callback** — гибкая обработка сообщений через callback-функции
- **Обработка ошибок** — корректная обработка ошибок с логированием в консоль
- **Безопасность SSR** — автоматическое определение окружения DOM runtime

## Основное использование

### Конструктор

Создаёт новый экземпляр BroadcastMessage с указанным именем канала и опциональным callback.

**Параметры:**
- `name: string` — идентификатор имени канала
- `callback?: (event: MessageEvent<Message>) => void` — опциональный callback для получаемых сообщений

```javascript
import { BroadcastMessage } from '@dxtmisha/functional'

// Создание канала с callback
const channel = new BroadcastMessage('user-updates', (event) => {
  console.log('Получено сообщение:', event.data)
})

// Создание канала без callback (можно установить позже)
const notifications = new BroadcastMessage('notifications')
```

### `post`

Отправляет сообщение всем подключённым вкладкам/окнам на том же канале.

**Параметры:**
- `message: Message` — сообщение для отправки

**Возвращает:** `this` — для цепочки вызовов

```javascript
import { BroadcastMessage } from '@dxtmisha/functional'

const userChannel = new BroadcastMessage('user-updates')

// Отправка простого сообщения
userChannel.post({ type: 'login', userId: 123 })

// Цепочка вызовов
userChannel
  .post({ type: 'status', value: 'online' })
  .post({ type: 'activity', timestamp: Date.now() })
```

### `setCallback`

Устанавливает или обновляет callback-функцию для обработки получаемых сообщений.

**Параметры:**
- `callback: (event: MessageEvent<Message>) => void` — callback-функция

**Возвращает:** `this` — для цепочки вызовов

```javascript
import { BroadcastMessage } from '@dxtmisha/functional'

const channel = new BroadcastMessage('app-events')

// Установка callback позже
channel.setCallback((event) => {
  console.log('Получено событие:', event.data)
})

// Обновление callback
channel.setCallback((event) => {
  if (event.data.type === 'refresh') {
    location.reload()
  }
})
```

## Расширенные примеры

### Синхронизация состояния между вкладками

```javascript
import { BroadcastMessage } from '@dxtmisha/functional'
import { ref } from 'vue'

// Создание реактивного состояния
const userStatus = ref('offline')

// Создание канала broadcast
const statusChannel = new BroadcastMessage('user-status', (event) => {
  // Обновление локального состояния при изменениях из других вкладок
  userStatus.value = event.data.status
  console.log('Статус обновлён из другой вкладки:', event.data.status)
})

// Функция для обновления статуса и оповещения других вкладок
function updateStatus(newStatus) {
  userStatus.value = newStatus
  statusChannel.post({ status: newStatus, timestamp: Date.now() })
}

// Использование
updateStatus('online')  // Обновляет эту вкладку и уведомляет другие
```

### Корзина покупок в нескольких вкладках

```javascript
import { BroadcastMessage } from '@dxtmisha/functional'

// Определение типов сообщений
interface CartMessage {
  action: 'add' | 'remove' | 'clear'
  productId?: number
  quantity?: number
}

// Создание канала с типизированными сообщениями
const cartChannel = new BroadcastMessage<CartMessage>('shopping-cart')

// Настройка обработчика сообщений
cartChannel.setCallback((event) => {
  const { action, productId, quantity } = event.data

  switch (action) {
    case 'add':
      console.log(`Добавление ${quantity} товара ${productId}`)
      addToCart(productId, quantity)
      break
    case 'remove':
      console.log(`Удаление товара ${productId}`)
      removeFromCart(productId)
      break
    case 'clear':
      console.log('Очистка корзины')
      clearCart()
      break
  }
})

// Трансляция обновлений корзины
function addProduct(productId, quantity) {
  addToCart(productId, quantity)
  cartChannel.post({ action: 'add', productId, quantity })
}
```

### Синхронизация состояния аутентификации

```javascript
import { BroadcastMessage } from '@dxtmisha/functional'

interface AuthMessage {
  type: 'login' | 'logout' | 'token-refresh'
  userId?: number
  token?: string
}

const authChannel = new BroadcastMessage<AuthMessage>('auth-events', (event) => {
  switch (event.data.type) {
    case 'login':
      console.log('Пользователь вошёл:', event.data.userId)
      location.reload()
      break

    case 'logout':
      console.log('Пользователь вышел, перенаправление...')
      localStorage.removeItem('auth-token')
      window.location.href = '/login'
      break

    case 'token-refresh':
      console.log('Токен обновлён')
      localStorage.setItem('auth-token', event.data.token)
      break
  }
})

// Трансляция входа
function handleLogin(userId, token) {
  localStorage.setItem('auth-token', token)
  authChannel.post({ type: 'login', userId, token })
}

// Трансляция выхода
function handleLogout() {
  localStorage.removeItem('auth-token')
  authChannel.post({ type: 'logout' })
}
```

### Уведомления в реальном времени

```javascript
import { BroadcastMessage } from '@dxtmisha/functional'

interface Notification {
  id: string
  title: string
  message: string
  type: 'info' | 'warning' | 'error' | 'success'
  timestamp: number
}

const notificationChannel = new BroadcastMessage<Notification>('notifications')

// Настройка обработчика уведомлений
notificationChannel.setCallback((event) => {
  const notification = event.data

  showNotification(notification.title, notification.message, notification.type)

  if (notification.type === 'error') {
    playErrorSound()
  }
})

// Отправка уведомления всем вкладкам
function broadcastNotification(title, message, type = 'info') {
  const notification = {
    id: crypto.randomUUID(),
    title,
    message,
    type,
    timestamp: Date.now()
  }

  notificationChannel.post(notification)
}

// Использование
broadcastNotification('Новое сообщение', 'У вас новое сообщение от Ивана', 'info')
```

## Детали реализации

### Уникальное именование каналов

Каждый экземпляр браузера генерирует уникальный идентификатор, сохраняемый в localStorage (`__broadcast-name`). Это гарантирует изоляцию каналов между различными экземплярами браузера, при этом позволяя коммуникацию внутри одного экземпляра между вкладками.

```javascript
// Формат имени канала: name_{случайные_7_цифр}__ваше-имя-канала
// Пример: name_1234567__user-updates
```

### Обработка ошибок

BroadcastMessage корректно обрабатывает ошибки и логирует их в консоль:

```javascript
const channel = new BroadcastMessage('my-channel')
// Если BroadcastChannel не поддерживается или инициализация не удалась,
// ошибка логируется: "BroadcastMessage my-channel: [детали ошибки]"
```

### Совместимость с SSR

Класс автоматически определяет окружение DOM runtime с помощью `isDomRuntime()`, что делает его безопасным для использования в контекстах SSR/SSG, где BroadcastChannel API недоступен.

## Типобезопасность

```typescript
import { BroadcastMessage } from '@dxtmisha/functional'

// Определение структуры сообщения
interface UserUpdate {
  userId: number
  action: 'online' | 'offline' | 'typing'
  timestamp: number
}

// Создание типизированного канала
const channel = new BroadcastMessage<UserUpdate>('users')

// TypeScript обеспечивает типобезопасность
channel.post({
  userId: 123,
  action: 'online',
  timestamp: Date.now()
}) // ✓ Валидно

channel.post({ invalid: 'data' }) // ✗ Ошибка TypeScript
```

## Совместимость с браузерами

BroadcastChannel API поддерживается в:
- Chrome/Edge 54+
- Firefox 38+
- Safari 15.4+
- Opera 41+

Для старых браузеров рассмотрите использование полифилла или механизма запасного варианта.
