stormcloud-video-player 0.8.37 → 0.8.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -143,8 +143,17 @@ function buildMQTTBrokerUrl() {
143
143
  }
144
144
  // src/utils/mqttClient.ts
145
145
  var LOG = "[StormcloudVideoPlayer][MQTT]";
146
+ var KEEPALIVE_SECONDS = 30;
147
+ var CONNECT_TIMEOUT_MS = 1e4;
148
+ var RECONNECT_BASE_MS = 5e3;
149
+ var RECONNECT_MAX_MS = 6e4;
150
+ var MAX_RECONNECT_ATTEMPTS = 8;
151
+ var COOLDOWN_MS = 10 * 6e4;
146
152
  var client = null;
147
153
  var status = "disconnected";
154
+ var reconnectAttempts = 0;
155
+ var reconnectTimer = null;
156
+ var stopped = false;
148
157
  function getMQTTStatus() {
149
158
  return status;
150
159
  }
@@ -158,8 +167,45 @@ function configureMQTT(overrides) {
158
167
  applyMQTTConfig(overrides);
159
168
  disconnectMQTT();
160
169
  }
161
- function initMQTTClient() {
162
- if (client || !isMQTTEnabled()) return;
170
+ function clearReconnectTimer() {
171
+ if (reconnectTimer) {
172
+ clearTimeout(reconnectTimer);
173
+ reconnectTimer = null;
174
+ }
175
+ }
176
+ function teardownClient() {
177
+ if (client) {
178
+ try {
179
+ client.removeAllListeners();
180
+ client.end(true);
181
+ } catch (unused) {}
182
+ client = null;
183
+ }
184
+ }
185
+ function scheduleReconnect() {
186
+ if (stopped || !isMQTTEnabled() || reconnectTimer) return;
187
+ if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
188
+ console.warn("".concat(LOG, " giving up after ").concat(reconnectAttempts, " attempts; cooling down for ").concat(COOLDOWN_MS / 6e4, "m"));
189
+ teardownClient();
190
+ status = "disconnected";
191
+ reconnectTimer = setTimeout(function() {
192
+ reconnectTimer = null;
193
+ reconnectAttempts = 0;
194
+ openConnection();
195
+ }, COOLDOWN_MS);
196
+ return;
197
+ }
198
+ var delay = Math.min(RECONNECT_MAX_MS, RECONNECT_BASE_MS * Math.pow(2, reconnectAttempts));
199
+ var jitter = Math.floor(Math.random() * 1e3);
200
+ reconnectAttempts += 1;
201
+ reconnectTimer = setTimeout(function() {
202
+ reconnectTimer = null;
203
+ openConnection();
204
+ }, delay + jitter);
205
+ }
206
+ function openConnection() {
207
+ if (stopped || !isMQTTEnabled()) return;
208
+ teardownClient();
163
209
  var url = buildMQTTBrokerUrl();
164
210
  status = "connecting";
165
211
  var clientId = "stormcloud-vp-".concat(Math.random().toString(36).slice(2, 9));
@@ -168,41 +214,48 @@ function initMQTTClient() {
168
214
  clientId: clientId,
169
215
  username: mqttConfig.username,
170
216
  password: mqttConfig.password,
171
- keepalive: 60,
217
+ keepalive: KEEPALIVE_SECONDS,
172
218
  clean: true,
173
- reconnectPeriod: 5e3,
174
- connectTimeout: 1e4,
175
- queueQoSZero: false
219
+ reconnectPeriod: 0,
220
+ connectTimeout: CONNECT_TIMEOUT_MS,
221
+ queueQoSZero: false,
222
+ reschedulePings: true
176
223
  });
177
224
  } catch (err) {
178
225
  status = "error";
179
226
  console.warn("".concat(LOG, " connect() threw:"), err);
227
+ scheduleReconnect();
180
228
  return;
181
229
  }
182
230
  client.on("connect", function() {
183
231
  status = "connected";
232
+ reconnectAttempts = 0;
233
+ clearReconnectTimer();
184
234
  console.info("".concat(LOG, " connected to ").concat(url));
185
235
  });
186
- client.on("reconnect", function() {
187
- status = "connecting";
188
- console.info("".concat(LOG, " reconnecting…"));
189
- });
190
236
  client.on("offline", function() {
191
- status = "disconnected";
237
+ if (status === "connected") status = "disconnected";
192
238
  console.warn("".concat(LOG, " offline"));
239
+ scheduleReconnect();
193
240
  });
194
241
  client.on("error", function(err) {
195
242
  status = "error";
196
243
  console.warn("".concat(LOG, " error:"), err.message);
244
+ scheduleReconnect();
197
245
  });
198
246
  client.on("close", function() {
199
- if (status === "connected") {
200
- status = "disconnected";
201
- }
247
+ if (status === "connected") status = "disconnected";
248
+ scheduleReconnect();
202
249
  });
203
250
  }
