clinic-connect-widget 1.0.2 → 1.0.3

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.
@@ -117,7 +117,7 @@ const collapsedHtml = `<div class="td-widget-fab" id="td-widget-fab">\r
117
117
  <div class="td-info">\r
118
118
  <div class="td-brand">\r
119
119
  <span class="material-symbols-outlined" style="font-size: 14px;">local_hospital</span>\r
120
- <span>Clinic</span>\r
120
+ <span>Tư vấn ngay</span>\r
121
121
  </div>\r
122
122
  <h3 class="td-cta">Khám online ngay</h3>\r
123
123
  <p class="td-sub">Bác sĩ đang Online</p>\r
@@ -277,7 +277,7 @@ const consultationHtml = `<div class="td-consultation-modal">\r
277
277
  </div>\r
278
278
  </div>\r
279
279
  </div>`;
280
- const patientFormHtml = '<div class="td-widget-expanded" id="td-patient-form">\r\n <header class="td-header">\r\n <div class="td-header-title">\r\n <div class="td-icon-box">\r\n <span class="material-symbols-outlined">medical_services</span>\r\n </div>\r\n <span>Clinic</span>\r\n </div>\r\n <button class="td-close-btn" id="td-form-close">\r\n <span class="material-symbols-outlined">close</span>\r\n </button>\r\n </header>\r\n\r\n <div class="td-content">\r\n <div class="td-form-container">\r\n <div class="td-form-page-header">\r\n <h1 class="td-form-title">Kết nối với Bác sĩ</h1>\r\n <p class="td-form-desc">Vui lòng nhập thông tin để bắt đầu tư vấn trực tuyến.</p>\r\n </div>\r\n\r\n <form class="td-form" id="td-info-form">\r\n <div class="td-input-group">\r\n <label class="td-label">Họ và tên</label>\r\n <input type="text" class="td-input" name="name" placeholder="VD: Nguyễn Văn A" required />\r\n </div>\r\n\r\n <div class="td-input-group">\r\n <label class="td-label">Số điện thoại</label>\r\n <div style="position: relative;">\r\n <input type="tel" class="td-input" name="phone" placeholder="VD: 0912 345 678" required />\r\n <div\r\n style="position: absolute; right: 12px; top: 50%; transform: translateY(-50%); color: var(--td-primary);">\r\n <span class="material-symbols-outlined" style="font-size: 20px;">smartphone</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class="td-input-group">\r\n <label class="td-label">Triệu chứng của bạn?</label>\r\n <div class="td-chips-group">\r\n <button type="button" class="td-chip active">Sốt <span class="material-symbols-outlined"\r\n style="font-size: 16px;">check</span></button>\r\n <button type="button" class="td-chip">Ho / Cảm cúm</button>\r\n <button type="button" class="td-chip">Đau đầu</button>\r\n <button type="button" class="td-chip">Đau bụng</button>\r\n </div>\r\n </div>\r\n\r\n <div class="td-input-group">\r\n <label class="td-label">Mô tả thêm <span style="color: var(--td-text-sub); font-weight: 400;">(Tùy\r\n chọn)</span></label>\r\n <textarea class="td-textarea" name="symptoms"\r\n placeholder="Bạn đang cảm thấy như thế nào?"></textarea>\r\n </div>\r\n\r\n <div style="margin-top: 8px;">\r\n <button type="submit" class="td-btn td-btn-primary" id="td-form-submit">\r\n Bắt đầu tư vấn\r\n <span class="material-symbols-outlined" style="margin-left: 8px;">arrow_forward</span>\r\n </button>\r\n </div>\r\n\r\n <div class="td-secure-note">\r\n <span class="material-symbols-outlined" style="font-size: 14px;">lock</span>\r\n Thông tin y tế được bảo mật 100%\r\n </div>\r\n </form>\r\n </div>\r\n </div>\r\n</div>';
280
+ const patientFormHtml = '<div class="td-widget-expanded" id="td-patient-form">\r\n <header class="td-header">\r\n <div class="td-header-title">\r\n <div class="td-icon-box">\r\n <span class="material-symbols-outlined">medical_services</span>\r\n </div>\r\n <span>Tư vấn trực tuyến</span>\r\n </div>\r\n <button class="td-close-btn" id="td-form-close">\r\n <span class="material-symbols-outlined">close</span>\r\n </button>\r\n </header>\r\n\r\n <div class="td-content">\r\n <div class="td-form-container">\r\n <div class="td-form-page-header">\r\n <h1 class="td-form-title">Kết nối với Bác sĩ</h1>\r\n <p class="td-form-desc">Vui lòng nhập thông tin để bắt đầu tư vấn trực tuyến.</p>\r\n </div>\r\n\r\n <form class="td-form" id="td-info-form">\r\n <div class="td-input-group">\r\n <label class="td-label">Họ và tên</label>\r\n <input type="text" class="td-input" name="name" placeholder="VD: Nguyễn Văn A" required />\r\n </div>\r\n\r\n <div class="td-input-group">\r\n <label class="td-label">Số điện thoại</label>\r\n <div style="position: relative;">\r\n <input type="tel" class="td-input" name="phone" placeholder="VD: 0912 345 678" required />\r\n <!-- <div\r\n style="position: absolute; right: 12px; top: 50%; transform: translateY(-50%); color: var(--td-primary);">\r\n <span class="material-symbols-outlined" style="font-size: 20px;">smartphone</span>\r\n </div> -->\r\n </div>\r\n </div>\r\n\r\n <!-- <div class="td-input-group">\r\n <label class="td-label">Triệu chứng của bạn?</label>\r\n <div class="td-chips-group">\r\n <button type="button" class="td-chip active">Sốt <span class="material-symbols-outlined"\r\n style="font-size: 16px;">check</span></button>\r\n <button type="button" class="td-chip">Ho / Cảm cúm</button>\r\n <button type="button" class="td-chip">Đau đầu</button>\r\n <button type="button" class="td-chip">Đau bụng</button>\r\n </div>\r\n </div> -->\r\n\r\n <div class="td-input-group">\r\n <label class="td-label">Mô tả thêm <span style="color: var(--td-text-sub); font-weight: 400;">(Tùy\r\n chọn)</span></label>\r\n <textarea class="td-textarea" name="symptoms"\r\n placeholder="Bạn đang cảm thấy như thế nào?"></textarea>\r\n </div>\r\n\r\n <div style="margin-top: 8px;">\r\n <button type="submit" class="td-btn td-btn-primary" id="td-form-submit">\r\n Bắt đầu tư vấn\r\n <span class="material-symbols-outlined" style="margin-left: 8px;">arrow_forward</span>\r\n </button>\r\n </div>\r\n\r\n <div class="td-secure-note">\r\n <span class="material-symbols-outlined" style="font-size: 14px;">lock</span>\r\n Thông tin y tế được bảo mật 100%\r\n </div>\r\n </form>\r\n </div>\r\n </div>\r\n</div>';
281
281
  const postConsultationHtml = `<div class="td-widget-expanded" id="td-summary-view">\r
