{"version":3,"sources":["/home/ubuntu24-new/Dev/stormcloud-vp/lib/utils/mqttClient.cjs","../../src/utils/mqttClient.ts","../../src/utils/mqttConfig.ts"],"names":["Object","create","__create","defineProperty","__defProp","__getOwnPropDesc","getOwnPropertyDescriptor","__getOwnPropNames","getOwnPropertyNames","__getProtoOf","getPrototypeOf","__hasOwnProp","prototype","hasOwnProperty","__export","target","all","name","get","enumerable","__copyProps","to","from","except","desc","key","call","__toESM","mod","isNodeMode","__esModule","value","__toCommonJS","mqttClient_exports","configureMQTT","disconnectMQTT","ensureMQTTClient","getMQTTStatus","initMQTTClient","isMQTTConfigured","isMQTTConnected","publishMQTT","module","exports","import_mqtt","require","DEFAULT_MQTT_CONFIG","enabled","brokerAddress","brokerPort","wsPort","password","topicPrefix","qos","mqttConfig","applyMQTTConfig","overrides","assign","isMQTTEnabled","buildMQTTBrokerUrl","brokerUrl","LOG","KEEPALIVE_SECONDS","CONNECT_TIMEOUT_MS","RECONNECT_BASE_MS","RECONNECT_MAX_MS","MAX_RECONNECT_ATTEMPTS","COOLDOWN_MS","client","status","reconnectAttempts","reconnectTimer","stopped","connected","clearReconnectTimer","clearTimeout","teardownClient","removeAllListeners","end","scheduleReconnect","console","warn","setTimeout","openConnection","delay","Math","min","jitter","floor","random","url","clientId","toString","slice","mqtt","connect","username","keepalive","clean","reconnectPeriod","connectTimeout","queueQoSZero","reschedulePings","timerVariant","err"],"mappings":"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","sourcesContent":["\"use strict\";\nvar __create = Object.create;\nvar __defProp = Object.defineProperty;\nvar __getOwnPropDesc = Object.getOwnPropertyDescriptor;\nvar __getOwnPropNames = Object.getOwnPropertyNames;\nvar __getProtoOf = Object.getPrototypeOf;\nvar __hasOwnProp = Object.prototype.hasOwnProperty;\nvar __export = (target, all) => {\n  for (var name in all)\n    __defProp(target, name, { get: all[name], enumerable: true });\n};\nvar __copyProps = (to, from, except, desc) => {\n  if (from && typeof from === \"object\" || typeof from === \"function\") {\n    for (let key of __getOwnPropNames(from))\n      if (!__hasOwnProp.call(to, key) && key !== except)\n        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });\n  }\n  return to;\n};\nvar __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(\n  // If the importer is in node compatibility mode or this is not an ESM\n  // file that has been converted to a CommonJS file using a Babel-\n  // compatible transform (i.e. \"__esModule\" has not been set), then set\n  // \"default\" to the CommonJS \"module.exports\" for node compatibility.\n  isNodeMode || !mod || !mod.__esModule ? __defProp(target, \"default\", { value: mod, enumerable: true }) : target,\n  mod\n));\nvar __toCommonJS = (mod) => __copyProps(__defProp({}, \"__esModule\", { value: true }), mod);\n\n// src/utils/mqttClient.ts\nvar mqttClient_exports = {};\n__export(mqttClient_exports, {\n  configureMQTT: () => configureMQTT,\n  disconnectMQTT: () => disconnectMQTT,\n  ensureMQTTClient: () => ensureMQTTClient,\n  getMQTTStatus: () => getMQTTStatus,\n  initMQTTClient: () => initMQTTClient,\n  isMQTTConfigured: () => isMQTTConfigured,\n  isMQTTConnected: () => isMQTTConnected,\n  publishMQTT: () => publishMQTT\n});\nmodule.exports = __toCommonJS(mqttClient_exports);\nvar import_mqtt = __toESM(require(\"mqtt\"), 1);\n\n// src/utils/mqttConfig.ts\nvar DEFAULT_MQTT_CONFIG = {\n  enabled: true,\n  brokerAddress: \"vecbae77.ala.us-east-1.emqxsl.com\",\n  brokerPort: 8883,\n  wsPort: 8084,\n  username: \"for-sonifi\",\n  password: \"sonifi-mqtt\",\n  topicPrefix: \"adstorm/players\",\n  qos: 1\n};\nvar mqttConfig = { ...DEFAULT_MQTT_CONFIG };\nfunction applyMQTTConfig(overrides) {\n  Object.assign(mqttConfig, overrides);\n}\nfunction isMQTTEnabled() {\n  return mqttConfig.enabled;\n}\nfunction buildMQTTBrokerUrl() {\n  if (mqttConfig.brokerUrl) return mqttConfig.brokerUrl;\n  return `wss://${mqttConfig.brokerAddress}:${mqttConfig.wsPort}/mqtt`;\n}\n\n// src/utils/mqttClient.ts\nvar LOG = \"[StormcloudVideoPlayer][MQTT]\";\nvar KEEPALIVE_SECONDS = 30;\nvar CONNECT_TIMEOUT_MS = 1e4;\nvar RECONNECT_BASE_MS = 5e3;\nvar RECONNECT_MAX_MS = 6e4;\nvar MAX_RECONNECT_ATTEMPTS = 8;\nvar COOLDOWN_MS = 10 * 6e4;\nvar client = null;\nvar status = \"disconnected\";\nvar reconnectAttempts = 0;\nvar reconnectTimer = null;\nvar stopped = false;\nfunction getMQTTStatus() {\n  return status;\n}\nfunction isMQTTConnected() {\n  return status === \"connected\" && client !== null && client.connected;\n}\nfunction isMQTTConfigured() {\n  return isMQTTEnabled();\n}\nfunction configureMQTT(overrides) {\n  applyMQTTConfig(overrides);\n  disconnectMQTT();\n}\nfunction clearReconnectTimer() {\n  if (reconnectTimer) {\n    clearTimeout(reconnectTimer);\n    reconnectTimer = null;\n  }\n}\nfunction teardownClient() {\n  if (client) {\n    try {\n      client.removeAllListeners();\n      client.end(true);\n    } catch {\n    }\n    client = null;\n  }\n}\nfunction scheduleReconnect() {\n  if (stopped || !isMQTTEnabled() || reconnectTimer) return;\n  if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {\n    console.warn(\n      `${LOG} giving up after ${reconnectAttempts} attempts; cooling down for ${COOLDOWN_MS / 6e4}m`\n    );\n    teardownClient();\n    status = \"disconnected\";\n    reconnectTimer = setTimeout(() => {\n      reconnectTimer = null;\n      reconnectAttempts = 0;\n      openConnection();\n    }, COOLDOWN_MS);\n    return;\n  }\n  const delay = Math.min(\n    RECONNECT_MAX_MS,\n    RECONNECT_BASE_MS * 2 ** reconnectAttempts\n  );\n  const jitter = Math.floor(Math.random() * 1e3);\n  reconnectAttempts += 1;\n  reconnectTimer = setTimeout(() => {\n    reconnectTimer = null;\n    openConnection();\n  }, delay + jitter);\n}\nfunction openConnection() {\n  if (stopped || !isMQTTEnabled()) return;\n  teardownClient();\n  const url = buildMQTTBrokerUrl();\n  status = \"connecting\";\n  const clientId = `stormcloud-vp-${Math.random().toString(36).slice(2, 9)}`;\n  try {\n    client = import_mqtt.default.connect(url, {\n      clientId,\n      username: mqttConfig.username,\n      password: mqttConfig.password,\n      keepalive: KEEPALIVE_SECONDS,\n      clean: true,\n      // Disable MQTT.js internal reconnect; we manage reconnection ourselves.\n      reconnectPeriod: 0,\n      connectTimeout: CONNECT_TIMEOUT_MS,\n      queueQoSZero: false,\n      reschedulePings: true,\n      // Root-cause fix for the Tizen 4.0 \"Keepalive timeout\" loop: MQTT.js's\n      // default timerVariant \"auto\" runs the keepalive interval inside a\n      // Blob-URL Web Worker (via worker-timers). On Samsung Tizen 4.0 (old\n      // WebKit) served from a file:// origin that worker can't tick reliably,\n      // so the keepalive counter trips onKeepaliveTimeout almost immediately\n      // after connect. Force the native setInterval timer instead; the main\n      // thread is free because video is decoded by the native AVPlay pipeline.\n      timerVariant: \"native\"\n    });\n  } catch (err) {\n    status = \"error\";\n    console.warn(`${LOG} connect() threw:`, err);\n    scheduleReconnect();\n    return;\n  }\n  client.on(\"connect\", () => {\n    status = \"connected\";\n    reconnectAttempts = 0;\n    clearReconnectTimer();\n    console.info(`${LOG} connected to ${url}`);\n  });\n  client.on(\"offline\", () => {\n    if (status === \"connected\") status = \"disconnected\";\n    console.warn(`${LOG} offline`);\n    scheduleReconnect();\n  });\n  client.on(\"error\", (err) => {\n    status = \"error\";\n    console.warn(`${LOG} error:`, err.message);\n    scheduleReconnect();\n  });\n  client.on(\"close\", () => {\n    if (status === \"connected\") status = \"disconnected\";\n    scheduleReconnect();\n  });\n}\nfunction initMQTTClient() {\n  if (client || !isMQTTEnabled()) return;\n  stopped = false;\n  reconnectAttempts = 0;\n  openConnection();\n}\nfunction ensureMQTTClient() {\n  if (isMQTTEnabled() && !client && !reconnectTimer) {\n    initMQTTClient();\n  }\n}\nfunction publishMQTT(topic, payload) {\n  if (!isMQTTEnabled()) {\n    return false;\n  }\n  ensureMQTTClient();\n  if (!client || !client.connected) {\n    return false;\n  }\n  try {\n    client.publish(topic, JSON.stringify(payload), { qos: mqttConfig.qos });\n    return true;\n  } catch (err) {\n    console.warn(`${LOG} publish failed on ${topic}:`, err);\n    return false;\n  }\n}\nfunction disconnectMQTT() {\n  stopped = true;\n  clearReconnectTimer();\n  teardownClient();\n  reconnectAttempts = 0;\n  status = \"disconnected\";\n}\n// Annotate the CommonJS export names for ESM import in node:\n0 && (module.exports = {\n  configureMQTT,\n  disconnectMQTT,\n  ensureMQTTClient,\n  getMQTTStatus,\n  initMQTTClient,\n  isMQTTConfigured,\n  isMQTTConnected,\n  publishMQTT\n});\n","import mqtt from \"mqtt\";\nimport type { MqttClient } from \"mqtt\";\nimport {\n  applyMQTTConfig,\n  buildMQTTBrokerUrl,\n  isMQTTEnabled,\n  mqttConfig,\n} from \"./mqttConfig\";\nimport type { MQTTConfig } from \"./mqttConfig\";\n\nconst LOG = \"[StormcloudVideoPlayer][MQTT]\";\n\nconst KEEPALIVE_SECONDS = 30;\nconst CONNECT_TIMEOUT_MS = 10_000;\n\nconst RECONNECT_BASE_MS = 5_000;\nconst RECONNECT_MAX_MS = 60_000;\nconst MAX_RECONNECT_ATTEMPTS = 8;\nconst COOLDOWN_MS = 10 * 60_000;\n\nexport type MQTTStatus = \"disconnected\" | \"connecting\" | \"connected\" | \"error\";\n\nlet client: MqttClient | null = null;\nlet status: MQTTStatus = \"disconnected\";\nlet reconnectAttempts = 0;\nlet reconnectTimer: ReturnType<typeof setTimeout> | null = null;\nlet stopped = false;\n\nexport function getMQTTStatus(): MQTTStatus {\n  return status;\n}\n\nexport function isMQTTConnected(): boolean {\n  return status === \"connected\" && client !== null && client.connected;\n}\n\nexport function isMQTTConfigured(): boolean {\n  return isMQTTEnabled();\n}\n\nexport function configureMQTT(overrides: Partial<MQTTConfig>): void {\n  applyMQTTConfig(overrides);\n  disconnectMQTT();\n}\n\nfunction clearReconnectTimer(): void {\n  if (reconnectTimer) {\n    clearTimeout(reconnectTimer);\n    reconnectTimer = null;\n  }\n}\n\nfunction teardownClient(): void {\n  if (client) {\n    try {\n      client.removeAllListeners();\n      client.end(true);\n    } catch {\n      // ignore teardown errors\n    }\n    client = null;\n  }\n}\n\nfunction scheduleReconnect(): void {\n  if (stopped || !isMQTTEnabled() || reconnectTimer) return;\n\n  if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {\n    console.warn(\n      `${LOG} giving up after ${reconnectAttempts} attempts; cooling down for ${\n        COOLDOWN_MS / 60_000\n      }m`\n    );\n    teardownClient();\n    status = \"disconnected\";\n    reconnectTimer = setTimeout(() => {\n      reconnectTimer = null;\n      reconnectAttempts = 0;\n      openConnection();\n    }, COOLDOWN_MS);\n    return;\n  }\n\n  const delay = Math.min(\n    RECONNECT_MAX_MS,\n    RECONNECT_BASE_MS * 2 ** reconnectAttempts\n  );\n  const jitter = Math.floor(Math.random() * 1_000);\n  reconnectAttempts += 1;\n\n  reconnectTimer = setTimeout(() => {\n    reconnectTimer = null;\n    openConnection();\n  }, delay + jitter);\n}\n\nfunction openConnection(): void {\n  if (stopped || !isMQTTEnabled()) return;\n\n  teardownClient();\n\n  const url = buildMQTTBrokerUrl();\n  status = \"connecting\";\n\n  const clientId = `stormcloud-vp-${Math.random().toString(36).slice(2, 9)}`;\n\n  try {\n    client = mqtt.connect(url, {\n      clientId,\n      username: mqttConfig.username,\n      password: mqttConfig.password,\n      keepalive: KEEPALIVE_SECONDS,\n      clean: true,\n      // Disable MQTT.js internal reconnect; we manage reconnection ourselves.\n      reconnectPeriod: 0,\n      connectTimeout: CONNECT_TIMEOUT_MS,\n      queueQoSZero: false,\n      reschedulePings: true,\n      // Root-cause fix for the Tizen 4.0 \"Keepalive timeout\" loop: MQTT.js's\n      // default timerVariant \"auto\" runs the keepalive interval inside a\n      // Blob-URL Web Worker (via worker-timers). On Samsung Tizen 4.0 (old\n      // WebKit) served from a file:// origin that worker can't tick reliably,\n      // so the keepalive counter trips onKeepaliveTimeout almost immediately\n      // after connect. Force the native setInterval timer instead; the main\n      // thread is free because video is decoded by the native AVPlay pipeline.\n      timerVariant: \"native\",\n    });\n  } catch (err) {\n    status = \"error\";\n    console.warn(`${LOG} connect() threw:`, err);\n    scheduleReconnect();\n    return;\n  }\n\n  client.on(\"connect\", () => {\n    status = \"connected\";\n    reconnectAttempts = 0;\n    clearReconnectTimer();\n    console.info(`${LOG} connected to ${url}`);\n  });\n\n  client.on(\"offline\", () => {\n    if (status === \"connected\") status = \"disconnected\";\n    console.warn(`${LOG} offline`);\n    scheduleReconnect();\n  });\n\n  client.on(\"error\", (err) => {\n    status = \"error\";\n    console.warn(`${LOG} error:`, err.message);\n    scheduleReconnect();\n  });\n\n  client.on(\"close\", () => {\n    if (status === \"connected\") status = \"disconnected\";\n    scheduleReconnect();\n  });\n}\n\nexport function initMQTTClient(): void {\n  if (client || !isMQTTEnabled()) return;\n  stopped = false;\n  reconnectAttempts = 0;\n  openConnection();\n}\n\nexport function ensureMQTTClient(): void {\n  if (isMQTTEnabled() && !client && !reconnectTimer) {\n    initMQTTClient();\n  }\n}\n\nexport function publishMQTT(\n  topic: string,\n  payload: Record<string, unknown>\n): boolean {\n  if (!isMQTTEnabled()) {\n    return false;\n  }\n\n  ensureMQTTClient();\n\n  if (!client || !client.connected) {\n    return false;\n  }\n\n  try {\n    client.publish(topic, JSON.stringify(payload), { qos: mqttConfig.qos });\n    return true;\n  } catch (err) {\n    console.warn(`${LOG} publish failed on ${topic}:`, err);\n    return false;\n  }\n}\n\nexport function disconnectMQTT(): void {\n  stopped = true;\n  clearReconnectTimer();\n  teardownClient();\n  reconnectAttempts = 0;\n  status = \"disconnected\";\n}\n","export const MQTT_CA_CERT_FILE = \"src/certs/emqxsl-ca.crt\";\n\nexport type MQTTConfig = {\n  enabled: boolean;\n  brokerAddress: string;\n  brokerPort: number;\n  wsPort: number;\n  username: string;\n  password: string;\n  topicPrefix: string;\n  qos: 0 | 1 | 2;\n  brokerUrl?: string;\n};\n\nexport const DEFAULT_MQTT_CONFIG: MQTTConfig = {\n  enabled: true,\n  brokerAddress: \"vecbae77.ala.us-east-1.emqxsl.com\",\n  brokerPort: 8883,\n  wsPort: 8084,\n  username: \"for-sonifi\",\n  password: \"sonifi-mqtt\",\n  topicPrefix: \"adstorm/players\",\n  qos: 1,\n};\n\nexport const mqttConfig: MQTTConfig = { ...DEFAULT_MQTT_CONFIG };\n\nexport function applyMQTTConfig(overrides: Partial<MQTTConfig>): void {\n  Object.assign(mqttConfig, overrides);\n}\n\nexport function isMQTTEnabled(): boolean {\n  return mqttConfig.enabled;\n}\n\nexport function buildMQTTBrokerUrl(): string {\n  if (mqttConfig.brokerUrl) return mqttConfig.brokerUrl;\n  return `wss://${mqttConfig.brokerAddress}:${mqttConfig.wsPort}/mqtt`;\n}\n\nexport function buildPlayerTopic(\n  licenseKey: string,\n  channel: \"metrics\" | \"impressions\" | \"heartbeat\"\n): string {\n  return `${mqttConfig.topicPrefix}/${licenseKey}/${channel}`;\n}\n"]}