@eka-care/medassist-widget-embed 0.2.7 → 0.2.9

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.
package/dist/iframe.html CHANGED
@@ -21,17 +21,37 @@
21
21
  position: fixed;
22
22
  top: 0;
23
23
  left: 0;
24
+ background-color: #1a1a1a;
24
25
  }
25
26
  #root {
26
27
  width: 100vw;
27
28
  height: 100vh;
28
29
  height: 100dvh; /* Dynamic viewport height for mobile */
29
30
  position: relative;
31
+ display: flex;
32
+ align-items: center;
33
+ justify-content: center;
34
+ background-color: #1a1a1a;
35
+ }
36
+ .loader {
37
+ width: 48px;
38
+ height: 48px;
39
+ border: 3px solid rgba(255, 255, 255, 0.1);
40
+ border-top-color: #09FBD3;
41
+ border-radius: 50%;
42
+ animation: loader-spin 0.8s linear infinite;
43
+ }
44
+ @keyframes loader-spin {
45
+ to {
46
+ transform: rotate(360deg);
47
+ }
30
48
  }
31
49
  </style>
32
50
  </head>
33
51
  <body>
34
- <div id="root"></div>
52
+ <div id="root">
53
+ <div class="loader" aria-hidden="true"></div>
54
+ </div>
35
55
  <!-- for local development -->
36
56
  <!-- <script src="./iframe.js" data-widget-assets="local" async></script> -->
37
57
 
package/dist/iframe.js CHANGED
@@ -40,6 +40,22 @@
40
40
  ...(apiTheme.title_img && { titleImg: apiTheme.title_img }),
41
41
  };
42
42
  }
43
+ /** Preload an image by URL so it is cached and loads faster when the widget uses it. */
44
+ function preloadImage(url) {
45
+ if (!url || typeof document === "undefined")
46
+ return;
47
+ try {
48
+ const link = document.createElement("link");
49
+ link.rel = "preload";
50
+ link.as = "image";
51
+ link.href = url;
52
+ document.head.appendChild(link);
53
+ }
54
+ catch {
55
+ const img = new Image();
56
+ img.src = url;
57
+ }
58
+ }
43
59
  /** Fetch agent-config from API with timeout; aborts if pending > 10s */
