embeddedaichatux 2.2.1 → 2.3.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 +87 -43
  2. package/package.json +1 -1
package/EmbeddedChat.js CHANGED
@@ -1,4 +1,4 @@
1
- class EmbeddedChat {
1
+ class EmbeddedChat {
2
2
  static instanceCounter = 0;
3
3
 
4
4
  constructor(containerDiv, chatId, options) {
@@ -7,6 +7,9 @@
7
7
 
8
8
  if (!containerDiv) {
9
9
  containerDiv = this.createChatContainer();
10
+ this.createdContainer = true;
11
+ } else {
12
+ this.createdContainer = false;
10
13
  }
11
14
  this.containerDiv = containerDiv;
12
15
  this.chatId = chatId;
@@ -55,6 +58,13 @@
55
58
  this.init();
56
59
  }
57
60
 
61
+ queueDOMUpdate(fn) {
62
+ requestAnimationFrame(() => {
63
+ if (this.isDestroyed) return;
64
+ fn();
65
+ });
66
+ }
67
+
58
68
  isSafari() {
59
69
  const ua = navigator.userAgent.toLowerCase();
60
70
  return ua.indexOf('safari') != -1 && ua.indexOf('chrome') == -1;
@@ -92,6 +102,8 @@
92
102
  }
93
103
 
94
104
  async updateIframes() {
105
+ if (this.isDestroyed) return;
106
+
95
107
  const cleanedServerUrl = this.serverUrl.endsWith('/')
96
108
  ? this.serverUrl.slice(0, -1)
97
109
  : this.serverUrl;
@@ -153,11 +165,13 @@
153
165
  if (this.iframe) {
154
166
  await this.waitForIframeLoad(this.iframe);
155
167
  }
168
+ if (this.isDestroyed) return;
156
169
 
157
170
  this.addIframeEventListeners();
158
171
 
159
172
  // Send auth to iframes after load
160
173
  await this.postAuthToIframes();
174
+ if (this.isDestroyed) return;
161
175
 
162
176
  // Restore ContactForm logic
163
177
  if (this.mode === 'ContactForm' || this.mode === 'PartnerOffers') {
@@ -187,32 +201,34 @@
187
201
  addWindowEventListeners() {
188
202
  if (this.windowListenersAdded) return;
189
203
 
190
- window.addEventListener("message", (e) => {
191
- if (this.targetOrigin !== "*" && e.origin !== this.targetOrigin) return;
192
- this.handleMessage(e);
193
- });
194
-
195
- window.addEventListener("scroll", () => {
196
- if (this.minimizeOnScroll && !this.mouseInsideChat) {
197
- this.minimizeOnScrollAction();
198
- }
199
- });
204
+ this.listeners = [
205
+ { type: "message", fn: (e) => {
206
+ if (this.targetOrigin !== "*" && e.origin !== this.targetOrigin) return;
207
+ this.handleMessage(e);
208
+ }},
209
+ { type: "scroll", fn: () => {
210
+ if (this.minimizeOnScroll && !this.mouseInsideChat) {
211
+ this.queueDOMUpdate(() => this.minimizeOnScrollAction());
212
+ }
213
+ }},
214
+ { type: "resize", fn: () => this.queueDOMUpdate(() => this.postResizeMessage()) }
215
+ ];
200
216
 
201
- const handleResizeTrigger = () => {
202
- this.postResizeMessage();
203
- };
217
+ this.listeners.forEach(l => window.addEventListener(l.type, l.fn));
204
218
 
219
+ const loadFn = () => this.queueDOMUpdate(() => this.postResizeMessage());
205
220
  if (document.readyState === 'complete') {
206
- handleResizeTrigger();
221
+ loadFn();
207
222
  } else {
208
- window.addEventListener("load", handleResizeTrigger);
223
+ this.listeners.push({ type: "load", fn: loadFn });
224
+ window.addEventListener("load", loadFn);
209
225
  }
210
226
 
211
- window.addEventListener("resize", handleResizeTrigger);
212
227
  this.windowListenersAdded = true;
213
228
  }
214
229
 
215
230
  postResizeMessage() {
231
+ if (this.isDestroyed) return;
216
232
  if (this.iframe?.contentWindow) {
217
233
  this.iframe.contentWindow.postMessage({
218
234
  message: "resize",
@@ -262,37 +278,36 @@
262
278
  if (msg === "updateMinimizedSize") {
263
279
  const h = data.minimizedHeight || data.height;
264
280
  const w = data.minimizedWidth || data.width;
265
- this.updateMinimizedSize(h, w);
281
+ this.queueDOMUpdate(() => this.updateMinimizedSize(h, w));
266
282
  return;
267
283
  }
268
284
 
269
285
  if (msg === "collapseMinimalInput") {
270
- if (this.minimizedIframe) {
271
- this.minimizedIframe.style.width = '80px';
272
- this.minimizedIframe.style.height = '80px';
273
- this.minimizedIframe.style.left = 'auto';
274
- this.minimizedIframe.style.right = '0';
275
- this.minimizedIframe.style.marginLeft = '0';
276
- this.minimizedIframe.style.marginRight = '0';
277
- this.minimizedIframe.style.pointerEvents = 'auto';
278
- }
286
+ this.queueDOMUpdate(() => {
287
+ if (this.minimizedIframe) {
288
+ Object.assign(this.minimizedIframe.style, {
289
+ width: '80px', height: '80px', left: 'auto', right: '0',
290
+ marginLeft: '0', marginRight: '0', pointerEvents: 'auto'
291
+ });
292
+ }
293
+ });
279
294
  return;
280
295
  }
281
296
 
282
297
  if (msg === "restoreMinimalInput") {
283
- if (this.minimizedIframe) {
284
- this.minimizedIframe.style.width = '100vw';
285
- this.minimizedIframe.style.maxWidth = `${Math.min(300, this.width) + 40}px`;
286
- this.minimizedIframe.style.height = this.theme === 'MinimalInput' ? "75px" : this.minimizedHeight;
287
- this.minimizedIframe.style.left = '0';
288
- this.minimizedIframe.style.right = '0';
289
- this.minimizedIframe.style.marginLeft = 'auto';
290
- this.minimizedIframe.style.marginRight = 'auto';
291
- this.minimizedIframe.style.pointerEvents = 'auto';
292
- }
293
- if (this.theme === 'MinimalInput' && this.position !== 'in-place') {
294
- this.containerDiv.style.pointerEvents = 'none';
295
- }
298
+ this.queueDOMUpdate(() => {
299
+ if (this.minimizedIframe) {
300
+ Object.assign(this.minimizedIframe.style, {
301
+ width: '100vw', maxWidth: `${Math.min(300, this.width) + 40}px`,
302
+ height: this.theme === 'MinimalInput' ? "75px" : this.minimizedHeight,
303
+ left: '0', right: '0', marginLeft: 'auto', marginRight: 'auto',
304
+ pointerEvents: 'auto'
305
+ });
306
+ }
307
+ if (this.theme === 'MinimalInput' && this.position !== 'in-place') {
308
+ this.containerDiv.style.pointerEvents = 'none';
309
+ }
310
+ });
296
311
  return;
297
312
  }
298
313
 
@@ -310,18 +325,18 @@
310
325
  if (data.theme) updates.theme = data.theme;
311
326
 
312
327
  if (Object.keys(updates).length > 0) {
313
- this.applyIframeUpdates(updates);
328
+ this.queueDOMUpdate(() => this.applyIframeUpdates(updates));
314
329
  }
315
330
 
316
331
  if (msg === "minimize") {
317
332
  if (typeof this.options.onMinimize === 'function') {
318
333
  this.options.onMinimize();
319
334
  } else if (this.mode !== 'ContactForm' && this.mode !== 'PartnerOffers') {
320
- this.animateMinimize();
335
+ this.queueDOMUpdate(() => this.animateMinimize());
321
336
  }
322
337
  } else if (msg === "show" || msg === "maximize") {
323
338
  const animate = data.animate === true;
324
- this.showMaximized(animate, data.initialMessage);
339
+ this.queueDOMUpdate(() => this.showMaximized(animate, data.initialMessage));
325
340
  } else if (msg === "navigate" && data.url) {
326
341
  window.location.href = data.url;
327
342
  } else if (data.type === "switchToChat" || msg === "switchToChat") {
@@ -344,10 +359,12 @@
344
359
  }
345
360
 
346
361
  animateMinimize() {
362
+ if (this.isDestroyed) return;
347
363
  if (this.mode !== 'ContactForm' && this.mode !== 'PartnerOffers') {
348
364
  this.iframe.style.height = this.minimizedHeight;
349
365
  this.iframe.style.opacity = '0';
350
366
  setTimeout(() => {
367
+ if (this.isDestroyed) return;
351
368
  this.iframe.style.display = "none";
352
369
  this.iframe.style.opacity = '1';
353
370
  this.minimizedIframe.style.display = "block";
@@ -362,6 +379,7 @@
362
379
  }
363
380
 
364
381
  updateMinimizedSize(contentHeight, contentWidth) {
382
+ if (this.isDestroyed) return;
365
383
  if (this.mode === 'ContactForm' || this.mode === 'PartnerOffers') return;
366
384
 
367
385
  this.hasMeasuredSize = true;
@@ -418,6 +436,7 @@
418
436
  }
419
437
 
420
438
  applyIframeUpdates({ locale, width, height, marginBottom, scale, theme } = {}) {
439
+ if (this.isDestroyed) return;
421
440
  if (theme && typeof theme === 'string' && theme !== this.theme) {
422
441
  this.theme = theme;
423
442
  console.log(`Theme updated to: ${this.theme}`);
@@ -542,6 +561,7 @@
542
561
  }
543
562
 
544
563
  showMaximized(animate = false, initialMessage = null) {
564
+ if (this.isDestroyed) return;
545
565
  const schedulePostMessage = () => {
546
566
  if (this.iframe.contentWindow) {
547
567
  this.iframe.contentWindow.postMessage({
@@ -552,6 +572,7 @@
552
572
  }, this.targetOrigin);
553
573
  } else {
554
574
  setTimeout(() => {
575
+ if (this.isDestroyed) return;
555
576
  if (this.iframe.contentWindow) {
556
577
  this.iframe.contentWindow.postMessage({
557
578
  message: "show",
@@ -596,6 +617,7 @@
596
617
  }
597
618
 
598
619
  minimizeOnScrollAction() {
620
+ if (this.isDestroyed) return;
599
621
  if (this.mode !== 'ContactForm' && this.mode !== 'PartnerOffers' && this.iframe.style.display !== "none") {
600
622
  this.animateMinimize();
601
623
  }
@@ -653,6 +675,7 @@
653
675
  }
654
676
 
655
677
  async postAuthToIframes() {
678
+ if (this.isDestroyed) return;
656
679
  if (typeof this.userTokenProvider !== 'function') return;
657
680
  try {
658
681
  const userToken = await this.userTokenProvider();
@@ -696,6 +719,27 @@
696
719
  }
697
720
  this.updateIframes();
698
721
  }
722
+
723
+ destroy() {
724
+ if (this.listeners) {
725
+ this.listeners.forEach(l => window.removeEventListener(l.type, l.fn));
726
+ this.listeners = null;
727
+ }
728
+
729
+ this.windowListenersAdded = false;
730
+ this.isDestroyed = true;
731
+
732
+ if (this.containerDiv) {
733
+ if (this.createdContainer && this.containerDiv.parentNode) {
734
+ this.containerDiv.parentNode.removeChild(this.containerDiv);
735
+ } else if (!this.createdContainer) {
736
+ this.containerDiv.innerHTML = '';
737
+ }
738
+ }
739
+ this.iframe = null;
740
+ this.minimizedIframe = null;
741
+ this.containerDiv = null;
742
+ }
699
743
  }
700
744
 
701
745
  export function initEmbeddedChat(containerDiv, chatId, options) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "embeddedaichatux",
3
- "version": "2.2.1",
3
+ "version": "2.3.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": {