Web APIs

JavaScript Channel Messaging API: Complete Port Communication Guide

Master the Channel Messaging API for creating direct communication channels. Learn MessagePort, MessageChannel, and secure cross-origin messaging.

By JavaScriptDoc Team
messagingportscommunicationworkersjavascript

JavaScript Channel Messaging API: Complete Port Communication Guide

The Channel Messaging API enables direct, two-way communication channels between different browsing contexts through MessagePort objects, providing secure and efficient message passing.

Understanding the Channel Messaging API

The Channel Messaging API creates pairs of connected ports that can be used to communicate between different contexts like windows, iframes, web workers, and service workers.

// Create a new message channel
const channel = new MessageChannel();

// Access the two ports
const port1 = channel.port1;
const port2 = channel.port2;

// Set up message handler on port1
port1.onmessage = (event) => {
  console.log('Port1 received:', event.data);
};

// Send message from port2
port2.postMessage('Hello from port2!');

// Ports must be started if using addEventListener
port1.start();
port2.start();

// Transfer port to another context
// otherWindow.postMessage('init', '*', [port2]);

// Close ports when done
port1.close();
port2.close();

Basic Port Communication

Port Manager

class PortManager {
  constructor() {
    this.ports = new Map();
    this.channels = new Map();
    this.handlers = new Map();
    this.messageQueue = new Map();
    this.debug = false;
  }

  // Create a new channel
  createChannel(channelId) {
    if (this.channels.has(channelId)) {
      throw new Error(`Channel ${channelId} already exists`);
    }

    const channel = new MessageChannel();

    this.channels.set(channelId, channel);
    this.ports.set(`${channelId}-port1`, channel.port1);
    this.ports.set(`${channelId}-port2`, channel.port2);

    // Setup default handlers
    this.setupPort(channel.port1, `${channelId}-port1`);
    this.setupPort(channel.port2, `${channelId}-port2`);

    if (this.debug) {
      console.log(`Created channel: ${channelId}`);
    }

    return channel;
  }

  // Setup port with error handling and logging
  setupPort(port, portId) {
    port.onmessage = (event) => {
      this.handleMessage(portId, event);
    };

    port.onmessageerror = (event) => {
      this.handleError(portId, event);
    };

    // Start the port
    port.start();
  }

  // Get a specific port
  getPort(portId) {
    return this.ports.get(portId);
  }

  // Send message through port
  sendMessage(portId, message, transfer = []) {
    const port = this.ports.get(portId);

    if (!port) {
      throw new Error(`Port ${portId} not found`);
    }

    try {
      const envelope = {
        id: this.generateMessageId(),
        timestamp: Date.now(),
        data: message,
      };

      port.postMessage(envelope, transfer);

      if (this.debug) {
        console.log(`Sent message on ${portId}:`, envelope);
      }

      return envelope.id;
    } catch (error) {
      console.error(`Failed to send message on ${portId}:`, error);
      throw error;
    }
  }

  // Handle incoming messages
  handleMessage(portId, event) {
    const message = event.data;

    if (this.debug) {
      console.log(`Received message on ${portId}:`, message);
    }

    // Check for queued messages
    if (this.messageQueue.has(portId)) {
      const queue = this.messageQueue.get(portId);
      queue.push(message);
    }

    // Execute handlers
    const handlers = this.handlers.get(portId) || [];
    handlers.forEach((handler) => {
      try {
        handler(message.data, message, event);
      } catch (error) {
        console.error(`Handler error on ${portId}:`, error);
      }
    });
  }

  // Handle message errors
  handleError(portId, event) {
    console.error(`Message error on ${portId}:`, event);
  }

  // Register message handler
  onMessage(portId, handler) {
    if (!this.handlers.has(portId)) {
      this.handlers.set(portId, []);
    }

    this.handlers.get(portId).push(handler);

    // Return unsubscribe function
    return () => {
      const handlers = this.handlers.get(portId);
      const index = handlers.indexOf(handler);
      if (index > -1) {
        handlers.splice(index, 1);
      }
    };
  }

  // Request-response pattern
  request(portId, message, timeout = 5000) {
    return new Promise((resolve, reject) => {
      const messageId = this.sendMessage(portId, {
        ...message,
        expectResponse: true,
      });

      const cleanup = this.onMessage(portId, (data, envelope) => {
        if (envelope.id === messageId || data.responseToId === messageId) {
          cleanup();
          clearTimeout(timeoutId);
          resolve(data);
        }
      });

      const timeoutId = setTimeout(() => {
        cleanup();
        reject(new Error(`Request timeout on ${portId}`));
      }, timeout);
    });
  }