282
282
  <header class="td-header">\r
283
283
  <h2 class="td-header-title" style="font-size: 16px;">Tổng kết tư vấn</h2>\r
@@ -433,7 +433,7 @@ const inlineTriggerHtml = `<style>\r
433
433
  <span class="td-inline-text">Bác sĩ đang Online</span>\r
434
434
  </div>\r
435
435
  \r
436
- <button id="td-inline-btn" style="\r
436
+ <button id="td-inline-btn" type="button" style="\r
437
437
  background: #0066ff; \r
438
438
  color: white; \r
439
439
  border: none; \r
@@ -454,6 +454,7 @@ const inlineTriggerHtml = `<style>\r
454
454
  </button>\r
455
455
  </div>\r
456
456
  </div>`;
457
+ const permissionModalHtml = '<div class="modalOverlay" id="td-permission-modal">\r\n <div class="modal">\r\n <div class="modalHead">\r\n <h2>Cho phép truy cập Microphone và Camera</h2>\r\n <button id="td-perm-close" class="btn btn-icon" aria-label="Đóng">\r\n <span class="material-symbols-outlined" style="font-size: 20px;">close</span>\r\n </button>\r\n </div>\r\n <div class="modalBody">\r\n Website sắp yêu cầu quyền <strong>Microphone</strong> và <strong>Camera</strong> để:\r\n <ul>\r\n <li>Gọi video trong phiên tư vấn.</li>\r\n <li>Kiểm tra thiết bị trước khi bắt đầu.</li>\r\n </ul>\r\n <div class="note">\r\n Sau khi bấm “Tiếp tục”, trình duyệt sẽ hiện thông báo hệ thống. Vui lòng chọn <strong>Allow</strong>.\r\n </div>\r\n </div>\r\n <div class="modalFoot">\r\n <button id="td-perm-cancel" class="btn">Hủy</button>\r\n <button id="td-perm-allow" class="btn primary">Tiếp tục</button>\r\n </div>\r\n </div>\r\n</div>\r\n\r\n<style>\r\n #td-permission-modal {\r\n --bg: var(--td-bg-light, #ffffff);\r\n --text: var(--td-text-main, #0d131c);\r\n --muted: var(--td-text-sub, #49699c);\r\n --border: var(--td-border, #e7ecf4);\r\n --primary: var(--td-primary, #0e65f1);\r\n --primary-hover: var(--td-primary-hover, #0b50c0);\r\n --shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);\r\n --radius: 12px;\r\n font-family: var(--font-family, system-ui, -apple-system, sans-serif);\r\n z-index: 2147483647;\r\n }\r\n\r\n /* Modal Overlay */\r\n .modalOverlay {\r\n position: fixed;\r\n inset: 0;\r\n top: 0;\r\n left: 0;\r\n right: 0;\r\n bottom: 0;\r\n background: rgba(0, 0, 0, .5);\r\n backdrop-filter: blur(2px);\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n padding: 18px;\r\n z-index: 2147483647;\r\n }\r\n\r\n .modal {\r\n width: min(500px, 94vw);\r\n border-radius: var(--radius);\r\n border: 1px solid var(--border);\r\n background: var(--bg);\r\n box-shadow: var(--shadow);\r\n overflow: hidden;\r\n color: var(--text);\r\n font-size: 14px;\r\n line-height: 1.5;\r\n }\r\n\r\n .modalHead {\r\n padding: 16px 20px;\r\n border-bottom: 1px solid var(--border);\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n gap: 10px;\r\n background: #f8f9fc;\r\n }\r\n\r\n .modalHead h2 {\r\n margin: 0;\r\n font-size: 16px;\r\n font-weight: 700;\r\n color: var(--text);\r\n }\r\n\r\n .modalBody {\r\n padding: 20px;\r\n color: var(--text);\r\n }\r\n\r\n .modalBody ul {\r\n margin: 12px 0 16px 0;\r\n padding-left: 20px;\r\n color: var(--muted);\r\n }\r\n\r\n .modalBody li {\r\n margin-bottom: 6px;\r\n }\r\n\r\n .modalFoot {\r\n padding: 16px 20px;\r\n display: flex;\r\n gap: 12px;\r\n justify-content: flex-end;\r\n border-top: 1px solid var(--border);\r\n background: #f8f9fc;\r\n }\r\n\r\n .note {\r\n margin-top: 12px;\r\n color: var(--muted);\r\n font-size: 13px;\r\n background: rgba(14, 101, 241, 0.05);\r\n padding: 10px;\r\n border-radius: 8px;\r\n border: 1px dashed var(--primary);\r\n }\r\n\r\n .note strong {\r\n color: var(--primary);\r\n }\r\n\r\n /* Buttons */\r\n .btn {\r\n appearance: none;\r\n border: 1px solid var(--border);\r\n background: white;\r\n color: var(--text);\r\n padding: 10px 16px;\r\n border-radius: 8px;\r\n cursor: pointer;\r\n transition: all 0.2s;\r\n font-weight: 600;\r\n font-family: inherit;\r\n font-size: 14px;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n }\r\n\r\n .btn:hover {\r\n background: #f1f5f9;\r\n border-color: #cbd5e1;\r\n }\r\n\r\n .btn-icon {\r\n padding: 4px;\r\n border: none;\r\n background: transparent;\r\n color: #64748b;\r\n border-radius: 50%;\r\n }\r\n\r\n .btn-icon:hover {\r\n background: rgba(0, 0, 0, 0.05);\r\n border-color: transparent;\r\n color: var(--text);\r\n }\r\n\r\n .btn.primary {\r\n background: var(--primary);\r\n border-color: var(--primary);\r\n color: white;\r\n }\r\n\r\n .btn.primary:hover {\r\n background: var(--primary-hover);\r\n border-color: var(--primary-hover);\r\n }\r\n</style>';
457
458
  const render = (template, data) => {
458
459
  var _a, _b, _c;
459
460
  let output = template;
@@ -471,6 +472,7 @@ const getConsultationHtml = (data) => render(consultationHtml, data);
471
472
  const getPatientFormHtml = (data) => render(patientFormHtml, data);
472
473
  const getPostConsultationHtml = (data) => render(postConsultationHtml, data);
473
474
  const getInlineTriggerHtml = (data) => render(inlineTriggerHtml, data);
475
+ const getPermissionModalHtml = (data) => render(permissionModalHtml, data);
474
476
  const PACKET_TYPES = /* @__PURE__ */ Object.create(null);
475
477
  PACKET_TYPES["open"] = "0";
476
478
  PACKET_TYPES["close"] = "1";
@@ -28017,6 +28019,41 @@ class LiveKitService {
28017
28019
  constructor() {
28018
28020
  this.room = null;
28019
28021
  this.wrapper = null;
28022
+ this.preAcquiredTracks = [];
28023
+ }
28024
+ /**
28025
+ * Request Camera & Mic permissions early
28026
+ */
28027
+ async requestPermissions() {
28028
+ try {
28029
+ console.log("[LiveKit] Requesting early permissions...");
28030
+ const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
28031
+ this.preAcquiredTracks = stream.getTracks();
28032
+ console.log("[LiveKit] Permissions granted, tracks cached:", this.preAcquiredTracks.length);
28033
+ return true;
28034
+ } catch (error) {
28035
+ console.error("[LiveKit] Permission request failed:", error);
28036
+ console.log(`Không thể xin quyền: ${error.name} - ${error.message}`);
28037
+ return false;
28038
+ }
28039
+ }
28040
+ /**
28041
+ * Check if permissions are already granted
28042
+ * @returns {Promise<boolean>}
28043
+ */
28044
+ async checkPermissions() {
28045
+ if (!navigator.permissions || !navigator.permissions.query) {
28046
+ return false;
28047
+ }
28048
+ try {
28049
+ const cam = await navigator.permissions.query({ name: "camera" });
28050
+ const mic = await navigator.permissions.query({ name: "microphone" });
28051
+ console.log("[LiveKit] Permissions check:", { cam: cam.state, mic: mic.state });
28052
+ return cam.state === "granted" && mic.state === "granted";
28053
+ } catch (error) {
28054
+ console.warn("[LiveKit] Permission check not supported or failed:", error);
28055
+ return false;
28056
+ }
28020
28057
  }
28021
28058
  /**
28022
28059
  * Connect to LiveKit Room
@@ -28064,8 +28101,23 @@ class LiveKitService {
28064
28101
  async publishLocalTracks() {
28065
28102
  const localContainer = this.wrapper.querySelector(".td-pip");
28066
28103
  try {
28067
- await this.room.localParticipant.setCameraEnabled(true);
28068
- const videoPub = Array.from(this.room.localParticipant.videoTrackPublications.values()).find((p) => p.source === "camera");
28104
+ let videoPub = null;
28105
+ if (this.preAcquiredTracks && this.preAcquiredTracks.length > 0) {
28106
+ console.log("[LiveKit] Publishing pre-acquired tracks...");
28107
+ const videoTrack = this.preAcquiredTracks.find((t) => t.kind === "video");
28108
+ const audioTrack = this.preAcquiredTracks.find((t) => t.kind === "audio");
28109
+ if (videoTrack) {
28110
+ videoPub = await this.room.localParticipant.publishTrack(videoTrack, { source: "camera" });
28111
+ }
28112
+ if (audioTrack) {
28113
+ await this.room.localParticipant.publishTrack(audioTrack, { source: "microphone" });
28114
+ }
28115
+ this.preAcquiredTracks = [];
28116
+ } else {
28117
+ await this.room.localParticipant.setCameraEnabled(true);
28118
+ await this.room.localParticipant.setMicrophoneEnabled(true);
28119
+ videoPub = Array.from(this.room.localParticipant.videoTrackPublications.values()).find((p) => p.source === "camera");
28120
+ }
28069
28121
  if (videoPub && videoPub.track && localContainer) {
28070
28122
  localContainer.innerHTML = "";
28071
28123
  localContainer.style.backgroundImage = "none";
@@ -28076,24 +28128,15 @@ class LiveKitService {
28076
28128
  videoEl.style.transform = "scale(-1, 1)";
28077
28129
  localContainer.appendChild(videoEl);
28078
28130
  console.log("[LiveKit] Local video attached");
28131
+ } else {
28132
+ console.warn("[LiveKit] No Video Publication found to attach!");
28079
28133
  }
28080
28134
  } catch (e2) {
28081
- console.error("[LiveKit] Failed to enable Camera:", e2);
28082
- if (e2.name === "NotAllowedError") {
28083
- alert("Không thể mở Camera. Hãy kiểm tra icon ổ khóa trên thanh địa chỉ hoặc cài đặt Quyền Riêng Tư (Privacy) của máy tính.");
28084
- }
28085
- }
28086
- try {
28087
- const devices = await Room.getLocalDevices("audioinput");
28088
- console.log("[LiveKit] Audio Input Devices:", devices);
28089
- if (devices.length === 0) {
28090
- alert("Không tìm thấy Micro nào trên thiết bị của bạn!");
28091
- }
28092
- await this.room.localParticipant.setMicrophoneEnabled(true);
28093
- } catch (e2) {
28094
- console.error("[LiveKit] Failed to enable Microphone:", e2);
28135
+ console.error("[LiveKit] Failed to publish tracks:", e2);
28095
28136
  if (e2.name === "NotAllowedError") {
28096
- alert("Không thể mở Micro. Cuộc gọi sẽ không tiếng.");
28137
+ alert("Không thể mở Camera/Microphone. Hãy kiểm tra icon khóa trên thanh địa chỉ hoặc cài đặt quyền riêng tư.");
28138
+ } else {
28139
+ alert(`Lỗi Camera: ${e2.message}`);
28097
28140
  }
28098
28141
  }
28099
28142
  }
@@ -28262,7 +28305,9 @@ class ClinicWidget {
28262
28305
  }
28263
28306
  if (consultBtn) {
28264
28307
  consultBtn.addEventListener("click", () => {
28265
- store.setState({ status: WidgetStates.PATIENT_FORM });
28308
+ consultBtn.addEventListener("click", () => {
28309
+ store.setState({ status: WidgetStates.PATIENT_FORM });
28310
+ });
28266
28311
  });
28267
28312
  }
28268
28313
  } else if (status === WidgetStates.PATIENT_FORM) {
@@ -28274,16 +28319,16 @@ class ClinicWidget {
28274
28319
  });
28275
28320
  }
28276
28321
  if (form) {
28277
- form.addEventListener("submit", (e2) => {
28322
+ form.addEventListener("submit", async (e2) => {
28278
28323
  e2.preventDefault();
28279
28324
  const formData = new FormData(form);
28280
28325
  const name = formData.get("name");
28281
28326
  const phone = formData.get("phone");
28282
- this.triggerCallback("onConsultationStarted", { name, phone });
28283
28327
  store.setState({
28284
- user: { name, phone },
28285
- status: WidgetStates.CONSULTATION
28328
+ user: { name, phone }
28286
28329
  });
28330
+ this.triggerCallback("onConsultationStarted", { name, phone });
28331
+ store.setState({ status: WidgetStates.CONSULTATION });
28287
28332
  });
28288
28333
  }
28289
28334
  } else if (status === WidgetStates.CONSULTATION) {
@@ -28291,7 +28336,9 @@ class ClinicWidget {
28291
28336
  const endBtn = this.shadowRoot.getElementById("td-consult-end");
28292
28337
  if (endBtn) {
28293
28338
  endBtn.addEventListener("click", () => {
28294
- this.stopSocket();
28339
+ if (this.livekit) {
28340
+ this.livekit.disconnect();
28341
+ }
28295
28342
  store.setState({ status: WidgetStates.COMPLETED });
28296
28343
  });
28297
28344
  }
@@ -28353,7 +28400,9 @@ class ClinicWidget {
28353
28400
  const btn = el.querySelector("#td-inline-btn");
28354
28401
  if (btn) {
28355
28402
  btn.addEventListener("click", () => {
28356
- store.setState({ status: WidgetStates.PATIENT_FORM });
28403
+ btn.addEventListener("click", () => {
28404
+ store.setState({ status: WidgetStates.PATIENT_FORM });
28405
+ });
28357
28406
  });
28358
28407
  }
28359
28408
  } else {
@@ -28394,18 +28443,26 @@ class ClinicWidget {
28394
28443
  store.setState({ doctorOnline: isOnline });
28395
28444
  }
28396
28445
  });
28397
- this.socket.on("customer:token", (data) => {
28446
+ this.socket.on("customer:token", async (data) => {
28398
28447
  console.log("Received token from server:", data);
28399
28448
  if (data.token) {
28400
28449
  if (!this.livekit) {
28401
28450
  this.livekit = new LiveKitService();
28402
28451
  }
28403
- const modal = this.shadowRoot.querySelector(".td-consultation-modal");
28404
- if (modal) {
28405
- console.log("[Widget] LiveKit Params:", { url: CONFIG.LIVEKIT_URL, token: data.token });
28406
- this.livekit.connect(CONFIG.LIVEKIT_URL, data.token, modal);
28452
+ const connectLiveKit = () => {
28453
+ const modal = this.shadowRoot.querySelector(".td-consultation-modal");
28454
+ if (modal) {
28455
+ console.log("[Widget] LiveKit Params:", { url: CONFIG.LIVEKIT_URL, token: data.token });
28456
+ this.livekit.connect(CONFIG.LIVEKIT_URL, data.token, modal);
28457
+ } else {
28458
+ console.error("Consultation modal not found for video rendering");
28459
+ }
28460
+ };
28461
+ const granted = await this.livekit.checkPermissions();
28462
+ if (granted) {
28463
+ connectLiveKit();
28407
28464
  } else {
28408
- console.error("Consultation modal not found for video rendering");
28465
+ this.showPermissionModal(connectLiveKit);
28409
28466
  }
28410
28467
  }
28411
28468
  });
@@ -28454,6 +28511,39 @@ class ClinicWidget {
28454
28511
  this.socket = null;
28455
28512
  }
28456
28513
  }
28514
+ showPermissionModal(onSuccessCallback) {
28515
+ if (this.shadowRoot.getElementById("td-permission-modal")) return;
28516
+ const state = store.getState();
28517
+ const div = document.createElement("div");
28518
+ div.innerHTML = getPermissionModalHtml(state);
28519
+ Array.from(div.children).forEach((child) => {
28520
+ this.shadowRoot.appendChild(child);
28521
+ });
28522
+ const modalEl = this.shadowRoot.getElementById("td-permission-modal");
28523
+ const closeBtn = modalEl.querySelector("#td-perm-close");
28524
+ const cancelBtn = modalEl.querySelector("#td-perm-cancel");
28525
+ const allowBtn = modalEl.querySelector("#td-perm-allow");
28526
+ const closeModal = () => {
28527
+ modalEl.remove();
28528
+ };
28529
+ const onAllow = async () => {
28530
+ closeModal();
28531
+ if (!this.livekit) {
28532
+ this.livekit = new LiveKitService();
28533
+ }
28534
+ const ok = await this.livekit.requestPermissions();
28535
+ if (ok) {
28536
+ if (typeof onSuccessCallback === "function") {
28537
+ onSuccessCallback();
28538
+ }
28539
+ } else {
28540
+ console.warn("User denied permissions via browser prompt");
28541
+ }
28542
+ };
28543
+ if (closeBtn) closeBtn.addEventListener("click", closeModal);
28544
+ if (cancelBtn) cancelBtn.addEventListener("click", closeModal);
28545
+ if (allowBtn) allowBtn.addEventListener("click", onAllow);
28546
+ }
28457
28547
  }
28458
28548
  (function() {
28459
28549
  const currentScript = document.querySelector("script[data-widget-id]");