44
60
  async function fetchAgentConfig(baseUrl, agentId) {
45
61
  var _a;
@@ -142,6 +158,26 @@
142
158
  const agentConfigPromise = earlyAgentId && earlyBaseUrl
143
159
  ? fetchAgentConfig(earlyBaseUrl, earlyAgentId)
144
160
  : Promise.resolve(undefined);
161
+ // Preload background image when available (theme URL param or agent-config)
162
+ if (typeof document !== "undefined" && document.head) {
163
+ const themeParam = urlParams.get("theme");
164
+ if (themeParam) {
165
+ try {
166
+ const theme = JSON.parse(themeParam);
167
+ if (theme.backgroundImage)
168
+ preloadImage(theme.backgroundImage);
169
+ }
170
+ catch {
171
+ // ignore
172
+ }
173
+ }
174
+ agentConfigPromise.then((agentConfig) => {
175
+ var _a;
176
+ const url = (_a = agentConfig === null || agentConfig === void 0 ? void 0 : agentConfig.theme) === null || _a === void 0 ? void 0 : _a.background_img;
177
+ if (url)
178
+ preloadImage(url);
179
+ });
180
+ }
145
181
  // Auto-initialize from URL parameters (no shadow DOM needed in iframe)
146
182
  const initializeFromUrlParams = async () => {
147
183
  var _a, _b, _c, _d, _e;
package/dist/index.d.ts CHANGED
@@ -34,6 +34,8 @@ type MedAssistInitConfig = {
34
34
  };
35
35
  /** Map agent-config API theme to widget theme (mode → textColor: dark→black, light→white) */
36
36
  declare function mapAgentConfigThemeToWidgetTheme(apiTheme: AgentConfig["theme"] | undefined): MedAssistInitConfig["theme"];
37
+ /** Preload an image by URL so it is cached and loads faster when the widget uses it. */
38
+ declare function preloadImage(url: string): void;
37
39
  /** Fetch agent-config from API and return theme from response data */
38
40
  declare function fetchAgentConfig(baseUrl: string, agentId: string): Promise<AgentConfig | undefined>;
39
41
  interface EkaMedAssistWindow extends Window {
@@ -51,6 +53,8 @@ declare const WIDGET_JS_URL: string;
51
53
  declare const WIDGET_CSS_URL: string;
52
54
  declare let widgetScriptPromise: Promise<void> | null;
53
55
  declare let widgetCssTextPromise: Promise<string> | null;
56
+ /** Start fetching widget CSS (shared promise). Used for preload and by loadWidgetCss. */
57
+ declare function getWidgetCssTextPromise(): Promise<string>;
54
58
  declare class MedAssistWidgetLoader extends HTMLElement {
55
59
  private defaultIconUrl;
56
60
  private widgetLoaded;
@@ -59,6 +63,10 @@ declare class MedAssistWidgetLoader extends HTMLElement {
59
63
  static get observedAttributes(): string[];
60
64
  connectedCallback(): void;
61
65
  attributeChangedCallback(name: string): void;
66
+ /** When in widget mode, start loading JS and CSS on page load so first open is faster. */
67
+ private preloadWidgetAssets;
68
+ /** Preload theme background image from init config, theme attribute, or agent-config API. */
69
+ private preloadBackgroundImage;
62
70
  private setupAuthExpirationListener;
63
71
  openFromBridge(): void;
64
72
  renderButton(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,QAAA,MAAM,gBAAgB,QAAO,iBAAiB,GAAG,IAahD,CAAC;AAEF,KAAK,iBAAiB,GAAG,YAAY,GAAG,aAAa,GAAG,SAAS,CAAC;AAElE,QAAA,MAAM,cAAc,GAClB,OAAO,MAAM,GAAG,IAAI,KACnB,iBAAiB,GAAG,SAStB,CAAC;AAEF,QAAA,MAAM,QAAQ,0BAAqB,CAAC;AAGpC,QAAA,MAAM,yBAAyB,QAK3B,CAAC;AAEL,6FAA6F;AAC7F,KAAK,WAAW,GAAG;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,EAAE,CAAC;IACxC,KAAK,CAAC,EAAE;QACN,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;QACxB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;KAClB,CAAA;CACF,CAAC;AAEF,KAAK,mBAAmB,GAAG;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,EAAE,CAAC;IACxC,KAAK,CAAC,EAAE;QACN,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,SAAS,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;KAC/B,CAAC;IACF,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,CAAC;AAEF,6FAA6F;AAC7F,iBAAS,gCAAgC,CAAC,QAAQ,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,SAAS,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAUlH;AAED,wFAAwF;AACxF,iBAAS,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAavC;AAED,sEAAsE;AACtE,iBAAe,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAYlG;AAED,UAAU,kBAAmB,SAAQ,MAAM;IACzC,YAAY,CAAC,EAAE;QACb,IAAI,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;QAC5C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;KACtB,CAAC;IACF,sBAAsB,CAAC,EAAE,mBAAmB,CAAC;CAC9C;AAED,QAAA,MAAM,qBAAqB,EAAE,mBAAwB,CAAC;AAEtD,QAAA,MAAM,gBAAgB,QAAO,WAAW,GAAG,IAK1C,CAAC;AAiCF,QAAA,MAAM,aAAa,QASf,CAAC;AAEL,QAAA,MAAM,kBAAkB,QAmBpB,CAAC;AAEL,QAAA,MAAM,aAAa,QAA6C,CAAC;AACjE,QAAA,MAAM,cAAc,QAA8C,CAAC;AAEnE,QAAA,IAAI,mBAAmB,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,IAAW,CAAC;AACrD,QAAA,IAAI,oBAAoB,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,IAAW,CAAC;AAExD,yFAAyF;AACzF,iBAAS,uBAAuB,IAAI,OAAO,CAAC,MAAM,CAAC,CAYlD;AAED,cAAM,qBAAsB,SAAQ,WAAW;IAC7C,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,YAAY,CAAU;IAC9B,OAAO,CAAC,WAAW,CAAoB;;IAavC,MAAM,KAAK,kBAAkB,IAAI,MAAM,EAAE,CAExC;IAED,iBAAiB,IAAI,IAAI;IASzB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAmB5C,0FAA0F;IAC1F,OAAO,CAAC,mBAAmB;IAM3B,6FAA6F;IAC7F,OAAO,CAAC,sBAAsB;IAoC9B,OAAO,CAAC,2BAA2B;IAe5B,cAAc,IAAI,IAAI;IAgB7B,YAAY,IAAI,IAAI;IAiEpB,kBAAkB,IAAI,IAAI;IA6BpB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IA6G9B,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBpC,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;CA0ClC"}
package/dist/index.js CHANGED
@@ -41,6 +41,23 @@ function mapAgentConfigThemeToWidgetTheme(apiTheme) {
41
41
  ...(apiTheme.title_img && { titleImg: apiTheme.title_img }),
42
42
  };
43
43
  }
44
+ /** Preload an image by URL so it is cached and loads faster when the widget uses it. */
45
+ function preloadImage(url) {
46
+ if (!url || typeof document === "undefined")
47
+ return;
48
+ try {
49
+ const link = document.createElement("link");
50
+ link.rel = "preload";
51
+ link.as = "image";
52
+ link.href = url;
53
+ document.head.appendChild(link);
54
+ }
55
+ catch {
56
+ // fallback: use Image to prime cache
57
+ const img = new Image();
58
+ img.src = url;
59
+ }
60
+ }
44
61
  /** Fetch agent-config from API and return theme from response data */
45
62
  async function fetchAgentConfig(baseUrl, agentId) {
46
63
  const url = `${baseUrl.replace(/\/$/, "")}/med-assist/agent-config/${agentId}`;
@@ -68,7 +85,7 @@ if (typeof window !== "undefined") {
68
85
  w.__ekaMedAssistConfig__ = globalMedAssistConfig;
69
86
  w.EkaMedAssist = {
70
87
  init(config) {
71
- var _a, _b;
88
+ var _a, _b, _c;
72
89
  Object.assign(globalMedAssistConfig, config);
73
90
  const el = getWidgetElement();
74
91
  if (!el)
@@ -89,7 +106,10 @@ if (typeof window !== "undefined") {
89
106
  console.warn("Failed to stringify context passed to init");
90
107
  }
91
108
  }
92
- (_b = (_a = el).openFromBridge) === null || _b === void 0 ? void 0 : _b.call(_a);
109
+ if ((_a = config.theme) === null || _a === void 0 ? void 0 : _a.backgroundImage) {
110
+ preloadImage(config.theme.backgroundImage);
111
+ }
112
+ (_c = (_b = el).openFromBridge) === null || _c === void 0 ? void 0 : _c.call(_b);
93
113
  },
94
114
  };
95
115
  }
@@ -126,6 +146,18 @@ const WIDGET_JS_URL = `${widgetAssetBaseUrl}medassist-widget.js`;
126
146
  const WIDGET_CSS_URL = `${widgetAssetBaseUrl}medassist-widget.css`;
127
147
  let widgetScriptPromise = null;
128
148
  let widgetCssTextPromise = null;
149
+ /** Start fetching widget CSS (shared promise). Used for preload and by loadWidgetCss. */
150
+ function getWidgetCssTextPromise() {
151
+ if (!widgetCssTextPromise) {
152
+ widgetCssTextPromise = fetch(WIDGET_CSS_URL).then((response) => {
153
+ if (!response.ok) {
154
+ throw new Error(`Unable to fetch widget styles from ${WIDGET_CSS_URL}`);
155
+ }
156
+ return response.text();
157
+ });
158
+ }
159
+ return widgetCssTextPromise;
160
+ }
129
161
  class MedAssistWidgetLoader extends HTMLElement {
130
162
  constructor() {
131
163
  super();
@@ -145,6 +177,7 @@ class MedAssistWidgetLoader extends HTMLElement {
145
177
  }
146
178
  else {
147
179
  this.renderButton();
180
+ this.preloadWidgetAssets();
148
181
  }
149
182
  }
150
183
  attributeChangedCallback(name) {
@@ -166,6 +199,49 @@ class MedAssistWidgetLoader extends HTMLElement {
166
199
  }
167
200
  }
168
201
  }
202
+ /** When in widget mode, start loading JS and CSS on page load so first open is faster. */
203
+ preloadWidgetAssets() {
204
+ getWidgetCssTextPromise(); // start CSS fetch (no inject until open)
205
+ void this.loadWidgetScript(); // start JS load
206
+ void this.preloadBackgroundImage();
207
+ }
208
+ /** Preload theme background image from init config, theme attribute, or agent-config API. */
209
+ preloadBackgroundImage() {
210
+ var _a;
211
+ const initConfig = typeof window !== "undefined"
212
+ ? window.__ekaMedAssistConfig__
213
+ : undefined;
214
+ const attributeTheme = this.getAttribute("theme")
215
+ ? (() => {
216
+ try {
217
+ return JSON.parse(this.getAttribute("theme") || "{}");
218
+ }
219
+ catch {
220
+ return undefined;
221
+ }
222
+ })()
223
+ : undefined;
224
+ const fromInit = (_a = initConfig === null || initConfig === void 0 ? void 0 : initConfig.theme) === null || _a === void 0 ? void 0 : _a.backgroundImage;
225
+ const fromAttr = attributeTheme === null || attributeTheme === void 0 ? void 0 : attributeTheme.backgroundImage;
226
+ if (fromInit)
227
+ preloadImage(fromInit);
228
+ if (fromAttr && fromAttr !== fromInit)
229
+ preloadImage(fromAttr);
230
+ const baseUrl = (initConfig === null || initConfig === void 0 ? void 0 : initConfig.baseUrl) || this.getAttribute("base-url") || "";
231
+ const agentId = (initConfig === null || initConfig === void 0 ? void 0 : initConfig.agentId) || this.getAttribute("agent-id") || "";
232
+ if (baseUrl && agentId) {
233
+ fetchAgentConfig(baseUrl, agentId)
234
+ .then((agentConfig) => {
235
+ var _a;
236
+ const url = (_a = agentConfig === null || agentConfig === void 0 ? void 0 : agentConfig.theme) === null || _a === void 0 ? void 0 : _a.background_img;
237
+ if (url)
238
+ preloadImage(url);
239
+ })
240
+ .catch(() => {
241
+ // ignore; widget will load config when it opens
242
+ });
243
+ }
244
+ }
169
245
  setupAuthExpirationListener() {
170
246
  if (typeof window === "undefined")
171
247
  return;
@@ -391,15 +467,7 @@ class MedAssistWidgetLoader extends HTMLElement {
391
467
  if (shadowRoot.querySelector("[data-medassist-style='true']")) {
392
468
  return;
393
469
  }
394
- if (!widgetCssTextPromise) {
395
- widgetCssTextPromise = fetch(WIDGET_CSS_URL).then((response) => {
396
- if (!response.ok) {
397
- throw new Error(`Unable to fetch widget styles from ${WIDGET_CSS_URL}`);
398
- }
399
- return response.text();
400
- });
401
- }
402
- const cssText = await widgetCssTextPromise;
470
+ const cssText = await getWidgetCssTextPromise();
403
471
  const styleTag = document.createElement("style");
404
472
  styleTag.setAttribute("data-medassist-style", "true");
405
473
  styleTag.textContent = cssText;
package/iframe.html CHANGED
@@ -21,17 +21,37 @@
21
21
  position: fixed;
22
22
  top: 0;
23
23
  left: 0;
24
+ background-color: #1a1a1a;
24
25
  }
25
26
  #root {
26
27
  width: 100vw;
27
28
  height: 100vh;
28
29
  height: 100dvh; /* Dynamic viewport height for mobile */
29
30
  position: relative;
31
+ display: flex;
32
+ align-items: center;
33
+ justify-content: center;
34
+ background-color: #1a1a1a;
35
+ }
36
+ .loader {
37
+ width: 48px;
38
+ height: 48px;
39
+ border: 3px solid rgba(255, 255, 255, 0.1);
40
+ border-top-color: #09FBD3;
41
+ border-radius: 50%;
42
+ animation: loader-spin 0.8s linear infinite;
43
+ }
44
+ @keyframes loader-spin {
45
+ to {
46
+ transform: rotate(360deg);
47
+ }
30
48
  }
31
49
  </style>
32
50
  </head>
33
51
  <body>
34
- <div id="root"></div>
52
+ <div id="root">
53
+ <div class="loader" aria-hidden="true"></div>
54
+ </div>
35
55
  <!-- for local development -->
36
56
  <!-- <script src="./iframe.js" data-widget-assets="local" async></script> -->
37
57
 
package/iframe.ts CHANGED
@@ -74,6 +74,21 @@
74
74
  };
75
75
  }
76
76
 
77
+ /** Preload an image by URL so it is cached and loads faster when the widget uses it. */
78
+ function preloadImage(url: string): void {
79
+ if (!url || typeof document === "undefined") return;
80
+ try {
81
+ const link = document.createElement("link");
82
+ link.rel = "preload";
83
+ link.as = "image";
84
+ link.href = url;
85
+ document.head.appendChild(link);
86
+ } catch {
87
+ const img = new Image();
88
+ img.src = url;
89
+ }
90
+ }
91
+
77
92
  /** Fetch agent-config from API with timeout; aborts if pending > 10s */
78
93
  async function fetchAgentConfig(
79
94
  baseUrl: string,
@@ -188,6 +203,23 @@
188
203
  ? fetchAgentConfig(earlyBaseUrl, earlyAgentId)
189
204
  : Promise.resolve(undefined);
190
205
 
206
+ // Preload background image when available (theme URL param or agent-config)
207
+ if (typeof document !== "undefined" && document.head) {
208
+ const themeParam = urlParams.get("theme");
209
+ if (themeParam) {
210
+ try {
211
+ const theme = JSON.parse(themeParam) as WidgetTheme;
212
+ if (theme.backgroundImage) preloadImage(theme.backgroundImage);
213
+ } catch {
214
+ // ignore
215
+ }
216
+ }
217
+ agentConfigPromise.then((agentConfig) => {
218
+ const url = agentConfig?.theme?.background_img;
219
+ if (url) preloadImage(url);
220
+ });
221
+ }
222
+
191
223
  // Auto-initialize from URL parameters (no shadow DOM needed in iframe)
192
224
  const initializeFromUrlParams = async (): Promise<void> => {
193
225
  const agentId = urlParams.get("agentId");
package/index.ts CHANGED
@@ -82,6 +82,22 @@ function mapAgentConfigThemeToWidgetTheme(apiTheme: AgentConfig["theme"] | undef
82
82
  };
83
83
  }
84
84
 
85
+ /** Preload an image by URL so it is cached and loads faster when the widget uses it. */
86
+ function preloadImage(url: string): void {
87
+ if (!url || typeof document === "undefined") return;
88
+ try {
89
+ const link = document.createElement("link");
90
+ link.rel = "preload";
91
+ link.as = "image";
92
+ link.href = url;
93
+ document.head.appendChild(link);
94
+ } catch {
95
+ // fallback: use Image to prime cache
96
+ const img = new Image();
97
+ img.src = url;
98
+ }
99
+ }
100
+
85
101
  /** Fetch agent-config from API and return theme from response data */
86
102
  async function fetchAgentConfig(baseUrl: string, agentId: string): Promise<AgentConfig | undefined> {
87
103
  const url = `${baseUrl.replace(/\/$/, "")}/med-assist/agent-config/${agentId}`;
@@ -136,6 +152,10 @@ if (typeof window !== "undefined") {
136
152
  }
137
153
  }
138
154
 
155
+ if (config.theme?.backgroundImage) {
156
+ preloadImage(config.theme.backgroundImage);
157
+ }
158
+
139
159
  (el as any).openFromBridge?.();
140
160
  },
141
161
  };
@@ -179,6 +199,21 @@ const WIDGET_CSS_URL = `${widgetAssetBaseUrl}medassist-widget.css`;
179
199
  let widgetScriptPromise: Promise<void> | null = null;
180
200
  let widgetCssTextPromise: Promise<string> | null = null;
181
201
 
202
+ /** Start fetching widget CSS (shared promise). Used for preload and by loadWidgetCss. */
203
+ function getWidgetCssTextPromise(): Promise<string> {
204
+ if (!widgetCssTextPromise) {
205
+ widgetCssTextPromise = fetch(WIDGET_CSS_URL).then((response) => {
206
+ if (!response.ok) {
207
+ throw new Error(
208
+ `Unable to fetch widget styles from ${WIDGET_CSS_URL}`
209
+ );
210
+ }
211
+ return response.text();
212
+ });
213
+ }
214
+ return widgetCssTextPromise;
215
+ }
216
+
182
217
  class MedAssistWidgetLoader extends HTMLElement {
183
218
  private defaultIconUrl: string;
184
219
  private widgetLoaded: boolean;
@@ -204,6 +239,7 @@ class MedAssistWidgetLoader extends HTMLElement {
204
239
  this.initializeFullMode();
205
240
  } else {
206
241
  this.renderButton();
242
+ this.preloadWidgetAssets();
207
243
  }
208
244
  }
209
245
 
@@ -226,6 +262,50 @@ class MedAssistWidgetLoader extends HTMLElement {
226
262
  }
227
263
  }
228
264
 
265
+ /** When in widget mode, start loading JS and CSS on page load so first open is faster. */
266
+ private preloadWidgetAssets(): void {
267
+ getWidgetCssTextPromise(); // start CSS fetch (no inject until open)
268
+ void this.loadWidgetScript(); // start JS load
269
+ void this.preloadBackgroundImage();
270
+ }
271
+
272
+ /** Preload theme background image from init config, theme attribute, or agent-config API. */
273
+ private preloadBackgroundImage(): void {
274
+ const initConfig =
275
+ typeof window !== "undefined"
276
+ ? (window as EkaMedAssistWindow).__ekaMedAssistConfig__
277
+ : undefined;
278
+ const attributeTheme = this.getAttribute("theme")
279
+ ? (() => {
280
+ try {
281
+ return JSON.parse(this.getAttribute("theme") || "{}") as { backgroundImage?: string };
282
+ } catch {
283
+ return undefined;
284
+ }
285
+ })()
286
+ : undefined;
287
+
288
+ const fromInit = initConfig?.theme?.backgroundImage;
289
+ const fromAttr = attributeTheme?.backgroundImage;
290
+ if (fromInit) preloadImage(fromInit);
291
+ if (fromAttr && fromAttr !== fromInit) preloadImage(fromAttr);
292
+
293
+ const baseUrl =
294
+ (initConfig?.baseUrl as string) || this.getAttribute("base-url") || "";
295
+ const agentId =
296
+ (initConfig?.agentId as string) || this.getAttribute("agent-id") || "";
297
+ if (baseUrl && agentId) {
298
+ fetchAgentConfig(baseUrl, agentId)
299
+ .then((agentConfig) => {
300
+ const url = agentConfig?.theme?.background_img;
301
+ if (url) preloadImage(url);
302
+ })
303
+ .catch(() => {
304
+ // ignore; widget will load config when it opens
305
+ });
306
+ }
307
+ }
308
+
229
309
  private setupAuthExpirationListener(): void {
230
310
  if (typeof window === "undefined") return;
231
311
 
@@ -470,18 +550,7 @@ class MedAssistWidgetLoader extends HTMLElement {
470
550
  return;
471
551
  }
472
552
 
473
- if (!widgetCssTextPromise) {
474
- widgetCssTextPromise = fetch(WIDGET_CSS_URL).then((response) => {
475
- if (!response.ok) {
476
- throw new Error(
477
- `Unable to fetch widget styles from ${WIDGET_CSS_URL}`
478
- );
479
- }
480
- return response.text();
481
- });
482
- }
483
-
484
- const cssText = await widgetCssTextPromise;
553
+ const cssText = await getWidgetCssTextPromise();
485
554
  const styleTag = document.createElement("style");
486
555
  styleTag.setAttribute("data-medassist-style", "true");
487
556
  styleTag.textContent = cssText;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eka-care/medassist-widget-embed",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "description": "Embeddable MedAssist widget loader built with Web Components.",
5
5
  "author": "Geethanjali S",
6
6
  "license": "MIT",
package/test.html CHANGED
@@ -30,7 +30,6 @@
30
30
  agent-id="MmE3Y2Y0NDctNTM4Mi00MDUzLWFmODgtZDRiOWM2NmMzMDBlIzcxNzY5NjA3ODkzNjIyNzg="
31
31
  icon-url="https://cdn.eka.care/bot-icon.svg" //add your own icon url here
32
32
  title="Moolchand Assist"
33
- environment="development"
34
33
  base-url="https://e3b3-125-17-125-117.ngrok-free.app/reloaded"
35
34
  </eka-medassist-widget>
36
35
  <!-- <script>