  // Transfer port to another context
  transferPort(portId, targetWindow, targetOrigin = '*', message = {}) {
    const port = this.ports.get(portId);

    if (!port) {
      throw new Error(`Port ${portId} not found`);
    }

    // Remove from local management
    this.ports.delete(portId);

    // Transfer port
    targetWindow.postMessage(
      {
        type: 'port-transfer',
        portId,
        ...message,
      },
      targetOrigin,
      [port]
    );

    if (this.debug) {
      console.log(`Transferred port ${portId} to ${targetOrigin}`);
    }
  }

  // Receive transferred port
  receivePort(portId, port) {
    this.ports.set(portId, port);
    this.setupPort(port, portId);

    if (this.debug) {
      console.log(`Received port: ${portId}`);
    }
  }

  // Close port
  closePort(portId) {
    const port = this.ports.get(portId);

    if (port) {
      port.close();
      this.ports.delete(portId);
      this.handlers.delete(portId);

      if (this.debug) {
        console.log(`Closed port: ${portId}`);
      }
    }
  }

  // Close channel
  closeChannel(channelId) {
    this.closePort(`${channelId}-port1`);
    this.closePort(`${channelId}-port2`);
    this.channels.delete(channelId);
  }

  // Generate unique message ID
  generateMessageId() {
    return `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
  }

  // Enable message queuing
  enableQueue(portId) {
    if (!this.messageQueue.has(portId)) {
      this.messageQueue.set(portId, []);
    }
  }

  // Get queued messages
  getQueuedMessages(portId) {
    const queue = this.messageQueue.get(portId) || [];
    this.messageQueue.delete(portId);
    return queue;
  }
}

// Usage
const portManager = new PortManager();
portManager.debug = true;

// Create a channel
const channel = portManager.createChannel('main');

// Listen on port1
portManager.onMessage('main-port1', (data) => {
  console.log('Port1 received:', data);
});

// Send from port2
portManager.sendMessage('main-port2', {
  type: 'greeting',
  text: 'Hello from port2!',
});

// Request-response
portManager
  .request('main-port2', {
    type: 'get-data',
    key: 'user',
  })
  .then((response) => {
    console.log('Response:', response);
  });

Worker Communication

Enhanced Worker Communication

class WorkerPortCommunicator {
  constructor() {
    this.workers = new Map();
    this.portManager = new PortManager();
  }

  // Create worker with port
  createWorker(workerId, scriptUrl, options = {}) {
    const channel = this.portManager.createChannel(`worker-${workerId}`);
    const worker = new Worker(scriptUrl, options);

    // Store worker reference
    this.workers.set(workerId, {
      worker,
      port: channel.port1,
      channel,
    });

    // Transfer port2 to worker
    worker.postMessage(
      {
        type: 'init',
        port: channel.port2,
      },
      [channel.port2]
    );

    // Setup main thread port
    this.setupMainThreadPort(workerId);

    return worker;
  }

  // Setup main thread port handlers
  setupMainThreadPort(workerId) {
    const portId = `worker-${workerId}-port1`;

    // Handle worker messages
    this.portManager.onMessage(portId, (data, envelope) => {
      this.handleWorkerMessage(workerId, data, envelope);
    });

    // Handle worker errors
    const worker = this.workers.get(workerId).worker;

    worker.onerror = (error) => {
      console.error(`Worker ${workerId} error:`, error);
      this.handleWorkerError(workerId, error);
    };
  }

  // Send message to worker
  sendToWorker(workerId, message, transfer = []) {
    const portId = `worker-${workerId}-port1`;
    return this.portManager.sendMessage(portId, message, transfer);
  }

  // Request from worker
  requestFromWorker(workerId, message, timeout = 5000) {
    const portId = `worker-${workerId}-port1`;
    return this.portManager.request(portId, message, timeout);
  }

  // Handle worker messages
  handleWorkerMessage(workerId, data, envelope) {
    // Emit custom event
    const event = new CustomEvent('worker-message', {
      detail: {
        workerId,
        data,
        envelope,
      },
    });

    window.dispatchEvent(event);
  }

  // Handle worker errors
  handleWorkerError(workerId, error) {
    const event = new CustomEvent('worker-error', {
      detail: {
        workerId,
        error,
      },
    });

    window.dispatchEvent(event);
  }

  // Terminate worker
  terminateWorker(workerId) {
    const workerInfo = this.workers.get(workerId);

    if (workerInfo) {
      // Close port
      this.portManager.closeChannel(`worker-${workerId}`);

      // Terminate worker
      workerInfo.worker.terminate();

      // Clean up
      this.workers.delete(workerId);
    }
  }

  // Broadcast to all workers
  broadcastToWorkers(message, transfer = []) {
    const promises = [];

    this.workers.forEach((workerInfo, workerId) => {
      promises.push(this.sendToWorker(workerId, message, transfer));
    });

    return Promise.all(promises);
  }

  // Create worker pool
  createWorkerPool(poolSize, scriptUrl, options = {}) {
    const pool = {
      workers: [],
      currentIndex: 0,
    };

    for (let i = 0; i < poolSize; i++) {
      const workerId = `pool-worker-${i}`;
      const worker = this.createWorker(workerId, scriptUrl, options);
      pool.workers.push(workerId);
    }

    return {
      execute: (message) => {
        const workerId = pool.workers[pool.currentIndex];
        pool.currentIndex = (pool.currentIndex + 1) % poolSize;
        return this.requestFromWorker(workerId, message);
      },

      broadcast: (message) => {
        return Promise.all(
          pool.workers.map((workerId) => this.sendToWorker(workerId, message))
        );
      },

      terminate: () => {
        pool.workers.forEach((workerId) => this.terminateWorker(workerId));
      },
    };
  }
}

// Worker script (worker.js)
const workerScript = `
  let port;
  
  self.onmessage = (event) => {
    if (event.data.type === 'init' && event.data.port) {
      port = event.data.port;
      setupPort();
    }
  };
  
  function setupPort() {
    port.onmessage = async (event) => {
      const message = event.data;
      console.log('Worker received:', message);
      
      // Handle different message types
      switch (message.data.type) {
        case 'compute':
          const result = await performComputation(message.data.input);
          port.postMessage({
            id: message.id,
            responseToId: message.id,
            data: { result }
          });
          break;
          
        case 'get-status':
          port.postMessage({
            id: message.id,
            responseToId: message.id,
            data: { 
              status: 'ready',
              memory: performance.memory
            }
          });
          break;
      }
    };
    
    // Notify ready
    port.postMessage({
      data: { type: 'ready' }
    });
  }
  
  async function performComputation(input) {
    // Simulate heavy computation
    let result = 0;
    for (let i = 0; i < input; i++) {
      result += Math.sqrt(i);
    }
    return result;
  }
`;

// Create blob URL for worker
const blob = new Blob([workerScript], { type: 'application/javascript' });
const workerUrl = URL.createObjectURL(blob);

// Usage
const communicator = new WorkerPortCommunicator();

// Create single worker
const worker = communicator.createWorker('compute-1', workerUrl);

// Listen for worker messages
window.addEventListener('worker-message', (event) => {
  console.log('Worker message:', event.detail);
});

// Send computation request
communicator
  .requestFromWorker('compute-1', {
    type: 'compute',
    input: 1000000,
  })
  .then((result) => {
    console.log('Computation result:', result);
  });

// Create worker pool
const pool = communicator.createWorkerPool(4, workerUrl);

// Execute tasks on pool
Promise.all([
  pool.execute({ type: 'compute', input: 100000 }),
  pool.execute({ type: 'compute', input: 200000 }),
  pool.execute({ type: 'compute', input: 300000 }),
]).then((results) => {
  console.log('Pool results:', results);
});

Cross-Frame Communication

Secure Frame Messaging

class FrameMessenger {
  constructor(options = {}) {
    this.options = {
      targetOrigin: '*',
      timeout: 5000,
      handshakeTimeout: 10000,
      ...options,
    };

    this.frames = new Map();
    this.portManager = new PortManager();
    this.pendingHandshakes = new Map();

    this.init();
  }

  // Initialize frame messenger
  init() {
    // Listen for incoming connections
    window.addEventListener('message', (event) => {
      this.handleMessage(event);
    });
  }

  // Handle incoming messages
  handleMessage(event) {
    // Validate origin if specified
    if (
      this.options.targetOrigin !== '*' &&
      event.origin !== this.options.targetOrigin
    ) {
      console.warn('Rejected message from origin:', event.origin);
      return;
    }

    const { type, frameId, data } = event.data || {};

    switch (type) {
      case 'frame-handshake-request':
        this.handleHandshakeRequest(event);
        break;

      case 'frame-handshake-response':
        this.handleHandshakeResponse(event);
        break;

      case 'frame-port-transfer':
        this.handlePortTransfer(event);
        break;
    }
  }

  // Connect to frame
  connectToFrame(frame, frameId, options = {}) {
    return new Promise((resolve, reject) => {
      const channel = this.portManager.createChannel(`frame-${frameId}`);

      // Store pending handshake
      this.pendingHandshakes.set(frameId, {
        resolve,
        reject,
        channel,
        timeout: setTimeout(() => {
          this.pendingHandshakes.delete(frameId);
          reject(new Error('Handshake timeout'));
        }, options.handshakeTimeout || this.options.handshakeTimeout),
      });

      // Send handshake request
      frame.postMessage(
        {
          type: 'frame-handshake-request',
          frameId,
          origin: window.location.origin,
        },
        options.targetOrigin || this.options.targetOrigin
      );
    });
  }

  // Handle handshake request
  handleHandshakeRequest(event) {
    const { frameId, origin } = event.data;

    // Create channel for this frame
    const channel = this.portManager.createChannel(`frame-${frameId}`);

    // Store frame info
    this.frames.set(frameId, {
      frameId,
      origin,
      source: event.source,
      port: channel.port1,
      connectedAt: Date.now(),
    });

    // Setup port handlers
    this.setupFramePort(frameId);

    // Send handshake response with port
    event.source.postMessage(
      {
        type: 'frame-handshake-response',
        frameId,
        success: true,
      },
      event.origin
    );

    // Transfer port
    setTimeout(() => {
      event.source.postMessage(
        {
          type: 'frame-port-transfer',
          frameId,
          port: channel.port2,
        },
        event.origin,
        [channel.port2]
      );
    }, 100);
  }

  // Handle handshake response
  handleHandshakeResponse(event) {
    const { frameId, success } = event.data;
    const pending = this.pendingHandshakes.get(frameId);

    if (pending && success) {
      clearTimeout(pending.timeout);

      // Store frame info
      this.frames.set(frameId, {
        frameId,
        origin: event.origin,
        source: event.source,
        port: pending.channel.port1,
        connectedAt: Date.now(),
      });

      // Setup port handlers
      this.setupFramePort(frameId);

      pending.resolve({
        frameId,
        port: pending.channel.port1,
      });

      this.pendingHandshakes.delete(frameId);
    }
  }

  // Handle port transfer
  handlePortTransfer(event) {
    const { frameId, port } = event.data;

    if (event.ports && event.ports[0]) {
      const pending = this.pendingHandshakes.get(frameId);

      if (pending) {
        // Complete handshake
        this.portManager.receivePort(`frame-${frameId}-port2`, event.ports[0]);

        // Update frame info
        const frame = this.frames.get(frameId);
        if (frame) {
          frame.port = event.ports[0];
        }
      } else {
        // Direct port transfer
        this.portManager.receivePort(`frame-${frameId}-port`, event.ports[0]);
        this.setupFramePort(frameId);
      }
    }
  }

  // Setup frame port handlers
  setupFramePort(frameId) {
    const portId = `frame-${frameId}-port1`;

    // Handle frame messages
    this.portManager.onMessage(portId, (data, envelope) => {
      this.handleFrameMessage(frameId, data, envelope);
    });

    // Emit connected event
    const event = new CustomEvent('frame-connected', {
      detail: { frameId },
    });
    window.dispatchEvent(event);
  }

  // Handle frame messages
  handleFrameMessage(frameId, data, envelope) {
    const event = new CustomEvent('frame-message', {
      detail: {
        frameId,
        data,
        envelope,
      },
    });

    window.dispatchEvent(event);
  }

  // Send to frame
  sendToFrame(frameId, message, transfer = []) {
    const frame = this.frames.get(frameId);

    if (!frame) {
      throw new Error(`Frame ${frameId} not connected`);
    }

    const portId = `frame-${frameId}-port1`;
    return this.portManager.sendMessage(portId, message, transfer);
  }

  // Request from frame
  requestFromFrame(frameId, message, timeout) {
    const portId = `frame-${frameId}-port1`;
    return this.portManager.request(
      portId,
      message,
      timeout || this.options.timeout
    );
  }

  // Broadcast to all frames
  broadcastToFrames(message, transfer = []) {
    const promises = [];

    this.frames.forEach((frame, frameId) => {
      promises.push(this.sendToFrame(frameId, message, transfer));
    });

    return Promise.all(promises);
  }

  // Disconnect frame
  disconnectFrame(frameId) {
    const frame = this.frames.get(frameId);

    if (frame) {
      // Close port
      this.portManager.closeChannel(`frame-${frameId}`);

      // Remove frame
      this.frames.delete(frameId);

      // Emit disconnected event
      const event = new CustomEvent('frame-disconnected', {
        detail: { frameId },
      });
      window.dispatchEvent(event);
    }
  }

  // Get connected frames
  getConnectedFrames() {
    return Array.from(this.frames.values()).map((frame) => ({
      frameId: frame.frameId,
      origin: frame.origin,
      connectedAt: frame.connectedAt,
    }));
  }
}

// Parent window usage
const messenger = new FrameMessenger({
  targetOrigin: 'https://trusted-domain.com',
});

// Connect to iframe
const iframe = document.getElementById('my-iframe');
messenger
  .connectToFrame(iframe.contentWindow, 'iframe-1')
  .then(({ frameId, port }) => {
    console.log('Connected to frame:', frameId);

    // Send message
    messenger.sendToFrame(frameId, {
      type: 'config',
      theme: 'dark',
    });
  });

// Listen for frame messages
window.addEventListener('frame-message', (event) => {
  console.log('Frame message:', event.detail);
});

// Child frame usage
const childMessenger = new FrameMessenger();

// Wait for connection
window.addEventListener('frame-connected', (event) => {
  const frameId = event.detail.frameId;

  // Send message to parent
  childMessenger.sendToFrame(frameId, {
    type: 'ready',
    capabilities: ['feature1', 'feature2'],
  });
});

Shared State Management

Distributed State with Ports

class PortBasedStateManager {
  constructor(options = {}) {
    this.options = {
      syncInterval: 1000,
      conflictResolution: 'vector-clock',
      ...options,
    };

    this.state = {};
    this.version = 0;
    this.vectorClock = new Map();
    this.peers = new Map();
    this.portManager = new PortManager();
    this.nodeId = this.generateNodeId();

    this.init();
  }

  // Initialize state manager
  init() {
    // Initialize vector clock
    this.vectorClock.set(this.nodeId, 0);

    // Start sync timer
    if (this.options.syncInterval > 0) {
      this.startSyncTimer();
    }
  }

  // Connect to peer
  connectToPeer(peerId, port) {
    // Receive the port
    this.portManager.receivePort(`peer-${peerId}`, port);

    // Store peer info
    this.peers.set(peerId, {
      peerId,
      port,
      lastSync: 0,
      vectorClock: new Map(),
    });

    // Setup handlers
    this.setupPeerHandlers(peerId);

    // Initial sync
    this.syncWithPeer(peerId);
  }

  // Setup peer handlers
  setupPeerHandlers(peerId) {
    const portId = `peer-${peerId}`;

    this.portManager.onMessage(portId, (data) => {
      this.handlePeerMessage(peerId, data);
    });
  }

  // Handle peer messages
  handlePeerMessage(peerId, message) {
    switch (message.type) {
      case 'state-update':
        this.handleStateUpdate(peerId, message.data);
        break;

      case 'sync-request':
        this.handleSyncRequest(peerId, message.data);
        break;

      case 'sync-response':
        this.handleSyncResponse(peerId, message.data);
        break;

      case 'vector-clock-update':
        this.handleVectorClockUpdate(peerId, message.data);
        break;
    }
  }

  // Update state
  updateState(path, value) {
    // Update local state
    this.setValueAtPath(this.state, path, value);

    // Increment vector clock
    this.vectorClock.set(this.nodeId, this.vectorClock.get(this.nodeId) + 1);
    this.version++;

    // Create update message
    const update = {
      path,
      value,
      version: this.version,
      vectorClock: this.serializeVectorClock(),
      timestamp: Date.now(),
      nodeId: this.nodeId,
    };

    // Broadcast to peers
    this.broadcastToPeers('state-update', update);

    return update;
  }

  // Handle state update from peer
  handleStateUpdate(peerId, update) {
    const { path, value, vectorClock, nodeId } = update;

    // Update peer's vector clock
    const peer = this.peers.get(peerId);
    if (peer) {
      peer.vectorClock = this.deserializeVectorClock(vectorClock);
    }

    // Check if update should be applied
    if (this.shouldApplyUpdate(update)) {
      // Apply update
      this.setValueAtPath(this.state, path, value);

      // Update vector clock
      this.mergeVectorClock(vectorClock);

      // Emit change event
      this.emitChange(path, value, update);
    } else {
      // Handle conflict
      this.handleConflict(path, value, update);
    }
  }

  // Check if update should be applied
  shouldApplyUpdate(update) {
    const { vectorClock, nodeId } = update;
    const remoteVC = this.deserializeVectorClock(vectorClock);

    // Compare vector clocks
    return (
      this.isVectorClockConcurrent(remoteVC) ||
      this.isVectorClockGreater(remoteVC)
    );
  }

  // Compare vector clocks
  isVectorClockGreater(remoteVC) {
    let hasGreater = false;
    let hasLess = false;

    remoteVC.forEach((value, nodeId) => {
      const localValue = this.vectorClock.get(nodeId) || 0;
      if (value > localValue) hasGreater = true;
      if (value < localValue) hasLess = true;
    });

    return hasGreater && !hasLess;
  }

  // Check if vector clocks are concurrent
  isVectorClockConcurrent(remoteVC) {
    let hasGreater = false;
    let hasLess = false;

    remoteVC.forEach((value, nodeId) => {
      const localValue = this.vectorClock.get(nodeId) || 0;
      if (value > localValue) hasGreater = true;
      if (value < localValue) hasLess = true;
    });

    return hasGreater && hasLess;
  }

  // Handle conflicts
  handleConflict(path, remoteValue, update) {
    const localValue = this.getValueAtPath(this.state, path);

    switch (this.options.conflictResolution) {
      case 'vector-clock':
        // Already handled by shouldApplyUpdate
        break;

      case 'last-write-wins':
        if (update.timestamp > this.getLastUpdateTime(path)) {
          this.setValueAtPath(this.state, path, remoteValue);
        }
        break;

      case 'custom':
        if (this.options.conflictResolver) {
          const resolvedValue = this.options.conflictResolver(
            localValue,
            remoteValue,
            update
          );
          this.setValueAtPath(this.state, path, resolvedValue);
        }
        break;
    }
  }

  // Sync with peer
  syncWithPeer(peerId) {
    const portId = `peer-${peerId}`;

    this.portManager.sendMessage(portId, {
      type: 'sync-request',
      data: {
        vectorClock: this.serializeVectorClock(),
        nodeId: this.nodeId,
      },
    });
  }

  // Handle sync request
  handleSyncRequest(peerId, data) {
    const { vectorClock } = data;
    const remoteVC = this.deserializeVectorClock(vectorClock);

    // Find state differences
    const updates = this.findStateDifferences(remoteVC);

    // Send sync response
    const portId = `peer-${peerId}`;
    this.portManager.sendMessage(portId, {
      type: 'sync-response',
      data: {
        updates,
        vectorClock: this.serializeVectorClock(),
        nodeId: this.nodeId,
      },
    });
  }

  // Handle sync response
  handleSyncResponse(peerId, data) {
    const { updates } = data;

    // Apply updates
    updates.forEach((update) => {
      this.handleStateUpdate(peerId, update);
    });
  }

  // Find state differences based on vector clock
  findStateDifferences(remoteVC) {
    const differences = [];

    // This is simplified - real implementation would track
    // updates with their vector clock timestamps
    this.vectorClock.forEach((value, nodeId) => {
      const remoteValue = remoteVC.get(nodeId) || 0;
      if (value > remoteValue) {
        // We have updates the peer doesn't have
        // Add relevant state updates to differences
      }
    });

    return differences;
  }

  // Broadcast to all peers
  broadcastToPeers(type, data) {
    this.peers.forEach((peer, peerId) => {
      const portId = `peer-${peerId}`;
      this.portManager.sendMessage(portId, {
        type,
        data,
      });
    });
  }

  // Start sync timer
  startSyncTimer() {
    this.syncInterval = setInterval(() => {
      this.peers.forEach((peer, peerId) => {
        this.syncWithPeer(peerId);
      });
    }, this.options.syncInterval);
  }

  // Stop sync timer
  stopSyncTimer() {
    if (this.syncInterval) {
      clearInterval(this.syncInterval);
      this.syncInterval = null;
    }
  }

  // Merge vector clocks
  mergeVectorClock(remoteVC) {
    const remote = this.deserializeVectorClock(remoteVC);

    remote.forEach((value, nodeId) => {
      const localValue = this.vectorClock.get(nodeId) || 0;
      this.vectorClock.set(nodeId, Math.max(localValue, value));
    });
  }

  // Serialize vector clock
  serializeVectorClock() {
    return Array.from(this.vectorClock.entries());
  }

  // Deserialize vector clock
  deserializeVectorClock(serialized) {
    return new Map(serialized);
  }

  // Helper methods
  setValueAtPath(obj, path, value) {
    const parts = path.split('.');
    const last = parts.pop();
    let current = obj;

    parts.forEach((part) => {
      if (!current[part]) current[part] = {};
      current = current[part];
    });

    current[last] = value;
  }

  getValueAtPath(obj, path) {
    const parts = path.split('.');
    let current = obj;

    for (const part of parts) {
      if (!current || !current[part]) return undefined;
      current = current[part];
    }

    return current;
  }

  getLastUpdateTime(path) {
    // In real implementation, track update times per path
    return 0;
  }

  generateNodeId() {
    return `node-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
  }

