stormcloud-video-player 0.6.12 → 0.6.13

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.
@@ -240,26 +240,95 @@ __export(tracking_exports, {
240
240
  },
241
241
  sendInitialTracking: function sendInitialTracking1() {
242
242
  return sendInitialTracking;
243
- },
244
- setMQTTTopicPrefix: function setMQTTTopicPrefix1() {
245
- return setMQTTTopicPrefix;
246
243
  }
247
244
  });
248
245
  module.exports = __toCommonJS(tracking_exports);
246
+ // src/utils/mqttConfig.ts
247
+ var DEFAULT_MQTT_CONFIG = {
248
+ enabled: true,
249
+ brokerAddress: "vecbae77.ala.us-east-1.emqxsl.com",
250
+ brokerPort: 8883,
251
+ wsPort: 8084,
252
+ username: "for-sonifi",
253
+ password: "sonifi-mqtt",
254
+ topicPrefix: "adstorm/players",
255
+ qos: 1
256
+ };
257
+ var mqttConfig = _object_spread({}, DEFAULT_MQTT_CONFIG);
258
+ function isMQTTEnabled() {
259
+ return mqttConfig.enabled;
260
+ }
261
+ function buildMQTTBrokerUrl() {
262
+ if (mqttConfig.brokerUrl) return mqttConfig.brokerUrl;
263
+ return "wss://".concat(mqttConfig.brokerAddress, ":").concat(mqttConfig.wsPort, "/mqtt");
264
+ }
265
+ function buildPlayerTopic(licenseKey, channel) {
266
+ return "".concat(mqttConfig.topicPrefix, "/").concat(licenseKey, "/").concat(channel);
267
+ }
249
268
  // src/utils/mqttClient.ts
250
269
  var import_mqtt = __toESM(require("mqtt"), 1);
251
270
  var LOG = "[StormcloudVideoPlayer][MQTT]";
252
271
  var client = null;
253
- function isMQTTConfigured() {
254
- return client !== null;
272
+ var status = "disconnected";
273
+ function initMQTTClient() {
274
+ if (client || !isMQTTEnabled()) return;
275
+ var url = buildMQTTBrokerUrl();
276
+ status = "connecting";
277
+ var clientId = "stormcloud-vp-".concat(Math.random().toString(36).slice(2, 9));
278
+ try {
279
+ client = import_mqtt.default.connect(url, {
280
+ clientId: clientId,
281
+ username: mqttConfig.username,
282
+ password: mqttConfig.password,
283
+ keepalive: 60,
284
+ clean: true,
285
+ reconnectPeriod: 5e3,
286
+ connectTimeout: 1e4,
287
+ queueQoSZero: false
288
+ });
289
+ } catch (err) {
290
+ status = "error";
291
+ console.warn("".concat(LOG, " connect() threw:"), err);
292
+ return;
293
+ }
294
+ client.on("connect", function() {
295
+ status = "connected";
296
+ console.info("".concat(LOG, " connected to ").concat(url));
297
+ });
298
+ client.on("reconnect", function() {
299
+ status = "connecting";
300
+ console.info("".concat(LOG, " reconnecting…"));
301
+ });
302
+ client.on("offline", function() {
303
+ status = "disconnected";
304
+ console.warn("".concat(LOG, " offline"));
305
+ });
306
+ client.on("error", function(err) {
307
+ status = "error";
308
+ console.warn("".concat(LOG, " error:"), err.message);
309
+ });
310
+ client.on("close", function() {
311
+ if (status === "connected") {
312
+ status = "disconnected";
313
+ }
314
+ });
315
+ }
316
+ function ensureMQTTClient() {
317
+ if (isMQTTEnabled() && !client) {
318
+ initMQTTClient();
319
+ }
255
320
  }
256
321
  function publishMQTT(topic, payload) {
322
+ if (!isMQTTEnabled()) {
323
+ return false;
324
+ }
325
+ ensureMQTTClient();
257
326
  if (!client) {
258
327
  return false;
259
328
  }
260
329
  try {
261
330
  client.publish(topic, JSON.stringify(payload), {
262
- qos: 1
331
+ qos: mqttConfig.qos
263
332
  });
264
333
  return true;
265
334
  } catch (err) {
@@ -486,171 +555,91 @@ function getBrowserID(clientInfo) {
486
555
  });
487
556
  })();
488
557
  }
