open-chat-studio-widget 0.8.0 → 0.9.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 (35) hide show
  1. package/README.md +1 -0
  2. package/dist/cjs/{index-Cf6K60f1.js → index-DDod9Zyw.js} +3 -3
  3. package/dist/cjs/{index-Cf6K60f1.js.map → index-DDod9Zyw.js.map} +1 -1
  4. package/dist/cjs/loader.cjs.js +2 -2
  5. package/dist/cjs/open-chat-studio-widget.cjs.entry.js +175 -22
  6. package/dist/cjs/open-chat-studio-widget.cjs.entry.js.map +1 -1
  7. package/dist/cjs/open-chat-studio-widget.cjs.js +2 -2
  8. package/dist/cjs/open-chat-studio-widget.entry.cjs.js.map +1 -1
  9. package/dist/collection/components/ocs-chat/ocs-chat.js +82 -4
  10. package/dist/collection/components/ocs-chat/ocs-chat.js.map +1 -1
  11. package/dist/collection/services/chat-session-service.js +88 -18
  12. package/dist/collection/services/chat-session-service.js.map +1 -1
  13. package/dist/collection/services/file-attachment-manager.js +6 -0
  14. package/dist/collection/services/file-attachment-manager.js.map +1 -1
  15. package/dist/components/open-chat-studio-widget.js +175 -21
  16. package/dist/components/open-chat-studio-widget.js.map +1 -1
  17. package/dist/esm/{index-DXf2dIht.js → index-iUBQH9om.js} +3 -3
  18. package/dist/esm/{index-DXf2dIht.js.map → index-iUBQH9om.js.map} +1 -1
  19. package/dist/esm/loader.js +3 -3
  20. package/dist/esm/open-chat-studio-widget.entry.js +175 -22
  21. package/dist/esm/open-chat-studio-widget.entry.js.map +1 -1
  22. package/dist/esm/open-chat-studio-widget.js +3 -3
  23. package/dist/open-chat-studio-widget/open-chat-studio-widget.entry.esm.js.map +1 -1
  24. package/dist/open-chat-studio-widget/open-chat-studio-widget.esm.js +1 -1
  25. package/dist/open-chat-studio-widget/p-9c925476.entry.js +4 -0
  26. package/dist/open-chat-studio-widget/p-9c925476.entry.js.map +1 -0
  27. package/dist/open-chat-studio-widget/{p-DXf2dIht.js → p-iUBQH9om.js} +2 -2
  28. package/dist/open-chat-studio-widget/{p-DXf2dIht.js.map → p-iUBQH9om.js.map} +1 -1
  29. package/dist/types/components/ocs-chat/ocs-chat.d.ts +14 -0
  30. package/dist/types/components.d.ts +8 -0
  31. package/dist/types/services/chat-session-service.d.ts +21 -0
  32. package/dist/types/services/file-attachment-manager.d.ts +2 -0
  33. package/package.json +1 -1
  34. package/dist/open-chat-studio-widget/p-ff47dabf.entry.js +0 -4
  35. package/dist/open-chat-studio-widget/p-ff47dabf.entry.js.map +0 -1
@@ -1,11 +1,11 @@
1
- import { b as bootstrapLazy } from './index-DXf2dIht.js';
2
- export { s as setNonce } from './index-DXf2dIht.js';
1
+ import { b as bootstrapLazy } from './index-iUBQH9om.js';
2
+ export { s as setNonce } from './index-iUBQH9om.js';
3
3
  import { g as globalScripts } from './app-globals-DQuL1Twl.js';
4
4
 