  emitChange(path, value, update) {
    const event = new CustomEvent('state-change', {
      detail: { path, value, update },
    });
    window.dispatchEvent(event);
  }

  // Get current state
  getState() {
    return { ...this.state };
  }

  // Disconnect from peer
  disconnectPeer(peerId) {
    this.portManager.closePort(`peer-${peerId}`);
    this.peers.delete(peerId);
  }

  // Cleanup
  destroy() {
    this.stopSyncTimer();
    this.peers.forEach((peer, peerId) => {
      this.disconnectPeer(peerId);
    });
  }
}

// Usage
const stateManager = new PortBasedStateManager({
  syncInterval: 2000,
  conflictResolution: 'vector-clock',
});

// Create channel for peer connection
const channel = new MessageChannel();

// Connect to peer (in another context)
stateManager.connectToPeer('peer-1', channel.port2);

// Update state
stateManager.updateState('user.name', 'John Doe');
stateManager.updateState('settings.theme', 'dark');

// Listen for state changes
window.addEventListener('state-change', (event) => {
  console.log('State changed:', event.detail);
});

Performance Optimization

Efficient Message Passing

class OptimizedPortMessenger {
  constructor() {
    this.portManager = new PortManager();
    this.messageBuffer = new Map();
    this.batchSize = 50;
    this.batchDelay = 16; // One frame
    this.compressionThreshold = 1024; // 1KB
  }

