@product7/product7-js 0.6.5 → 0.6.8
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/product7-js.js +370 -22
- package/dist/product7-js.js.map +1 -1
- package/dist/product7-js.min.js +1 -1
- package/dist/product7-js.min.js.map +1 -1
- package/package.json +1 -1
- package/src/core/WebSocketService.js +69 -10
- package/src/styles/liveChat-components.js +159 -0
- package/src/styles/liveChat-core.js +4 -0
- package/src/widgets/LiveChatWidget.js +23 -0
- package/src/widgets/liveChat/components/LiveChatPanel.js +3 -2
- package/src/widgets/liveChat/views/FeedbackFormView.js +104 -0
- package/src/widgets/liveChat/views/HomeView.js +8 -10
package/dist/product7-js.js
CHANGED
|
@@ -4282,10 +4282,173 @@
|
|
|
4282
4282
|
opacity: 0.7;
|
|
4283
4283
|
cursor: not-allowed;
|
|
4284
4284
|
}
|
|
4285
|
+
|
|
4286
|
+
/* ========================================
|
|
4287
|
+
FEEDBACK FORM VIEW
|
|
4288
|
+
======================================== */
|
|
4289
|
+
|
|
4290
|
+
.liveChat-feedback-view {
|
|
4291
|
+
display: flex;
|
|
4292
|
+
flex-direction: column;
|
|
4293
|
+
height: 100%;
|
|
4294
|
+
overflow: hidden;
|
|
4295
|
+
}
|
|
4296
|
+
|
|
4297
|
+
.liveChat-feedback-header {
|
|
4298
|
+
display: flex;
|
|
4299
|
+
align-items: center;
|
|
4300
|
+
gap: var(--spacing-3);
|
|
4301
|
+
padding: var(--spacing-4) var(--spacing-4);
|
|
4302
|
+
border-bottom: 1px solid var(--border-color);
|
|
4303
|
+
flex-shrink: 0;
|
|
4304
|
+
}
|
|
4305
|
+
|
|
4306
|
+
.liveChat-feedback-title {
|
|
4307
|
+
font-size: var(--font-size-base);
|
|
4308
|
+
font-weight: var(--font-weight-semibold);
|
|
4309
|
+
color: var(--text-primary);
|
|
4310
|
+
}
|
|
4311
|
+
|
|
4312
|
+
.liveChat-feedback-body {
|
|
4313
|
+
display: flex;
|
|
4314
|
+
flex-direction: column;
|
|
4315
|
+
gap: var(--spacing-4);
|
|
4316
|
+
padding: var(--spacing-5) var(--spacing-4);
|
|
4317
|
+
flex: 1;
|
|
4318
|
+
overflow-y: auto;
|
|
4319
|
+
}
|
|
4320
|
+
|
|
4321
|
+
.liveChat-feedback-prompt {
|
|
4322
|
+
margin: 0;
|
|
4323
|
+
font-size: var(--font-size-sm);
|
|
4324
|
+
color: var(--text-secondary);
|
|
4325
|
+
}
|
|
4326
|
+
|
|
4327
|
+
|
|
4328
|
+
.liveChat-feedback-input {
|
|
4329
|
+
width: 100%;
|
|
4330
|
+
border: 1px solid var(--border-color);
|
|
4331
|
+
border-radius: var(--radius-md);
|
|
4332
|
+
padding: var(--spacing-3);
|
|
4333
|
+
font-size: var(--font-size-sm);
|
|
4334
|
+
font-family: inherit;
|
|
4335
|
+
color: var(--text-primary);
|
|
4336
|
+
background: var(--msg-bg);
|
|
4337
|
+
box-sizing: border-box;
|
|
4338
|
+
transition: border-color var(--transition-fast);
|
|
4339
|
+
}
|
|
4340
|
+
|
|
4341
|
+
.liveChat-feedback-input::placeholder {
|
|
4342
|
+
color: var(--msg-text-secondary);
|
|
4343
|
+
}
|
|
4344
|
+
|
|
4345
|
+
.liveChat-feedback-input:focus {
|
|
4346
|
+
outline: none;
|
|
4347
|
+
border-color: var(--color-primary);
|
|
4348
|
+
}
|
|
4349
|
+
|
|
4350
|
+
.liveChat-feedback-textarea {
|
|
4351
|
+
width: 100%;
|
|
4352
|
+
resize: none;
|
|
4353
|
+
border: 1px solid var(--border-color);
|
|
4354
|
+
border-radius: var(--radius-md);
|
|
4355
|
+
padding: var(--spacing-3);
|
|
4356
|
+
font-size: var(--font-size-sm);
|
|
4357
|
+
font-family: inherit;
|
|
4358
|
+
color: var(--text-primary);
|
|
4359
|
+
background: var(--msg-bg);
|
|
4360
|
+
box-sizing: border-box;
|
|
4361
|
+
transition: border-color var(--transition-fast);
|
|
4362
|
+
}
|
|
4363
|
+
|
|
4364
|
+
.liveChat-feedback-textarea::placeholder {
|
|
4365
|
+
color: var(--msg-text-secondary);
|
|
4366
|
+
}
|
|
4367
|
+
|
|
4368
|
+
.liveChat-feedback-textarea:focus {
|
|
4369
|
+
outline: none;
|
|
4370
|
+
border-color: var(--color-primary);
|
|
4371
|
+
}
|
|
4372
|
+
|
|
4373
|
+
.liveChat-feedback-submit {
|
|
4374
|
+
display: flex;
|
|
4375
|
+
align-items: center;
|
|
4376
|
+
justify-content: center;
|
|
4377
|
+
padding: var(--spacing-3);
|
|
4378
|
+
border-radius: var(--radius-md);
|
|
4379
|
+
font-size: var(--font-size-sm);
|
|
4380
|
+
font-weight: var(--font-weight-medium);
|
|
4381
|
+
font-family: inherit;
|
|
4382
|
+
cursor: pointer;
|
|
4383
|
+
border: none;
|
|
4384
|
+
background: var(--color-primary);
|
|
4385
|
+
color: #ffffff;
|
|
4386
|
+
transition: all var(--transition-fast);
|
|
4387
|
+
}
|
|
4388
|
+
|
|
4389
|
+
.liveChat-feedback-submit:hover:not(:disabled) {
|
|
4390
|
+
background: var(--color-primary-hover);
|
|
4391
|
+
}
|
|
4392
|
+
|
|
4393
|
+
.liveChat-feedback-submit:disabled {
|
|
4394
|
+
opacity: 0.5;
|
|
4395
|
+
cursor: not-allowed;
|
|
4396
|
+
}
|
|
4397
|
+
|
|
4398
|
+
.liveChat-feedback-thankyou {
|
|
4399
|
+
display: flex;
|
|
4400
|
+
flex-direction: column;
|
|
4401
|
+
align-items: center;
|
|
4402
|
+
justify-content: center;
|
|
4403
|
+
flex: 1;
|
|
4404
|
+
padding: var(--spacing-6) var(--spacing-4);
|
|
4405
|
+
text-align: center;
|
|
4406
|
+
gap: var(--spacing-3);
|
|
4407
|
+
}
|
|
4408
|
+
|
|
4409
|
+
.liveChat-feedback-thankyou-emoji {
|
|
4410
|
+
font-size: 48px;
|
|
4411
|
+
line-height: 1;
|
|
4412
|
+
}
|
|
4413
|
+
|
|
4414
|
+
.liveChat-feedback-thankyou h3 {
|
|
4415
|
+
margin: 0;
|
|
4416
|
+
font-size: var(--font-size-lg);
|
|
4417
|
+
font-weight: var(--font-weight-semibold);
|
|
4418
|
+
color: var(--text-primary);
|
|
4419
|
+
}
|
|
4420
|
+
|
|
4421
|
+
.liveChat-feedback-thankyou p {
|
|
4422
|
+
margin: 0;
|
|
4423
|
+
font-size: var(--font-size-sm);
|
|
4424
|
+
color: var(--text-secondary);
|
|
4425
|
+
}
|
|
4426
|
+
|
|
4427
|
+
.liveChat-feedback-done-btn {
|
|
4428
|
+
margin-top: var(--spacing-2);
|
|
4429
|
+
padding: var(--spacing-2) var(--spacing-5);
|
|
4430
|
+
border-radius: var(--radius-md);
|
|
4431
|
+
font-size: var(--font-size-sm);
|
|
4432
|
+
font-weight: var(--font-weight-medium);
|
|
4433
|
+
font-family: inherit;
|
|
4434
|
+
cursor: pointer;
|
|
4435
|
+
border: 1px solid var(--border-color);
|
|
4436
|
+
background: transparent;
|
|
4437
|
+
color: var(--text-primary);
|
|
4438
|
+
transition: all var(--transition-fast);
|
|
4439
|
+
}
|
|
4440
|
+
|
|
4441
|
+
.liveChat-feedback-done-btn:hover {
|
|
4442
|
+
background: var(--bg-secondary);
|
|
4443
|
+
}
|
|
4285
4444
|
`;
|
|
4286
4445
|
|
|
4287
4446
|
const liveChatCoreStyles = `
|
|
4288
4447
|
|
|
4448
|
+
.liveChat-widget * {
|
|
4449
|
+
font-weight: 500;
|
|
4450
|
+
}
|
|
4451
|
+
|
|
4289
4452
|
.liveChat-launcher {
|
|
4290
4453
|
position: fixed;
|
|
4291
4454
|
z-index: var(--z-modal);
|
|
@@ -8216,11 +8379,28 @@
|
|
|
8216
8379
|
|
|
8217
8380
|
this.ws = null;
|
|
8218
8381
|
this.reconnectAttempts = 0;
|
|
8219
|
-
|
|
8220
|
-
|
|
8382
|
+
// Reconnect indefinitely with capped exponential backoff. The
|
|
8383
|
+
// previous 5-attempt hard cap meant ~31s of churn on a flaky network
|
|
8384
|
+
// permanently killed the live-chat connection until page refresh —
|
|
8385
|
+
// the dominant "messages don't arrive" symptom in production.
|
|
8386
|
+
this.reconnectBaseDelay = 1000;
|
|
8387
|
+
this.reconnectMaxDelay = 30_000;
|
|
8221
8388
|
this.pingInterval = null;
|
|
8222
8389
|
this.isConnected = false;
|
|
8223
8390
|
|
|
8391
|
+
// Heartbeat watchdog. The browser's onclose/onerror only fires when
|
|
8392
|
+
// it notices the TCP socket is dead — on stalled-but-not-closed
|
|
8393
|
+
// connections (NAT timeouts, sleeping mobile, some intermediaries)
|
|
8394
|
+
// that can take many minutes. We track the wall-clock time of the
|
|
8395
|
+
// last received frame and force a reconnect if nothing arrives
|
|
8396
|
+
// within the timeout. Server's protocol-level pings come every 54s
|
|
8397
|
+
// (pkg/websocket/client.go pingPeriod) and our app-level pings/pongs
|
|
8398
|
+
// every 30s, so 90s catches a dead pipe with one cycle of headroom.
|
|
8399
|
+
this.lastFrameAt = 0;
|
|
8400
|
+
this.heartbeatTimeoutMs = 90_000;
|
|
8401
|
+
this.heartbeatCheckIntervalMs = 15_000;
|
|
8402
|
+
this.heartbeatInterval = null;
|
|
8403
|
+
|
|
8224
8404
|
// Event listeners
|
|
8225
8405
|
this._listeners = new Map();
|
|
8226
8406
|
|
|
@@ -8244,6 +8424,8 @@
|
|
|
8244
8424
|
return;
|
|
8245
8425
|
}
|
|
8246
8426
|
|
|
8427
|
+
this._intentionallyClosed = false;
|
|
8428
|
+
|
|
8247
8429
|
// Mock mode - simulate connection
|
|
8248
8430
|
if (this.mock) {
|
|
8249
8431
|
this.isConnected = true;
|
|
@@ -8275,13 +8457,15 @@
|
|
|
8275
8457
|
*/
|
|
8276
8458
|
disconnect() {
|
|
8277
8459
|
this.isConnected = false;
|
|
8278
|
-
this.
|
|
8460
|
+
this._intentionallyClosed = true; // Prevent reconnection
|
|
8279
8461
|
|
|
8280
8462
|
if (this.pingInterval) {
|
|
8281
8463
|
clearInterval(this.pingInterval);
|
|
8282
8464
|
this.pingInterval = null;
|
|
8283
8465
|
}
|
|
8284
8466
|
|
|
8467
|
+
this._stopHeartbeat();
|
|
8468
|
+
|
|
8285
8469
|
if (this.ws) {
|
|
8286
8470
|
this.ws.close();
|
|
8287
8471
|
this.ws = null;
|
|
@@ -8293,6 +8477,35 @@
|
|
|
8293
8477
|
}
|
|
8294
8478
|
}
|
|
8295
8479
|
|
|
8480
|
+
_startHeartbeat() {
|
|
8481
|
+
this._stopHeartbeat();
|
|
8482
|
+
this.lastFrameAt = Date.now();
|
|
8483
|
+
this.heartbeatInterval = setInterval(() => {
|
|
8484
|
+
if (Date.now() - this.lastFrameAt > this.heartbeatTimeoutMs) {
|
|
8485
|
+
console.warn(
|
|
8486
|
+
`[WebSocket] No frames in ${this.heartbeatTimeoutMs}ms, forcing reconnect`
|
|
8487
|
+
);
|
|
8488
|
+
// Closing the socket fires onclose, which schedules a
|
|
8489
|
+
// reconnect through the normal path.
|
|
8490
|
+
this._stopHeartbeat();
|
|
8491
|
+
if (this.ws) {
|
|
8492
|
+
try {
|
|
8493
|
+
this.ws.close();
|
|
8494
|
+
} catch (_) {
|
|
8495
|
+
// ignore
|
|
8496
|
+
}
|
|
8497
|
+
}
|
|
8498
|
+
}
|
|
8499
|
+
}, this.heartbeatCheckIntervalMs);
|
|
8500
|
+
}
|
|
8501
|
+
|
|
8502
|
+
_stopHeartbeat() {
|
|
8503
|
+
if (this.heartbeatInterval) {
|
|
8504
|
+
clearInterval(this.heartbeatInterval);
|
|
8505
|
+
this.heartbeatInterval = null;
|
|
8506
|
+
}
|
|
8507
|
+
}
|
|
8508
|
+
|
|
8296
8509
|
/**
|
|
8297
8510
|
* Subscribe to events
|
|
8298
8511
|
* @param {string} event - Event name
|
|
@@ -8342,6 +8555,8 @@
|
|
|
8342
8555
|
console.log('[WebSocket] Connected');
|
|
8343
8556
|
this.isConnected = true;
|
|
8344
8557
|
this.reconnectAttempts = 0;
|
|
8558
|
+
this._intentionallyClosed = false;
|
|
8559
|
+
this._startHeartbeat();
|
|
8345
8560
|
this._emit('connected', {});
|
|
8346
8561
|
|
|
8347
8562
|
// Start ping interval to keep connection alive
|
|
@@ -8351,6 +8566,9 @@
|
|
|
8351
8566
|
}
|
|
8352
8567
|
|
|
8353
8568
|
_onMessage(event) {
|
|
8569
|
+
// Any inbound frame counts as a sign of life for the watchdog —
|
|
8570
|
+
// including pongs, message:new, typing, etc.
|
|
8571
|
+
this.lastFrameAt = Date.now();
|
|
8354
8572
|
try {
|
|
8355
8573
|
const data = JSON.parse(event.data);
|
|
8356
8574
|
const { type, payload } = data;
|
|
@@ -8395,8 +8613,13 @@
|
|
|
8395
8613
|
this.pingInterval = null;
|
|
8396
8614
|
}
|
|
8397
8615
|
|
|
8616
|
+
this._stopHeartbeat();
|
|
8617
|
+
|
|
8398
8618
|
this._emit('disconnected', { code: event.code, reason: event.reason });
|
|
8399
|
-
|
|
8619
|
+
// Skip reconnect if disconnect() was called intentionally.
|
|
8620
|
+
if (!this._intentionallyClosed) {
|
|
8621
|
+
this._scheduleReconnect();
|
|
8622
|
+
}
|
|
8400
8623
|
}
|
|
8401
8624
|
|
|
8402
8625
|
_onError(error) {
|
|
@@ -8405,14 +8628,13 @@
|
|
|
8405
8628
|
}
|
|
8406
8629
|
|
|
8407
8630
|
_scheduleReconnect() {
|
|
8408
|
-
if (this.
|
|
8409
|
-
console.log('[WebSocket] Max reconnect attempts reached');
|
|
8410
|
-
this._emit('reconnect_failed', {});
|
|
8411
|
-
return;
|
|
8412
|
-
}
|
|
8631
|
+
if (this._intentionallyClosed) return;
|
|
8413
8632
|
|
|
8414
8633
|
this.reconnectAttempts++;
|
|
8415
|
-
const delay =
|
|
8634
|
+
const delay = Math.min(
|
|
8635
|
+
this.reconnectBaseDelay * Math.pow(2, this.reconnectAttempts - 1),
|
|
8636
|
+
this.reconnectMaxDelay
|
|
8637
|
+
);
|
|
8416
8638
|
console.log(
|
|
8417
8639
|
`[WebSocket] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`
|
|
8418
8640
|
);
|
|
@@ -9431,11 +9653,12 @@
|
|
|
9431
9653
|
</div>`;
|
|
9432
9654
|
}
|
|
9433
9655
|
|
|
9434
|
-
// Hide nav in chat and
|
|
9656
|
+
// Hide nav in chat, prechat and feedback views
|
|
9435
9657
|
if (navContainer) {
|
|
9436
9658
|
const hideNav =
|
|
9437
9659
|
this.state.currentView === 'chat' ||
|
|
9438
|
-
this.state.currentView === 'prechat'
|
|
9660
|
+
this.state.currentView === 'prechat' ||
|
|
9661
|
+
this.state.currentView === 'feedback';
|
|
9439
9662
|
navContainer.style.display = hideNav ? 'none' : '';
|
|
9440
9663
|
}
|
|
9441
9664
|
}
|
|
@@ -11173,8 +11396,11 @@
|
|
|
11173
11396
|
const sendIcon = `<iconify-icon icon="ph:paper-plane-right" width="20" height="20" style="flex-shrink: 0;"></iconify-icon>`;
|
|
11174
11397
|
const caretIcon = `<iconify-icon icon="ph:caret-right" width="20" height="20" style="flex-shrink: 0;"></iconify-icon>`;
|
|
11175
11398
|
|
|
11176
|
-
const
|
|
11177
|
-
|
|
11399
|
+
const isUnavailable = this.state.businessHoursState === 'offline' || this.state.businessHoursState === 'away';
|
|
11400
|
+
const buttonLabel = isUnavailable ? 'Leave us a message' : this.state.startButtonText;
|
|
11401
|
+
const buttonSubtext = isUnavailable
|
|
11402
|
+
? "We'll get back to you when we're back"
|
|
11403
|
+
: (this.state.responseTime || 'We typically reply within a few minutes');
|
|
11178
11404
|
|
|
11179
11405
|
const recentCardHtml = openConversation
|
|
11180
11406
|
? this._renderRecentMessageCard(openConversation)
|
|
@@ -11184,8 +11410,8 @@
|
|
|
11184
11410
|
${recentCardHtml}
|
|
11185
11411
|
<button class="liveChat-home-message-btn">
|
|
11186
11412
|
<div class="liveChat-home-continue-info">
|
|
11187
|
-
<span class="liveChat-home-continue-label">${
|
|
11188
|
-
<span class="liveChat-home-message-subtext">${
|
|
11413
|
+
<span class="liveChat-home-continue-label">${buttonLabel}</span>
|
|
11414
|
+
<span class="liveChat-home-message-subtext">${buttonSubtext}</span>
|
|
11189
11415
|
</div>
|
|
11190
11416
|
${sendIcon}
|
|
11191
11417
|
</button>
|
|
@@ -11349,12 +11575,7 @@
|
|
|
11349
11575
|
const feedbackBtn = this.element.querySelector('.liveChat-feedback-btn');
|
|
11350
11576
|
if (feedbackBtn) {
|
|
11351
11577
|
feedbackBtn.addEventListener('click', () => {
|
|
11352
|
-
|
|
11353
|
-
this.state.setOpen(false);
|
|
11354
|
-
this.options.onFeedbackClick();
|
|
11355
|
-
} else if (this.state.urls?.feedback) {
|
|
11356
|
-
window.open(this.state.urls.feedback, '_blank');
|
|
11357
|
-
}
|
|
11578
|
+
this.state.setView('feedback');
|
|
11358
11579
|
});
|
|
11359
11580
|
}
|
|
11360
11581
|
|
|
@@ -11408,6 +11629,111 @@
|
|
|
11408
11629
|
}
|
|
11409
11630
|
}
|
|
11410
11631
|
|
|
11632
|
+
class FeedbackFormView {
|
|
11633
|
+
constructor(state, options = {}) {
|
|
11634
|
+
this.state = state;
|
|
11635
|
+
this.options = options;
|
|
11636
|
+
this.element = null;
|
|
11637
|
+
this._isSubmitting = false;
|
|
11638
|
+
this._selectedRating = null;
|
|
11639
|
+
}
|
|
11640
|
+
|
|
11641
|
+
render() {
|
|
11642
|
+
this.element = document.createElement('div');
|
|
11643
|
+
this.element.className = 'liveChat-view liveChat-feedback-view';
|
|
11644
|
+
this._renderForm();
|
|
11645
|
+
return this.element;
|
|
11646
|
+
}
|
|
11647
|
+
|
|
11648
|
+
_renderForm() {
|
|
11649
|
+
this.element.innerHTML = `
|
|
11650
|
+
<div class="liveChat-feedback-header">
|
|
11651
|
+
<button class="sdk-btn-icon liveChat-feedback-back-btn">
|
|
11652
|
+
<iconify-icon icon="ph:arrow-left" width="20" height="20"></iconify-icon>
|
|
11653
|
+
</button>
|
|
11654
|
+
<span class="liveChat-feedback-title">Leave us feedback</span>
|
|
11655
|
+
</div>
|
|
11656
|
+
<div class="liveChat-feedback-body">
|
|
11657
|
+
<p class="liveChat-feedback-prompt">Share your thoughts with us. We read every message.</p>
|
|
11658
|
+
<input
|
|
11659
|
+
type="text"
|
|
11660
|
+
class="liveChat-feedback-input"
|
|
11661
|
+
placeholder="Title"
|
|
11662
|
+
/>
|
|
11663
|
+
<textarea
|
|
11664
|
+
class="liveChat-feedback-textarea"
|
|
11665
|
+
placeholder="Your feedback..."
|
|
11666
|
+
rows="5"
|
|
11667
|
+
></textarea>
|
|
11668
|
+
<button class="liveChat-feedback-submit">Send feedback</button>
|
|
11669
|
+
</div>
|
|
11670
|
+
`;
|
|
11671
|
+
this._attachEvents();
|
|
11672
|
+
}
|
|
11673
|
+
|
|
11674
|
+
_renderThankYou() {
|
|
11675
|
+
this.element.innerHTML = `
|
|
11676
|
+
<div class="liveChat-feedback-header">
|
|
11677
|
+
<button class="sdk-btn-icon liveChat-feedback-back-btn">
|
|
11678
|
+
<iconify-icon icon="ph:arrow-left" width="20" height="20"></iconify-icon>
|
|
11679
|
+
</button>
|
|
11680
|
+
<span class="liveChat-feedback-title">Leave us feedback</span>
|
|
11681
|
+
</div>
|
|
11682
|
+
<div class="liveChat-feedback-thankyou">
|
|
11683
|
+
<span class="liveChat-feedback-thankyou-emoji">🙏</span>
|
|
11684
|
+
<h3>Thanks for your feedback!</h3>
|
|
11685
|
+
<p>We appreciate you taking the time to share your thoughts.</p>
|
|
11686
|
+
<button class="liveChat-feedback-done-btn">Done</button>
|
|
11687
|
+
</div>
|
|
11688
|
+
`;
|
|
11689
|
+
this.element.querySelector('.liveChat-feedback-back-btn').addEventListener('click', () => {
|
|
11690
|
+
this.state.setView('home');
|
|
11691
|
+
});
|
|
11692
|
+
this.element.querySelector('.liveChat-feedback-done-btn').addEventListener('click', () => {
|
|
11693
|
+
this.state.setView('home');
|
|
11694
|
+
});
|
|
11695
|
+
}
|
|
11696
|
+
|
|
11697
|
+
_attachEvents() {
|
|
11698
|
+
this.element.querySelector('.liveChat-feedback-back-btn').addEventListener('click', () => {
|
|
11699
|
+
this.state.setView('home');
|
|
11700
|
+
});
|
|
11701
|
+
|
|
11702
|
+
const submitBtn = this.element.querySelector('.liveChat-feedback-submit');
|
|
11703
|
+
submitBtn.addEventListener('click', async () => {
|
|
11704
|
+
if (this._isSubmitting) return;
|
|
11705
|
+
const title = this.element.querySelector('.liveChat-feedback-input').value.trim();
|
|
11706
|
+
const message = this.element.querySelector('.liveChat-feedback-textarea').value.trim();
|
|
11707
|
+
await this._submit(title, message);
|
|
11708
|
+
});
|
|
11709
|
+
}
|
|
11710
|
+
|
|
11711
|
+
async _submit(title, message) {
|
|
11712
|
+
this._isSubmitting = true;
|
|
11713
|
+
const submitBtn = this.element.querySelector('.liveChat-feedback-submit');
|
|
11714
|
+
if (submitBtn) {
|
|
11715
|
+
submitBtn.disabled = true;
|
|
11716
|
+
submitBtn.textContent = 'Sending...';
|
|
11717
|
+
}
|
|
11718
|
+
|
|
11719
|
+
try {
|
|
11720
|
+
if (this.options.onSubmitFeedback) {
|
|
11721
|
+
await this.options.onSubmitFeedback({ title, message });
|
|
11722
|
+
}
|
|
11723
|
+
} catch (e) {
|
|
11724
|
+
console.warn('[FeedbackFormView] Submit error:', e);
|
|
11725
|
+
}
|
|
11726
|
+
|
|
11727
|
+
this._renderThankYou();
|
|
11728
|
+
}
|
|
11729
|
+
|
|
11730
|
+
destroy() {
|
|
11731
|
+
if (this.element && this.element.parentNode) {
|
|
11732
|
+
this.element.parentNode.removeChild(this.element);
|
|
11733
|
+
}
|
|
11734
|
+
}
|
|
11735
|
+
}
|
|
11736
|
+
|
|
11411
11737
|
class PreChatFormView {
|
|
11412
11738
|
constructor(state, options = {}) {
|
|
11413
11739
|
this.state = state;
|
|
@@ -11726,6 +12052,7 @@
|
|
|
11726
12052
|
this.panel.registerView('messages', ConversationsView);
|
|
11727
12053
|
this.panel.registerView('chat', ChatView);
|
|
11728
12054
|
this.panel.registerView('prechat', PreChatFormView);
|
|
12055
|
+
this.panel.registerView('feedback', FeedbackFormView);
|
|
11729
12056
|
this.panel.registerView('help', HelpView);
|
|
11730
12057
|
this.panel.registerView('changelog', ChangelogView);
|
|
11731
12058
|
|
|
@@ -12081,13 +12408,34 @@
|
|
|
12081
12408
|
this._wsUnsubscribers.push(
|
|
12082
12409
|
this.wsService.on('conversation_closed', this._handleConversationClosed)
|
|
12083
12410
|
);
|
|
12411
|
+
// Track first vs reconnect locally so we only backfill on reconnects.
|
|
12412
|
+
// First connect is right after _initWebSocket() and the surrounding
|
|
12413
|
+
// flow has already loaded fresh data — re-fetching would be wasted.
|
|
12414
|
+
let wsHasConnectedBefore = false;
|
|
12084
12415
|
this._wsUnsubscribers.push(
|
|
12085
12416
|
this.wsService.on('connected', () => {
|
|
12086
12417
|
console.log('[LiveChatWidget] WebSocket connected');
|
|
12418
|
+
const isReconnect = wsHasConnectedBefore;
|
|
12419
|
+
wsHasConnectedBefore = true;
|
|
12087
12420
|
if (this.LiveChatState.activeConversationId) {
|
|
12088
12421
|
this.wsService.send('conversation:subscribe', {
|
|
12089
12422
|
conversation_id: this.LiveChatState.activeConversationId,
|
|
12090
12423
|
});
|
|
12424
|
+
// On reconnect, refetch the active conversation's messages.
|
|
12425
|
+
// The server doesn't replay events that fired during the WS
|
|
12426
|
+
// disconnect, so anything broadcast in the gap is otherwise
|
|
12427
|
+
// permanently lost from the customer's view until they
|
|
12428
|
+
// refresh the page.
|
|
12429
|
+
if (isReconnect) {
|
|
12430
|
+
this.fetchMessages(this.LiveChatState.activeConversationId).catch(
|
|
12431
|
+
(err) => {
|
|
12432
|
+
console.error(
|
|
12433
|
+
'[LiveChatWidget] Failed to backfill messages on reconnect:',
|
|
12434
|
+
err
|
|
12435
|
+
);
|
|
12436
|
+
}
|
|
12437
|
+
);
|
|
12438
|
+
}
|
|
12091
12439
|
}
|
|
12092
12440
|
})
|
|
12093
12441
|
);
|