5
5
  const defineCustomElements = async (win, options) => {
6
6
  if (typeof window === 'undefined') return undefined;
7
7
  await globalScripts();
8
- return bootstrapLazy([["open-chat-studio-widget",[[257,"open-chat-studio-widget",{"chatbotId":[1,"chatbot-id"],"apiBaseUrl":[1,"api-base-url"],"buttonText":[1,"button-text"],"iconUrl":[1,"icon-url"],"embedKey":[1,"embed-key"],"buttonShape":[1,"button-shape"],"showButton":[4,"show-button"],"mode":[1],"headerText":[1,"header-text"],"newChatConfirmationMessage":[1,"new-chat-confirmation-message"],"visible":[1028],"position":[1025],"welcomeMessages":[1,"welcome-messages"],"starterQuestions":[1,"starter-questions"],"userId":[1,"user-id"],"userName":[1,"user-name"],"persistentSession":[4,"persistent-session"],"persistentSessionExpire":[2,"persistent-session-expire"],"allowFullScreen":[4,"allow-full-screen"],"allowAttachments":[4,"allow-attachments"],"typingIndicatorText":[1,"typing-indicator-text"],"language":[1],"translationsUrl":[1,"translations-url"],"pageContext":[1040,"page-context"],"versionNumber":[2,"version-number"],"sessionId":[1,"session-id"],"error":[32],"messages":[32],"activeSessionId":[32],"isLoading":[32],"isTyping":[32],"typingProgressMessage":[32],"messageInput":[32],"currentPollTaskId":[32],"isDragging":[32],"dragOffset":[32],"windowPosition":[32],"fullscreenPosition":[32],"parsedWelcomeMessages":[32],"parsedStarterQuestions":[32],"generatedUserId":[32],"isFullscreen":[32],"showNewChatConfirmation":[32],"selectedFiles":[32],"isUploadingFiles":[32],"isButtonDragging":[32],"buttonWasDragged":[32]},null,{"pageContext":["pageContextHandler"],"chatbotId":["chatbotConfigHandler"],"versionNumber":["chatbotConfigHandler"],"visible":["visibilityHandler"]}]]]], options);
8
+ return bootstrapLazy([["open-chat-studio-widget",[[257,"open-chat-studio-widget",{"chatbotId":[1,"chatbot-id"],"apiBaseUrl":[1,"api-base-url"],"buttonText":[1,"button-text"],"iconUrl":[1,"icon-url"],"embedKey":[1,"embed-key"],"buttonShape":[1,"button-shape"],"showButton":[4,"show-button"],"mode":[1],"headerText":[1,"header-text"],"newChatConfirmationMessage":[1,"new-chat-confirmation-message"],"visible":[1028],"position":[1025],"welcomeMessages":[1,"welcome-messages"],"starterQuestions":[1,"starter-questions"],"userId":[1,"user-id"],"userName":[1,"user-name"],"persistentSession":[4,"persistent-session"],"persistentSessionExpire":[2,"persistent-session-expire"],"allowFullScreen":[4,"allow-full-screen"],"allowAttachments":[4,"allow-attachments"],"typingIndicatorText":[1,"typing-indicator-text"],"language":[1],"translationsUrl":[1,"translations-url"],"pageContext":[1040,"page-context"],"versionNumber":[2,"version-number"],"sessionId":[1,"session-id"],"sessionToken":[1,"session-token"],"error":[32],"messages":[32],"activeSessionId":[32],"isLoading":[32],"isTyping":[32],"typingProgressMessage":[32],"messageInput":[32],"currentPollTaskId":[32],"isDragging":[32],"dragOffset":[32],"windowPosition":[32],"fullscreenPosition":[32],"parsedWelcomeMessages":[32],"parsedStarterQuestions":[32],"generatedUserId":[32],"isFullscreen":[32],"showNewChatConfirmation":[32],"selectedFiles":[32],"isUploadingFiles":[32],"isButtonDragging":[32],"buttonWasDragged":[32]},null,{"pageContext":["pageContextHandler"],"chatbotId":["chatbotConfigHandler"],"versionNumber":["chatbotConfigHandler"],"visible":["visibilityHandler"]}]]]], options);
9
9
  };
10
10
 
11
11
  export { defineCustomElements };
@@ -1,4 +1,4 @@
1
- import { h, r as registerInstance, E as Env, H as Host, g as getElement } from './index-DXf2dIht.js';
1
+ import { h, r as registerInstance, E as Env, H as Host, g as getElement } from './index-iUBQH9om.js';
2
2
 