  // Send message with optimization
  sendOptimized(portId, message, options = {}) {
    const { priority = 'normal', batch = true, compress = true } = options;

    // High priority messages bypass optimization
    if (priority === 'high') {
      return this.sendDirect(portId, message);
    }

    // Batch messages if enabled
    if (batch) {
      return this.batchMessage(portId, message);
    }

    // Compress large messages
    if (compress && this.shouldCompress(message)) {
      return this.sendCompressed(portId, message);
    }

    return this.sendDirect(portId, message);
  }

  // Send message directly
  sendDirect(portId, message) {
    return this.portManager.sendMessage(portId, message);
  }

  // Batch messages
  batchMessage(portId, message) {
    if (!this.messageBuffer.has(portId)) {
      this.messageBuffer.set(portId, []);

      // Schedule batch send
      setTimeout(() => {
        this.flushBatch(portId);
      }, this.batchDelay);
    }

    this.messageBuffer.get(portId).push(message);

    // Flush if batch is full
    if (this.messageBuffer.get(portId).length >= this.batchSize) {
      this.flushBatch(portId);
    }
  }

  // Flush message batch
  flushBatch(portId) {
    const messages = this.messageBuffer.get(portId);

    if (!messages || messages.length === 0) return;

    // Send batched messages
    this.portManager.sendMessage(portId, {
      type: 'batch',
      messages: messages,
    });

    // Clear buffer
    this.messageBuffer.delete(portId);
  }

