{"version":3,"file":"BroadcastChannelLeader.cjs","sources":["../../../src/coordination/BroadcastChannelLeader.ts"],"sourcesContent":["import { BaseLeaderElection } from './LeaderElection'\n\ninterface LeaderMessage {\n  type: `heartbeat` | `election` | `leadership-claim`\n  tabId: string\n  timestamp: number\n}\n\nexport class BroadcastChannelLeader extends BaseLeaderElection {\n  private channelName: string\n  private tabId: string\n  private channel: BroadcastChannel | null = null\n  private heartbeatInterval: number | null = null\n  private electionTimeout: number | null = null\n  private lastLeaderHeartbeat = 0\n  private readonly heartbeatIntervalMs = 5000\n  private readonly electionTimeoutMs = 10000\n\n  constructor(channelName = `offline-executor-leader`) {\n    super()\n    this.channelName = channelName\n    this.tabId = crypto.randomUUID()\n    this.setupChannel()\n  }\n\n  private setupChannel(): void {\n    if (!this.isBroadcastChannelSupported()) {\n      return\n    }\n\n    this.channel = new BroadcastChannel(this.channelName)\n    this.channel.addEventListener(`message`, this.handleMessage)\n  }\n\n  private handleMessage = (event: MessageEvent<LeaderMessage>): void => {\n    const { type, tabId, timestamp } = event.data\n\n    if (tabId === this.tabId) {\n      return\n    }\n\n    switch (type) {\n      case `heartbeat`:\n        if (this.isLeaderState && tabId < this.tabId) {\n          this.releaseLeadership()\n        } else if (!this.isLeaderState) {\n          this.lastLeaderHeartbeat = timestamp\n          this.cancelElection()\n        }\n        break\n\n      case `election`:\n        if (this.isLeaderState) {\n          this.sendHeartbeat()\n        } else if (tabId > this.tabId) {\n          this.startElection()\n        }\n        break\n\n      case `leadership-claim`:\n        if (this.isLeaderState && tabId < this.tabId) {\n          this.releaseLeadership()\n        }\n        break\n    }\n  }\n\n  async requestLeadership(): Promise<boolean> {\n    if (!this.isBroadcastChannelSupported()) {\n      return false\n    }\n\n    if (this.isLeaderState) {\n      return true\n    }\n\n    this.startElection()\n\n    return new Promise((resolve) => {\n      setTimeout(() => {\n        resolve(this.isLeaderState)\n      }, 1000)\n    })\n  }\n\n  private startElection(): void {\n    if (this.electionTimeout) {\n      return\n    }\n\n    this.sendMessage({\n      type: `election`,\n      tabId: this.tabId,\n      timestamp: Date.now(),\n    })\n\n    this.electionTimeout = window.setTimeout(() => {\n      const timeSinceLastHeartbeat = Date.now() - this.lastLeaderHeartbeat\n\n      if (timeSinceLastHeartbeat > this.electionTimeoutMs) {\n        this.claimLeadership()\n      }\n\n      this.electionTimeout = null\n    }, this.electionTimeoutMs)\n  }\n\n  private cancelElection(): void {\n    if (this.electionTimeout) {\n      clearTimeout(this.electionTimeout)\n      this.electionTimeout = null\n    }\n  }\n\n  private claimLeadership(): void {\n    this.notifyLeadershipChange(true)\n    this.sendMessage({\n      type: `leadership-claim`,\n      tabId: this.tabId,\n      timestamp: Date.now(),\n    })\n    this.startHeartbeat()\n  }\n\n  private startHeartbeat(): void {\n    if (this.heartbeatInterval) {\n      return\n    }\n\n    this.sendHeartbeat()\n\n    this.heartbeatInterval = window.setInterval(() => {\n      this.sendHeartbeat()\n    }, this.heartbeatIntervalMs)\n  }\n\n  private stopHeartbeat(): void {\n    if (this.heartbeatInterval) {\n      clearInterval(this.heartbeatInterval)\n      this.heartbeatInterval = null\n    }\n  }\n\n  private sendHeartbeat(): void {\n    this.sendMessage({\n      type: `heartbeat`,\n      tabId: this.tabId,\n      timestamp: Date.now(),\n    })\n  }\n\n  private sendMessage(message: LeaderMessage): void {\n    if (this.channel) {\n      this.channel.postMessage(message)\n    }\n  }\n\n  releaseLeadership(): void {\n    this.stopHeartbeat()\n    this.cancelElection()\n    this.notifyLeadershipChange(false)\n  }\n\n  private isBroadcastChannelSupported(): boolean {\n    return typeof BroadcastChannel !== `undefined`\n  }\n\n  static isSupported(): boolean {\n    return typeof BroadcastChannel !== `undefined`\n  }\n\n  dispose(): void {\n    this.releaseLeadership()\n\n    if (this.channel) {\n      this.channel.removeEventListener(`message`, this.handleMessage)\n      this.channel.close()\n      this.channel = null\n    }\n  }\n}\n"],"names":["BaseLeaderElection"],"mappings":"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"}