@eshal-bot/chat-widget 0.1.65 → 0.1.66

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.
@@ -8010,20 +8010,21 @@
8010
8010
  * Sends a streaming chat request using fetch
8011
8011
  * @param {object} request - Chat request object
8012
8012
  * @param {string} apiBaseUrl - Base URL for the API
8013
+ * @param {Function|undefined} getAuthToken - async fn returning a Bearer token (in-app JWT path), or undefined (Mode B proxy)
8013
8014
  */
8014
- const sendStreamingRequest = async (request, apiBaseUrl, organizationId, secretKey) => {
8015
+ const sendStreamingRequest = async (request, apiBaseUrl, getAuthToken) => {
8015
8016
  const headers = {
8016
8017
  'Content-Type': 'application/json',
8017
8018
  Accept: 'text/event-stream'
8018
8019
  };
8019
8020
 
8020
- // Mode A — secret key (direct embed): key replaces orgId in the header
8021
- // Mode B — proxy (@eshal-bot/server): no header from widget; proxy injects x-eshal-sig + x-eshal-org
8022
- // Mode C — legacy: orgId sent directly
8023
- if (secretKey) {
8024
- headers['x-eshal-key'] = secretKey;
8025
- } else if (organizationId) {
8026
- headers['x-eshal-org'] = organizationId;
8021
+ // In-app JWT path: call getAuthToken() and set Authorization header
8022
+ // Mode B proxy: no auth header from widget; proxy injects x-eshal-sig + x-eshal-org server-side
8023
+ if (typeof getAuthToken === 'function') {
8024
+ const token = await getAuthToken();
8025
+ if (token) {
8026
+ headers['Authorization'] = "Bearer ".concat(token);
8027
+ }
8027
8028
  }
8028
8029
  const response = await fetch("".concat(apiBaseUrl.replace(/\/$/, ''), "/api/v1/support"), {
8029
8030
  method: 'POST',
@@ -8347,20 +8348,33 @@
8347
8348
  // Handle text_chunk events
8348
8349
  if (parsed.type === 'text_chunk') {
8349
8350
  var _parsed$choices;
8351
+ // ALWAYS create the skeleton on the first text_chunk, even when it
8352
+ // carries no content (a role-only "skeleton" chunk — delta.role
8353
+ // without delta.content — is the backend's "an assistant turn is
8354
+ // starting" signal used by the input/source-clarification short-
8355
+ // circuits in chat_utils.py). Without this, a clarification turn
8356
+ // that emits [text_chunk role-only] -> [complete content+
8357
+ // finalContent] never created the assistant bubble (this branch
8358
+ // skipped on empty content; the COMPLETE branch's updateFinalMessage
8359
+ // requires lastMessage.role === 'assistant' to render — see
8360
+ // updateFinalMessage line ~189) → the follow-up question was
8361
+ // silently dropped, even though the wire carried everything
8362
+ // correctly. Mirrors handleStreamEvent (line ~257) and packages/
8363
+ // frontend ChatService.ts (line ~213) which both create the
8364
+ // skeleton unconditionally on the first text_chunk.
8365
+ if (!hasReceivedFirstChunk.current) {
8366
+ hasReceivedFirstChunk.current = true;
8367
+ setMessages(prev => {
8368
+ const lastMessage = prev[prev.length - 1];
8369
+ if (!lastMessage || lastMessage.role !== 'assistant' || !lastMessage.isProcessing) {
8370
+ const skeletonMessage = createSkeletonMessage();
8371
+ return [...(prev || []), skeletonMessage];
8372
+ }
8373
+ return prev;
8374
+ });
8375
+ }
8350
8376
  const content = ((_parsed$choices = parsed.choices) === null || _parsed$choices === void 0 || (_parsed$choices = _parsed$choices[0]) === null || _parsed$choices === void 0 || (_parsed$choices = _parsed$choices.delta) === null || _parsed$choices === void 0 ? void 0 : _parsed$choices.content) || parsed.content;
8351
8377
  if (content) {
8352
- // Create skeleton on first chunk if not already created
8353
- if (!hasReceivedFirstChunk.current) {
8354
- hasReceivedFirstChunk.current = true;
8355
- setMessages(prev => {
8356
- const lastMessage = prev[prev.length - 1];
8357
- if (!lastMessage || lastMessage.role !== 'assistant' || !lastMessage.isProcessing) {
8358
- const skeletonMessage = createSkeletonMessage();
8359
- return [...(prev || []), skeletonMessage];
8360
- }
8361
- return prev;
8362
- });
8363
- }
8364
8378
  accumulatedText.current += content;
8365
8379
  updateMessage(accumulatedText.current);
8366
8380
  }
@@ -9133,7 +9147,7 @@
9133
9147
  apiBaseUrl,
9134
9148
  apiKey,
9135
9149
  organizationId,
9136
- secretKey,
9150
+ getAuthToken,
9137
9151
  autoOpen,
9138
9152
  openDelay,
9139
9153
  darkMode,
@@ -9770,7 +9784,7 @@
9770
9784
  if (apiType === 'userName' || apiType === 'email' || apiType === 'phone') {
9771
9785
  // For onboarding fields, just consume the stream without processing.
9772
9786
  // Single attempt — no retry, no user-visible partial state to clean up.
9773
- const stream = await sendStreamingRequest(chatRequest, apiBaseUrl, organizationId, secretKey);
9787
+ const stream = await sendStreamingRequest(chatRequest, apiBaseUrl, getAuthToken);
9774
9788
  const reader = stream.getReader();
9775
9789
  const decoder = new TextDecoder();
9776
9790
  while (true) {
@@ -9789,7 +9803,7 @@
9789
9803
  let lastErr;
9790
9804
  for (let attempt = 0; attempt <= STREAM_RETRY_DELAYS_MS.length; attempt += 1) {
9791
9805
  try {
9792
- const stream = await sendStreamingRequest(chatRequest, apiBaseUrl, organizationId, secretKey);
9806
+ const stream = await sendStreamingRequest(chatRequest, apiBaseUrl, getAuthToken);
9793
9807
  await processStream(stream, setMessages);
9794
9808
  updateActivity(organizationId);
9795
9809
  return {
@@ -9817,7 +9831,7 @@
9817
9831
 
9818
9832
  // Non-chat, non-onboarding apiType (shouldn't currently happen):
9819
9833
  // fire and forget, single attempt.
9820
- await sendStreamingRequest(chatRequest, apiBaseUrl, organizationId);
9834
+ await sendStreamingRequest(chatRequest, apiBaseUrl, getAuthToken);
9821
9835
  updateActivity(organizationId);
9822
9836
  return {
9823
9837
  success: true
@@ -9853,7 +9867,7 @@
9853
9867
  // UX-M09: release the concurrency guard so the next send can proceed.
9854
9868
  isSendingRef.current = false;
9855
9869
  }
9856
- }, [apiBaseUrl, apiKey, getNextMessageId, organizationId, messages, bidiSessionId, routingChoice, widgetFilter, provideCitations]);
9870
+ }, [apiBaseUrl, apiKey, getNextMessageId, getAuthToken, messages, bidiSessionId, routingChoice, widgetFilter, provideCitations]);
9857
9871
  const handleOnboardingFormSubmit = reactExports.useCallback(async answers => {
9858
9872
  const questions = getOnboardingQuestions(onboardingQuestions);
9859
9873
  if (questions.length === 0) return;
@@ -94371,7 +94385,7 @@
94371
94385
  let {
94372
94386
  sources = [],
94373
94387
  apiBaseUrl,
94374
- organizationId,
94388
+ getAuthToken,
94375
94389
  isDark,
94376
94390
  textColor,
94377
94391
  fontFamily,
@@ -94397,7 +94411,7 @@
94397
94411
  // corpora that have none of the above.
94398
94412
  const _downloadKey = s => s.url || s.source_id || s.source_name || "";
94399
94413
  const handleFileDownload = async source => {
94400
- if (!apiBaseUrl || !organizationId) return;
94414
+ if (!apiBaseUrl) return;
94401
94415
  // SharePoint proxy URLs are GET endpoints that stream the file
94402
94416
  // directly via Microsoft Graph using the stored OAuth credential.
94403
94417
  // Bypass the legacy POST /file-download lookup (which expects an
@@ -94423,11 +94437,14 @@
94423
94437
  // set, or future deployments that set it) pass through
94424
94438
  // unchanged.
94425
94439
  const spUrl = source.url.startsWith("http") ? source.url : "".concat(apiBaseUrl.replace(/\/$/, "")).concat(source.url);
94440
+ const spHeaders = {};
94441
+ if (typeof getAuthToken === 'function') {
94442
+ const token = await getAuthToken();
94443
+ if (token) spHeaders['Authorization'] = "Bearer ".concat(token);
94444
+ }
94426
94445
  res = await fetch(spUrl, {
94427
94446
  method: "GET",
94428
- headers: {
94429
- "x-eshal-org": organizationId
94430
- },
94447
+ headers: spHeaders,
94431
94448
  credentials: "include"
94432
94449
  });
94433
94450
  } else {
@@ -94443,15 +94460,18 @@
94443
94460
  // - source.source_name (the user's original filename)
94444
94461
  // Sending both lets the server skip the Milvus resolution
94445
94462
  // AND advertise the readable name in Content-Disposition.
94463
+ const dlHeaders = {
94464
+ "Content-Type": "application/json"
94465
+ };
94466
+ if (typeof getAuthToken === 'function') {
94467
+ const token = await getAuthToken();
94468
+ if (token) dlHeaders['Authorization'] = "Bearer ".concat(token);
94469
+ }
94446
94470
  res = await fetch(url, {
94447
94471
  method: "POST",
94448
- headers: {
94449
- "Content-Type": "application/json",
94450
- "x-eshal-org": organizationId
94451
- },
94472
+ headers: dlHeaders,
94452
94473
  credentials: "include",
94453
94474
  body: JSON.stringify({
94454
- org_id: organizationId,
94455
94475
  source_id: source.source_id,
94456
94476
  file_name: source.url || "",
94457
94477
  source_name: source.source_name || ""
@@ -94636,7 +94656,7 @@
94636
94656
  conciergeName,
94637
94657
  companyName,
94638
94658
  apiBaseUrl,
94639
- organizationId,
94659
+ getAuthToken,
94640
94660
  onPromptSuggestionClick,
94641
94661
  allowFeedback = true,
94642
94662
  provideCitations = true,
@@ -94808,7 +94828,7 @@
94808
94828
  }), /*#__PURE__*/jsxRuntimeExports.jsx(MessageSources, {
94809
94829
  sources: message.sources,
94810
94830
  apiBaseUrl: apiBaseUrl,
94811
- organizationId: organizationId,
94831
+ getAuthToken: getAuthToken,
94812
94832
  isDark: isDark,
94813
94833
  textColor: messageTextColor,
94814
94834
  fontFamily: messageFontFamily,
@@ -94834,7 +94854,7 @@
94834
94854
  }), /*#__PURE__*/jsxRuntimeExports.jsx(MessageSources, {
94835
94855
  sources: message.sources,
94836
94856
  apiBaseUrl: apiBaseUrl,
94837
- organizationId: organizationId,
94857
+ getAuthToken: getAuthToken,
94838
94858
  isDark: isDark,
94839
94859
  textColor: messageTextColor,
94840
94860
  fontFamily: messageFontFamily,
@@ -95014,7 +95034,7 @@
95014
95034
  conciergeName,
95015
95035
  companyName,
95016
95036
  apiBaseUrl,
95017
- organizationId,
95037
+ getAuthToken,
95018
95038
  onPromptSuggestionClick,
95019
95039
  allowFeedback,
95020
95040
  provideCitations,
@@ -95058,7 +95078,7 @@
95058
95078
  conciergeName: conciergeName,
95059
95079
  companyName: companyName,
95060
95080
  apiBaseUrl: apiBaseUrl,
95061
- organizationId: organizationId,
95081
+ getAuthToken: getAuthToken,
95062
95082
  onPromptSuggestionClick: onPromptSuggestionClick,
95063
95083
  allowFeedback: allowFeedback,
95064
95084
  provideCitations: provideCitations,
@@ -95903,7 +95923,7 @@
95903
95923
  conciergeName,
95904
95924
  companyName,
95905
95925
  apiBaseUrl,
95906
- organizationId,
95926
+ getAuthToken,
95907
95927
  onPromptSuggestionClick,
95908
95928
  conversationResetKey,
95909
95929
  allowFeedback,
@@ -96085,7 +96105,7 @@
96085
96105
  conciergeName: conciergeName,
96086
96106
  companyName: companyName,
96087
96107
  apiBaseUrl: apiBaseUrl,
96088
- organizationId: organizationId,
96108
+ getAuthToken: getAuthToken,
96089
96109
  allowFeedback: allowFeedback,
96090
96110
  provideCitations: provideCitations,
96091
96111
  onFeedback: onFeedback,
@@ -96117,7 +96137,7 @@
96117
96137
  conciergeName: conciergeName,
96118
96138
  companyName: companyName,
96119
96139
  apiBaseUrl: apiBaseUrl,
96120
- organizationId: organizationId,
96140
+ getAuthToken: getAuthToken,
96121
96141
  headerTextColor: headerTextColor
96122
96142
  }, "skeleton-waiting");
96123
96143
  }
@@ -99632,8 +99652,7 @@
99632
99652
  showPoweredBy = true,
99633
99653
  apiBaseUrl,
99634
99654
  apiKey,
99635
- organizationId,
99636
- secretKey,
99655
+ getAuthToken,
99637
99656
  conversationId,
99638
99657
  conciergeId,
99639
99658
  onboardingQuestions = [],
@@ -99781,7 +99800,7 @@
99781
99800
  setCsatSubmitted(submitted);
99782
99801
  if (submitted) setShowCsat(false);
99783
99802
  } catch (_unused4) {}
99784
- }, [conversationId, organizationId]);
99803
+ }, [conversationId]);
99785
99804
 
99786
99805
  // Auto-scroll hook
99787
99806
  useAutoScroll(messagesContainerRef, [messages, isLoading], {
@@ -99919,14 +99938,15 @@
99919
99938
  const handleCsatRating = async (rating, format) => {
99920
99939
  setCsatSubmitted(true); // optimistic UI
99921
99940
  setShowCsat(false);
99922
- markCsatSubmitted(organizationId); // persist so page refresh doesn't re-show CSAT
99941
+ markCsatSubmitted(undefined); // persist so page refresh doesn't re-show CSAT
99923
99942
 
99924
99943
  try {
99925
99944
  const headers = {
99926
99945
  "Content-Type": "application/json"
99927
99946
  };
99928
- if (organizationId) {
99929
- headers["x-eshal-org"] = organizationId;
99947
+ if (typeof getAuthToken === 'function') {
99948
+ const token = await getAuthToken();
99949
+ if (token) headers['Authorization'] = "Bearer ".concat(token);
99930
99950
  }
99931
99951
  const response = await fetch("".concat(apiBaseUrl.replace(/\/$/, ""), "/api/v1/support/csat"), {
99932
99952
  method: "POST",
@@ -99936,7 +99956,6 @@
99936
99956
  conversationId,
99937
99957
  rating,
99938
99958
  ratingFormat: format,
99939
- organizationId,
99940
99959
  conciergeId
99941
99960
  })
99942
99961
  });
@@ -99960,8 +99979,9 @@
99960
99979
  const headers = {
99961
99980
  "Content-Type": "application/json"
99962
99981
  };
99963
- if (organizationId) {
99964
- headers["x-eshal-org"] = organizationId;
99982
+ if (typeof getAuthToken === 'function') {
99983
+ const token = await getAuthToken();
99984
+ if (token) headers['Authorization'] = "Bearer ".concat(token);
99965
99985
  }
99966
99986
  const ratingFormat = normalizeCsatFormat(csatFormat);
99967
99987
  const response = await fetch("".concat(apiBaseUrl.replace(/\/$/, ""), "/api/v1/support/csat"), {
@@ -99972,7 +99992,6 @@
99972
99992
  conversationId,
99973
99993
  rating: String(resolveRating),
99974
99994
  ratingFormat,
99975
- organizationId,
99976
99995
  conciergeId,
99977
99996
  feedback: resolveFeedbackText || undefined
99978
99997
  })
@@ -100006,7 +100025,7 @@
100006
100025
  } finally {
100007
100026
  setIsSubmittingResolve(false);
100008
100027
  }
100009
- }, [isCsatEnabled, isSubmittingResolve, isConversationResolved, resolveRating, resolveFeedbackText, conversationId, organizationId, conciergeId, apiBaseUrl, csatFormat, onResetConversation, onRefreshChat, onWidgetFilterChange]);
100028
+ }, [isCsatEnabled, isSubmittingResolve, isConversationResolved, resolveRating, resolveFeedbackText, conversationId, getAuthToken, conciergeId, apiBaseUrl, csatFormat, onResetConversation, onRefreshChat, onWidgetFilterChange]);
100010
100029
 
100011
100030
  // Handle X button: first click shows form, second click closes
100012
100031
  const handleCloseClick = reactExports.useCallback(() => {
@@ -100344,7 +100363,7 @@
100344
100363
  conciergeName: conciergeName,
100345
100364
  companyName: companyName,
100346
100365
  apiBaseUrl: apiBaseUrl,
100347
- organizationId: organizationId,
100366
+ getAuthToken: getAuthToken,
100348
100367
  conversationResetKey: conversationId,
100349
100368
  allowFeedback: allowFeedback,
100350
100369
  provideCitations: provideCitations,
@@ -100429,8 +100448,7 @@
100429
100448
 
100430
100449
  /**
100431
100450
  * @param {string|undefined|null} apiBaseUrl
100432
- * @param {string|undefined|null} orgId
100433
- * @param {string|undefined|null} secretKey — Mode A/B: orgId resolved server-side
100451
+ * @param {Function|undefined|null} getAuthToken - async fn returning a Bearer token (in-app JWT path), or undefined/null (Mode B proxy)
100434
100452
  * @returns {{
100435
100453
  * categories: Array<{id:string,label:string,count:number,tags:string[]}>,
100436
100454
  * loading: boolean,
@@ -100438,17 +100456,15 @@
100438
100456
  * hasFilters: boolean,
100439
100457
  * }}
100440
100458
  */
100441
- function useFilterCatalog(apiBaseUrl, orgId, secretKey) {
100442
- // Mode A: secretKey present, orgId null → key-based endpoint
100443
- // Mode C: orgId present (legacy) → orgId-in-path endpoint
100444
- const hasAuth = !!(apiBaseUrl && (orgId || secretKey));
100459
+ function useFilterCatalog(apiBaseUrl, getAuthToken) {
100460
+ const hasAuth = !!apiBaseUrl;
100445
100461
  const [state, setState] = reactExports.useState({
100446
100462
  categories: [],
100447
100463
  loading: hasAuth,
100448
100464
  error: null
100449
100465
  });
100450
100466
  reactExports.useEffect(() => {
100451
- if (!apiBaseUrl || !orgId && !secretKey) {
100467
+ if (!apiBaseUrl) {
100452
100468
  setState({
100453
100469
  categories: [],
100454
100470
  loading: false,
@@ -100456,7 +100472,7 @@
100456
100472
  });
100457
100473
  return;
100458
100474
  }
100459
- const cacheKey = secretKey ? "".concat(apiBaseUrl, "|__key__") : "".concat(apiBaseUrl, "|").concat(orgId);
100475
+ const cacheKey = getAuthToken ? "".concat(apiBaseUrl, "|__session__") : "".concat(apiBaseUrl, "|__proxy__");
100460
100476
  const cached = _PROCESS_CACHE.get(cacheKey);
100461
100477
  if (cached) {
100462
100478
  setState({
@@ -100472,45 +100488,54 @@
100472
100488
  error: null
100473
100489
  }));
100474
100490
  const base = apiBaseUrl.replace(/\/$/, "");
100475
- const url = secretKey ? "".concat(base, "/api/v1/widget/filters") : "".concat(base, "/api/v1/widget/filters/").concat(encodeURIComponent(orgId));
100476
- const headers = {
100477
- Accept: "application/json"
100478
- };
100479
- if (secretKey) headers["x-eshal-key"] = secretKey;
100480
- fetch(url, {
100481
- method: "GET",
100482
- headers
100483
- }).then(async res => {
100484
- if (!res.ok) {
100485
- throw new Error("filter catalog HTTP ".concat(res.status));
100491
+ const url = "".concat(base, "/api/v1/widget/filters");
100492
+ (async () => {
100493
+ try {
100494
+ const headers = {
100495
+ Accept: "application/json"
100496
+ };
100497
+ // In-app JWT path: set Authorization header
100498
+ // Mode B proxy: no auth header; proxy handles org resolution
100499
+ if (typeof getAuthToken === 'function') {
100500
+ const token = await getAuthToken();
100501
+ if (token) {
100502
+ headers['Authorization'] = "Bearer ".concat(token);
100503
+ }
100504
+ }
100505
+ const res = await fetch(url, {
100506
+ method: "GET",
100507
+ headers
100508
+ });
100509
+ if (!res.ok) {
100510
+ throw new Error("filter catalog HTTP ".concat(res.status));
100511
+ }
100512
+ const body = await res.json();
100513
+ const categories = Array.isArray(body === null || body === void 0 ? void 0 : body.categories) ? body.categories : [];
100514
+ if (cancelled) return;
100515
+ _PROCESS_CACHE.set(cacheKey, categories);
100516
+ setState({
100517
+ categories,
100518
+ loading: false,
100519
+ error: null
100520
+ });
100521
+ } catch (err) {
100522
+ if (cancelled) return;
100523
+ // Fail-soft: log and present empty so the UI hides itself.
100524
+ // We do NOT bubble the error to the caller because chat
100525
+ // should keep working even if the catalog is unreachable.
100526
+ // eslint-disable-next-line no-console
100527
+ console.warn("[useFilterCatalog] catalog fetch failed:", err);
100528
+ setState({
100529
+ categories: [],
100530
+ loading: false,
100531
+ error: err
100532
+ });
100486
100533
  }
100487
- const body = await res.json();
100488
- return Array.isArray(body === null || body === void 0 ? void 0 : body.categories) ? body.categories : [];
100489
- }).then(categories => {
100490
- if (cancelled) return;
100491
- _PROCESS_CACHE.set(cacheKey, categories);
100492
- setState({
100493
- categories,
100494
- loading: false,
100495
- error: null
100496
- });
100497
- }).catch(err => {
100498
- if (cancelled) return;
100499
- // Fail-soft: log and present empty so the UI hides itself.
100500
- // We do NOT bubble the error to the caller because chat
100501
- // should keep working even if the catalog is unreachable.
100502
- // eslint-disable-next-line no-console
100503
- console.warn("[useFilterCatalog] catalog fetch failed:", err);
100504
- setState({
100505
- categories: [],
100506
- loading: false,
100507
- error: err
100508
- });
100509
- });
100534
+ })();
100510
100535
  return () => {
100511
100536
  cancelled = true;
100512
100537
  };
100513
- }, [apiBaseUrl, orgId, secretKey]);
100538
+ }, [apiBaseUrl, getAuthToken]);
100514
100539
  const hasFilters = reactExports.useMemo(() => Array.isArray(state.categories) && state.categories.length > 0, [state.categories]);
100515
100540
  return _objectSpread2(_objectSpread2({}, state), {}, {
100516
100541
  hasFilters
@@ -100589,24 +100614,28 @@
100589
100614
  /**
100590
100615
  * Fetches agent configuration from the deploy-agent endpoint
100591
100616
  * @param {string} apiBaseUrl - Base URL for the API
100592
- * @param {string} orgId - Organization ID
100617
+ * @param {Function|undefined} getAuthToken - async fn returning a Bearer token (in-app JWT path), or undefined (Mode B proxy)
100593
100618
  * @returns {Promise<object>} Agent configuration object
100594
100619
  */
100595
- const fetchAgentConfig = async (apiBaseUrl, orgId, secretKey) => {
100620
+ const fetchAgentConfig = async (apiBaseUrl, getAuthToken) => {
100596
100621
  if (!apiBaseUrl) {
100597
100622
  throw new Error("apiBaseUrl is required");
100598
100623
  }
100599
- // Mode A: secretKey in header, no orgId in URL
100600
- // Mode B (proxy): no secretKey, no orgId — proxy intercepts and signs the request
100601
- // Mode C legacy: orgId in URL path
100602
-
100603
100624
  try {
100604
100625
  const base = apiBaseUrl.replace(/\/$/, '');
100605
- const url = orgId ? "".concat(base, "/api/v1/deploy-agent/").concat(orgId) : "".concat(base, "/api/v1/deploy-agent");
100626
+ const url = "".concat(base, "/api/v1/deploy-agent");
100606
100627
  const headers = {
100607
100628
  "Content-Type": "application/json"
100608
100629
  };
100609
- if (secretKey) headers['x-eshal-key'] = secretKey;
100630
+
100631
+ // In-app JWT path: set Authorization header
100632
+ // Mode B proxy: no auth header; proxy handles org resolution
100633
+ if (typeof getAuthToken === 'function') {
100634
+ const token = await getAuthToken();
100635
+ if (token) {
100636
+ headers['Authorization'] = "Bearer ".concat(token);
100637
+ }
100638
+ }
100610
100639
  const response = await fetch(url, {
100611
100640
  method: "GET",
100612
100641
  headers,
@@ -100626,11 +100655,10 @@
100626
100655
  const ChatWidget = _ref => {
100627
100656
  var _agentConfig$concierg;
100628
100657
  let {
100629
- // Auth — at least one of secretKey (Mode A/B) or orgId (Mode C legacy) is needed.
100630
- secretKey,
100631
- // sk_live_... from ChatWidget.init({ secretKey, apiBaseUrl })
100632
- orgId,
100633
- // legacy — kept for backward compatibility
100658
+ /**
100659
+ * @param {Function|undefined} getAuthToken - async fn returning a Keycloak Bearer token (in-app JWT path)
100660
+ */
100661
+ getAuthToken,
100634
100662
  apiBaseUrl,
100635
100663
  // Optional override props (will override fetched config if provided)
100636
100664
  darkMode,
@@ -100686,13 +100714,9 @@
100686
100714
  setIsLoadingConfig(false);
100687
100715
  return;
100688
100716
  }
100689
- // Mode B proxy: neither secretKey nor orgId in browser — proxy handles auth
100690
-
100691
100717
  try {
100692
100718
  setIsLoadingConfig(true);
100693
- // Pass secretKey for Mode A/B — fetchAgentConfig will send it as a
100694
- // header and omit the orgId path segment. Pass orgId for Mode C legacy.
100695
- const config = await fetchAgentConfig(apiBaseUrl, orgId, secretKey);
100719
+ const config = await fetchAgentConfig(apiBaseUrl, getAuthToken);
100696
100720
  setAgentConfig(config);
100697
100721
  setConfigError(null);
100698
100722
  } catch (error) {
@@ -100703,11 +100727,11 @@
100703
100727
  }
100704
100728
  };
100705
100729
  loadAgentConfig();
100706
- }, [orgId, secretKey, apiBaseUrl]);
100730
+ }, [apiBaseUrl, getAuthToken]);
100707
100731
 
100708
100732
  // Map API response to widget props (use props as overrides if provided)
100709
100733
  const widgetConfig = reactExports.useMemo(() => {
100710
- var _agentConfig$config, _cfg$identity, _cfg$branding, _cfg$typography, _cfg$messages, _cfg$voice, _cfg$onboarding, _cfg$disclaimer, _cfg$csat, _cfg$inactivity, _cfg$behavior, _cfg$security, _cfg$layout, _ref2, _messages$bundles$loc, _messages$bundles, _messages$bundles$en, _messages$bundles2, _ref3, _ref4, _ref5, _ref6, _branding$chatbotAvat, _branding$chatbotAvat2, _ref7, _ref8, _ref9, _ref0, _ref1, _ref10, _ref11, _ref12, _typography$headerTex, _ref13, _ref14, _ref15, _ref16, _ref17, _ref18, _inactivity$timeoutVa, _inactivity$timeoutUn, _ref19, _ref20, _ref21, _ref22, _ref23, _ref24, _ref25, _ref26, _csat$enabled, _csat$format, _csat$prompt, _csat$triggerType, _csat$idleTimeoutMins, _csat$followUpEnabled, _csat$followUpPrompt, _behavior$autoPromptE, _behavior$allowFeedba, _behavior$provideCita;
100734
+ var _agentConfig$config, _cfg$identity, _cfg$branding, _cfg$typography, _cfg$messages, _cfg$voice, _cfg$onboarding, _cfg$disclaimer, _cfg$csat, _cfg$inactivity, _cfg$behavior, _cfg$security, _cfg$layout, _ref2, _messages$bundles$loc, _messages$bundles, _messages$bundles$en, _messages$bundles2, _ref3, _ref4, _ref5, _ref6, _branding$chatbotAvat, _branding$chatbotAvat2, _ref7, _ref8, _ref9, _ref0, _ref1, _ref10, _ref11, _ref12, _typography$headerTex, _ref13, _ref14, _ref15, _ref16, _ref17, _ref18, _inactivity$timeoutVa, _inactivity$timeoutUn, _ref19, _ref20, _ref21, _ref22, _ref23, _ref24, _ref25, _ref26, _csat$enabled, _csat$format, _csat$prompt, _csat$triggerType, _csat$idleTimeoutMins, _csat$followUpEnabled, _csat$followUpPrompt, _behavior$autoPromptE, _behavior$allowFeedba, _behavior$provideCita, _agentConfig$organiza;
100711
100735
  if (!agentConfig) {
100712
100736
  return null;
100713
100737
  }
@@ -100766,7 +100790,6 @@
100766
100790
  fontFamily: (_ref1 = fontFamily !== null && fontFamily !== void 0 ? fontFamily : typography.agentMessageBubbleFontFamily) !== null && _ref1 !== void 0 ? _ref1 : "Inter",
100767
100791
  fontSize: (_ref10 = fontSize !== null && fontSize !== void 0 ? fontSize : typography.agentMessageBubbleFontSize) !== null && _ref10 !== void 0 ? _ref10 : "14px",
100768
100792
  defaultLanguage: locale,
100769
- organizationId: orgId,
100770
100793
  headerTextBold: (_ref11 = headerTextBold !== null && headerTextBold !== void 0 ? headerTextBold : typography.headerTextBold) !== null && _ref11 !== void 0 ? _ref11 : false,
100771
100794
  headerTextItalic: (_ref12 = headerTextItalic !== null && headerTextItalic !== void 0 ? headerTextItalic : typography.headerTextItalic) !== null && _ref12 !== void 0 ? _ref12 : false,
100772
100795
  headerTextColor: (_typography$headerTex = typography.headerTextColor) !== null && _typography$headerTex !== void 0 ? _typography$headerTex : "#FFFFFF",
@@ -100803,9 +100826,12 @@
100803
100826
  // every message and tell the backend to skip citation generation.
100804
100827
  // Defaults to true so agents that haven't set the field keep showing
100805
100828
  // citations (matches the testing-lab / chatbot-preview default).
100806
- provideCitations: (_behavior$provideCita = behavior.provideCitations) !== null && _behavior$provideCita !== void 0 ? _behavior$provideCita : true
100829
+ provideCitations: (_behavior$provideCita = behavior.provideCitations) !== null && _behavior$provideCita !== void 0 ? _behavior$provideCita : true,
100830
+ // organizationId from the backend response — used internally for
100831
+ // session isolation (localStorage keys) and WS paths. Not a user prop.
100832
+ organizationId: (_agentConfig$organiza = agentConfig.organizationId) !== null && _agentConfig$organiza !== void 0 ? _agentConfig$organiza : null
100807
100833
  };
100808
- }, [agentConfig, darkMode, primaryColor, position, companyName, conciergeName, companyLogo, welcomeMessage, quickQuestions, quickQuestionsEnabled, textColor, agentMessageBubbleColor, userMessageBoxColor, assistantTextColor, userTextColor, fontFamily, fontSize, defaultLanguage, orgId, headerTextBold, headerTextItalic, enableVoiceInteraction, disclaimerEnabled, disclaimerText, disclaimerPosition, disclaimerLinkLabel, disclaimerLinkUrl, showPoweredBy, onboardingQuestions, onboardingEnabled, collectionPrompt, allowedDomains, agentMessageBubbleFontFamily, agentMessageBubbleFontSize, userMessageBubbleFontFamily, userMessageBubbleFontSize
100834
+ }, [agentConfig, darkMode, primaryColor, position, companyName, conciergeName, companyLogo, welcomeMessage, quickQuestions, quickQuestionsEnabled, textColor, agentMessageBubbleColor, userMessageBoxColor, assistantTextColor, userTextColor, fontFamily, fontSize, defaultLanguage, headerTextBold, headerTextItalic, enableVoiceInteraction, disclaimerEnabled, disclaimerText, disclaimerPosition, disclaimerLinkLabel, disclaimerLinkUrl, showPoweredBy, onboardingQuestions, onboardingEnabled, collectionPrompt, allowedDomains, agentMessageBubbleFontFamily, agentMessageBubbleFontSize, userMessageBubbleFontFamily, userMessageBubbleFontSize
100809
100835
  // CSAT dependencies
100810
100836
  // (kept minimal because these come from agentConfig)
100811
100837
  ]);
@@ -100824,7 +100850,7 @@
100824
100850
  quickQuestions: [],
100825
100851
  apiBaseUrl: apiBaseUrl || "",
100826
100852
  apiKey: apiKey || "",
100827
- organizationId: orgId || "",
100853
+ getAuthToken,
100828
100854
  autoOpen: false,
100829
100855
  openDelay: 0,
100830
100856
  darkMode: false,
@@ -100832,7 +100858,7 @@
100832
100858
  onboardingQuestions: [],
100833
100859
  onboardingEnabled: false,
100834
100860
  collectionPrompt: undefined
100835
- }), [apiBaseUrl, apiKey, orgId]);
100861
+ }), [apiBaseUrl, apiKey, getAuthToken]);
100836
100862
 
100837
100863
  // ALWAYS call hooks before any early returns (Rules of Hooks)
100838
100864
  const {
@@ -100881,7 +100907,7 @@
100881
100907
  // knowledge-api LoadBalancer.
100882
100908
  apiKey,
100883
100909
  organizationId: widgetConfig.organizationId,
100884
- secretKey,
100910
+ getAuthToken,
100885
100911
  autoOpen,
100886
100912
  openDelay,
100887
100913
  darkMode: widgetConfig.darkMode,
@@ -100908,25 +100934,33 @@
100908
100934
  // navigation (but not a page refresh — feedback is not written to
100909
100935
  // localStorage so it resets on reload as intended).
100910
100936
  updateMessageFeedback(messageContent, feedback);
100911
- if (!orgId && !secretKey || !conversationId || !apiBaseUrl) return;
100937
+ if (!conversationId || !apiBaseUrl) return;
100912
100938
  const base = apiBaseUrl.replace(/\/$/, "");
100913
- const feedbackHeaders = {
100914
- "Content-Type": "application/json"
100915
- };
100916
- if (secretKey) feedbackHeaders['x-eshal-key'] = secretKey;else if (orgId) feedbackHeaders['x-eshal-org'] = orgId;
100917
- fetch("".concat(base, "/api/v1/support/feedback"), {
100918
- method: "POST",
100919
- headers: feedbackHeaders,
100920
- credentials: "include",
100921
- body: JSON.stringify({
100922
- messageContent,
100923
- conversationId,
100924
- organizationId: orgId,
100925
- feedback,
100926
- feedbackText
100927
- })
100928
- }).catch(err => console.error("Feedback submission error:", err));
100929
- }, [orgId, conversationId, apiBaseUrl, updateMessageFeedback]);
100939
+ (async () => {
100940
+ try {
100941
+ const feedbackHeaders = {
100942
+ "Content-Type": "application/json"
100943
+ };
100944
+ if (typeof getAuthToken === 'function') {
100945
+ const token = await getAuthToken();
100946
+ if (token) feedbackHeaders['Authorization'] = "Bearer ".concat(token);
100947
+ }
100948
+ await fetch("".concat(base, "/api/v1/support/feedback"), {
100949
+ method: "POST",
100950
+ headers: feedbackHeaders,
100951
+ credentials: "include",
100952
+ body: JSON.stringify({
100953
+ messageContent,
100954
+ conversationId,
100955
+ feedback,
100956
+ feedbackText
100957
+ })
100958
+ });
100959
+ } catch (err) {
100960
+ console.error("Feedback submission error:", err);
100961
+ }
100962
+ })();
100963
+ }, [conversationId, apiBaseUrl, getAuthToken, updateMessageFeedback]);
100930
100964
 
100931
100965
  // Refresh chat handler — reset conversation
100932
100966
  const handleRefreshChat = reactExports.useCallback(() => {
@@ -100942,7 +100976,7 @@
100942
100976
  const {
100943
100977
  categories: filterCategories,
100944
100978
  hasFilters: showFilterStep
100945
- } = useFilterCatalog(apiBaseUrl, orgId, secretKey);
100979
+ } = useFilterCatalog(apiBaseUrl, getAuthToken);
100946
100980
 
100947
100981
  // Copy chat handler — copy all messages to clipboard
100948
100982
  const handleCopyChat = reactExports.useCallback(() => {
@@ -101047,8 +101081,7 @@
101047
101081
  showPoweredBy: widgetConfig.showPoweredBy,
101048
101082
  apiBaseUrl: apiBaseUrl,
101049
101083
  apiKey: apiKey,
101050
- organizationId: widgetConfig.organizationId,
101051
- secretKey: secretKey,
101084
+ getAuthToken: getAuthToken,
101052
101085
  conversationId: conversationId,
101053
101086
  conciergeId: (_agentConfig$concierg = agentConfig === null || agentConfig === void 0 ? void 0 : agentConfig.conciergeId) !== null && _agentConfig$concierg !== void 0 ? _agentConfig$concierg : agentConfig === null || agentConfig === void 0 ? void 0 : agentConfig.id,
101054
101087
  onboardingQuestions: widgetConfig.onboardingQuestions,
@@ -101196,15 +101229,10 @@
101196
101229
  }
101197
101230
 
101198
101231
  // Render component
101199
- // Mode A/B: secretKey present — orgId derived server-side from the key
101200
- // Mode C legacy: orgId in config — backward compatible
101201
- // Both: apiBaseUrl required
101202
- // Mode B proxy: apiBaseUrl alone is enough — no secretKey or orgId in browser
101232
+ // Mode B proxy: apiBaseUrl alone is enough — no key or orgId in browser
101233
+ // In-app JWT: getAuthToken fn passed via config
101203
101234
  const hasNewAuth = config.apiBaseUrl;
101204
101235
  const widgetProps = hasNewAuth ? _objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({
101205
- // Auth props — secretKey replaces orgId in Mode A/B
101206
- secretKey: config.secretKey || null,
101207
- orgId: config.orgId || null,
101208
101236
  apiBaseUrl: config.apiBaseUrl
101209
101237
  }, options.wsBaseUrl !== undefined && {
101210
101238
  wsBaseUrl: config.wsBaseUrl