  // Check if message should be compressed
  shouldCompress(message) {
    const size = JSON.stringify(message).length;
    return size > this.compressionThreshold;
  }

  // Send compressed message
  async sendCompressed(portId, message) {
    const compressed = await this.compress(message);

    return this.portManager.sendMessage(portId, {
      type: 'compressed',
      data: compressed,
    });
  }

  // Compress data
  async compress(data) {
    const json = JSON.stringify(data);
    const encoder = new TextEncoder();
    const uint8Array = encoder.encode(json);

    // Use CompressionStream if available
    if ('CompressionStream' in window) {
      const stream = new Response(uint8Array).body.pipeThrough(
        new CompressionStream('gzip')
      );

      const compressed = await new Response(stream).arrayBuffer();
      return compressed;
    }

    // Fallback to simple compression
    return this.simpleCompress(json);
  }

  // Simple compression fallback
  simpleCompress(str) {
    // Simple RLE compression for demo
    let compressed = '';
    let count = 1;

    for (let i = 0; i < str.length; i++) {
      if (str[i] === str[i + 1]) {
        count++;
      } else {
        compressed += count > 1 ? count + str[i] : str[i];
        count = 1;
      }
    }

    return compressed;
  }

  // Setup optimized receiver
  setupOptimizedReceiver(portId, handler) {
    this.portManager.onMessage(portId, async (message) => {
      // Handle batched messages
      if (message.type === 'batch') {
        message.messages.forEach((msg) => handler(msg));
        return;
      }

      // Handle compressed messages
      if (message.type === 'compressed') {
        const decompressed = await this.decompress(message.data);
        handler(decompressed);
        return;
      }

      // Regular message
      handler(message);
    });
  }

