embeddedaichatux 2.0.1 → 2.2.0

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.
Files changed (2) hide show
  1. package/EmbeddedChat.js +270 -135
  2. package/package.json +1 -1
package/EmbeddedChat.js CHANGED
@@ -1,5 +1,10 @@
1
1
  class EmbeddedChat {
2
+ static instanceCounter = 0;
3
+
2
4
  constructor(containerDiv, chatId, options) {
5
+ EmbeddedChat.instanceCounter++;
6
+ this.instanceId = `ec_${EmbeddedChat.instanceCounter}_${Date.now()}`;
7
+
3
8
  if (!containerDiv) {
4
9
  containerDiv = this.createChatContainer();
5
10
  }
@@ -14,7 +19,8 @@
14
19
  this.width = options.width || 320; // Default width
15
20
  this.marginBottom = options.marginBottom || 0;
16
21
  this.enableAnimation = options.enableAnimation !== false; // Default to true if not provided
17
- this.minimizedHeight = `${this.height * 0.3}px`; // 30% of the full height
22
+ this.minimizedHeight = "180px"; // Sensible default instead of 30% calculation
23
+ this.hasMeasuredSize = false;
18
24
  this.mouseInsideChat = false;
19
25
  this.hasRefreshed = false; // Flag to prevent endless loop
20
26
  this.conversationId = null;
@@ -24,19 +30,25 @@
24
30
  // Determine transition styles
25
31
  const transitionStyle = this.enableAnimation ? 'transition: height 0.3s ease, opacity 0.3s ease;' : '';
26
32
 
27
- this.positionStyle = this.containerDiv.dataset.position === 'in-place' ?
28
- `width:100%; height:${this.height}px; ${transitionStyle}` :
33
+ this.position = this.options.position || this.containerDiv.dataset.position || 'fixed';
34
+ this.positionStyle = this.position === 'in-place' ?
35
+ `position: relative; width:100%; height:${this.height}px; ${transitionStyle}` :
29
36
  `position: fixed; right: 1em; bottom: 0; width: ${this.width}px; z-index: 100000; max-width:90vw; max-height: 90vh; ${transitionStyle}`;
30
37
  this.mode = options.mode || 'Chat'; // default to 'chat'
31
38
  this.minimizeOnScroll = false; // Default to false
32
- if (this.mode === 'ContactForm') {
33
- // Adjust position style for contact form if mode is 'ContactForm'
34
- this.positionStyle = `width:100%; height:${this.height}px; ${transitionStyle}`;
39
+ if (this.mode === 'ContactForm' || this.mode === 'PartnerOffers') {
40
+ // Adjust position style for contact form/offers - force relative/absolute to occupy container
41
+ this.positionStyle = `position: relative; width:100%; height:${this.height}px; ${transitionStyle}`;
42
+ this.containerDiv.style.position = 'relative';
35
43
  this.containerDiv.style.height = `${this.height}px`;
36
44
  }
37
45
 
38
46
  this.conversationId = this.getStoredConversationId();
39
47
  this.sessionInfo = options.sessionInfo || null;
48
+
49
+ // Initialize window event listeners only once per instance
50
+ this.addWindowEventListeners();
51
+
40
52
  this.init();
41
53
  }
42
54
 
@@ -81,31 +93,35 @@
81
93
  ? this.serverUrl.slice(0, -1)
82
94
  : this.serverUrl;
83
95
  const baseIframeUrl = `${cleanedServerUrl}/ChatUX/${this.chatId}`;
84
- const minimizedPositionStyle = `
85
- position: fixed; right: 0; bottom: 0;
86
- width: ${this.width}px;
87
- height: ${this.minimizedHeight};
88
- z-index: 100000;
89
- ${this.enableAnimation ? 'transition: height 0.3s ease, opacity 0.3s ease;' : ''}
90
- `;
96
+ const minimizedPositionStyle = this.position === 'in-place' ?
97
+ `position: relative; width: 100%; height: ${this.minimizedHeight}; ${this.enableAnimation ? 'transition: height 0.3s ease, opacity 0.3s ease;' : ''}` :
98
+ `position: fixed; right: 0; bottom: 0; width: 150px; height: ${this.minimizedHeight}; z-index: 100000; ${this.enableAnimation ? 'transition: height 0.3s ease, opacity 0.3s ease;' : ''}`;
91
99
 
92
100
  const params = {
93
101
  isPreview: this.isPreview,
94
102
  mode: this.mode,
95
103
  locale: this.locale,
96
- conversationId: this.conversationId
104
+ conversationId: this.conversationId,
105
+ instanceId: this.instanceId,
106
+ customStyle: this.options.customStyle,
107
+ customJs: this.options.customJs
97
108
  };
98
109
 
110
+ const iframeId = `ec-iframe-${this.instanceId}`;
111
+ const minimizedIframeId = `ec-minimized-${this.instanceId}`;
112
+
99
113
  const iframeHtml = `
100
114
  <iframe
101
- id="embedded-chat"
115
+ id="${iframeId}"
116
+ title="AI chatbot"
102
117
  style="${this.positionStyle}; display: none; border:none; overflow:hidden;"
103
118
  frameborder="0"
104
119
  sandbox="allow-same-origin allow-scripts allow-popups allow-forms allow-top-navigation-by-user-activation"
105
120
  src="${this.buildIframeUrl(baseIframeUrl, params)}"
106
121
  ></iframe>
107
122
  <iframe
108
- id="embedded-chat-minimized"
123
+ id="${minimizedIframeId}"
124
+ title="AI chatbot"
109
125
  style="${minimizedPositionStyle}; display: none; border:none; overflow:hidden;"
110
126
  frameborder="0"
111
127
  sandbox="allow-same-origin allow-scripts allow-popups allow-forms"
@@ -115,26 +131,27 @@
115
131
 
116
132
  // inject and capture references
117
133
  this.containerDiv.innerHTML = iframeHtml;
118
- this.iframe = this.containerDiv.querySelector('#embedded-chat');
119
- this.minimizedIframe = this.containerDiv.querySelector('#embedded-chat-minimized');
134
+ this.iframe = this.containerDiv.querySelector(`#${iframeId}`);
135
+ this.minimizedIframe = this.containerDiv.querySelector(`#${minimizedIframeId}`);
120
136
 
121
- // wait for the real <iframe> element to load
122
- await this.waitForIframeLoad(this.iframe);
137
+ // Wait for main iframe to load
138
+ if (this.iframe) {
139
+ await this.waitForIframeLoad(this.iframe);
140
+ }
123
141
 
124
- // now that this.iframe & this.minimizedIframe exist, hook up events
125
- this.addEventListeners();
142
+ this.addIframeEventListeners();
126
143
 
127
144
  // Send auth to iframes after load
128
145
  await this.postAuthToIframes();
129
146
 
130
147
  // restore your ContactForm logic
131
- if (this.mode === 'ContactForm') {
148
+ if (this.mode === 'ContactForm' || this.mode === 'PartnerOffers') {
132
149
  this.iframe.style.maxWidth = '100%';
133
150
  this.iframe.style.display = 'block';
134
151
  }
135
152
  }
136
153
 
137
- addEventListeners() {
154
+ addIframeEventListeners() {
138
155
  if (!this.iframe || !this.minimizedIframe) {
139
156
  console.error('EmbeddedChat: iframe or minimizedIframe is not defined when trying to add event listeners.', this.iframe, this.minimizedIframe);
140
157
  return;
@@ -143,6 +160,12 @@
143
160
  this.iframe.addEventListener("mouseleave", () => { this.mouseInsideChat = false; });
144
161
  this.minimizedIframe.addEventListener("mouseenter", () => { this.mouseInsideChat = true; });
145
162
  this.minimizedIframe.addEventListener("mouseleave", () => { this.mouseInsideChat = false; });
163
+ }
164
+
165
+ addWindowEventListeners() {
166
+
167
+ // Use a flag to ensure window listeners aren't duplicated for this specific instance if setMode is called (though it shouldn't be)
168
+ if (this.windowListenersAdded) return;
146
169
 
147
170
  window.addEventListener("message", (e) => {
148
171
  // Only trust messages from our server (when targetOrigin is not "*")
@@ -156,13 +179,18 @@
156
179
  }
157
180
  });
158
181
 
159
- window.addEventListener("load", () => {
182
+ const handleResizeTrigger = () => {
160
183
  this.postResizeMessage();
161
- });
184
+ };
162
185
 
163
- window.addEventListener("resize", () => {
164
- this.postResizeMessage();
165
- });
186
+ if (document.readyState === 'complete') {
187
+ handleResizeTrigger();
188
+ } else {
189
+ window.addEventListener("load", handleResizeTrigger);
190
+ }
191
+
192
+ window.addEventListener("resize", handleResizeTrigger);
193
+ this.windowListenersAdded = true;
166
194
  }
167
195
 
168
196
  postResizeMessage() {
@@ -170,12 +198,28 @@
170
198
  this.iframe.contentWindow.postMessage({
171
199
  message: "resize",
172
200
  width: window.innerWidth, // Ensure correct size
173
- height: window.innerHeight
201
+ height: window.innerHeight,
202
+ instanceId: this.instanceId
174
203
  }, this.targetOrigin);
175
204
  }
176
205
  }
177
206
 
178
207
  handleMessage(e) {
208
+ // Only trust messages from our server (when targetOrigin is not "*")
209
+ if (this.targetOrigin !== "*" && e.origin !== this.targetOrigin) return;
210
+
211
+ if (typeof e.data !== "object") {
212
+ // Preserve original handling for string messages
213
+ if (typeof e.data === "string") {
214
+ if (e.data === "minimize") {
215
+ this.animateMinimize();
216
+ } else if (e.data === "show" || e.data === "maximize") {
217
+ this.showMaximized();
218
+ }
219
+ }
220
+ return;
221
+ }
222
+
179
223
  console.log('[EmbeddedChat] Received message:', {
180
224
  origin: e.origin,
181
225
  type: e.data?.type,
@@ -184,73 +228,95 @@
184
228
  data: e.data
185
229
  });
186
230
 
231
+ const data = e.data;
232
+ const msg = (data.message || data.type || "").toString();
233
+ const incomingChatId = (data.chatId || "").toString().toLowerCase();
234
+ const targetChatId = (this.chatId || "").toString().toLowerCase();
235
+ const incomingInstanceId = data.instanceId;
236
+
237
+ // Priority 1: Handle instance-specific routing
238
+ if (incomingInstanceId && incomingInstanceId !== this.instanceId) return;
239
+
240
+ // Priority 2: Bail out if chatId doesn't match and is provided (legacy/fallback)
241
+ if (!incomingInstanceId && incomingChatId && targetChatId && incomingChatId !== targetChatId) return;
242
+
187
243
  // Allow the iframe to request a fresh token
188
- if (typeof e.data === "object" && e.data?.type === "requestAuth") {
189
- console.log('[EmbeddedChat] Received requestAuth message, calling postAuthToIframes');
244
+ if (data.type === "requestAuth") {
190
245
  this.postAuthToIframes();
191
246
  return;
192
247
  }
193
248
 
194
- if (typeof e.data === "object" && (!e.data.chatId || e.data.chatId === this.chatId)) {
195
- const updates = {};
249
+ // Priority 1: Handle minimized size updates (CRITICAL: Bail out early and DO NOT pollute configuration)
250
+ if (msg === "updateMinimizedSize") {
251
+ // Support both new and legacy keys for compatibility with cached scripts
252
+ const h = data.minimizedHeight || data.height;
253
+ const w = data.minimizedWidth || data.width;
254
+ this.updateMinimizedSize(h, w);
255
+ return;
256
+ }
257
+
258
+ const updates = {};
196
259
 
197
- if (e.data.type === "setMinimizeOnScroll" || e.data.minimizeOnScroll !== undefined) {
198
- const minimizeValue = e.data.type === "setMinimizeOnScroll" ? e.data.value : e.data.minimizeOnScroll;
199
- this.minimizeOnScroll = minimizeValue === true || minimizeValue === "true";
200
- }
260
+ if (data.type === "setMinimizeOnScroll" || data.minimizeOnScroll !== undefined) {
261
+ const minimizeValue = data.type === "setMinimizeOnScroll" ? data.value : data.minimizeOnScroll;
262
+ this.minimizeOnScroll = minimizeValue === true || minimizeValue === "true";
263
+ }
201
264
 
202
- if (e.data.locale) {
203
- updates.locale = e.data.locale;
204
- }
205
- if (e.data.width) {
206
- updates.width = e.data.width;
207
- }
208
- if (e.data.height) {
209
- updates.height = e.data.height;
210
- }
211
- if (e.data.marginBottom) {
212
- updates.marginBottom = e.data.marginBottom;
213
- }
214
- if (e.data.scale) {
215
- updates.scale = e.data.scale;
216
- }
265
+ if (data.locale) {
266
+ updates.locale = data.locale;
267
+ }
268
+ if (data.width) {
269
+ updates.width = data.width;
270
+ }
271
+ if (data.height) {
272
+ updates.height = data.height;
273
+ }
274
+ if (data.marginBottom) {
275
+ updates.marginBottom = data.marginBottom;
276
+ }
277
+ if (data.scale) {
278
+ updates.scale = data.scale;
279
+ }
217
280
 
218
- // Apply iframe updates in one go
219
- if (Object.keys(updates).length > 0) {
220
- this.applyIframeUpdates(updates);
221
- }
281
+ // Apply iframe updates in one go
282
+ if (Object.keys(updates).length > 0) {
283
+ this.applyIframeUpdates(updates);
284
+ }
222
285
 
223
- // Handle maximize/minimize
224
- if (e.data.message) {
225
- if (e.data.message === "minimize") {
226
- if (this.mode !== 'ContactForm') {
227
- this.animateMinimize();
228
- }
229
- } else if (e.data.message === "show" || e.data.message === "maximize") {
230
- const animate = e.data.animate === true;
231
- this.showMaximized(animate);
232
- } else if (e.data.message === "navigate" && e.data.url) {
233
- // Handle the navigate message
234
- window.location.href = e.data.url;
235
- }
286
+ // Handle maximize/minimize/navigate
287
+ if (msg === "minimize") {
288
+ if (typeof this.options.onMinimize === 'function') {
289
+ this.options.onMinimize();
290
+ } else if (this.mode !== 'ContactForm' && this.mode !== 'PartnerOffers') {
291
+ this.animateMinimize();
236
292
  }
237
-
238
- // Handle conversationId update
239
- if (e.data.conversationId) {
240
- this.setConversationId(e.data.conversationId);
293
+ } else if (msg === "show" || msg === "maximize") {
294
+ const animate = data.animate === true;
295
+ this.showMaximized(animate);
296
+ } else if (msg === "navigate" && data.url) {
297
+ window.location.href = data.url;
298
+ } else if (data.type === "switchToChat" || msg === "switchToChat") {
299
+ if (typeof this.options.onSwitchToChat === 'function') {
300
+ this.options.onSwitchToChat(data.partner);
301
+ } else {
302
+ this.setMode('Chat', { ...data, initialState: 'Maximized' });
241
303
  }
242
- } else if (typeof e.data === "string") {
243
- // Preserve original handling for string messages
244
- if (e.data === "minimize") {
245
- this.animateMinimize();
246
- } else if (e.data === "show" || e.data === "maximize") {
247
- this.showMaximized();
304
+ } else if (data.type === "switchToContactForm" || msg === "switchToContactForm") {
305
+ if (typeof this.options.onSwitchToContactForm === 'function') {
306
+ this.options.onSwitchToContactForm(data.partner);
307
+ } else {
308
+ this.setMode('ContactForm', { partner: data.partner, initialState: 'Maximized' });
248
309
  }
249
310
  }
311
+
312
+ // Handle conversationId update
313
+ if (data.conversationId) {
314
+ this.setConversationId(data.conversationId);
315
+ }
250
316
  }
251
317
 
252
318
  animateMinimize() {
253
- if (this.mode !== 'ContactForm') {
319
+ if (this.mode !== 'ContactForm' && this.mode !== 'PartnerOffers') {
254
320
  this.iframe.style.height = this.minimizedHeight;
255
321
  this.iframe.style.opacity = '0';
256
322
  setTimeout(() => {
@@ -259,12 +325,39 @@
259
325
  this.minimizedIframe.style.display = "block";
260
326
  this.minimizedIframe.style.height = this.minimizedHeight;
261
327
  this.minimizedIframe.style.opacity = '1';
328
+
329
+ // Request a height update after showing
330
+ if (this.minimizedIframe.contentWindow) {
331
+ this.minimizedIframe.contentWindow.postMessage({ message: "requestHeightUpdate", instanceId: this.instanceId }, this.targetOrigin);
332
+ }
262
333
  }, this.enableAnimation ? 300 : 0);
263
334
  }
264
335
  }
265
336
 
337
+ updateMinimizedSize(contentHeight, contentWidth) {
338
+ if (this.mode === 'ContactForm' || this.mode === 'PartnerOffers') return;
339
+
340
+ this.hasMeasuredSize = true;
341
+
342
+ // Apply height update
343
+ if (contentHeight && contentHeight >= 10) {
344
+ const newHeight = `${contentHeight}px`;
345
+ this.minimizedHeight = newHeight;
346
+ if (this.minimizedIframe) {
347
+ this.minimizedIframe.style.height = newHeight;
348
+ }
349
+ }
350
+
351
+ // Apply width update
352
+ if (contentWidth && contentWidth >= 10) {
353
+ if (this.minimizedIframe) {
354
+ this.minimizedIframe.style.width = `${contentWidth}px`;
355
+ }
356
+ }
357
+ }
358
+
266
359
  animateMaximize() {
267
- if (this.mode !== 'ContactForm') {
360
+ if (this.mode !== 'ContactForm' && this.mode !== 'PartnerOffers') {
268
361
  // Start the animation for minimizing iframe
269
362
  this.minimizedIframe.style.height = `${this.height}px`;
270
363
  this.minimizedIframe.style.opacity = '0';
@@ -298,33 +391,26 @@
298
391
  // Update width if provided and different
299
392
  const parsedWidth = parseInt(width, 10);
300
393
  if (!isNaN(parsedWidth) && parsedWidth > 0 && parsedWidth !== this.width) {
301
- this.width = Math.max(parsedWidth, 320);
394
+ this.width = parsedWidth;
302
395
  console.log(`Width updated to: ${this.width}px`);
303
396
 
304
- // Apply width to container
305
- this.containerDiv.style.width = `${this.width}px`;
306
-
307
- // Apply width to the main iframe
308
- if (this.iframe) {
309
- this.iframe.style.width = `${this.width}px`;
310
- }
311
-
312
- // Apply width to the minimized iframe
313
- if (this.minimizedIframe) {
314
- this.minimizedIframe.style.width = `${this.width}px`;
397
+ // Apply width only if not in-place
398
+ if (this.position !== 'in-place') {
399
+ this.containerDiv.style.width = `${this.width}px`;
400
+ if (this.iframe) {
401
+ this.iframe.style.width = `${this.width}px`;
402
+ }
403
+ } else {
404
+ this.containerDiv.style.width = '100%';
405
+ if (this.iframe) {
406
+ this.iframe.style.width = '100%';
407
+ }
315
408
  }
316
409
  }
317
410
 
318
411
  // Apply scale if provided
319
412
  if (typeof scale === 'number' && scale > 0) {
320
- if (this.iframe) {
321
- this.iframe.style.transform = `scale(${scale})`;
322
- this.iframe.style.transformOrigin = "bottom right";
323
- }
324
- if (this.minimizedIframe) {
325
- this.minimizedIframe.style.transform = `scale(${scale})`;
326
- this.minimizedIframe.style.transformOrigin = "bottom right";
327
- }
413
+ this.applyScale(scale);
328
414
  console.log(`Scale applied: ${scale}`);
329
415
  }
330
416
 
@@ -342,28 +428,43 @@
342
428
 
343
429
  // Clamp so that height + marginBottom doesn't exceed 90% of viewport
344
430
  const viewportHeight = window.innerHeight;
345
- if (parsedHeight + parsedMarginBottom > 0.9 * viewportHeight) {
346
- parsedHeight = Math.max(0, 0.9 * viewportHeight - parsedMarginBottom);
431
+ let effectiveHeight = parsedHeight;
432
+ if (effectiveHeight + parsedMarginBottom > 0.9 * viewportHeight) {
433
+ effectiveHeight = Math.max(100, 0.9 * viewportHeight - parsedMarginBottom);
347
434
  }
348
435
 
349
436
  // Only apply if there's an actual change
350
437
  if (parsedHeight !== this.height || parsedMarginBottom !== this.marginBottom) {
351
438
  this.height = parsedHeight;
352
439
  this.marginBottom = parsedMarginBottom;
353
- this.minimizedHeight = `${this.height * 0.3}px`;
354
440
 
355
- console.log(`Height updated to: ${this.height}px, marginBottom: ${this.marginBottom}px`);
441
+ // Only use the 30% estimate if we haven't received a real measurement yet
442
+ if (!this.hasMeasuredSize) {
443
+ this.minimizedHeight = `${this.height * 0.3}px`;
444
+ }
356
445
 
357
- // Apply the updated height/marginBottom if not ContactForm
358
- if (this.mode !== 'ContactForm') {
359
- if (this.containerDiv.dataset.position === 'in-place') {
446
+ console.log(`Height updated to: ${this.height}px (clamped to ${effectiveHeight}px for display), marginBottom: ${this.marginBottom}px`);
447
+
448
+ // Apply the updated height/marginBottom
449
+ if (this.mode !== 'ContactForm' && this.mode !== 'PartnerOffers') {
450
+ if (this.position === 'in-place') {
360
451
  // "in-place" uses full width, sets containerDiv height
361
- this.containerDiv.style.height = `${this.height}px`;
452
+ this.containerDiv.style.position = 'relative';
453
+ this.containerDiv.style.width = '100%';
454
+ this.containerDiv.style.height = `${effectiveHeight}px`;
362
455
  if (this.iframe) {
363
- this.iframe.style.height = `${this.height}px`;
456
+ this.iframe.style.position = 'relative';
457
+ this.iframe.style.width = '100%';
458
+ this.iframe.style.height = `${effectiveHeight}px`;
459
+ this.iframe.style.bottom = 'unset';
460
+ this.iframe.style.right = 'unset';
364
461
  }
365
462
  if (this.minimizedIframe) {
463
+ this.minimizedIframe.style.position = 'relative';
464
+ this.minimizedIframe.style.width = '100%';
366
465
  this.minimizedIframe.style.height = this.minimizedHeight;
466
+ this.minimizedIframe.style.bottom = 'unset';
467
+ this.minimizedIframe.style.right = 'unset';
367
468
  }
368
469
  } else {
369
470
  // position: fixed (typical floating chat)
@@ -392,7 +493,8 @@
392
493
  if (this.iframe.contentWindow) {
393
494
  this.iframe.contentWindow.postMessage({
394
495
  message: "show",
395
- sessionInfo: this.sessionInfo || null
496
+ sessionInfo: this.sessionInfo || null,
497
+ instanceId: this.instanceId
396
498
  }, this.targetOrigin);
397
499
  } else {
398
500
  // Schedule a single retry with a delay
@@ -400,7 +502,8 @@
400
502
  if (this.iframe.contentWindow) {
401
503
  this.iframe.contentWindow.postMessage({
402
504
  message: "show",
403
- sessionInfo: this.sessionInfo || null
505
+ sessionInfo: this.sessionInfo || null,
506
+ instanceId: this.instanceId
404
507
  }, this.targetOrigin);
405
508
  } else {
406
509
  console.warn("iframe contentWindow is not available.");
@@ -425,16 +528,21 @@
425
528
  }
426
529
 
427
530
  applyScale(scale) {
428
- if (this.mode !== 'ContactForm') {
429
- this.iframe.style.transform = `scale(${scale})`;
430
- this.iframe.style.transformOrigin = "bottom right";
431
- this.minimizedIframe.style.transform = `scale(${scale})`;
432
- this.minimizedIframe.style.transformOrigin = "bottom right";
531
+ if (this.mode !== 'ContactForm' && this.mode !== 'PartnerOffers') {
532
+ const origin = this.position === 'in-place' ? 'top center' : 'bottom right';
533
+ if (this.iframe) {
534
+ this.iframe.style.transform = `scale(${scale})`;
535
+ this.iframe.style.transformOrigin = origin;
536
+ }
537
+ if (this.minimizedIframe) {
538
+ this.minimizedIframe.style.transform = `scale(${scale})`;
539
+ this.minimizedIframe.style.transformOrigin = origin;
540
+ }
433
541
  }
434
542
  }
435
543
 
436
544
  minimizeOnScrollAction() {
437
- if (this.mode !== 'ContactForm' && this.iframe.style.display !== "none") {
545
+ if (this.mode !== 'ContactForm' && this.mode !== 'PartnerOffers' && this.iframe.style.display !== "none") {
438
546
  this.animateMinimize();
439
547
  }
440
548
  }
@@ -445,7 +553,7 @@
445
553
 
446
554
  // Send session info immediately if the chat is already maximized
447
555
  if (this.iframe?.contentWindow && this.iframe.style.display !== "none") {
448
- this.iframe.contentWindow.postMessage({ type: 'setSessionInfo', sessionInfo: this.sessionInfo }, this.targetOrigin);
556
+ this.iframe.contentWindow.postMessage({ type: 'setSessionInfo', sessionInfo: this.sessionInfo, instanceId: this.instanceId }, this.targetOrigin);
449
557
  }
450
558
  }
451
559
 
@@ -464,8 +572,8 @@
464
572
  urlParams.set('isPreview', 'true');
465
573
  }
466
574
 
467
- if (params.mode === 'ContactForm') {
468
- urlParams.set('mode', 'ContactForm');
575
+ if (params.mode === 'ContactForm' || params.mode === 'PartnerOffers' || params.mode === 'Chat') {
576
+ urlParams.set('mode', params.mode);
469
577
  }
470
578
 
471
579
  if (params.locale) {
@@ -485,17 +593,12 @@
485
593
  return `${baseIframeUrl}?${urlParams.toString()}`;
486
594
  }
487
595
 
488
- waitForIframeLoad(iframe, timeout = 5000) {
489
- return new Promise((resolve, reject) => {
490
- // Check if the iframe is already loaded
491
- if (iframe.contentDocument || iframe.contentWindow) {
492
- resolve();
493
- return;
494
- }
495
-
596
+ waitForIframeLoad(iframe, timeout = 10000) {
597
+ return new Promise((resolve) => {
496
598
  // Set up a timeout to avoid waiting indefinitely
497
599
  const timeoutId = setTimeout(() => {
498
- reject(new Error('Iframe did not load within the timeout period.'));
600
+ console.warn('EmbeddedChat: Iframe load timeout, proceeding with initialization.');
601
+ resolve();
499
602
  }, timeout);
500
603
 
501
604
  // Add the load event listener
@@ -514,7 +617,6 @@
514
617
  }
515
618
 
516
619
  try {
517
- console.log('[EmbeddedChat] Calling userTokenProvider...');
518
620
  const userToken = await this.userTokenProvider();
519
621
 
520
622
  if (!userToken) {
@@ -522,14 +624,13 @@
522
624
  return;
523
625
  }
524
626
 
525
- console.log('[EmbeddedChat] Got user token, length:', userToken?.length || 0);
526
-
527
627
  if (this.iframe?.contentWindow) {
528
628
  console.log('[EmbeddedChat] Sending auth message to iframe with targetOrigin:', this.targetOrigin);
529
629
  this.iframe.contentWindow.postMessage({
530
630
  type: 'auth',
531
631
  userToken: userToken,
532
- chatId: this.chatId
632
+ chatId: this.chatId,
633
+ instanceId: this.instanceId
533
634
  }, this.targetOrigin);
534
635
  console.log('[EmbeddedChat] Auth message sent successfully');
535
636
  } else {
@@ -540,6 +641,33 @@
540
641
  console.error('[EmbeddedChat] Failed to get user token:', err);
541
642
  }
542
643
  }
644
+
645
+ setMode(newMode, options = {}) {
646
+ if (this.mode === newMode && (!options.chatId || options.chatId === this.chatId)) return;
647
+
648
+ console.log(`[EmbeddedChat] Setting mode to ${newMode}`, options);
649
+ this.mode = newMode;
650
+ if (options.chatId) {
651
+ this.chatId = options.chatId;
652
+ }
653
+ this.options = { ...this.options, ...options };
654
+ const transitionStyle = this.enableAnimation ? 'transition: height 0.3s ease, opacity 0.3s ease;' : '';
655
+ if (this.mode === 'ContactForm' || this.mode === 'PartnerOffers') {
656
+ this.positionStyle = `position: relative; width:100%; height:${this.height}px; ${transitionStyle}`;
657
+ this.containerDiv.style.position = 'relative';
658
+ this.containerDiv.style.height = `${this.height}px`;
659
+ } else {
660
+ this.positionStyle = this.position === 'in-place' ?
661
+ `position: relative; width:100%; height:${this.height}px; ${transitionStyle}` :
662
+ `position: fixed; right: 1em; bottom: 0; width: ${this.width}px; z-index: 100000; max-width:90vw; max-height: 90vh; ${transitionStyle}`;
663
+ if (this.position === 'in-place') {
664
+ this.containerDiv.style.position = 'relative';
665
+ this.containerDiv.style.width = '100%';
666
+ this.containerDiv.style.height = `${this.height}px`;
667
+ }
668
+ }
669
+ this.updateIframes();
670
+ }
543
671
  }
544
672
 
545
673
  export function initEmbeddedChat(containerDiv, chatId, options) {
@@ -550,3 +678,10 @@ export function initContactForm(containerDiv, chatId, options) {
550
678
  const clonedOptions = { ...options, mode: 'ContactForm' };
551
679
  return new EmbeddedChat(containerDiv, chatId, clonedOptions);
552
680
  }
681
+
682
+ export function initOffers(options = {}) {
683
+ const chatId = options.chatConfigId;
684
+ const mergedOptions = { ...options, mode: 'PartnerOffers', height: options.height || 500 };
685
+ return new EmbeddedChat(options.container, chatId, mergedOptions);
686
+ }
687
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "embeddedaichatux",
3
- "version": "2.0.1",
3
+ "version": "2.2.0",
4
4
  "description": "A lightweight and customizable embedded AI chat UI component that seamlessly integrates into web applications, offering minimized and expanded views, with iframe-based content rendering.",
5
5
  "main": "EmbeddedChat.js",
6
6
  "scripts": {