3
3
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
4
4
  const OcsWidgetAvatar = () => {
@@ -4468,6 +4468,8 @@ var ar = {
4468
4468
  "status.starting": "جارٍ بدء المحادثة...",
4469
4469
  "status.typing": "جارٍ تحضير الرد",
4470
4470
  "status.uploading": "جارٍ التحميل",
4471
+ "status.sessionExpired": "انتهت صلاحية جلسة الدردشة. سيتم بدء محادثة جديدة — يُرجى إعادة إرسال رسالتك.",
4472
+ "status.sessionError": "لم تعد جلسة الدردشة هذه متاحة.",
4471
4473
  "modal.newChatTitle": "بدء محادثة جديدة",
4472
4474
  "modal.newChatBody": "بدء محادثة جديدة سيؤدي إلى مسح المحادثة الحالية. هل ترغب بالمتابعة؟",
4473
4475
  "modal.cancel": "إلغاء",
@@ -4500,6 +4502,8 @@ var en = {
4500
4502
  "status.starting": "Starting chat...",
4501
4503
  "status.typing": "Preparing response",
4502
4504
  "status.uploading": "Uploading",
4505
+ "status.sessionExpired": "Your chat session expired. Starting a new chat — please resend your message.",
4506
+ "status.sessionError": "This chat session is no longer available.",
4503
4507
  "modal.newChatTitle": "Start New Chat",
4504
4508
  "modal.newChatBody": "Starting a new chat will clear your current conversation. Continue?",
4505
4509
  "modal.cancel": "Cancel",
@@ -4532,6 +4536,8 @@ var es = {
4532
4536
  "status.starting": "Iniciando chat...",
4533
4537
  "status.typing": "Preparando respuesta",
4534
4538
  "status.uploading": "Subiendo",
4539
+ "status.sessionExpired": "Tu sesión de chat ha expirado. Se iniciará un nuevo chat: vuelve a enviar tu mensaje.",
4540
+ "status.sessionError": "Esta sesión de chat ya no está disponible.",
4535
4541
  "modal.newChatTitle": "Iniciar Nuevo Chat",
4536
4542
  "modal.newChatBody": "Iniciar un nuevo chat borrará tu conversación actual. ¿Continuar?",
4537
4543
  "modal.cancel": "Cancelar",
@@ -4564,6 +4570,8 @@ var fr = {
4564
4570
  "status.starting": "Démarrage du chat...",
4565
4571
  "status.typing": "Préparation de la réponse",
4566
4572
  "status.uploading": "Téléversement",
4573
+ "status.sessionExpired": "Votre session de chat a expiré. Un nouveau chat va démarrer — veuillez renvoyer votre message.",
4574
+ "status.sessionError": "Cette session de chat n'est plus disponible.",
4567
4575
  "modal.newChatTitle": "Nouveau Chat",
4568
4576
  "modal.newChatBody": "Démarrer un nouveau chat effacera votre conversation actuelle. Continuer?",
4569
4577
  "modal.cancel": "Annuler",
@@ -4596,6 +4604,8 @@ var hi = {
4596
4604
  "status.starting": "चैट शुरू हो रही है...",
4597
4605
  "status.typing": "जवाब तैयार किया जा रहा है",
4598
4606
  "status.uploading": "अपलोड हो रहा है",
4607
+ "status.sessionExpired": "आपका चैट सत्र समाप्त हो गया है। एक नई चैट शुरू हो रही है — कृपया अपना संदेश फिर से भेजें।",
4608
+ "status.sessionError": "यह चैट सत्र अब उपलब्ध नहीं है।",
4599
4609
  "modal.newChatTitle": "नई चैट शुरू करें",
4600
4610
  "modal.newChatBody": "नई चैट शुरू करने से आपकी मौजूदा बातचीत हट जाएगी। क्या आप जारी रखना चाहते हैं?",
4601
4611
  "modal.cancel": "रद्द करें",
@@ -4628,6 +4638,8 @@ var it = {
4628
4638
  "status.starting": "Avvio della chat...",
4629
4639
  "status.typing": "Preparazione della risposta",
4630
4640
  "status.uploading": "Caricamento",
4641
+ "status.sessionExpired": "La tua sessione di chat è scaduta. Verrà avviata una nuova chat: invia di nuovo il tuo messaggio.",
4642
+ "status.sessionError": "Questa sessione di chat non è più disponibile.",
4631
4643
  "modal.newChatTitle": "Avvia nuova chat",
4632
4644
  "modal.newChatBody": "Avviare una nuova chat cancellerà la conversazione attuale. Continuare?",
4633
4645
  "modal.cancel": "Annulla",
@@ -4660,6 +4672,8 @@ var pt = {
4660
4672
  "status.starting": "Iniciando chat...",
4661
4673
  "status.typing": "Preparando resposta",
4662
4674
  "status.uploading": "Carregando",
4675
+ "status.sessionExpired": "Sua sessão de chat expirou. Iniciando um novo chat — reenvie sua mensagem.",
4676
+ "status.sessionError": "Esta sessão de chat não está mais disponível.",
4663
4677
  "modal.newChatTitle": "Iniciar novo chat",
4664
4678
  "modal.newChatBody": "Iniciar um novo chat apagará sua conversa atual. Deseja continuar?",
4665
4679
  "modal.cancel": "Cancelar",
@@ -4692,6 +4706,8 @@ var sw = {
4692
4706
  "status.starting": "Inaanzisha gumzo...",
4693
4707
  "status.typing": "Inatayarisha jibu",
4694
4708
  "status.uploading": "Inapakia",
4709
+ "status.sessionExpired": "Kipindi chako cha gumzo kimeisha. Gumzo jipya linaanza — tafadhali tuma tena ujumbe wako.",
4710
+ "status.sessionError": "Kipindi hiki cha gumzo hakipatikani tena.",
4695
4711
  "modal.newChatTitle": "Anza gumzo jipya",
4696
4712
  "modal.newChatBody": "Kuanza gumzo jipya kutafuta gumzo lako la sasa. Je, ungependa kuendelea?",
4697
4713
  "modal.cancel": "Ghairi",
@@ -4724,6 +4740,8 @@ var uk = {
4724
4740
  "status.starting": "Запуск чату...",
4725
4741
  "status.typing": "Готується відповідь",
4726
4742
  "status.uploading": "Завантаження",
4743
+ "status.sessionExpired": "Ваш сеанс чату завершився. Розпочинається новий чат — будь ласка, надішліть повідомлення ще раз.",
4744
+ "status.sessionError": "Цей сеанс чату більше недоступний.",
4727
4745
  "modal.newChatTitle": "Почати новий чат",
4728
4746
  "modal.newChatBody": "Початок нового чату видалить вашу поточну розмову. Продовжити?",
4729
4747
  "modal.cancel": "Скасувати",
@@ -4980,55 +4998,54 @@ function currentDomainMatchesApiBaseUrl(apiBaseUrl) {
4980
4998
  return window.location.origin === apiBase.origin;
4981
4999
  }
4982
5000
 
5001
+ class SessionAccessError extends Error {
5002
+ constructor(status, code, message) {
5003
+ super(message);
5004
+ this.name = 'SessionAccessError';
5005
+ this.status = status;
5006
+ this.code = code;
5007
+ }
5008
+ }
4983
5009
  class ChatSessionService {
4984
5010
  constructor(options) {
4985
5011
  var _a, _b, _c, _d;
4986
5012
  this.apiBaseUrl = options.apiBaseUrl;
4987
5013
  this.embedKey = options.embedKey;
4988
5014
  this.widgetVersion = options.widgetVersion;
5015
+ this.sessionToken = options.sessionToken;
4989
5016
  this.csrfTokenProvider = (_a = options.csrfTokenProvider) !== null && _a !== void 0 ? _a : getCSRFToken;
4990
5017
  this.taskPollingIntervalMs = (_b = options.taskPollingIntervalMs) !== null && _b !== void 0 ? _b : 1000;
4991
5018
  this.taskPollingMaxAttempts = (_c = options.taskPollingMaxAttempts) !== null && _c !== void 0 ? _c : 120;
4992
5019
  this.messagePollingIntervalMs = (_d = options.messagePollingIntervalMs) !== null && _d !== void 0 ? _d : 30000;
4993
5020
  }
4994
5021
  async startSession(requestBody) {
4995
- const response = await fetch(`${this.apiBaseUrl}/api/chat/start/`, {
5022
+ const response = await this.request(`${this.apiBaseUrl}/api/chat/start/`, {
4996
5023
  method: 'POST',
4997
5024
  headers: this.getJsonHeaders(),
4998
5025
  body: JSON.stringify(requestBody),
4999
5026
  });
5000
5027
  if (!response.ok) {
5001
- throw new Error(`Failed to start session: ${response.statusText}`);
5028
+ await this.raiseForStatus(response, 'Failed to start session');
5002
5029
  }
5003
5030
  return response.json();
5004
5031
  }
5005
5032
  async sendMessage(sessionId, payload) {
5006
- const response = await fetch(`${this.apiBaseUrl}/api/chat/${sessionId}/message/`, {
5033
+ const response = await this.request(`${this.apiBaseUrl}/api/chat/${sessionId}/message/`, {
5007
5034
  method: 'POST',
5008
5035
  headers: this.getJsonHeaders(),
5009
5036
  body: JSON.stringify(payload),
5010
5037
  });
5011
5038
  if (!response.ok) {
5012
- throw new Error(`Failed to send message: ${response.statusText}`);
5039
+ await this.raiseForStatus(response, 'Failed to send message');
5013
5040
  }
5014
5041
  return response.json();
5015
5042
  }
5016
5043
  async pollTaskOnce(sessionId, taskId) {
5017
- const response = await fetch(`${this.apiBaseUrl}/api/chat/${sessionId}/${taskId}/poll/`, {
5044
+ const response = await this.request(`${this.apiBaseUrl}/api/chat/${sessionId}/${taskId}/poll/`, {
5018
5045
  headers: this.getCommonHeaders(),
5019
5046
  });
5020
5047
  if (!response.ok) {
5021
- let errorMessage = `Failed to poll task: ${response.statusText}`;
5022
- try {
5023
- const data = (await response.json());
5024
- if (data === null || data === void 0 ? void 0 : data.error) {
5025
- errorMessage = data.error;
5026
- }
5027
- }
5028
- catch (_a) {
5029
- // non-JSON body; keep statusText fallback
5030
- }
5031
- throw new Error(errorMessage);
5048
+ await this.raiseForStatus(response, 'Failed to poll task');
5032
5049
  }
5033
5050
  return response.json();
5034
5051
  }
@@ -5088,11 +5105,11 @@ class ChatSessionService {
5088
5105
  if (since) {
5089
5106
  url.searchParams.set('since', since);
5090
5107
  }
5091
- const response = await fetch(url.toString(), {
5108
+ const response = await this.request(url.toString(), {
5092
5109
  headers: this.getCommonHeaders(),
5093
5110
  });
5094
5111
  if (!response.ok) {
5095
- throw new Error(`Failed to poll messages: ${response.statusText}`);
5112
+ await this.raiseForStatus(response, 'Failed to poll messages');
5096
5113
  }
5097
5114
  return response.json();
5098
5115
  }
@@ -5149,6 +5166,74 @@ class ChatSessionService {
5149
5166
  this.messagePollingTimer = undefined;
5150
5167
  }
5151
5168
  }
5169
+ setSessionToken(token) {
5170
+ this.sessionToken = token;
5171
+ }
5172
+ async request(input, init) {
5173
+ const response = await fetch(input, init);
5174
+ this.checkSunsetHeaders(response);
5175
+ return response;
5176
+ }
5177
+ /**
5178
+ * Log a deprecation warning (RFC 8594 `Deprecation`/`Sunset`/`Link` headers)
5179
+ * when the server reports that this widget version is deprecated. Warns
5180
+ * during the deprecation window and errors once the sunset date has passed.
5181
+ * Logs at most once per level so polling does not flood the console.
5182
+ */
5183
+ checkSunsetHeaders(response) {
5184
+ const headers = response === null || response === void 0 ? void 0 : response.headers;
5185
+ if (!headers || typeof headers.get !== 'function') {
5186
+ return;
5187
+ }
5188
+ if (headers.get('Deprecation') !== 'true') {
5189
+ return;
5190
+ }
5191
+ const sunsetAt = this.parseSunsetDate(headers.get('Sunset'));
5192
+ const pastSunset = sunsetAt !== null && Date.now() >= sunsetAt.getTime();
5193
+ const level = pastSunset ? 'error' : 'warn';
5194
+ if (this.loggedSunsetLevel === level) {
5195
+ return;
5196
+ }
5197
+ this.loggedSunsetLevel = level;
5198
+ const upgradeUrl = this.parseSuccessorUrl(headers.get('Link'));
5199
+ const upgradeSuffix = upgradeUrl ? ` Upgrade: ${upgradeUrl}` : '';
5200
+ const sunsetText = sunsetAt ? sunsetAt.toUTCString() : 'an upcoming date';
5201
+ if (level === 'error') {
5202
+ console.error(`[open-chat-studio-widget] Widget version ${this.widgetVersion} is past its sunset date ` + `(${sunsetText}) and may stop working.${upgradeSuffix}`);
5203
+ }
5204
+ else {
5205
+ console.warn(`[open-chat-studio-widget] Widget version ${this.widgetVersion} is deprecated and will stop ` + `working after ${sunsetText}.${upgradeSuffix}`);
5206
+ }
5207
+ }
5208
+ parseSunsetDate(sunset) {
5209
+ if (!sunset) {
5210
+ return null;
5211
+ }
5212
+ const parsed = new Date(sunset);
5213
+ return Number.isNaN(parsed.getTime()) ? null : parsed;
5214
+ }
5215
+ parseSuccessorUrl(link) {
5216
+ const match = link === null || link === void 0 ? void 0 : link.match(/<([^>]+)>\s*;\s*rel="?successor-version"?/);
5217
+ return match === null || match === void 0 ? void 0 : match[1];
5218
+ }
5219
+ async raiseForStatus(response, fallbackPrefix) {
5220
+ let message = `${fallbackPrefix}: ${response.statusText}`;
5221
+ let code;
5222
+ try {
5223
+ const data = (await response.json());
5224
+ if (data === null || data === void 0 ? void 0 : data.error) {
5225
+ message = data.error;
5226
+ }
5227
+ code = data === null || data === void 0 ? void 0 : data.code;
5228
+ }
5229
+ catch (_a) {
5230
+ // non-JSON body; keep statusText fallback
5231
+ }
5232
+ if (response.status === 403) {
5233
+ throw new SessionAccessError(response.status, code, message);
5234
+ }
5235
+ throw new Error(message);
5236
+ }
5152
5237
  getJsonHeaders() {
5153
5238
  const headers = this.getCommonHeaders();
5154
5239
  headers['Content-Type'] = 'application/json';
@@ -5165,6 +5250,9 @@ class ChatSessionService {
5165
5250
  if (this.embedKey) {
5166
5251
  headers['X-Embed-Key'] = this.embedKey;
5167
5252
  }
5253
+ if (this.sessionToken) {
5254
+ headers['X-Session-Token'] = this.sessionToken;
5255
+ }
5168
5256
  return headers;
5169
5257
  }
5170
5258
  }
@@ -5231,8 +5319,13 @@ class FileAttachmentManager {
5231
5319
  formData.append('participant_name', context.participantName);
5232
5320
  }
5233
5321
  try {
5322
+ const headers = {};
5323
+ if (context.sessionToken) {
5324
+ headers['X-Session-Token'] = context.sessionToken;
5325
+ }
5234
5326
  const response = await fetch(`${context.apiBaseUrl}/api/chat/${context.sessionId}/upload/`, {
5235
5327
  method: 'POST',
5328
+ headers,
5236
5329
  body: formData,
5237
5330
  });
5238
5331
  if (!response.ok) {
@@ -5242,6 +5335,7 @@ class FileAttachmentManager {
5242
5335
  selectedFiles: this.markPendingFilesWithError(existingFiles, errorMessage),
5243
5336
  uploadedIds,
5244
5337
  errorMessage,
5338
+ tokenRejected: response.status === 403,
5245
5339
  };
5246
5340
  }
5247
5341
  const data = await this.safeJson(response);
@@ -5527,13 +5621,15 @@ const OcsChat = class {
5527
5621
  if (this.isSessionBound()) {
5528
5622
  // Bound to an externally-managed session: the host page is the source of truth.
5529
5623
  this.activeSessionId = this.sessionId;
5624
+ this.applySessionToken(this.sessionToken);
5530
5625
  }
5531
5626
  else if (this.persistentSession && this.isLocalStorageAvailable()) {
5532
5627
  // Always try to load existing session if localStorage is available
5533
- const { sessionId, messages } = this.loadSessionFromStorage();
5628
+ const { sessionId, messages, sessionToken } = this.loadSessionFromStorage();
5534
5629
  if (sessionId && messages) {
5535
5630
  this.activeSessionId = sessionId;
5536
5631
  this.messages = messages;
5632
+ this.applySessionToken(sessionToken);
5537
5633
  }
5538
5634
  }
5539
5635
  this.parseWelcomeMessages();
@@ -5582,6 +5678,11 @@ const OcsChat = class {
5582
5678
  this.removeButtonEventListeners();
5583
5679
  window.removeEventListener('resize', this.handleWindowResize);
5584
5680
  }
5681
+ applySessionToken(token) {
5682
+ var _a;
5683
+ this.currentSessionToken = token;
5684
+ (_a = this.chatService) === null || _a === void 0 ? void 0 : _a.setSessionToken(token);
5685
+ }
5585
5686
  getChatService() {
5586
5687
  if (!this.chatService) {
5587
5688
  this.chatService = new ChatSessionService({
@@ -5591,6 +5692,7 @@ const OcsChat = class {
5591
5692
  taskPollingIntervalMs: OcsChat.TASK_POLLING_INTERVAL_MS,
5592
5693
  taskPollingMaxAttempts: OcsChat.TASK_POLLING_MAX_ATTEMPTS,
5593
5694
  messagePollingIntervalMs: OcsChat.MESSAGE_POLLING_INTERVAL_MS,
5695
+ sessionToken: this.currentSessionToken,
5594
5696
  });
5595
5697
  }
5596
5698
  return this.chatService;
@@ -5606,6 +5708,27 @@ const OcsChat = class {
5606
5708
  this.saveSessionToStorage();
5607
5709
  this.scrollToBottom();
5608
5710
  }
5711
+ /**
5712
+ * Recover from a rejected session token (403). Unbound widgets discard the
5713
+ * dead session/token, show a notice, and start fresh on the next send; bound
5714
+ * widgets cannot restart a host-owned session, so they surface an error.
5715
+ */
5716
+ handleSessionAccessError() {
5717
+ this.cleanup();
5718
+ this.isLoading = false;
5719
+ this.isTyping = false;
5720
+ this.isUploadingFiles = false;
5721
+ this.typingProgressMessage = '';
5722
+ if (this.isSessionBound()) {
5723
+ this.addErrorMessage(this.translationManager.get('status.sessionError', 'This chat session is no longer available.'));
5724
+ return;
5725
+ }
5726
+ this.sessionEpoch += 1;
5727
+ this.activeSessionId = undefined;
5728
+ this.applySessionToken(undefined);
5729
+ this.clearSessionStorage();
5730
+ this.addErrorMessage(this.translationManager.get('status.sessionExpired', 'Your chat session expired. Starting a new chat — please resend your message.'));
5731
+ }
5609
5732
  handleError(errorText) {
5610
5733
  // show as system message
5611
5734
  this.addErrorMessage(errorText);
@@ -5678,12 +5801,14 @@ const OcsChat = class {
5678
5801
  this.currentPollTaskId = '';
5679
5802
  }
5680
5803
  async startSession() {
5804
+ var _a;
5681
5805
  const epoch = this.sessionEpoch;
5682
5806
  try {
5683
5807
  this.isLoading = true;
5684
5808
  const userId = this.getOrGenerateUserId();
5685
5809
  const requestBody = {
5686
5810
  chatbot_id: this.chatbotId,
5811
+ use_session_token: true,
5687
5812
  session_data: {
5688
5813
  source: 'widget',
5689
5814
  page_url: window.location.href,
@@ -5700,6 +5825,7 @@ const OcsChat = class {
5700
5825
  if (epoch !== this.sessionEpoch)
5701
5826
  return;
5702
5827
  this.activeSessionId = data.session_id;
5828
+ this.applySessionToken((_a = data.session_token) !== null && _a !== void 0 ? _a : undefined);
5703
5829
  this.saveSessionToStorage();
5704
5830
  this.startMessagePolling();
5705
5831
  }
@@ -5732,6 +5858,10 @@ const OcsChat = class {
5732
5858
  catch (error) {
5733
5859
  if (epoch !== this.sessionEpoch)
5734
5860
  return;
5861
+ if (error instanceof SessionAccessError) {
5862
+ this.handleSessionAccessError();
5863
+ return;
5864
+ }
5735
5865
  console.warn('Failed to load chat history:', error);
5736
5866
  }
5737
5867
  this.startMessagePolling();
@@ -5747,8 +5877,12 @@ const OcsChat = class {
5747
5877
  sessionId: this.activeSessionId,
5748
5878
  participantId: this.getOrGenerateUserId(),
5749
5879
  participantName: this.userName,
5880
+ sessionToken: this.currentSessionToken,
5750
5881
  });
5751
5882
  this.selectedFiles = uploadResult.selectedFiles;
5883
+ if (uploadResult.tokenRejected) {
5884
+ throw new SessionAccessError(403, 'session_token_required', uploadResult.errorMessage || 'Session token rejected');
5885
+ }
5752
5886
  return uploadResult.uploadedIds;
5753
5887
  }
5754
5888
  finally {
@@ -5840,6 +5974,10 @@ const OcsChat = class {
5840
5974
  catch (error) {
5841
5975
  if (epoch !== this.sessionEpoch)
5842
5976
  return;
5977
+ if (error instanceof SessionAccessError) {
5978
+ this.handleSessionAccessError();
5979
+ return;
5980
+ }
5843
5981
  const errorText = error instanceof Error ? error.message : 'Failed to send message';
5844
5982
  this.handleError(errorText);
5845
5983
  }
@@ -6014,8 +6152,12 @@ const OcsChat = class {
6014
6152
  },
6015
6153
  onError: error => {
6016
6154
  this.typingProgressMessage = '';
6017
- this.handleError(error.message);
6018
6155
  this.taskPollingHandle = undefined;
6156
+ if (error instanceof SessionAccessError) {
6157
+ this.handleSessionAccessError();
6158
+ return;
6159
+ }
6160
+ this.handleError(error.message);
6019
6161
  this.startMessagePolling();
6020
6162
  },
6021
6163
  });
@@ -6377,6 +6519,7 @@ const OcsChat = class {
6377
6519
  messages: `ocs-chat-messages-${this.chatbotId}`,
6378
6520
  lastActivity: `ocs-chat-activity-${this.chatbotId}`,
6379
6521
  visible: `ocs-chat-visible-${this.chatbotId}`,
6522
+ sessionToken: `ocs-chat-token-${this.chatbotId}`,
6380
6523
  };
6381
6524
  }
6382
6525
  saveSessionToStorage() {
@@ -6388,6 +6531,12 @@ const OcsChat = class {
6388
6531
  if (this.activeSessionId) {
6389
6532
  localStorage.setItem(keys.sessionId, this.activeSessionId);
6390
6533
  localStorage.setItem(keys.lastActivity, new Date().toISOString());
6534
+ if (this.currentSessionToken) {
6535
+ localStorage.setItem(keys.sessionToken, this.currentSessionToken);
6536
+ }
6537
+ else {
6538
+ localStorage.removeItem(keys.sessionToken);
6539
+ }
6391
6540
  }
6392
6541
  localStorage.setItem(keys.messages, JSON.stringify(this.messages));
6393
6542
  }
@@ -6396,6 +6545,7 @@ const OcsChat = class {
6396
6545
  }
6397
6546
  }
6398
6547
  loadSessionFromStorage() {
6548
+ var _a;
6399
6549
  const keys = this.getStorageKeys();
6400
6550
  try {
6401
6551
  if (this.persistentSessionExpire > 0) {
@@ -6423,7 +6573,8 @@ const OcsChat = class {
6423
6573
  messages = [];
6424
6574
  }
6425
6575
  }
6426
- return { sessionId, messages };
6576
+ const sessionToken = (_a = localStorage.getItem(keys.sessionToken)) !== null && _a !== void 0 ? _a : undefined;
6577
+ return { sessionId, messages, sessionToken };
6427
6578
  }
6428
6579
  catch (error) {
6429
6580
  // fall back to starting a new session
@@ -6495,6 +6646,7 @@ const OcsChat = class {
6495
6646
  localStorage.removeItem(keys.messages);
6496
6647
  localStorage.removeItem(keys.lastActivity);
6497
6648
  localStorage.removeItem(keys.visible);
6649
+ localStorage.removeItem(keys.sessionToken);
6498
6650
  }
6499
6651
  catch (error) {
6500
6652
  console.warn('Failed to clear chat session from localStorage:', error);
@@ -6536,6 +6688,7 @@ const OcsChat = class {
6536
6688
  // A session provided by the host page (session-id prop) cannot be cleared;
6537
6689
  // stay bound to it. Unbound widgets start a new session on the next message.
6538
6690
  this.activeSessionId = this.sessionId;
6691
+ this.applySessionToken(this.isSessionBound() ? this.sessionToken : undefined);
6539
6692
  this.messages = [];
6540
6693
  this.isTyping = false;
6541
6694
  this.currentPollTaskId = '';