489
- var mqttTopicPrefix = "adstorm";
490
- function setMQTTTopicPrefix(prefix) {
491
- mqttTopicPrefix = prefix || "adstorm";
558
+ function canPublish(licenseKey) {
559
+ return Boolean(isMQTTEnabled() && licenseKey);
492
560
  }
493
- var PLAYER_TRACKING_BASE_URL = "https://adstorm.co/api-adstorm-dev/adstorm/player-tracking";
494
- var TRACK_URL = "".concat(PLAYER_TRACKING_BASE_URL, "/metrics/ingest");
495
- var HEARTBEAT_URL = "".concat(PLAYER_TRACKING_BASE_URL, "/heartbeat");
496
- var IMPRESSIONS_URL = "".concat(PLAYER_TRACKING_BASE_URL, "/impressions/ingest");
497
- function buildHeaders(licenseKey) {
498
- var headers = {
499
- "Content-Type": "application/json"
500
- };
501
- if (licenseKey) headers["Authorization"] = "Bearer ".concat(licenseKey);
502
- return headers;
503
- }
504
- function postJson(url, licenseKey, body) {
561
+ function buildPlayerMetricEvent() {
505
562
  return _async_to_generator(function() {
506
- var response;
507
- return _ts_generator(this, function(_state) {
508
- switch(_state.label){
509
- case 0:
510
- return [
511
- 4,
512
- fetch(url, {
513
- method: "POST",
514
- headers: buildHeaders(licenseKey),
515
- body: JSON.stringify(body)
516
- })
517
- ];
518
- case 1:
519
- response = _state.sent();
520
- if (!response.ok) throw new Error("HTTP error! status: ".concat(response.status));
521
- return [
522
- 4,
523
- response.json()
524
- ];
525
- case 2:
526
- _state.sent();
527
- return [
528
- 2
529
- ];
530
- }
531
- });
532
- })();
533
- }
534
- function buildPlayerMetricEvent(_0) {
535
- return _async_to_generator(function(licenseKey) {
536
- var context, flags, _flags_captureAt, clientInfo, browserId, captureAt;
563
+ var context, flags, _flags_captureAt, _flags_adLoaded, _flags_adDetect, clientInfo, playerId, captureAt;
537
564
  var _arguments = arguments;
538
565
  return _ts_generator(this, function(_state) {
539
566
  switch(_state.label){
540
567
  case 0:
541
- context = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {}, flags = _arguments.length > 2 && _arguments[2] !== void 0 ? _arguments[2] : {};
568
+ context = _arguments.length > 0 && _arguments[0] !== void 0 ? _arguments[0] : {}, flags = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {};
542
569
  clientInfo = getClientInfo();
543
570
  return [
544
571
  4,
545
572
  getBrowserID(clientInfo)
546
573
  ];
547
574
  case 1:
548
- browserId = _state.sent();
575
+ playerId = _state.sent();
549
576
  captureAt = (_flags_captureAt = flags.captureAt) !== null && _flags_captureAt !== void 0 ? _flags_captureAt : /* @__PURE__ */ new Date().toISOString();
550
577
  return [
551
578
  2,
552
- {
553
- player_id: browserId,
554
- browserId: browserId,
579
+ _object_spread({
580
+ player_id: playerId,
555
581
  device_type: clientInfo.deviceType,
556
- deviceType: clientInfo.deviceType,
557
- input_stream_type: context.inputStreamType,
558
- os: clientInfo.os,
559
- ad_loaded: flags.adLoaded,
560
- ad_detect: flags.adDetect,
561
- license_key: licenseKey,
562
- capture_at: captureAt,
563
- timestamp: captureAt
564
- }
582
+ os: clientInfo.os.toLowerCase(),
583
+ ad_loaded: (_flags_adLoaded = flags.adLoaded) !== null && _flags_adLoaded !== void 0 ? _flags_adLoaded : false,
584
+ ad_detect: (_flags_adDetect = flags.adDetect) !== null && _flags_adDetect !== void 0 ? _flags_adDetect : false,
585
+ capture_at: captureAt
586
+ }, context.inputStreamType ? {
587
+ input_stream_type: context.inputStreamType
588
+ } : {})
565
589
  ];
566
590
  }
567
591
  });
568
592
  }).apply(this, arguments);
569
593
  }
570
- function publishOrPost(mqttTopic, httpUrl, licenseKey, body) {
571
- return _async_to_generator(function() {
572
- return _ts_generator(this, function(_state) {
573
- switch(_state.label){
574
- case 0:
575
- if (isMQTTConfigured()) {
576
- publishMQTT(mqttTopic, body);
577
- return [
578
- 2
579
- ];
580
- }
581
- return [
582
- 4,
583
- postJson(httpUrl, licenseKey, body)
584
- ];
585
- case 1:
586
- _state.sent();
587
- return [
588
- 2
589
- ];
590
- }
591
- });
592
- })();
594
+ function publishTracking(licenseKey, channel, body) {
595
+ ensureMQTTClient();
596
+ publishMQTT(buildPlayerTopic(licenseKey, channel), body);
593
597
  }
594
598
  function sendInitialTracking(_0) {
595
599
  return _async_to_generator(function(licenseKey) {
596
- var context, clientInfo, browserId, captureAt, trackingData, metricsBody, error;
600
+ var context, metricEvent, error;
597
601
  var _arguments = arguments;
598
602
  return _ts_generator(this, function(_state) {
599
603
  switch(_state.label){
600
604
  case 0:
601
605
  context = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {};
606
+ if (!canPublish(licenseKey)) return [
607
+ 2
608
+ ];
602
609
  _state.label = 1;
603
610
  case 1:
604
611
  _state.trys.push([
605
612
  1,
606
- 4,
613
+ 3,
607
614
  ,
608
- 5
615
+ 4
609
616
  ]);
610
- clientInfo = getClientInfo();
611
617
  return [
612
618
  4,
613
- getBrowserID(clientInfo)
619
+ buildPlayerMetricEvent(context, {
620
+ adLoaded: false,
621
+ adDetect: false
622
+ })
614
623
  ];
615
624
  case 2:
616
- browserId = _state.sent();
617
- captureAt = /* @__PURE__ */ new Date().toISOString();
618
- trackingData = _object_spread({
619
- browserId: browserId
620
- }, clientInfo);
621
- metricsBody = {
625
+ metricEvent = _state.sent();
626
+ publishTracking(licenseKey, "metrics", {
622
627
  events: [
623
- {
624
- player_id: browserId,
625
- device_type: clientInfo.deviceType,
626
- input_stream_type: context.inputStreamType,
627
- os: clientInfo.os,
628
- ad_loaded: false,
629
- ad_detect: false,
630
- license_key: licenseKey,
631
- capture_at: captureAt
632
- }
633
- ],
634
- trackingData: trackingData
635
- };
636
- return [
637
- 4,
638
- publishOrPost("".concat(mqttTopicPrefix, "/tracking/metrics"), TRACK_URL, licenseKey, metricsBody)
639
- ];
640
- case 3:
641
- _state.sent();
628
+ metricEvent
629
+ ]
630
+ });
642
631
  return [
643
632
  3,
644
- 5
633
+ 4
645
634
  ];
646
- case 4:
635
+ case 3:
647
636
  error = _state.sent();
648
637
  console.error("[StormcloudVideoPlayer] Error sending initial tracking data:", error);
649
638
  return [
650
639
  3,
651
- 5
640
+ 4
652
641
  ];
653
- case 5:
642
+ case 4:
654
643
  return [
655
644
  2
656
645
  ];
@@ -748,61 +737,54 @@ function sendAdLoadedTracking(_0, _1) {
748
737
  }
749
738
  function sendAdImpressionTracking(_0, _1) {
750
739
  return _async_to_generator(function(licenseKey, adImpressionInfo) {
751
- var context, metricEvent, heartbeatBody, impressionsBody, error;
740
+ var context, metricEvent, error;
752
741
  var _arguments = arguments;
753
742
  return _ts_generator(this, function(_state) {
754
743
  switch(_state.label){
755
744
  case 0:
756
745
  context = _arguments.length > 2 && _arguments[2] !== void 0 ? _arguments[2] : {};
746
+ if (!canPublish(licenseKey)) return [
747
+ 2
748
+ ];
757
749
  _state.label = 1;
758
750
  case 1:
759
751
  _state.trys.push([
760
752
  1,
761
- 4,
753
+ 3,
762
754
  ,
763
- 5
755
+ 4
764
756
  ]);
765
757
  return [
766
758
  4,
767
- buildPlayerMetricEvent(licenseKey, context, {
759
+ buildPlayerMetricEvent(context, {
768
760
  captureAt: adImpressionInfo.timestamp
769
761
  })
770
762
  ];
771
763
  case 2:
772
764
  metricEvent = _state.sent();
773
- heartbeatBody = metricEvent;
774
- impressionsBody = {
765
+ publishTracking(licenseKey, "heartbeat", metricEvent);
766
+ publishTracking(licenseKey, "impressions", {
775
767
  events: [
776
768
  {
777
769
  player_id: metricEvent.player_id,
778
770
  ad_played_count: 1,
779
771
  ad_url: adImpressionInfo.adUrl,
780
- license_key: licenseKey,
781
772
  capture_at: adImpressionInfo.timestamp
782
773
  }
783
774
  ]
784
- };
785
- return [
786
- 4,
787
- Promise.all([
788
- publishOrPost("".concat(mqttTopicPrefix, "/tracking/heartbeat"), HEARTBEAT_URL, licenseKey, heartbeatBody),
789
- publishOrPost("".concat(mqttTopicPrefix, "/tracking/impressions"), IMPRESSIONS_URL, licenseKey, impressionsBody)
790
- ])
791
- ];
792
- case 3:
793
- _state.sent();
775
+ });
794
776
  return [
795
777
  3,
796
- 5
778
+ 4
797
779
  ];
798
- case 4:
780
+ case 3:
799
781
  error = _state.sent();
800
782
  console.error("[StormcloudVideoPlayer] Error sending ad impression tracking:", error);
801
783
  return [
802
784
  3,
803
- 5
785
+ 4
804
786
  ];
805
- case 5:
787
+ case 4:
806
788
  return [
807
789
  2
808
790
  ];
@@ -818,38 +800,36 @@ function sendHeartbeat(_0) {
818
800
  switch(_state.label){
819
801
  case 0:
820
802
  context = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {}, flags = _arguments.length > 2 && _arguments[2] !== void 0 ? _arguments[2] : {};
803
+ if (!canPublish(licenseKey)) return [
804
+ 2
805
+ ];
821
806
  _state.label = 1;
822
807
  case 1:
823
808
  _state.trys.push([
824
809
  1,
825
- 4,
810
+ 3,
826
811
  ,
827
- 5
812
+ 4
828
813
  ]);
829
814
  return [
830
815
  4,
831
- buildPlayerMetricEvent(licenseKey, context, flags)
816
+ buildPlayerMetricEvent(context, flags)
832
817
  ];
833
818
  case 2:
834
819
  heartbeatData = _state.sent();
835
- return [
836
- 4,
837
- publishOrPost("".concat(mqttTopicPrefix, "/tracking/heartbeat"), HEARTBEAT_URL, licenseKey, heartbeatData)
838
- ];
839
- case 3:
840
- _state.sent();
820
+ publishTracking(licenseKey, "heartbeat", heartbeatData);
841
821
  return [
842
822
  3,
843
- 5
823
+ 4
844
824
  ];
845
- case 4:
825
+ case 3:
846
826
  error = _state.sent();
847
827
  console.error("[StormcloudVideoPlayer] Error sending heartbeat:", error);
848
828
  return [
849
829
  3,
850
- 5
830
+ 4
851
831
  ];
852
- case 5:
832
+ case 4:
853
833
  return [
854
834
  2
855
835
  ];
@@ -865,7 +845,6 @@ function sendHeartbeat(_0) {
865
845
  sendAdImpressionTracking: sendAdImpressionTracking,
866
846
  sendAdLoadedTracking: sendAdLoadedTracking,
867
847
  sendHeartbeat: sendHeartbeat,
868
- sendInitialTracking: sendInitialTracking,
869
- setMQTTTopicPrefix: setMQTTTopicPrefix
848
+ sendInitialTracking: sendInitialTracking
870
849
  });
871
850
  //# sourceMappingURL=tracking.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/ubuntu24-new/Dev/stormcloud-vp/lib/utils/tracking.cjs","../../src/utils/tracking.ts","../../src/utils/mqttClient.ts"],"names":["__create","Object","create","__defProp","defineProperty","__getOwnPropDesc","getOwnPropertyDescriptor","__getOwnPropNames","getOwnPropertyNames","__getProtoOf","getPrototypeOf","__hasOwnProp","prototype","hasOwnProperty","__export","target","all","name","get","enumerable","__copyProps","to","from","except","desc","key","call","value","mod","getClientInfo","sendAdDetectTracking","sendAdImpressionTracking","isNodeMode","__esModule","__toCommonJS","tracking_exports","getBrowserID","sendAdLoadedTracking","sendHeartbeat","sendInitialTracking","setMQTTTopicPrefix","module","exports","import_mqtt","__toESM","require","LOG","client","isMQTTConfigured","publishMQTT","topic","payload","publish","JSON","stringify","qos","err","console","warn","cachedBrowserId","screen","window","navigator","ua","userAgent","platform","vendor","maxTouchPoints","memory","deviceMemory","hardwareConcurrency","screenInfo","width","height","availWidth","orientation","pixelDepth","deviceType","brand","model","tizenMatch","tvMatch","trim","type","os","isSmartTV","isAndroid","isWebView","isWebApp","includes","m","match","test","androidModelMatch","outerHeight","outerWidth","substring","domain","location","hostname","origin","path","pathname"],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wFAS+D,SAAA,UAC/D,4BAAA,qBAGS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAZLA,KAAAA,IAAWC,OAAOC,MAAM;YACxBC,UAAYF,GAAAA,IAAOG,cAAc;QACjCC,mBAAmBJ,OAAOK,wBAAwB;IACtD,EAAIC,oBAAoBN,OAAOO,mBAAmB;IAClD,EAAIC,EAAAA,UAAAA,GAAeR,OAAOS,CAAAA,aAAc;QACpCC,IAAAA,OAAAA,IAAeV,IAAAA,CAAAA,EAAOW,SAAS,CAACC,CAAAA,GAAAA,QAAAA,CAAAA,CAAc,UAAA,QAAA;QAC9CC,IAAAA,OAAW,QAAA,CAAA,SAACC,CAAAA,OAAQC,CAAAA;QACtB,IAAK,IAAIC,GAAAA,KAAQD,GAAAA,CACfb,UAAUY,IAAAA,GAAAA,CAAQE,MAAM,CAAA,CAAA,QAAA,QAAA;UAAEC,KAAKF,GAAG,CAACC,KAAK;UAAEE,MAAAA,MAAY,iBAAA,IAAA,CAAA;MAAK,EAAA,EAAA,UAAA,oBAAA,8BAAA,QAAA,WAAA,MAAA,KAAA,EAAA,WAAA,oBAAA,+BAAA,SAAA,UAAA,MAAA,GAAA,YAAA;IAC/D,WAAA,OAAA,UAAA,CAAA,8BAAA,OAAA,IAAA,OAAA,SAAA,CAAA,UAAA,KAAA,QAAA,EAAA,iBAAA,OAAA,MAAA,cAAA,sCAAA,6BAAA,eAAA,WAAA,cAAA,iDAAA,2BAAA,KAAA,MAAA,KAAA;IACA,EAAIC,KAAAA,SAAc,qBAACC,IAAIC,MAAMC,QAAQC;QACnC,IAAIF,QAAQ,CAAA,OAAOA,qCAAP,SAAOA,KAAG,MAAM,YAAY,OAAOA,SAAS,YAAY;gBAC7D,kCAAA,2BAAA;;;+BAAA,IAAIG,MAAJ;+BACH,IAAI,CAACd,aAAae,IAAI,CAACL,IAAII,QAAQA,QAAQF,QACzCpB,UAAUkB,IAAII,KAAK;mCAAEP,KAAK,SAALA;6CAAWI,IAAI,CAACG,IAAI;;wBAAEN,OAAAA,CAAAA,IAAY,CAAEK,CAAAA,OAAOnB,iBAAiBiB,MAAMG,IAAG,KAAMD,KAAKL,UAAU;oBAAC,CAAA,QAAA,CAAA,QAAA;;wBAFpH,QAAK,YAAWZ,kBAAkBe,0BAA7B,SAAA,6BAAA,QAAA,yBAAA;;gBAAA;gBAAA;;;yBAAA,GAAA,QAAA,kBAAA;sDAAA,SAAA,8EAAA,IAAA,CAAA,SAAA;;;wBAAA,GAAA,QAAA;8BAAA,IAAA,eAAA;;;;;YAKT,gBADE,GAAOD,gBAIP,aAGuEM,MAAYR,MAAY,EAC/FS,GAE0BR,WAAYjB,CAA8BwB,GAAO,cANV,UCrBnEE,aAAAC,kBAAAC;;;;sBDgBE,EAAA,iBAAA;;wBAAA;;oBACA,oBAAOV,KAAAA,SAAAA,CAAAA;yBACT,CAAA,OAAA,WAAA,iBAAA,iBAAA,OAAA,MAAA,cAAA,qCAAA,eAAA,MAAA,CAAA,GAAA;;;;;;;;;;;;;;wBAC4CN,OAAAA,CAASa,KAAAA,CAAAA,CAAO,KAAA,CAAA,CAAO5B,SAASS,CAAAA,IAAAA,QAAamB;4BAAAA;4BAAAA,CAAQ,CAAC;4BAAA,EAAGR,YACnG,sEAAsE;;;;yBAD5BL;oBAG1C,IAAA,OAAA,gBAAA,aAAA,4BAAsE;wBACtE,cAAA,IAAA,cAAA,MAAA,CAAA,0BAAqE;oBACrEiB,OAAAA,KAAc,CAACJ,OAAO,CAACA,IAAIK,UAAU,GAAG9B,UAAUY,QAAQ,WAAW;wBAAEY,CAAOC,MAAAA,SAAAA,mBAAAA;wBAAKT,SAAY,IAAA,WAAA,KAAA,MAAA;wBAAK,CAAKJ,OACzGa,GAAAA,IAAAA,KAAAA,MAAAA,EAAAA,IAAAA,MAAAA,CAAAA,EAAAA,GAAAA,KAAAA,UAAAA,CAAAA;;oBAEEM,aAAe,sBAACN;oBAAoBzB;;wBAAAA,EAAU,CAAC,GAAG,CAAA,MAAA,CAAA,MAAc,CAAA,WAAA;;;oBAAxCiB,aAAYjB;oBAA8BwB,UAAO,MAAA,IAAA,CAAA,IAAA,WAAA,aAAA,GAAA,CAAA,SAAA;+BAAA,EAAA,QAAA,CAAA,IAAA,QAAA,CAAA,GAAA;uBAAA,IAAA,CAAA;oBAAK,EAAIC,gBAAAA;;;;;;;oBC3BtFO,QAAAA,IAAAA,CAAAA,IAAA,CAAA;;;;;;6BAAAC;;wBAAAP,OAAAA,EAAA,SAAAA,OAAAA,UAAAA,CAAAA;mCAAAA,KAAAA,CAAAA,IAAAA,OAAAA;;sBAAAC,sBAAA,SAAAA;mCAAAA,KAAAA,GAAAA,CAAAA,MAAAA,QAAAA,CAAAA,IAAAA,QAAAA,CAAAA,GAAAA;;oBAAAC,SAAAA,KAAAA,MAAAA,EAAA,CAAA,QAAAA,CAAAA,IAAAA,SAAAA,CAAAA,GAAAA,IAAAA,QAAAA,CAAAA,IAAAA;iCAAAA,KAAAA,CAAAA,eAAAA,YAAAA,MAAAA,EAAAA,MAAAA,CAAAA,IAAAA;;;;;;;QAAAM,sBAAA,SAAAA;;eAAAA,OAAAA;;MAAAC,eAAA,CAAA,QAAAA,EAAAA;eAAAA;;IAAAC,YAAAA,GAAAA,OAAAA,MAAA,SAAAA,WAAAA;eAAAA,KAAAA,GAAAA,OAAAA,0BAAAA;;IAAAC,KAAAA,aAAAA,EAAA,QAAA,CAAAA;eAAAA,GAAAA;QAAAA,gBAAAA;IAAAA;;IAAA,OAAA;AAAAC,OAAAC,OAAA,GAAAR,aAAAC;AD2CA,SAAA,SAAA,EAA0B,CAAA,EAAA,UAAA,EAAA,IAAA;;YE3C1BQ;;;;oBAAiBC;;wBAAAA,CAAAC,KAAAA,GAAA,EAAA,OAAA;4BAGXC,MAAM,EAAA;4BAIRC,SAA4B,aAAA;4BAYzB,KAASC,CAAAA,KAAAA,SAAAA,CAAAA;0BACd,OAAOD,WAAW;;;oBApBpBJ,UAAiBC,CAAAA;oBAqBjB,IAAA,CAAA,SAAA,EAAA,EAAA,MAAA,IAAA,MAAA,uBAAA,OAAA,SAAA,MAAA;oBAuDO;;wBAAA,CAASK,QAAAA,IACdC,KAAA,EACAC,OAAA;;;oBAFK;;;;;;QAIL,IAAI,CAACJ,QAAQ;;QACX,CAAO;wCAAA,UAAA;YAAA,SAAA,OAGAK,kBAFT;;;;;oBADS,UAAA,oEAAA,CAAA,GAAA,QAAA,oEAAA,CAAA;oBACT,aAAA;oBACI;;wBAAA,aAAA;;;oBAAA,YAAA;oBACFL,OAAOK,MAAAA,EAAA,CAAQF,gBAARE,MAAQF,IAAOG,KAAKC,cAApBF,8BAAAA,mBAAoBE,IAAA,CAAUH,QAAAA,EAAU,CAAA,IAAA,OAAA,WAAA;;;2BAAEI,KAAK;gCAAE,OAAA;2CACxD,OAAO;4BACT,EAAA,OAASC,IAAAA,CAAK,UAAA,UAAA;gCACZC,QAAQC,IAAA,CAAK,GAA4BR,GAAAA,IAAzBJ,KAAG,CAAA,sBAA2B,OAALI,OAAK,MAAKM;gCACnD,OAAO,QAAA,QAAA,eAAA;4BACT,IAAA,WAAA,EAAA;4BACF,WAAA,MAAA,QAAA;4BF3BA,WAAA,MAAA,GAAwB,KAAA;4BCrDpBG,aAAAA,KAAiC;4BAE9B,KAAS9B,OAAAA;gCASL+B,OAAAA,EACCA,UACIA,UACCA,UACCA,qBAAAA,UACFA,UAkEVC,SAA6BA,UAK/BA,4BAAAA,gBAsBWC;0BA1Gb,IAAMC,KAAKD,UAAUE,SAAA;;;;QACrB,IAAMC,WAAWH,UAAUG,QAAA;;IAC3B,IAAMC,CAAAA,EAASJ,UAAUI,EAAAA,IAAA,IAAU,CAAA,EAAA,OAAA,EAAA,UAAA,EAAA,IAAA;;;;;sBACnC,EAAA,EAAMC,iBAAiBL,CAAAA,SAAUK,cAAA,IAAkB;wBACnD,IAAMC,QAAAA,CAAUN,UAAkBO,YAAA,IAAgB;wBAClD,IAAMC,sBAAsBR,UAAUQ,mBAAA,IAAuB;;;sBAE7D,IAAMC,aAAa;;;wBACjBC,KAAA,GAAOZ,CAAAA,SAAAA,YAAAA,QAAAA,8BAAAA,QAAQY,KAAA;;;;;;;;;YACfC,MAAA,GAAQb,WAAAA,oBAAAA,+BAAAA,SAAQa,MAAA;;QAChBC,CAAAA,GAAA,GAAYd,WAAAA;wCAAAA,UAAAA;YAAAA,SAEZe,WAAcf,CACdgB,WACF,WAEIC,WAAqD,GACrDC,MAAQ,OAgBVC,KAAQC,aAAa,SAA0BC,OAAjBD,UAAA,CAAW,EAAE,EAAA,KAAW,OAAPC,SAAUC,IAAA,KAAS;;;;;oBAvBtDtB,UAAAA,KAAAA,+DAAAA,CAAAA,yBAAAA,SAAQc,UAAA;;;;;;;;;oBAEpBC,aAAcf,WAAAA,oBAAAA,gCAAAA,sBAAAA,SAAQe,WAAA,cAARf,0CAAAA,oBAA6BuB,IAAA,KAAQ;oBACvCvB;;wBAAAA,IAAAA,SAAAA,WAAAA,+BAAAA,SAAQgB,UAAA;;;oBAApBA,QAAA,GAAYhB,CAAAA;oBACd,YAAA,aAAA,GAAA,IAAA,OAAA,WAAA;oBAEIiB,eAAqD;wBAAA,WAAA;uBAAA;oBACrDC,cAAQ;wBACZ,EAAIM,IAAAA,CAAK;4BACLL,QAAQ;gCACRM,UAAY,CAAA;gCACZC,UAAY,GAAA,WAAA,UAAA;gCACZC,UAAY,SAAA,QAAA,eAAA;gCACZC,IAAAA,KAAW,MAAA,EAAA;gCAEXzB,CAAG0B,QAAA,CAAS,CAAA,SAAU;gCACxBX,MAAQ,KAAA;gCAAMM,GAAK,UAAA;gCAASC,UAAY,EAAA;4BAAMR,aAAa;;wCAE3DE,QAAQW,IAAI,SAAa,OAAJA,CAAA,CAAE,EAAE,IAAK;oBAChC,OAAA,IAAW3B,GAAG0B,QAAA,CAAS,UAAU;;;wBAC/BX,MAAQ,SAAWM,EAAK,OAALA,IAAK,aAAA,wBAASC,WAAkBR,CAAN,WAC7C,CADgE,GAC1DG,aAAajB,GAAG4B,KAAA,CAAM;;;wBAD5Bb;;;;;;oBAGAC;oBACF,OAAA,CAAA,GAAWhB,EAAAA,CAAG0B,QAAA,CAAS,YAAY,2CAAA;;;;;;;;;;;YACdL,KAAK;;QAASC,CAAAA,KAAY;wCAAA,UAAA,EAAA,YAAA;YAAA,SAESR;;;;;oBAFT,UAAA,oEAAA,CAAA;;;;;;;;;;;wBAC/C,CAAA,IAAWd,GAAG0B,MAAAA,EAAA,CAAS,SAAA,GAAY1B,GAAG0B,GAAAA,KAAA,CAAS,UAAU;8BACvDX,QAAQ;8BAASM,KAAK,IAAA,aAAA,SAAA;4BAAcC,YAAY;;;oBADlD;;;;;;oBACwDR,UAAa;oBACrE,OAAA,CAAA,GAAWd,EAAAA,CAAG0B,QAAA,CAAS,cAAe1B,CAAAA,GAAG0B,QAAA,CAAS,WAAWvB,OAAOuB,OAAAA,CAAA,CAAS,OAAM,GAAI;;;;;;;;;;;YACrEL,KAAK;;QAAcC,CAAAA,KAAY;wCAAA,UAAA,EAAA,YAAA;YAAA,SAEIR,UAAa;;;;;oBAFjB,UAAA,oEAAA,CAAA;;;;;;;;;;;wBACjD,CAAA,IAAWd,GAAG0B,MAAAA,EAAA,CAAS,SAAA,KAAe1B,CAAAA,GAAG0B,QAAA,CAAS,cAAc1B,GAAG0B,QAAA,CAAS,KAAI,GAAI;8BAClFX,QAAQ;8BAAMM,KAAK,IAAA,aAAA,SAAA;4BAAcC,YAAY;;;oBAD/C;;;;;;oBACqDR;oBACrD,OAAA,CAAA,GAAWd,EAAAA,CAAG0B,QAAA,CAAS,YAAY1B,GAAG0B,QAAA,CAAS,UAAU,kBAAA;;;;;;;;;;;YACvCL,KAAK;;QAAWC,CAAAA,KAAY;wCAAA,UAAA,EAAA,gBAAA;YAAA,SAC9C,aACgCA,eAAkBR,WAAa;;;;;oBAFjB,UAAA,oEAAA,CAAA;;;;;;;;;oBAChCY;;wBAAS,SAAY,cAAA,YAAA,SAAA;8BACjCX,QAAQ,CAAA,iBAAA,SAAA;4BAASM,KAAK;;;oBADxB,CAAA,IAAWrB,GAAG0B,MAAAA,EAAA,CAAS;oBACSJ,UAAY,MAAA;oBAAMR,kBAAa;wBAC/D,MAAA;4BAEId,GAAG0B,QAAA,CAAS,YAAY;gCAC1BH,UAAY,CAAA,YAAA,SAAA;gCAAMF,GAAK,cAAA;gCACvBP,QAAAA,GAAa,SAASe,IAAA,CAAK7B,KAAAA,CAAM,WAAW;gCAC5C,EAAII,WAAAA,QAAmB,KAAKJ,GAAG0B,QAAA,CAAS,gBAAgB1B,GAAG0B,QAAA,CAAS,WAAW;kCAC7EZ,UAAAA,GAAa,cAAA,SAAA;gCAAMQ,YAAY;;wBAEjC;;;wBACA,EAAMQ,MAAAA,GAAAA,WAAoB9B,GAAG4B,KAAA,CAAM;8BACnC,IAAIE,QACN,GAAA,OAAA,YADMA,KACN,wBAEI,WAHEA,IAIJT,IADqBQ,CAChB,GADgB,CAAK7B,GACdc,CAJR,CAAoB,AAGO,EAHN,EAAGE,OAIH,CAJWc,iBAAA,CAAkB,EAAC;8BAKvD,IAAI/B,QACN,EADgBK,CAChB,OAAA,MADgB,GAAiB,KAAK,GACtC,IAD6CyB,IAAA,CAAK7B,KAAKc,YAGnD,CAHgE,AAG/DS,aAAa,CAACD,EACjB,IAAItB,GAAG0B,IADuB,CAAC,GACxB,CAAS,AAAcL,KADUQ,AACL,IADK,CAAK7B,EACjB,GADsB;;;;wBATlD;;;;;;qBAYEqB,KAAK;4BAASP,KAAAA,CAAAA,OAAa,0DAAA;;;;;;;;;;;YAE7B,OAAA,IAAWd,GAAG0B,QAAA,CAAS,UAAU;;SAAEL,EAAK,CAALA;wCAAK,UAAA;YAAA,SAAA,OAAiC,eAS3EG;;;;;oBAT0C,UAAA,oEAAA,CAAA,GAAA,QAAA,oEAAA,CAAA;;;;;;;;;oBAAiC;;wBAAA,uBAAA,YAAA,SAAA;;;oBAAA,gBAAA;;;wBAC3E,cAEA,CAAIT,EAAU,OAAVA,SAAU,QAAA,IAAW,sBACvB,IAAIZ,OAAOuB,IACX,IADW,AACPvB,CADgB,MACTuB,CACX,IAAIvB,EAF6BH,CACtB,CAAS,CADgB0B,EAEzBA,MAFyB,CAAS,AACfX,CACnB,CAAS,MADkB,GADkBA,KAEtBf,GAF8B,AAE3B0B,QAAA,CAAS,QAAQX,QAAQ;;;oBALhE;;;;;;oBAQAS,KAAY,uBAAuBK,IAAA,CAAK7B;oBACxC,IAAIF,EAAAA,EAAAA,KAAAA,CAAAA,EAAAA,oBAAAA,8BAAAA,QAAQiC,WAAA,MAAgB,KAAKjC,EAAAA,WAAAA,oBAAAA,+BAAAA,SAAQkC,UAAA,MAAe,GAAGR,YAAY;;;;;;;;;;;QAOvE,OAAO;;QACLT,OAAAA,8CAAAA;QACAM,IAAAA,CAAAA,OAAAA,GAAAA;wBACAL,OAAOA,SAAShB,GAAGiC,SAAA,CAAU,GAAG,MAAM;yBACtCnB,YAAAA;gCACAQ,WAAAA;oCACAC,WAAAA;gCACAC,WAAAA;yBACAC,UAAAA;+BACAS,QAAQpC,OAAOqC,QAAA,CAASC,QAAA;8BACxBC,QAAQvC,OAAOqC,QAAA,CAASE,MAAA;SACxBC,MAAMxC,OAAOqC,QAAA,CAASI,QAAA","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/tracking.ts\nvar tracking_exports = {};\n__export(tracking_exports, {\n getBrowserID: () => getBrowserID,\n getClientInfo: () => getClientInfo,\n sendAdDetectTracking: () => sendAdDetectTracking,\n sendAdImpressionTracking: () => sendAdImpressionTracking,\n sendAdLoadedTracking: () => sendAdLoadedTracking,\n sendHeartbeat: () => sendHeartbeat,\n sendInitialTracking: () => sendInitialTracking,\n setMQTTTopicPrefix: () => setMQTTTopicPrefix\n});\nmodule.exports = __toCommonJS(tracking_exports);\n\n// src/utils/mqttClient.ts\nvar import_mqtt = __toESM(require(\"mqtt\"), 1);\nvar LOG = \"[StormcloudVideoPlayer][MQTT]\";\nvar client = null;\nfunction isMQTTConfigured() {\n return client !== null;\n}\nfunction publishMQTT(topic, payload) {\n if (!client) {\n return false;\n }\n try {\n client.publish(topic, JSON.stringify(payload), { qos: 1 });\n return true;\n } catch (err) {\n console.warn(`${LOG} publish failed on ${topic}:`, err);\n return false;\n }\n}\n\n// src/utils/tracking.ts\nvar cachedBrowserId = null;\nfunction getClientInfo() {\n const ua = navigator.userAgent;\n const platform = navigator.platform;\n const vendor = navigator.vendor || \"\";\n const maxTouchPoints = navigator.maxTouchPoints || 0;\n const memory = navigator.deviceMemory || null;\n const hardwareConcurrency = navigator.hardwareConcurrency || 1;\n const screenInfo = {\n width: screen?.width,\n height: screen?.height,\n availWidth: screen?.availWidth,\n availHeight: screen?.availHeight,\n orientation: screen?.orientation?.type || \"\",\n pixelDepth: screen?.pixelDepth\n };\n let deviceType = \"desktop\";\n let brand = \"Unknown\";\n let os = \"Unknown\";\n let model = \"\";\n let isSmartTV = false;\n let isAndroid = false;\n let isWebView = false;\n let isWebApp = false;\n if (ua.includes(\"Web0S\")) {\n brand = \"LG\";\n os = \"webOS\";\n isSmartTV = true;\n deviceType = \"tv\";\n const m = ua.match(/Web0S\\/([^\\s]+)/);\n model = m ? `webOS ${m[1]}` : \"webOS TV\";\n } else if (ua.includes(\"Tizen\")) {\n brand = \"Samsung\";\n os = \"Tizen\";\n isSmartTV = true;\n deviceType = \"tv\";\n const tizenMatch = ua.match(/Tizen\\/([^\\s]+)/);\n const tvMatch = ua.match(/(?:Smart-TV|SMART-TV|TV)/i) ? \"Smart TV\" : \"\";\n model = tizenMatch ? `Tizen ${tizenMatch[1]} ${tvMatch}`.trim() : \"Tizen TV\";\n } else if (ua.includes(\"Philips\")) {\n brand = \"Philips\";\n os = \"Saphi\";\n isSmartTV = true;\n deviceType = \"tv\";\n } else if (ua.includes(\"Sharp\") || ua.includes(\"AQUOS\")) {\n brand = \"Sharp\";\n os = \"Android TV\";\n isSmartTV = true;\n deviceType = \"tv\";\n } else if (ua.includes(\"Android\") && (ua.includes(\"Sony\") || vendor.includes(\"Sony\"))) {\n brand = \"Sony\";\n os = \"Android TV\";\n isSmartTV = true;\n deviceType = \"tv\";\n } else if (ua.includes(\"Android\") && (ua.includes(\"NetCast\") || ua.includes(\"LG\"))) {\n brand = \"LG\";\n os = \"Android TV\";\n isSmartTV = true;\n deviceType = \"tv\";\n } else if (ua.includes(\" Roku\") || ua.includes(\"Roku/\")) {\n brand = \"Roku\";\n os = \"Roku OS\";\n isSmartTV = true;\n deviceType = \"tv\";\n } else if (ua.includes(\"AppleTV\")) {\n brand = \"Apple\";\n os = \"tvOS\";\n isSmartTV = true;\n deviceType = \"tv\";\n }\n if (ua.includes(\"Android\")) {\n isAndroid = true;\n os = \"Android\";\n deviceType = /Mobile/.test(ua) ? \"mobile\" : \"tablet\";\n if (maxTouchPoints === 0 || ua.includes(\"Google TV\") || ua.includes(\"XiaoMi\")) {\n deviceType = \"tv\";\n isSmartTV = true;\n brand = brand === \"Unknown\" ? \"Android TV\" : brand;\n }\n const androidModelMatch = ua.match(/\\(([^)]*Android[^)]*)\\)/);\n if (androidModelMatch?.[1]) model = androidModelMatch[1];\n }\n if (/iPad|iPhone|iPod/.test(ua)) {\n os = \"iOS\";\n deviceType = \"mobile\";\n brand = \"Apple\";\n if (navigator.maxTouchPoints > 1 && /iPad/.test(ua)) deviceType = \"tablet\";\n }\n if (!isAndroid && !isSmartTV && !/Mobile/.test(ua)) {\n if (ua.includes(\"Windows\")) {\n os = \"Windows\";\n deviceType = \"desktop\";\n } else if (ua.includes(\"Mac\") && !/iPhone/.test(ua)) {\n os = \"macOS\";\n deviceType = \"desktop\";\n if (maxTouchPoints > 1) deviceType = \"tablet\";\n } else if (ua.includes(\"Linux\")) {\n os = \"Linux\";\n deviceType = \"desktop\";\n }\n }\n if (brand === \"Unknown\") {\n if (vendor.includes(\"Google\") || ua.includes(\"Chrome\")) brand = \"Google\";\n if (vendor.includes(\"Apple\")) brand = \"Apple\";\n if (vendor.includes(\"Samsung\") || ua.includes(\"SM-\")) brand = \"Samsung\";\n }\n isWebView = /wv|WebView|Linux; U;/.test(ua);\n if (window?.outerHeight === 0 && window?.outerWidth === 0) isWebView = true;\n isWebApp = window.matchMedia(\"(display-mode: standalone)\").matches || window.navigator.standalone === true || window.screen?.orientation?.angle !== void 0;\n return {\n brand,\n os,\n model: model || ua.substring(0, 50) + \"...\",\n deviceType,\n isSmartTV,\n isAndroid,\n isWebView,\n isWebApp,\n domain: window.location.hostname,\n origin: window.location.origin,\n path: window.location.pathname,\n userAgent: ua,\n vendor,\n platform,\n screen: screenInfo,\n hardwareConcurrency,\n deviceMemory: memory,\n maxTouchPoints,\n language: navigator.language,\n languages: navigator.languages?.join(\",\") || \"\",\n cookieEnabled: navigator.cookieEnabled,\n doNotTrack: navigator.doNotTrack || \"\",\n referrer: document.referrer,\n visibilityState: document.visibilityState\n };\n}\nasync function getBrowserID(clientInfo) {\n if (cachedBrowserId) return cachedBrowserId;\n const fingerprintString = JSON.stringify(clientInfo);\n if (typeof crypto !== \"undefined\" && crypto.subtle?.digest) {\n try {\n await crypto.subtle.digest(\"SHA-256\", new Uint8Array([1, 2, 3]));\n let encodedData;\n if (typeof TextEncoder !== \"undefined\") {\n encodedData = new TextEncoder().encode(fingerprintString);\n } else {\n const utf8 = unescape(encodeURIComponent(fingerprintString));\n const buffer = new Uint8Array(utf8.length);\n for (let i = 0; i < utf8.length; i++) buffer[i] = utf8.charCodeAt(i);\n encodedData = buffer;\n }\n const hashBuffer = await crypto.subtle.digest(\"SHA-256\", encodedData);\n const hashHex = Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n cachedBrowserId = hashHex;\n return hashHex;\n } catch {\n console.warn(\"[StormcloudVideoPlayer] crypto.subtle not supported, using fallback hash\");\n }\n }\n let hash = 0;\n for (let i = 0; i < fingerprintString.length; i++) {\n const char = fingerprintString.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash;\n }\n const fallbackHash = Math.abs(hash).toString(16).padStart(8, \"0\");\n const timestamp = Date.now().toString(16).padStart(12, \"0\");\n const random = Math.random().toString(16).substring(2, 14).padStart(12, \"0\");\n cachedBrowserId = (fallbackHash + timestamp + random).padEnd(64, \"0\");\n return cachedBrowserId;\n}\nvar mqttTopicPrefix = \"adstorm\";\nfunction setMQTTTopicPrefix(prefix) {\n mqttTopicPrefix = prefix || \"adstorm\";\n}\nvar PLAYER_TRACKING_BASE_URL = \"https://adstorm.co/api-adstorm-dev/adstorm/player-tracking\";\nvar TRACK_URL = `${PLAYER_TRACKING_BASE_URL}/metrics/ingest`;\nvar HEARTBEAT_URL = `${PLAYER_TRACKING_BASE_URL}/heartbeat`;\nvar IMPRESSIONS_URL = `${PLAYER_TRACKING_BASE_URL}/impressions/ingest`;\nfunction buildHeaders(licenseKey) {\n const headers = { \"Content-Type\": \"application/json\" };\n if (licenseKey) headers[\"Authorization\"] = `Bearer ${licenseKey}`;\n return headers;\n}\nasync function postJson(url, licenseKey, body) {\n const response = await fetch(url, {\n method: \"POST\",\n headers: buildHeaders(licenseKey),\n body: JSON.stringify(body)\n });\n if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);\n await response.json();\n}\nasync function buildPlayerMetricEvent(licenseKey, context = {}, flags = {}) {\n const clientInfo = getClientInfo();\n const browserId = await getBrowserID(clientInfo);\n const captureAt = flags.captureAt ?? (/* @__PURE__ */ new Date()).toISOString();\n return {\n player_id: browserId,\n browserId,\n device_type: clientInfo.deviceType,\n deviceType: clientInfo.deviceType,\n input_stream_type: context.inputStreamType,\n os: clientInfo.os,\n ad_loaded: flags.adLoaded,\n ad_detect: flags.adDetect,\n license_key: licenseKey,\n capture_at: captureAt,\n timestamp: captureAt\n };\n}\nasync function publishOrPost(mqttTopic, httpUrl, licenseKey, body) {\n if (isMQTTConfigured()) {\n publishMQTT(mqttTopic, body);\n return;\n }\n await postJson(httpUrl, licenseKey, body);\n}\nasync function sendInitialTracking(licenseKey, context = {}) {\n try {\n const clientInfo = getClientInfo();\n const browserId = await getBrowserID(clientInfo);\n const captureAt = (/* @__PURE__ */ new Date()).toISOString();\n const trackingData = { browserId, ...clientInfo };\n const metricsBody = {\n events: [\n {\n player_id: browserId,\n device_type: clientInfo.deviceType,\n input_stream_type: context.inputStreamType,\n os: clientInfo.os,\n ad_loaded: false,\n ad_detect: false,\n license_key: licenseKey,\n capture_at: captureAt\n }\n ],\n trackingData\n };\n await publishOrPost(\n `${mqttTopicPrefix}/tracking/metrics`,\n TRACK_URL,\n licenseKey,\n metricsBody\n );\n } catch (error) {\n console.error(\"[StormcloudVideoPlayer] Error sending initial tracking data:\", error);\n }\n}\nasync function sendAdDetectTracking(licenseKey, adDetectInfo, context = {}) {\n try {\n await sendHeartbeat(licenseKey, context, {\n adDetect: true,\n captureAt: adDetectInfo.timestamp\n });\n } catch (error) {\n console.error(\"[StormcloudVideoPlayer] Error sending ad detect tracking:\", error);\n }\n}\nasync function sendAdLoadedTracking(licenseKey, adLoadedInfo, context = {}) {\n try {\n await sendHeartbeat(licenseKey, context, {\n adLoaded: true,\n captureAt: adLoadedInfo.timestamp\n });\n } catch (error) {\n console.error(\"[StormcloudVideoPlayer] Error sending ad loaded tracking:\", error);\n }\n}\nasync function sendAdImpressionTracking(licenseKey, adImpressionInfo, context = {}) {\n try {\n const metricEvent = await buildPlayerMetricEvent(licenseKey, context, {\n captureAt: adImpressionInfo.timestamp\n });\n const heartbeatBody = metricEvent;\n const impressionsBody = {\n events: [\n {\n player_id: metricEvent.player_id,\n ad_played_count: 1,\n ad_url: adImpressionInfo.adUrl,\n license_key: licenseKey,\n capture_at: adImpressionInfo.timestamp\n }\n ]\n };\n await Promise.all([\n publishOrPost(\n `${mqttTopicPrefix}/tracking/heartbeat`,\n HEARTBEAT_URL,\n licenseKey,\n heartbeatBody\n ),\n publishOrPost(\n `${mqttTopicPrefix}/tracking/impressions`,\n IMPRESSIONS_URL,\n licenseKey,\n impressionsBody\n )\n ]);\n } catch (error) {\n console.error(\"[StormcloudVideoPlayer] Error sending ad impression tracking:\", error);\n }\n}\nasync function sendHeartbeat(licenseKey, context = {}, flags = {}) {\n try {\n const heartbeatData = await buildPlayerMetricEvent(licenseKey, context, flags);\n await publishOrPost(\n `${mqttTopicPrefix}/tracking/heartbeat`,\n HEARTBEAT_URL,\n licenseKey,\n heartbeatData\n );\n } catch (error) {\n console.error(\"[StormcloudVideoPlayer] Error sending heartbeat:\", error);\n }\n}\n// Annotate the CommonJS export names for ESM import in node:\n0 && (module.exports = {\n getBrowserID,\n getClientInfo,\n sendAdDetectTracking,\n sendAdImpressionTracking,\n sendAdLoadedTracking,\n sendHeartbeat,\n sendInitialTracking,\n setMQTTTopicPrefix\n});\n","import type {\n ClientInfo,\n TrackingData,\n AdDetectInfo,\n AdLoadedInfo,\n AdImpressionInfo,\n PlayerAnalyticsContext,\n} from \"../types\";\nimport { isMQTTConfigured, publishMQTT } from \"./mqttClient\";\n\nlet cachedBrowserId: string | null = null;\n\nexport function getClientInfo(): ClientInfo {\n const ua = navigator.userAgent;\n const platform = navigator.platform;\n const vendor = navigator.vendor || \"\";\n const maxTouchPoints = navigator.maxTouchPoints || 0;\n const memory = (navigator as any).deviceMemory || null;\n const hardwareConcurrency = navigator.hardwareConcurrency || 1;\n\n const screenInfo = {\n width: screen?.width,\n height: screen?.height,\n availWidth: screen?.availWidth,\n availHeight: screen?.availHeight,\n orientation: (screen?.orientation as any)?.type || \"\",\n pixelDepth: screen?.pixelDepth,\n };\n\n let deviceType: \"tv\" | \"mobile\" | \"tablet\" | \"desktop\" = \"desktop\";\n let brand = \"Unknown\";\n let os = \"Unknown\";\n let model = \"\";\n let isSmartTV = false;\n let isAndroid = false;\n let isWebView = false;\n let isWebApp = false;\n\n if (ua.includes(\"Web0S\")) {\n brand = \"LG\"; os = \"webOS\"; isSmartTV = true; deviceType = \"tv\";\n const m = ua.match(/Web0S\\/([^\\s]+)/);\n model = m ? `webOS ${m[1]}` : \"webOS TV\";\n } else if (ua.includes(\"Tizen\")) {\n brand = \"Samsung\"; os = \"Tizen\"; isSmartTV = true; deviceType = \"tv\";\n const tizenMatch = ua.match(/Tizen\\/([^\\s]+)/);\n const tvMatch = ua.match(/(?:Smart-TV|SMART-TV|TV)/i) ? \"Smart TV\" : \"\";\n model = tizenMatch ? `Tizen ${tizenMatch[1]} ${tvMatch}`.trim() : \"Tizen TV\";\n } else if (ua.includes(\"Philips\")) {\n brand = \"Philips\"; os = \"Saphi\"; isSmartTV = true; deviceType = \"tv\";\n } else if (ua.includes(\"Sharp\") || ua.includes(\"AQUOS\")) {\n brand = \"Sharp\"; os = \"Android TV\"; isSmartTV = true; deviceType = \"tv\";\n } else if (ua.includes(\"Android\") && (ua.includes(\"Sony\") || vendor.includes(\"Sony\"))) {\n brand = \"Sony\"; os = \"Android TV\"; isSmartTV = true; deviceType = \"tv\";\n } else if (ua.includes(\"Android\") && (ua.includes(\"NetCast\") || ua.includes(\"LG\"))) {\n brand = \"LG\"; os = \"Android TV\"; isSmartTV = true; deviceType = \"tv\";\n } else if (ua.includes(\" Roku\") || ua.includes(\"Roku/\")) {\n brand = \"Roku\"; os = \"Roku OS\"; isSmartTV = true; deviceType = \"tv\";\n } else if (ua.includes(\"AppleTV\")) {\n brand = \"Apple\"; os = \"tvOS\"; isSmartTV = true; deviceType = \"tv\";\n }\n\n if (ua.includes(\"Android\")) {\n isAndroid = true; os = \"Android\";\n deviceType = /Mobile/.test(ua) ? \"mobile\" : \"tablet\";\n if (maxTouchPoints === 0 || ua.includes(\"Google TV\") || ua.includes(\"XiaoMi\")) {\n deviceType = \"tv\"; isSmartTV = true;\n brand = brand === \"Unknown\" ? \"Android TV\" : brand;\n }\n const androidModelMatch = ua.match(/\\(([^)]*Android[^)]*)\\)/);\n if (androidModelMatch?.[1]) model = androidModelMatch[1];\n }\n\n if (/iPad|iPhone|iPod/.test(ua)) {\n os = \"iOS\"; deviceType = \"mobile\"; brand = \"Apple\";\n if (navigator.maxTouchPoints > 1 && /iPad/.test(ua)) deviceType = \"tablet\";\n }\n\n if (!isAndroid && !isSmartTV && !/Mobile/.test(ua)) {\n if (ua.includes(\"Windows\")) { os = \"Windows\"; deviceType = \"desktop\"; }\n else if (ua.includes(\"Mac\") && !/iPhone/.test(ua)) {\n os = \"macOS\"; deviceType = \"desktop\";\n if (maxTouchPoints > 1) deviceType = \"tablet\";\n } else if (ua.includes(\"Linux\")) { os = \"Linux\"; deviceType = \"desktop\"; }\n }\n\n if (brand === \"Unknown\") {\n if (vendor.includes(\"Google\") || ua.includes(\"Chrome\")) brand = \"Google\";\n if (vendor.includes(\"Apple\")) brand = \"Apple\";\n if (vendor.includes(\"Samsung\") || ua.includes(\"SM-\")) brand = \"Samsung\";\n }\n\n isWebView = /wv|WebView|Linux; U;/.test(ua);\n if (window?.outerHeight === 0 && window?.outerWidth === 0) isWebView = true;\n\n isWebApp =\n window.matchMedia(\"(display-mode: standalone)\").matches ||\n (window.navigator as any).standalone === true ||\n window.screen?.orientation?.angle !== undefined;\n\n return {\n brand,\n os,\n model: model || ua.substring(0, 50) + \"...\",\n deviceType,\n isSmartTV,\n isAndroid,\n isWebView,\n isWebApp,\n domain: window.location.hostname,\n origin: window.location.origin,\n path: window.location.pathname,\n userAgent: ua,\n vendor,\n platform,\n screen: screenInfo,\n hardwareConcurrency,\n deviceMemory: memory,\n maxTouchPoints,\n language: navigator.language,\n languages: navigator.languages?.join(\",\") || \"\",\n cookieEnabled: navigator.cookieEnabled,\n doNotTrack: navigator.doNotTrack || \"\",\n referrer: document.referrer,\n visibilityState: document.visibilityState,\n };\n}\n\nexport async function getBrowserID(clientInfo: ClientInfo): Promise<string> {\n if (cachedBrowserId) return cachedBrowserId;\n\n const fingerprintString = JSON.stringify(clientInfo);\n\n if (typeof crypto !== \"undefined\" && crypto.subtle?.digest) {\n try {\n await crypto.subtle.digest(\"SHA-256\", new Uint8Array([1, 2, 3]));\n let encodedData: BufferSource;\n if (typeof TextEncoder !== \"undefined\") {\n encodedData = new TextEncoder().encode(fingerprintString);\n } else {\n const utf8 = unescape(encodeURIComponent(fingerprintString));\n const buffer = new Uint8Array(utf8.length);\n for (let i = 0; i < utf8.length; i++) buffer[i] = utf8.charCodeAt(i);\n encodedData = buffer;\n }\n const hashBuffer = await crypto.subtle.digest(\"SHA-256\", encodedData);\n const hashHex = Array.from(new Uint8Array(hashBuffer))\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n cachedBrowserId = hashHex;\n return hashHex;\n } catch {\n console.warn(\"[StormcloudVideoPlayer] crypto.subtle not supported, using fallback hash\");\n }\n }\n\n let hash = 0;\n for (let i = 0; i < fingerprintString.length; i++) {\n const char = fingerprintString.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash;\n }\n const fallbackHash = Math.abs(hash).toString(16).padStart(8, \"0\");\n const timestamp = Date.now().toString(16).padStart(12, \"0\");\n const random = Math.random().toString(16).substring(2, 14).padStart(12, \"0\");\n cachedBrowserId = (fallbackHash + timestamp + random).padEnd(64, \"0\");\n return cachedBrowserId;\n}\n\nlet mqttTopicPrefix = \"adstorm\";\n\nexport function setMQTTTopicPrefix(prefix: string): void {\n mqttTopicPrefix = prefix || \"adstorm\";\n}\n\nconst PLAYER_TRACKING_BASE_URL =\n \"https://adstorm.co/api-adstorm-dev/adstorm/player-tracking\";\nconst TRACK_URL = `${PLAYER_TRACKING_BASE_URL}/metrics/ingest`;\nconst HEARTBEAT_URL = `${PLAYER_TRACKING_BASE_URL}/heartbeat`;\nconst IMPRESSIONS_URL = `${PLAYER_TRACKING_BASE_URL}/impressions/ingest`;\n\ntype PlayerMetricFlags = {\n adLoaded?: boolean;\n adDetect?: boolean;\n captureAt?: string;\n};\n\nfunction buildHeaders(licenseKey: string | undefined): Record<string, string> {\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (licenseKey) headers[\"Authorization\"] = `Bearer ${licenseKey}`;\n return headers;\n}\n\nasync function postJson(\n url: string,\n licenseKey: string | undefined,\n body: Record<string, unknown>\n): Promise<void> {\n const response = await fetch(url, {\n method: \"POST\",\n headers: buildHeaders(licenseKey),\n body: JSON.stringify(body),\n });\n if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);\n await response.json();\n}\n\nasync function buildPlayerMetricEvent(\n licenseKey: string | undefined,\n context: PlayerAnalyticsContext = {},\n flags: PlayerMetricFlags = {}\n): Promise<Record<string, unknown>> {\n const clientInfo = getClientInfo();\n const browserId = await getBrowserID(clientInfo);\n const captureAt = flags.captureAt ?? new Date().toISOString();\n\n return {\n player_id: browserId,\n browserId,\n device_type: clientInfo.deviceType,\n deviceType: clientInfo.deviceType,\n input_stream_type: context.inputStreamType,\n os: clientInfo.os,\n ad_loaded: flags.adLoaded,\n ad_detect: flags.adDetect,\n license_key: licenseKey,\n capture_at: captureAt,\n timestamp: captureAt,\n };\n}\n\nasync function publishOrPost(\n mqttTopic: string,\n httpUrl: string,\n licenseKey: string | undefined,\n body: Record<string, unknown>\n): Promise<void> {\n if (isMQTTConfigured()) {\n publishMQTT(mqttTopic, body);\n return;\n }\n await postJson(httpUrl, licenseKey, body);\n}\n\n\nexport async function sendInitialTracking(\n licenseKey?: string,\n context: PlayerAnalyticsContext = {}\n): Promise<void> {\n try {\n const clientInfo = getClientInfo();\n const browserId = await getBrowserID(clientInfo);\n const captureAt = new Date().toISOString();\n\n const trackingData: TrackingData = { browserId, ...clientInfo };\n\n const metricsBody: Record<string, unknown> = {\n events: [\n {\n player_id: browserId,\n device_type: clientInfo.deviceType,\n input_stream_type: context.inputStreamType,\n os: clientInfo.os,\n ad_loaded: false,\n ad_detect: false,\n license_key: licenseKey,\n capture_at: captureAt,\n },\n ],\n trackingData,\n };\n\n await publishOrPost(\n `${mqttTopicPrefix}/tracking/metrics`,\n TRACK_URL,\n licenseKey,\n metricsBody\n );\n } catch (error) {\n console.error(\"[StormcloudVideoPlayer] Error sending initial tracking data:\", error);\n }\n}\n\nexport async function sendAdDetectTracking(\n licenseKey: string | undefined,\n adDetectInfo: AdDetectInfo,\n context: PlayerAnalyticsContext = {}\n): Promise<void> {\n try {\n await sendHeartbeat(licenseKey, context, {\n adDetect: true,\n captureAt: adDetectInfo.timestamp,\n });\n } catch (error) {\n console.error(\"[StormcloudVideoPlayer] Error sending ad detect tracking:\", error);\n }\n}\n\nexport async function sendAdLoadedTracking(\n licenseKey: string | undefined,\n adLoadedInfo: AdLoadedInfo,\n context: PlayerAnalyticsContext = {}\n): Promise<void> {\n try {\n await sendHeartbeat(licenseKey, context, {\n adLoaded: true,\n captureAt: adLoadedInfo.timestamp,\n });\n } catch (error) {\n console.error(\"[StormcloudVideoPlayer] Error sending ad loaded tracking:\", error);\n }\n}\n\nexport async function sendAdImpressionTracking(\n licenseKey: string | undefined,\n adImpressionInfo: AdImpressionInfo,\n context: PlayerAnalyticsContext = {}\n): Promise<void> {\n try {\n const metricEvent = await buildPlayerMetricEvent(licenseKey, context, {\n captureAt: adImpressionInfo.timestamp,\n });\n\n const heartbeatBody = metricEvent;\n const impressionsBody: Record<string, unknown> = {\n events: [\n {\n player_id: metricEvent.player_id,\n ad_played_count: 1,\n ad_url: adImpressionInfo.adUrl,\n license_key: licenseKey,\n capture_at: adImpressionInfo.timestamp,\n },\n ],\n };\n\n await Promise.all([\n publishOrPost(\n `${mqttTopicPrefix}/tracking/heartbeat`,\n HEARTBEAT_URL,\n licenseKey,\n heartbeatBody\n ),\n publishOrPost(\n `${mqttTopicPrefix}/tracking/impressions`,\n IMPRESSIONS_URL,\n licenseKey,\n impressionsBody\n ),\n ]);\n } catch (error) {\n console.error(\"[StormcloudVideoPlayer] Error sending ad impression tracking:\", error);\n }\n}\n\nexport async function sendHeartbeat(\n licenseKey?: string,\n context: PlayerAnalyticsContext = {},\n flags: PlayerMetricFlags = {}\n): Promise<void> {\n try {\n const heartbeatData = await buildPlayerMetricEvent(licenseKey, context, flags);\n await publishOrPost(\n `${mqttTopicPrefix}/tracking/heartbeat`,\n HEARTBEAT_URL,\n licenseKey,\n heartbeatData\n );\n } catch (error) {\n console.error(\"[StormcloudVideoPlayer] Error sending heartbeat:\", error);\n }\n}\n","import mqtt from \"mqtt\";\nimport type { MqttClient } from \"mqtt\";\n\nconst LOG = \"[StormcloudVideoPlayer][MQTT]\";\n\nexport type MQTTStatus = \"disconnected\" | \"connecting\" | \"connected\" | \"error\";\n\nlet client: MqttClient | null = null;\nlet status: MQTTStatus = \"disconnected\";\nlet brokerUrl = \"\";\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 client !== null;\n}\n\nexport function initMQTTClient(\n url: string,\n _topicPrefix = \"adstorm\"\n): void {\n if (client) return;\n\n brokerUrl = url;\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 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 publishMQTT(\n topic: string,\n payload: Record<string, unknown>\n): boolean {\n if (!client) {\n return false;\n }\n try {\n client.publish(topic, JSON.stringify(payload), { qos: 1 });\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 brokerUrl = \"\";\n }\n}\n"]}
1
+ {"version":3,"sources":["/home/ubuntu24-new/Dev/stormcloud-vp/lib/utils/tracking.cjs","../../src/utils/tracking.ts","../../src/utils/mqttConfig.ts","../../src/utils/mqttClient.ts"],"names":["tracking_exports","licenseKey","mqttConfig","__create","Object","create","__defProp","defineProperty","__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","getBrowserID","getClientInfo","sendAdDetectTracking","sendAdImpressionTracking","sendAdLoadedTracking","sendHeartbeat","sendInitialTracking","module","exports","DEFAULT_MQTT_CONFIG","enabled","brokerAddress","brokerPort","wsPort","username","password","topicPrefix","qos","isMQTTEnabled","buildMQTTBrokerUrl","brokerUrl","buildPlayerTopic","channel","import_mqtt","require","LOG","client","status","initMQTTClient","keepalive","mqtt","clean","connect","reconnectPeriod","url","err","console","on","warn","clientId","connectTimeout","queueQoSZero","info","message","ensureMQTTClient","publishMQTT","topic","payload","publish","JSON","stringify","cachedBrowserId","screen","window","navigator","ua","userAgent","screenInfo","vendor","maxTouchPoints","memory","deviceMemory","hardwareConcurrency","width","availWidth","availHeight","os","pixelDepth","deviceType","brand","model","isAndroid","isWebView","isWebApp","isSmartTV","m","match","includes","tizenMatch","tvMatch","trim"],"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/tracking.ts\nvar tracking_exports = {};\n__export(tracking_exports, {\n getBrowserID: () => getBrowserID,\n getClientInfo: () => getClientInfo,\n sendAdDetectTracking: () => sendAdDetectTracking,\n sendAdImpressionTracking: () => sendAdImpressionTracking,\n sendAdLoadedTracking: () => sendAdLoadedTracking,\n sendHeartbeat: () => sendHeartbeat,\n sendInitialTracking: () => sendInitialTracking\n});\nmodule.exports = __toCommonJS(tracking_exports);\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 isMQTTEnabled() {\n return mqttConfig.enabled;\n}\nfunction buildMQTTBrokerUrl() {\n if (mqttConfig.brokerUrl) return mqttConfig.brokerUrl;\n return `wss://${mqttConfig.brokerAddress}:${mqttConfig.wsPort}/mqtt`;\n}\nfunction buildPlayerTopic(licenseKey, channel) {\n return `${mqttConfig.topicPrefix}/${licenseKey}/${channel}`;\n}\n\n// src/utils/mqttClient.ts\nvar import_mqtt = __toESM(require(\"mqtt\"), 1);\nvar LOG = \"[StormcloudVideoPlayer][MQTT]\";\nvar client = null;\nvar status = \"disconnected\";\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}\n\n// src/utils/tracking.ts\nvar cachedBrowserId = null;\nfunction getClientInfo() {\n const ua = navigator.userAgent;\n const platform = navigator.platform;\n const vendor = navigator.vendor || \"\";\n const maxTouchPoints = navigator.maxTouchPoints || 0;\n const memory = navigator.deviceMemory || null;\n const hardwareConcurrency = navigator.hardwareConcurrency || 1;\n const screenInfo = {\n width: screen?.width,\n height: screen?.height,\n availWidth: screen?.availWidth,\n availHeight: screen?.availHeight,\n orientation: screen?.orientation?.type || \"\",\n pixelDepth: screen?.pixelDepth\n };\n let deviceType = \"desktop\";\n let brand = \"Unknown\";\n let os = \"Unknown\";\n let model = \"\";\n let isSmartTV = false;\n let isAndroid = false;\n let isWebView = false;\n let isWebApp = false;\n if (ua.includes(\"Web0S\")) {\n brand = \"LG\";\n os = \"webOS\";\n isSmartTV = true;\n deviceType = \"tv\";\n const m = ua.match(/Web0S\\/([^\\s]+)/);\n model = m ? `webOS ${m[1]}` : \"webOS TV\";\n } else if (ua.includes(\"Tizen\")) {\n brand = \"Samsung\";\n os = \"Tizen\";\n isSmartTV = true;\n deviceType = \"tv\";\n const tizenMatch = ua.match(/Tizen\\/([^\\s]+)/);\n const tvMatch = ua.match(/(?:Smart-TV|SMART-TV|TV)/i) ? \"Smart TV\" : \"\";\n model = tizenMatch ? `Tizen ${tizenMatch[1]} ${tvMatch}`.trim() : \"Tizen TV\";\n } else if (ua.includes(\"Philips\")) {\n brand = \"Philips\";\n os = \"Saphi\";\n isSmartTV = true;\n deviceType = \"tv\";\n } else if (ua.includes(\"Sharp\") || ua.includes(\"AQUOS\")) {\n brand = \"Sharp\";\n os = \"Android TV\";\n isSmartTV = true;\n deviceType = \"tv\";\n } else if (ua.includes(\"Android\") && (ua.includes(\"Sony\") || vendor.includes(\"Sony\"))) {\n brand = \"Sony\";\n os = \"Android TV\";\n isSmartTV = true;\n deviceType = \"tv\";\n } else if (ua.includes(\"Android\") && (ua.includes(\"NetCast\") || ua.includes(\"LG\"))) {\n brand = \"LG\";\n os = \"Android TV\";\n isSmartTV = true;\n deviceType = \"tv\";\n } else if (ua.includes(\" Roku\") || ua.includes(\"Roku/\")) {\n brand = \"Roku\";\n os = \"Roku OS\";\n isSmartTV = true;\n deviceType = \"tv\";\n } else if (ua.includes(\"AppleTV\")) {\n brand = \"Apple\";\n os = \"tvOS\";\n isSmartTV = true;\n deviceType = \"tv\";\n }\n if (ua.includes(\"Android\")) {\n isAndroid = true;\n os = \"Android\";\n deviceType = /Mobile/.test(ua) ? \"mobile\" : \"tablet\";\n if (maxTouchPoints === 0 || ua.includes(\"Google TV\") || ua.includes(\"XiaoMi\")) {\n deviceType = \"tv\";\n isSmartTV = true;\n brand = brand === \"Unknown\" ? \"Android TV\" : brand;\n }\n const androidModelMatch = ua.match(/\\(([^)]*Android[^)]*)\\)/);\n if (androidModelMatch?.[1]) model = androidModelMatch[1];\n }\n if (/iPad|iPhone|iPod/.test(ua)) {\n os = \"iOS\";\n deviceType = \"mobile\";\n brand = \"Apple\";\n if (navigator.maxTouchPoints > 1 && /iPad/.test(ua)) deviceType = \"tablet\";\n }\n if (!isAndroid && !isSmartTV && !/Mobile/.test(ua)) {\n if (ua.includes(\"Windows\")) {\n os = \"Windows\";\n deviceType = \"desktop\";\n } else if (ua.includes(\"Mac\") && !/iPhone/.test(ua)) {\n os = \"macOS\";\n deviceType = \"desktop\";\n if (maxTouchPoints > 1) deviceType = \"tablet\";\n } else if (ua.includes(\"Linux\")) {\n os = \"Linux\";\n deviceType = \"desktop\";\n }\n }\n if (brand === \"Unknown\") {\n if (vendor.includes(\"Google\") || ua.includes(\"Chrome\")) brand = \"Google\";\n if (vendor.includes(\"Apple\")) brand = \"Apple\";\n if (vendor.includes(\"Samsung\") || ua.includes(\"SM-\")) brand = \"Samsung\";\n }\n isWebView = /wv|WebView|Linux; U;/.test(ua);\n if (window?.outerHeight === 0 && window?.outerWidth === 0) isWebView = true;\n isWebApp = window.matchMedia(\"(display-mode: standalone)\").matches || window.navigator.standalone === true || window.screen?.orientation?.angle !== void 0;\n return {\n brand,\n os,\n model: model || ua.substring(0, 50) + \"...\",\n deviceType,\n isSmartTV,\n isAndroid,\n isWebView,\n isWebApp,\n domain: window.location.hostname,\n origin: window.location.origin,\n path: window.location.pathname,\n userAgent: ua,\n vendor,\n platform,\n screen: screenInfo,\n hardwareConcurrency,\n deviceMemory: memory,\n maxTouchPoints,\n language: navigator.language,\n languages: navigator.languages?.join(\",\") || \"\",\n cookieEnabled: navigator.cookieEnabled,\n doNotTrack: navigator.doNotTrack || \"\",\n referrer: document.referrer,\n visibilityState: document.visibilityState\n };\n}\nasync function getBrowserID(clientInfo) {\n if (cachedBrowserId) return cachedBrowserId;\n const fingerprintString = JSON.stringify(clientInfo);\n if (typeof crypto !== \"undefined\" && crypto.subtle?.digest) {\n try {\n await crypto.subtle.digest(\"SHA-256\", new Uint8Array([1, 2, 3]));\n let encodedData;\n if (typeof TextEncoder !== \"undefined\") {\n encodedData = new TextEncoder().encode(fingerprintString);\n } else {\n const utf8 = unescape(encodeURIComponent(fingerprintString));\n const buffer = new Uint8Array(utf8.length);\n for (let i = 0; i < utf8.length; i++) buffer[i] = utf8.charCodeAt(i);\n encodedData = buffer;\n }\n const hashBuffer = await crypto.subtle.digest(\"SHA-256\", encodedData);\n const hashHex = Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n cachedBrowserId = hashHex;\n return hashHex;\n } catch {\n console.warn(\"[StormcloudVideoPlayer] crypto.subtle not supported, using fallback hash\");\n }\n }\n let hash = 0;\n for (let i = 0; i < fingerprintString.length; i++) {\n const char = fingerprintString.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash;\n }\n const fallbackHash = Math.abs(hash).toString(16).padStart(8, \"0\");\n const timestamp = Date.now().toString(16).padStart(12, \"0\");\n const random = Math.random().toString(16).substring(2, 14).padStart(12, \"0\");\n cachedBrowserId = (fallbackHash + timestamp + random).padEnd(64, \"0\");\n return cachedBrowserId;\n}\nfunction canPublish(licenseKey) {\n return Boolean(isMQTTEnabled() && licenseKey);\n}\nasync function buildPlayerMetricEvent(context = {}, flags = {}) {\n const clientInfo = getClientInfo();\n const playerId = await getBrowserID(clientInfo);\n const captureAt = flags.captureAt ?? (/* @__PURE__ */ new Date()).toISOString();\n return {\n player_id: playerId,\n device_type: clientInfo.deviceType,\n os: clientInfo.os.toLowerCase(),\n ad_loaded: flags.adLoaded ?? false,\n ad_detect: flags.adDetect ?? false,\n capture_at: captureAt,\n ...context.inputStreamType ? { input_stream_type: context.inputStreamType } : {}\n };\n}\nfunction publishTracking(licenseKey, channel, body) {\n ensureMQTTClient();\n publishMQTT(buildPlayerTopic(licenseKey, channel), body);\n}\nasync function sendInitialTracking(licenseKey, context = {}) {\n if (!canPublish(licenseKey)) return;\n try {\n const metricEvent = await buildPlayerMetricEvent(context, {\n adLoaded: false,\n adDetect: false\n });\n publishTracking(licenseKey, \"metrics\", {\n events: [metricEvent]\n });\n } catch (error) {\n console.error(\"[StormcloudVideoPlayer] Error sending initial tracking data:\", error);\n }\n}\nasync function sendAdDetectTracking(licenseKey, adDetectInfo, context = {}) {\n try {\n await sendHeartbeat(licenseKey, context, {\n adDetect: true,\n captureAt: adDetectInfo.timestamp\n });\n } catch (error) {\n console.error(\"[StormcloudVideoPlayer] Error sending ad detect tracking:\", error);\n }\n}\nasync function sendAdLoadedTracking(licenseKey, adLoadedInfo, context = {}) {\n try {\n await sendHeartbeat(licenseKey, context, {\n adLoaded: true,\n captureAt: adLoadedInfo.timestamp\n });\n } catch (error) {\n console.error(\"[StormcloudVideoPlayer] Error sending ad loaded tracking:\", error);\n }\n}\nasync function sendAdImpressionTracking(licenseKey, adImpressionInfo, context = {}) {\n if (!canPublish(licenseKey)) return;\n try {\n const metricEvent = await buildPlayerMetricEvent(context, {\n captureAt: adImpressionInfo.timestamp\n });\n publishTracking(licenseKey, \"heartbeat\", metricEvent);\n publishTracking(licenseKey, \"impressions\", {\n events: [\n {\n player_id: metricEvent.player_id,\n ad_played_count: 1,\n ad_url: adImpressionInfo.adUrl,\n capture_at: adImpressionInfo.timestamp\n }\n ]\n });\n } catch (error) {\n console.error(\"[StormcloudVideoPlayer] Error sending ad impression tracking:\", error);\n }\n}\nasync function sendHeartbeat(licenseKey, context = {}, flags = {}) {\n if (!canPublish(licenseKey)) return;\n try {\n const heartbeatData = await buildPlayerMetricEvent(context, flags);\n publishTracking(licenseKey, \"heartbeat\", heartbeatData);\n } catch (error) {\n console.error(\"[StormcloudVideoPlayer] Error sending heartbeat:\", error);\n }\n}\n// Annotate the CommonJS export names for ESM import in node:\n0 && (module.exports = {\n getBrowserID,\n getClientInfo,\n sendAdDetectTracking,\n sendAdImpressionTracking,\n sendAdLoadedTracking,\n sendHeartbeat,\n sendInitialTracking\n});\n","import type {\n ClientInfo,\n AdDetectInfo,\n AdLoadedInfo,\n AdImpressionInfo,\n PlayerAnalyticsContext,\n} from \"../types\";\nimport { buildPlayerTopic, isMQTTEnabled } from \"./mqttConfig\";\nimport { ensureMQTTClient, publishMQTT } from \"./mqttClient\";\n\nlet cachedBrowserId: string | null = null;\n\nexport function getClientInfo(): ClientInfo {\n const ua = navigator.userAgent;\n const platform = navigator.platform;\n const vendor = navigator.vendor || \"\";\n const maxTouchPoints = navigator.maxTouchPoints || 0;\n const memory = (navigator as any).deviceMemory || null;\n const hardwareConcurrency = navigator.hardwareConcurrency || 1;\n\n const screenInfo = {\n width: screen?.width,\n height: screen?.height,\n availWidth: screen?.availWidth,\n availHeight: screen?.availHeight,\n orientation: (screen?.orientation as any)?.type || \"\",\n pixelDepth: screen?.pixelDepth,\n };\n\n let deviceType: \"tv\" | \"mobile\" | \"tablet\" | \"desktop\" = \"desktop\";\n let brand = \"Unknown\";\n let os = \"Unknown\";\n let model = \"\";\n let isSmartTV = false;\n let isAndroid = false;\n let isWebView = false;\n let isWebApp = false;\n\n if (ua.includes(\"Web0S\")) {\n brand = \"LG\"; os = \"webOS\"; isSmartTV = true; deviceType = \"tv\";\n const m = ua.match(/Web0S\\/([^\\s]+)/);\n model = m ? `webOS ${m[1]}` : \"webOS TV\";\n } else if (ua.includes(\"Tizen\")) {\n brand = \"Samsung\"; os = \"Tizen\"; isSmartTV = true; deviceType = \"tv\";\n const tizenMatch = ua.match(/Tizen\\/([^\\s]+)/);\n const tvMatch = ua.match(/(?:Smart-TV|SMART-TV|TV)/i) ? \"Smart TV\" : \"\";\n model = tizenMatch ? `Tizen ${tizenMatch[1]} ${tvMatch}`.trim() : \"Tizen TV\";\n } else if (ua.includes(\"Philips\")) {\n brand = \"Philips\"; os = \"Saphi\"; isSmartTV = true; deviceType = \"tv\";\n } else if (ua.includes(\"Sharp\") || ua.includes(\"AQUOS\")) {\n brand = \"Sharp\"; os = \"Android TV\"; isSmartTV = true; deviceType = \"tv\";\n } else if (ua.includes(\"Android\") && (ua.includes(\"Sony\") || vendor.includes(\"Sony\"))) {\n brand = \"Sony\"; os = \"Android TV\"; isSmartTV = true; deviceType = \"tv\";\n } else if (ua.includes(\"Android\") && (ua.includes(\"NetCast\") || ua.includes(\"LG\"))) {\n brand = \"LG\"; os = \"Android TV\"; isSmartTV = true; deviceType = \"tv\";\n } else if (ua.includes(\" Roku\") || ua.includes(\"Roku/\")) {\n brand = \"Roku\"; os = \"Roku OS\"; isSmartTV = true; deviceType = \"tv\";\n } else if (ua.includes(\"AppleTV\")) {\n brand = \"Apple\"; os = \"tvOS\"; isSmartTV = true; deviceType = \"tv\";\n }\n\n if (ua.includes(\"Android\")) {\n isAndroid = true; os = \"Android\";\n deviceType = /Mobile/.test(ua) ? \"mobile\" : \"tablet\";\n if (maxTouchPoints === 0 || ua.includes(\"Google TV\") || ua.includes(\"XiaoMi\")) {\n deviceType = \"tv\"; isSmartTV = true;\n brand = brand === \"Unknown\" ? \"Android TV\" : brand;\n }\n const androidModelMatch = ua.match(/\\(([^)]*Android[^)]*)\\)/);\n if (androidModelMatch?.[1]) model = androidModelMatch[1];\n }\n\n if (/iPad|iPhone|iPod/.test(ua)) {\n os = \"iOS\"; deviceType = \"mobile\"; brand = \"Apple\";\n if (navigator.maxTouchPoints > 1 && /iPad/.test(ua)) deviceType = \"tablet\";\n }\n\n if (!isAndroid && !isSmartTV && !/Mobile/.test(ua)) {\n if (ua.includes(\"Windows\")) { os = \"Windows\"; deviceType = \"desktop\"; }\n else if (ua.includes(\"Mac\") && !/iPhone/.test(ua)) {\n os = \"macOS\"; deviceType = \"desktop\";\n if (maxTouchPoints > 1) deviceType = \"tablet\";\n } else if (ua.includes(\"Linux\")) { os = \"Linux\"; deviceType = \"desktop\"; }\n }\n\n if (brand === \"Unknown\") {\n if (vendor.includes(\"Google\") || ua.includes(\"Chrome\")) brand = \"Google\";\n if (vendor.includes(\"Apple\")) brand = \"Apple\";\n if (vendor.includes(\"Samsung\") || ua.includes(\"SM-\")) brand = \"Samsung\";\n }\n\n isWebView = /wv|WebView|Linux; U;/.test(ua);\n if (window?.outerHeight === 0 && window?.outerWidth === 0) isWebView = true;\n\n isWebApp =\n window.matchMedia(\"(display-mode: standalone)\").matches ||\n (window.navigator as any).standalone === true ||\n window.screen?.orientation?.angle !== undefined;\n\n return {\n brand,\n os,\n model: model || ua.substring(0, 50) + \"...\",\n deviceType,\n isSmartTV,\n isAndroid,\n isWebView,\n isWebApp,\n domain: window.location.hostname,\n origin: window.location.origin,\n path: window.location.pathname,\n userAgent: ua,\n vendor,\n platform,\n screen: screenInfo,\n hardwareConcurrency,\n deviceMemory: memory,\n maxTouchPoints,\n language: navigator.language,\n languages: navigator.languages?.join(\",\") || \"\",\n cookieEnabled: navigator.cookieEnabled,\n doNotTrack: navigator.doNotTrack || \"\",\n referrer: document.referrer,\n visibilityState: document.visibilityState,\n };\n}\n\nexport async function getBrowserID(clientInfo: ClientInfo): Promise<string> {\n if (cachedBrowserId) return cachedBrowserId;\n\n const fingerprintString = JSON.stringify(clientInfo);\n\n if (typeof crypto !== \"undefined\" && crypto.subtle?.digest) {\n try {\n await crypto.subtle.digest(\"SHA-256\", new Uint8Array([1, 2, 3]));\n let encodedData: BufferSource;\n if (typeof TextEncoder !== \"undefined\") {\n encodedData = new TextEncoder().encode(fingerprintString);\n } else {\n const utf8 = unescape(encodeURIComponent(fingerprintString));\n const buffer = new Uint8Array(utf8.length);\n for (let i = 0; i < utf8.length; i++) buffer[i] = utf8.charCodeAt(i);\n encodedData = buffer;\n }\n const hashBuffer = await crypto.subtle.digest(\"SHA-256\", encodedData);\n const hashHex = Array.from(new Uint8Array(hashBuffer))\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n cachedBrowserId = hashHex;\n return hashHex;\n } catch {\n console.warn(\"[StormcloudVideoPlayer] crypto.subtle not supported, using fallback hash\");\n }\n }\n\n let hash = 0;\n for (let i = 0; i < fingerprintString.length; i++) {\n const char = fingerprintString.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash;\n }\n const fallbackHash = Math.abs(hash).toString(16).padStart(8, \"0\");\n const timestamp = Date.now().toString(16).padStart(12, \"0\");\n const random = Math.random().toString(16).substring(2, 14).padStart(12, \"0\");\n cachedBrowserId = (fallbackHash + timestamp + random).padEnd(64, \"0\");\n return cachedBrowserId;\n}\n\ntype PlayerMetricFlags = {\n adLoaded?: boolean;\n adDetect?: boolean;\n captureAt?: string;\n};\n\nfunction canPublish(licenseKey: string | undefined): licenseKey is string {\n return Boolean(isMQTTEnabled() && licenseKey);\n}\n\nasync function buildPlayerMetricEvent(\n context: PlayerAnalyticsContext = {},\n flags: PlayerMetricFlags = {}\n): Promise<Record<string, unknown>> {\n const clientInfo = getClientInfo();\n const playerId = await getBrowserID(clientInfo);\n const captureAt = flags.captureAt ?? new Date().toISOString();\n\n return {\n player_id: playerId,\n device_type: clientInfo.deviceType,\n os: clientInfo.os.toLowerCase(),\n ad_loaded: flags.adLoaded ?? false,\n ad_detect: flags.adDetect ?? false,\n capture_at: captureAt,\n ...(context.inputStreamType ? { input_stream_type: context.inputStreamType } : {}),\n };\n}\n\nfunction publishTracking(\n licenseKey: string,\n channel: \"metrics\" | \"impressions\" | \"heartbeat\",\n body: Record<string, unknown>\n): void {\n ensureMQTTClient();\n publishMQTT(buildPlayerTopic(licenseKey, channel), body);\n}\n\nexport async function sendInitialTracking(\n licenseKey?: string,\n context: PlayerAnalyticsContext = {}\n): Promise<void> {\n if (!canPublish(licenseKey)) return;\n\n try {\n const metricEvent = await buildPlayerMetricEvent(context, {\n adLoaded: false,\n adDetect: false,\n });\n\n publishTracking(licenseKey, \"metrics\", {\n events: [metricEvent],\n });\n } catch (error) {\n console.error(\"[StormcloudVideoPlayer] Error sending initial tracking data:\", error);\n }\n}\n\nexport async function sendAdDetectTracking(\n licenseKey: string | undefined,\n adDetectInfo: AdDetectInfo,\n context: PlayerAnalyticsContext = {}\n): Promise<void> {\n try {\n await sendHeartbeat(licenseKey, context, {\n adDetect: true,\n captureAt: adDetectInfo.timestamp,\n });\n } catch (error) {\n console.error(\"[StormcloudVideoPlayer] Error sending ad detect tracking:\", error);\n }\n}\n\nexport async function sendAdLoadedTracking(\n licenseKey: string | undefined,\n adLoadedInfo: AdLoadedInfo,\n context: PlayerAnalyticsContext = {}\n): Promise<void> {\n try {\n await sendHeartbeat(licenseKey, context, {\n adLoaded: true,\n captureAt: adLoadedInfo.timestamp,\n });\n } catch (error) {\n console.error(\"[StormcloudVideoPlayer] Error sending ad loaded tracking:\", error);\n }\n}\n\nexport async function sendAdImpressionTracking(\n licenseKey: string | undefined,\n adImpressionInfo: AdImpressionInfo,\n context: PlayerAnalyticsContext = {}\n): Promise<void> {\n if (!canPublish(licenseKey)) return;\n\n try {\n const metricEvent = await buildPlayerMetricEvent(context, {\n captureAt: adImpressionInfo.timestamp,\n });\n\n publishTracking(licenseKey, \"heartbeat\", metricEvent);\n publishTracking(licenseKey, \"impressions\", {\n events: [\n {\n player_id: metricEvent.player_id,\n ad_played_count: 1,\n ad_url: adImpressionInfo.adUrl,\n capture_at: adImpressionInfo.timestamp,\n },\n ],\n });\n } catch (error) {\n console.error(\"[StormcloudVideoPlayer] Error sending ad impression tracking:\", error);\n }\n}\n\nexport async function sendHeartbeat(\n licenseKey?: string,\n context: PlayerAnalyticsContext = {},\n flags: PlayerMetricFlags = {}\n): Promise<void> {\n if (!canPublish(licenseKey)) return;\n\n try {\n const heartbeatData = await buildPlayerMetricEvent(context, flags);\n publishTracking(licenseKey, \"heartbeat\", heartbeatData);\n } catch (error) {\n console.error(\"[StormcloudVideoPlayer] Error sending heartbeat:\", error);\n }\n}\n","export 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","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"]}