251
+ function initMQTTClient() {
252
+ if (client || !isMQTTEnabled()) return;
253
+ stopped = false;
254
+ reconnectAttempts = 0;
255
+ openConnection();
256
+ }
204
257
  function ensureMQTTClient() {
205
- if (isMQTTEnabled() && !client) {
258
+ if (isMQTTEnabled() && !client && !reconnectTimer) {
206
259
  initMQTTClient();
207
260
  }
208
261
  }
@@ -211,7 +264,7 @@ function publishMQTT(topic, payload) {
211
264
  return false;
212
265
  }
213
266
  ensureMQTTClient();
214
- if (!client) {
267
+ if (!client || !client.connected) {
215
268
  return false;
216
269
  }
217
270
  try {
@@ -225,11 +278,11 @@ function publishMQTT(topic, payload) {
225
278
  }
226
279
  }
227
280
  function disconnectMQTT() {
228
- if (client) {
229
- client.end(true);
230
- client = null;
231
- status = "disconnected";
232
- }
281
+ stopped = true;
282
+ clearReconnectTimer();
283
+ teardownClient();
284
+ reconnectAttempts = 0;
285
+ status = "disconnected";
233
286
  }
234
287
  // Annotate the CommonJS export names for ESM import in node:
235
288
  0 && (module.exports = {
@@ -1 +1 @@
1
- {"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","username","password","topicPrefix","qos","mqttConfig","applyMQTTConfig","overrides","assign","isMQTTEnabled","buildMQTTBrokerUrl","brokerUrl","LOG","client","status","connected","url","clientId","Math","random","toString","slice"],"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 client = null;\nvar status = \"disconnected\";\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 initMQTTClient() {\n if (client || !isMQTTEnabled()) return;\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: 60,\n clean: true,\n reconnectPeriod: 5e3,\n connectTimeout: 1e4,\n queueQoSZero: false\n });\n } catch (err) {\n status = \"error\";\n console.warn(`${LOG} connect() threw:`, err);\n return;\n }\n client.on(\"connect\", () => {\n status = \"connected\";\n console.info(`${LOG} connected to ${url}`);\n });\n client.on(\"reconnect\", () => {\n status = \"connecting\";\n console.info(`${LOG} reconnecting\\u2026`);\n });\n client.on(\"offline\", () => {\n status = \"disconnected\";\n console.warn(`${LOG} offline`);\n });\n client.on(\"error\", (err) => {\n status = \"error\";\n console.warn(`${LOG} error:`, err.message);\n });\n client.on(\"close\", () => {\n if (status === \"connected\") {\n status = \"disconnected\";\n }\n });\n}\nfunction ensureMQTTClient() {\n if (isMQTTEnabled() && !client) {\n initMQTTClient();\n }\n}\nfunction publishMQTT(topic, payload) {\n if (!isMQTTEnabled()) {\n return false;\n }\n ensureMQTTClient();\n if (!client) {\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 if (client) {\n client.end(true);\n client = null;\n status = \"disconnected\";\n }\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\nexport type MQTTStatus = \"disconnected\" | \"connecting\" | \"connected\" | \"error\";\n\nlet client: MqttClient | null = null;\nlet status: MQTTStatus = \"disconnected\";\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\nexport function initMQTTClient(): void {\n if (client || !isMQTTEnabled()) return;\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: 60,\n clean: true,\n reconnectPeriod: 5000,\n connectTimeout: 10_000,\n queueQoSZero: false,\n });\n } catch (err) {\n status = \"error\";\n console.warn(`${LOG} connect() threw:`, err);\n return;\n }\n\n client.on(\"connect\", () => {\n status = \"connected\";\n console.info(`${LOG} connected to ${url}`);\n });\n\n client.on(\"reconnect\", () => {\n status = \"connecting\";\n console.info(`${LOG} reconnecting…`);\n });\n\n client.on(\"offline\", () => {\n status = \"disconnected\";\n console.warn(`${LOG} offline`);\n });\n\n client.on(\"error\", (err) => {\n status = \"error\";\n console.warn(`${LOG} error:`, err.message);\n });\n\n client.on(\"close\", () => {\n if (status === \"connected\") {\n status = \"disconnected\";\n }\n });\n}\n\nexport function ensureMQTTClient(): void {\n if (isMQTTEnabled() && !client) {\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) {\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 if (client) {\n client.end(true);\n client = null;\n status = \"disconnected\";\n }\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"]}
1
+ {"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","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 reconnectPeriod: 0,\n connectTimeout: CONNECT_TIMEOUT_MS,\n queueQoSZero: false,\n reschedulePings: true\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 reconnectPeriod: 0,\n connectTimeout: CONNECT_TIMEOUT_MS,\n queueQoSZero: false,\n reschedulePings: true,\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"]}
@@ -271,10 +271,56 @@ function buildPlayerTopic(licenseKey, channel) {
271
271
  // src/utils/mqttClient.ts
272
272
  var import_mqtt = __toESM(require("mqtt"), 1);
273
273
  var LOG = "[StormcloudVideoPlayer][MQTT]";
274
+ var KEEPALIVE_SECONDS = 30;
275
+ var CONNECT_TIMEOUT_MS = 1e4;
276
+ var RECONNECT_BASE_MS = 5e3;
277
+ var RECONNECT_MAX_MS = 6e4;
278
+ var MAX_RECONNECT_ATTEMPTS = 8;
279
+ var COOLDOWN_MS = 10 * 6e4;
274
280
  var client = null;
275
281
  var status = "disconnected";
276
- function initMQTTClient() {
277
- if (client || !isMQTTEnabled()) return;
282
+ var reconnectAttempts = 0;
283
+ var reconnectTimer = null;
284
+ var stopped = false;
285
+ function clearReconnectTimer() {
286
+ if (reconnectTimer) {
287
+ clearTimeout(reconnectTimer);
288
+ reconnectTimer = null;
289
+ }
290
+ }
291
+ function teardownClient() {
292
+ if (client) {
293
+ try {
294
+ client.removeAllListeners();
295
+ client.end(true);
296
+ } catch (unused) {}
297
+ client = null;
298
+ }
299
+ }
300
+ function scheduleReconnect() {
301
+ if (stopped || !isMQTTEnabled() || reconnectTimer) return;
302
+ if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
303
+ console.warn("".concat(LOG, " giving up after ").concat(reconnectAttempts, " attempts; cooling down for ").concat(COOLDOWN_MS / 6e4, "m"));
304
+ teardownClient();
305
+ status = "disconnected";
306
+ reconnectTimer = setTimeout(function() {
307
+ reconnectTimer = null;
308
+ reconnectAttempts = 0;
309
+ openConnection();
310
+ }, COOLDOWN_MS);
311
+ return;
312
+ }
313
+ var delay = Math.min(RECONNECT_MAX_MS, RECONNECT_BASE_MS * Math.pow(2, reconnectAttempts));
314
+ var jitter = Math.floor(Math.random() * 1e3);
315
+ reconnectAttempts += 1;
316
+ reconnectTimer = setTimeout(function() {
317
+ reconnectTimer = null;
318
+ openConnection();
319
+ }, delay + jitter);
320
+ }
321
+ function openConnection() {
322
+ if (stopped || !isMQTTEnabled()) return;
323
+ teardownClient();
278
324
  var url = buildMQTTBrokerUrl();
279
325
  status = "connecting";
280
326
  var clientId = "stormcloud-vp-".concat(Math.random().toString(36).slice(2, 9));
@@ -283,41 +329,48 @@ function initMQTTClient() {
283
329
  clientId: clientId,
284
330
  username: mqttConfig.username,
285
331
  password: mqttConfig.password,
286
- keepalive: 60,
332
+ keepalive: KEEPALIVE_SECONDS,
287
333
  clean: true,
288
- reconnectPeriod: 5e3,
289
- connectTimeout: 1e4,
290
- queueQoSZero: false
334
+ reconnectPeriod: 0,
335
+ connectTimeout: CONNECT_TIMEOUT_MS,
336
+ queueQoSZero: false,
337
+ reschedulePings: true
291
338
  });
292
339
  } catch (err) {
293
340
  status = "error";
294
341
  console.warn("".concat(LOG, " connect() threw:"), err);
342
+ scheduleReconnect();
295
343
  return;
296
344
  }
297
345
  client.on("connect", function() {
298
346
  status = "connected";
347
+ reconnectAttempts = 0;
348
+ clearReconnectTimer();
299
349
  console.info("".concat(LOG, " connected to ").concat(url));
300
350
  });
301
- client.on("reconnect", function() {
302
- status = "connecting";
303
- console.info("".concat(LOG, " reconnecting…"));
304
- });
305
351
  client.on("offline", function() {
306
- status = "disconnected";
352
+ if (status === "connected") status = "disconnected";
307
353
  console.warn("".concat(LOG, " offline"));
354
+ scheduleReconnect();
308
355
  });
309
356
  client.on("error", function(err) {
310
357
  status = "error";
311
358
  console.warn("".concat(LOG, " error:"), err.message);
359
+ scheduleReconnect();
312
360
  });
313
361
  client.on("close", function() {
314
- if (status === "connected") {
315
- status = "disconnected";
316
- }
362
+ if (status === "connected") status = "disconnected";
363
+ scheduleReconnect();
317
364
  });
318
365
  }
366
+ function initMQTTClient() {
367
+ if (client || !isMQTTEnabled()) return;
368
+ stopped = false;
369
+ reconnectAttempts = 0;
370
+ openConnection();
371
+ }
319
372
  function ensureMQTTClient() {
320
- if (isMQTTEnabled() && !client) {
373
+ if (isMQTTEnabled() && !client && !reconnectTimer) {
321
374
  initMQTTClient();
322
375
  }
323
376
  }
@@ -326,7 +379,7 @@ function publishMQTT(topic, payload) {
326
379
  return false;
327
380
  }
328
381
  ensureMQTTClient();
329
- if (!client) {
382
+ if (!client || !client.connected) {
330
383
  return false;
331
384
  }
332
385
  try {