  // Decompress data
  async decompress(data) {
    if ('DecompressionStream' in window) {
      const stream = new Response(data).body.pipeThrough(
        new DecompressionStream('gzip')
      );

      const decompressed = await new Response(stream).text();
      return JSON.parse(decompressed);
    }

    // Fallback
    return this.simpleDecompress(data);
  }

  // Simple decompression fallback
  simpleDecompress(str) {
    // Reverse simple RLE compression
    let decompressed = '';
    let i = 0;

    while (i < str.length) {
      if (/\d/.test(str[i])) {
        let count = '';
        while (i < str.length && /\d/.test(str[i])) {
          count += str[i++];
        }
        decompressed += str[i].repeat(parseInt(count));
      } else {
        decompressed += str[i];
      }
      i++;
    }

    return JSON.parse(decompressed);
  }

  // Create transferable objects
  createTransferable(data) {
    if (data instanceof ArrayBuffer) {
      return { data, transfer: [data] };
    }

    if (data instanceof ImageBitmap) {
      return { data, transfer: [data] };
    }

    if (typeof data === 'object' && data.buffer instanceof ArrayBuffer) {
      return { data: data.buffer, transfer: [data.buffer] };
    }

    return { data, transfer: [] };
  }

  // Send with transferables
  sendWithTransfer(portId, message, transferables = []) {
    // Automatically detect transferables
    const autoTransfer = this.detectTransferables(message);
    const allTransfer = [...transferables, ...autoTransfer];

    return this.portManager.sendMessage(portId, message, allTransfer);
  }

  // Detect transferable objects
  detectTransferables(obj, found = []) {
    if (
      obj instanceof ArrayBuffer ||
      obj instanceof MessagePort ||
      obj instanceof ImageBitmap
    ) {
      found.push(obj);
    } else if (typeof obj === 'object' && obj !== null) {
      Object.values(obj).forEach((value) => {
        this.detectTransferables(value, found);
      });
    }

    return found;
  }
}

// Usage
const optimized = new OptimizedPortMessenger();

// Send regular message
optimized.sendOptimized('port-1', {
  type: 'update',
  data: 'small payload',
});

// Send large message (will be compressed)
const largeData = new Array(1000).fill('data').join('');
optimized.sendOptimized('port-1', {
  type: 'bulk',
  data: largeData,
});

// Send high priority message
optimized.sendOptimized(
  'port-1',
  {
    type: 'critical',
    alert: 'System error',
  },
  { priority: 'high' }
);

// Send with transferables
const buffer = new ArrayBuffer(1024);
optimized.sendWithTransfer('port-1', {
  type: 'binary',
  data: buffer,
});

// Setup receiver
optimized.setupOptimizedReceiver('port-1', (message) => {
  console.log('Received:', message);
});

Best Practices

  1. Always start ports when using addEventListener

    port.addEventListener('message', handler);
    port.start(); // Required!
    
  2. Transfer ports, don't copy

    // Good - transfers ownership
    window.postMessage('init', '*', [port]);
    
    // Bad - port becomes unusable
    window.postMessage({ port }, '*');
    
  3. Close ports when done

    // Clean up resources
    port.close();
    
  4. Validate messages

    port.onmessage = (event) => {
      if (!isValidMessage(event.data)) return;
      processMessage(event.data);
    };
    

Conclusion

The Channel Messaging API enables powerful communication patterns:

  • Direct port-to-port communication
  • Worker orchestration with dedicated channels
  • Secure frame messaging with origin validation
  • Distributed state management
  • Performance optimization with batching and compression
  • Complex messaging patterns

Key takeaways:

  • Use MessageChannel for dedicated communication
  • Transfer ports for ownership handoff
  • Always validate message origins
  • Close ports to free resources
  • Consider performance optimizations
  • Build robust error handling

Create sophisticated multi-context applications with secure, efficient communication!