@eshal-bot/chat-widget 0.1.64 → 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,16 +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) => {
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
- // Add organization ID header if provided
8021
- if (organizationId) {
8022
- 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
+ }
8023
8028
  }
8024
8029
  const response = await fetch("".concat(apiBaseUrl.replace(/\/$/, ''), "/api/v1/support"), {
8025
8030
  method: 'POST',
@@ -8343,20 +8348,33 @@
8343
8348
  // Handle text_chunk events
8344
8349
  if (parsed.type === 'text_chunk') {
8345
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
+ }
8346
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;
8347
8377
  if (content) {
8348
- // Create skeleton on first chunk if not already created
8349
- if (!hasReceivedFirstChunk.current) {
8350
- hasReceivedFirstChunk.current = true;
8351
- setMessages(prev => {
8352
- const lastMessage = prev[prev.length - 1];
8353
- if (!lastMessage || lastMessage.role !== 'assistant' || !lastMessage.isProcessing) {
8354
- const skeletonMessage = createSkeletonMessage();
8355
- return [...(prev || []), skeletonMessage];
8356
- }
8357
- return prev;
8358
- });
8359
- }
8360
8378
  accumulatedText.current += content;
8361
8379
  updateMessage(accumulatedText.current);
8362
8380
  }
@@ -9129,6 +9147,7 @@
9129
9147
  apiBaseUrl,
9130
9148
  apiKey,
9131
9149
  organizationId,
9150
+ getAuthToken,
9132
9151
  autoOpen,
9133
9152
  openDelay,
9134
9153
  darkMode,
@@ -9765,7 +9784,7 @@
9765
9784
  if (apiType === 'userName' || apiType === 'email' || apiType === 'phone') {
9766
9785
  // For onboarding fields, just consume the stream without processing.
9767
9786
  // Single attempt — no retry, no user-visible partial state to clean up.
9768
- const stream = await sendStreamingRequest(chatRequest, apiBaseUrl, organizationId);
9787
+ const stream = await sendStreamingRequest(chatRequest, apiBaseUrl, getAuthToken);
9769
9788
  const reader = stream.getReader();
9770
9789
  const decoder = new TextDecoder();
9771
9790
  while (true) {
@@ -9784,7 +9803,7 @@
9784
9803
  let lastErr;
9785
9804
  for (let attempt = 0; attempt <= STREAM_RETRY_DELAYS_MS.length; attempt += 1) {
9786
9805
  try {
9787
- const stream = await sendStreamingRequest(chatRequest, apiBaseUrl, organizationId);
9806
+ const stream = await sendStreamingRequest(chatRequest, apiBaseUrl, getAuthToken);
9788
9807
  await processStream(stream, setMessages);
9789
9808
  updateActivity(organizationId);
9790
9809
  return {
@@ -9812,7 +9831,7 @@
9812
9831
 
9813
9832
  // Non-chat, non-onboarding apiType (shouldn't currently happen):
9814
9833
  // fire and forget, single attempt.
9815
- await sendStreamingRequest(chatRequest, apiBaseUrl, organizationId);
9834
+ await sendStreamingRequest(chatRequest, apiBaseUrl, getAuthToken);
9816
9835
  updateActivity(organizationId);
9817
9836
  return {
9818
9837
  success: true
@@ -9848,7 +9867,7 @@
9848
9867
  // UX-M09: release the concurrency guard so the next send can proceed.
9849
9868
  isSendingRef.current = false;
9850
9869
  }
9851
- }, [apiBaseUrl, apiKey, getNextMessageId, organizationId, messages, bidiSessionId, routingChoice, widgetFilter, provideCitations]);
9870
+ }, [apiBaseUrl, apiKey, getNextMessageId, getAuthToken, messages, bidiSessionId, routingChoice, widgetFilter, provideCitations]);
9852
9871
  const handleOnboardingFormSubmit = reactExports.useCallback(async answers => {
9853
9872
  const questions = getOnboardingQuestions(onboardingQuestions);
9854
9873
  if (questions.length === 0) return;
@@ -94366,7 +94385,7 @@
94366
94385
  let {
94367
94386
  sources = [],
94368
94387
  apiBaseUrl,
94369
- organizationId,
94388
+ getAuthToken,
94370
94389
  isDark,
94371
94390
  textColor,
94372
94391
  fontFamily,
@@ -94392,7 +94411,7 @@
94392
94411
  // corpora that have none of the above.
94393
94412
  const _downloadKey = s => s.url || s.source_id || s.source_name || "";
94394
94413
  const handleFileDownload = async source => {
94395
- if (!apiBaseUrl || !organizationId) return;
94414
+ if (!apiBaseUrl) return;
94396
94415
  // SharePoint proxy URLs are GET endpoints that stream the file
94397
94416
  // directly via Microsoft Graph using the stored OAuth credential.
94398
94417
  // Bypass the legacy POST /file-download lookup (which expects an
@@ -94418,11 +94437,14 @@
94418
94437
  // set, or future deployments that set it) pass through
94419
94438
  // unchanged.
94420
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
+ }
94421
94445
  res = await fetch(spUrl, {
94422
94446
  method: "GET",
94423
- headers: {
94424
- "x-eshal-org": organizationId
94425
- },
94447
+ headers: spHeaders,
94426
94448
  credentials: "include"
94427
94449
  });
94428
94450
  } else {
@@ -94438,15 +94460,18 @@
94438
94460
  // - source.source_name (the user's original filename)
94439
94461
  // Sending both lets the server skip the Milvus resolution
94440
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
+ }
94441
94470
  res = await fetch(url, {
94442
94471
  method: "POST",
94443
- headers: {
94444
- "Content-Type": "application/json",
94445
- "x-eshal-org": organizationId
94446
- },
94472
+ headers: dlHeaders,
94447
94473
  credentials: "include",
94448
94474
  body: JSON.stringify({
94449
- org_id: organizationId,
94450
94475
  source_id: source.source_id,
94451
94476
  file_name: source.url || "",
94452
94477
  source_name: source.source_name || ""
@@ -94631,7 +94656,7 @@
94631
94656
  conciergeName,
94632
94657
  companyName,
94633
94658
  apiBaseUrl,
94634
- organizationId,
94659
+ getAuthToken,
94635
94660
  onPromptSuggestionClick,
94636
94661
  allowFeedback = true,
94637
94662
  provideCitations = true,
@@ -94803,7 +94828,7 @@
94803
94828
  }), /*#__PURE__*/jsxRuntimeExports.jsx(MessageSources, {
94804
94829
  sources: message.sources,
94805
94830
  apiBaseUrl: apiBaseUrl,
94806
- organizationId: organizationId,
94831
+ getAuthToken: getAuthToken,
94807
94832
  isDark: isDark,
94808
94833
  textColor: messageTextColor,
94809
94834
  fontFamily: messageFontFamily,
@@ -94829,7 +94854,7 @@
94829
94854
  }), /*#__PURE__*/jsxRuntimeExports.jsx(MessageSources, {
94830
94855
  sources: message.sources,
94831
94856
  apiBaseUrl: apiBaseUrl,
94832
- organizationId: organizationId,
94857
+ getAuthToken: getAuthToken,
94833
94858
  isDark: isDark,
94834
94859
  textColor: messageTextColor,
94835
94860
  fontFamily: messageFontFamily,
@@ -95009,7 +95034,7 @@
95009
95034
  conciergeName,
95010
95035
  companyName,
95011
95036
  apiBaseUrl,
95012
- organizationId,
95037
+ getAuthToken,
95013
95038
  onPromptSuggestionClick,
95014
95039
  allowFeedback,
95015
95040
  provideCitations,
@@ -95053,7 +95078,7 @@
95053
95078
  conciergeName: conciergeName,
95054
95079
  companyName: companyName,
95055
95080
  apiBaseUrl: apiBaseUrl,
95056
- organizationId: organizationId,
95081
+ getAuthToken: getAuthToken,
95057
95082
  onPromptSuggestionClick: onPromptSuggestionClick,
95058
95083
  allowFeedback: allowFeedback,
95059
95084
  provideCitations: provideCitations,
@@ -95898,7 +95923,7 @@
95898
95923
  conciergeName,
95899
95924
  companyName,
95900
95925
  apiBaseUrl,
95901
- organizationId,
95926
+ getAuthToken,
95902
95927
  onPromptSuggestionClick,
95903
95928
  conversationResetKey,
95904
95929
  allowFeedback,
@@ -96080,7 +96105,7 @@
96080
96105
  conciergeName: conciergeName,
96081
96106
  companyName: companyName,
96082
96107
  apiBaseUrl: apiBaseUrl,
96083
- organizationId: organizationId,
96108
+ getAuthToken: getAuthToken,
96084
96109
  allowFeedback: allowFeedback,
96085
96110
  provideCitations: provideCitations,
96086
96111
  onFeedback: onFeedback,
@@ -96112,7 +96137,7 @@
96112
96137
  conciergeName: conciergeName,
96113
96138
  companyName: companyName,
96114
96139
  apiBaseUrl: apiBaseUrl,
96115
- organizationId: organizationId,
96140
+ getAuthToken: getAuthToken,
96116
96141
  headerTextColor: headerTextColor
96117
96142
  }, "skeleton-waiting");
96118
96143
  }
@@ -99627,7 +99652,7 @@
99627
99652
  showPoweredBy = true,
99628
99653
  apiBaseUrl,
99629
99654
  apiKey,
99630
- organizationId,
99655
+ getAuthToken,
99631
99656
  conversationId,
99632
99657
  conciergeId,
99633
99658
  onboardingQuestions = [],
@@ -99775,7 +99800,7 @@
99775
99800
  setCsatSubmitted(submitted);
99776
99801
  if (submitted) setShowCsat(false);
99777
99802
  } catch (_unused4) {}
99778
- }, [conversationId, organizationId]);
99803
+ }, [conversationId]);
99779
99804
 
99780
99805
  // Auto-scroll hook
99781
99806
  useAutoScroll(messagesContainerRef, [messages, isLoading], {
@@ -99913,14 +99938,15 @@
99913
99938
  const handleCsatRating = async (rating, format) => {
99914
99939
  setCsatSubmitted(true); // optimistic UI
99915
99940
  setShowCsat(false);
99916
- markCsatSubmitted(organizationId); // persist so page refresh doesn't re-show CSAT
99941
+ markCsatSubmitted(undefined); // persist so page refresh doesn't re-show CSAT
99917
99942
 
99918
99943
  try {
99919
99944
  const headers = {
99920
99945
  "Content-Type": "application/json"
99921
99946
  };
99922
- if (organizationId) {
99923
- headers["x-eshal-org"] = organizationId;
99947
+ if (typeof getAuthToken === 'function') {
99948
+ const token = await getAuthToken();
99949
+ if (token) headers['Authorization'] = "Bearer ".concat(token);
99924
99950
  }
99925
99951
  const response = await fetch("".concat(apiBaseUrl.replace(/\/$/, ""), "/api/v1/support/csat"), {
99926
99952
  method: "POST",
@@ -99930,7 +99956,6 @@
99930
99956
  conversationId,
99931
99957
  rating,
99932
99958
  ratingFormat: format,
99933
- organizationId,
99934
99959
  conciergeId
99935
99960
  })
99936
99961
  });
@@ -99954,8 +99979,9 @@
99954
99979
  const headers = {
99955
99980
  "Content-Type": "application/json"
99956
99981
  };
99957
- if (organizationId) {
99958
- headers["x-eshal-org"] = organizationId;
99982
+ if (typeof getAuthToken === 'function') {
99983
+ const token = await getAuthToken();
99984
+ if (token) headers['Authorization'] = "Bearer ".concat(token);
99959
99985
  }
99960
99986
  const ratingFormat = normalizeCsatFormat(csatFormat);
99961
99987
  const response = await fetch("".concat(apiBaseUrl.replace(/\/$/, ""), "/api/v1/support/csat"), {
@@ -99966,7 +99992,6 @@
99966
99992
  conversationId,
99967
99993
  rating: String(resolveRating),
99968
99994
  ratingFormat,
99969
- organizationId,
99970
99995
  conciergeId,
99971
99996
  feedback: resolveFeedbackText || undefined
99972
99997
  })
@@ -100000,7 +100025,7 @@
100000
100025
  } finally {
100001
100026
  setIsSubmittingResolve(false);
100002
100027
  }
100003
- }, [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]);
100004
100029
 
100005
100030
  // Handle X button: first click shows form, second click closes
100006
100031
  const handleCloseClick = reactExports.useCallback(() => {
@@ -100338,7 +100363,7 @@
100338
100363
  conciergeName: conciergeName,
100339
100364
  companyName: companyName,
100340
100365
  apiBaseUrl: apiBaseUrl,
100341
- organizationId: organizationId,
100366
+ getAuthToken: getAuthToken,
100342
100367
  conversationResetKey: conversationId,
100343
100368
  allowFeedback: allowFeedback,
100344
100369
  provideCitations: provideCitations,
@@ -100423,7 +100448,7 @@
100423
100448
 
100424
100449
  /**
100425
100450
  * @param {string|undefined|null} apiBaseUrl
100426
- * @param {string|undefined|null} orgId
100451
+ * @param {Function|undefined|null} getAuthToken - async fn returning a Bearer token (in-app JWT path), or undefined/null (Mode B proxy)
100427
100452
  * @returns {{
100428
100453
  * categories: Array<{id:string,label:string,count:number,tags:string[]}>,
100429
100454
  * loading: boolean,
@@ -100431,14 +100456,15 @@
100431
100456
  * hasFilters: boolean,
100432
100457
  * }}
100433
100458
  */
100434
- function useFilterCatalog(apiBaseUrl, orgId) {
100459
+ function useFilterCatalog(apiBaseUrl, getAuthToken) {
100460
+ const hasAuth = !!apiBaseUrl;
100435
100461
  const [state, setState] = reactExports.useState({
100436
100462
  categories: [],
100437
- loading: !!(apiBaseUrl && orgId),
100463
+ loading: hasAuth,
100438
100464
  error: null
100439
100465
  });
100440
100466
  reactExports.useEffect(() => {
100441
- if (!apiBaseUrl || !orgId) {
100467
+ if (!apiBaseUrl) {
100442
100468
  setState({
100443
100469
  categories: [],
100444
100470
  loading: false,
@@ -100446,8 +100472,8 @@
100446
100472
  });
100447
100473
  return;
100448
100474
  }
100449
- const key = "".concat(apiBaseUrl, "|").concat(orgId);
100450
- const cached = _PROCESS_CACHE.get(key);
100475
+ const cacheKey = getAuthToken ? "".concat(apiBaseUrl, "|__session__") : "".concat(apiBaseUrl, "|__proxy__");
100476
+ const cached = _PROCESS_CACHE.get(cacheKey);
100451
100477
  if (cached) {
100452
100478
  setState({
100453
100479
  categories: cached,
@@ -100461,43 +100487,55 @@
100461
100487
  loading: true,
100462
100488
  error: null
100463
100489
  }));
100464
- const url = "".concat(apiBaseUrl.replace(/\/$/, ""), "/api/v1/widget/filters/").concat(encodeURIComponent(orgId));
100465
- fetch(url, {
100466
- method: "GET",
100467
- headers: {
100468
- Accept: "application/json"
100469
- }
100470
- }).then(async res => {
100471
- if (!res.ok) {
100472
- throw new Error("filter catalog HTTP ".concat(res.status));
100490
+ const base = apiBaseUrl.replace(/\/$/, "");
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
+ });
100473
100533
  }
100474
- const body = await res.json();
100475
- return Array.isArray(body === null || body === void 0 ? void 0 : body.categories) ? body.categories : [];
100476
- }).then(categories => {
100477
- if (cancelled) return;
100478
- _PROCESS_CACHE.set(key, categories);
100479
- setState({
100480
- categories,
100481
- loading: false,
100482
- error: null
100483
- });
100484
- }).catch(err => {
100485
- if (cancelled) return;
100486
- // Fail-soft: log and present empty so the UI hides itself.
100487
- // We do NOT bubble the error to the caller because chat
100488
- // should keep working even if the catalog is unreachable.
100489
- // eslint-disable-next-line no-console
100490
- console.warn("[useFilterCatalog] catalog fetch failed:", err);
100491
- setState({
100492
- categories: [],
100493
- loading: false,
100494
- error: err
100495
- });
100496
- });
100534
+ })();
100497
100535
  return () => {
100498
100536
  cancelled = true;
100499
100537
  };
100500
- }, [apiBaseUrl, orgId]);
100538
+ }, [apiBaseUrl, getAuthToken]);
100501
100539
  const hasFilters = reactExports.useMemo(() => Array.isArray(state.categories) && state.categories.length > 0, [state.categories]);
100502
100540
  return _objectSpread2(_objectSpread2({}, state), {}, {
100503
100541
  hasFilters
@@ -100576,20 +100614,31 @@
100576
100614
  /**
100577
100615
  * Fetches agent configuration from the deploy-agent endpoint
100578
100616
  * @param {string} apiBaseUrl - Base URL for the API
100579
- * @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)
100580
100618
  * @returns {Promise<object>} Agent configuration object
100581
100619
  */
100582
- const fetchAgentConfig = async (apiBaseUrl, orgId) => {
100583
- if (!apiBaseUrl || !orgId) {
100584
- throw new Error("apiBaseUrl and orgId are required");
100620
+ const fetchAgentConfig = async (apiBaseUrl, getAuthToken) => {
100621
+ if (!apiBaseUrl) {
100622
+ throw new Error("apiBaseUrl is required");
100585
100623
  }
100586
100624
  try {
100587
- const url = "".concat(apiBaseUrl.replace(/\/$/, ''), "/api/v1/deploy-agent/").concat(orgId);
100625
+ const base = apiBaseUrl.replace(/\/$/, '');
100626
+ const url = "".concat(base, "/api/v1/deploy-agent");
100627
+ const headers = {
100628
+ "Content-Type": "application/json"
100629
+ };
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
+ }
100588
100639
  const response = await fetch(url, {
100589
100640
  method: "GET",
100590
- headers: {
100591
- "Content-Type": "application/json"
100592
- },
100641
+ headers,
100593
100642
  credentials: "include"
100594
100643
  });
100595
100644
  if (!response.ok) {
@@ -100606,8 +100655,10 @@
100606
100655
  const ChatWidget = _ref => {
100607
100656
  var _agentConfig$concierg;
100608
100657
  let {
100609
- // Required props for dynamic config
100610
- orgId,
100658
+ /**
100659
+ * @param {Function|undefined} getAuthToken - async fn returning a Keycloak Bearer token (in-app JWT path)
100660
+ */
100661
+ getAuthToken,
100611
100662
  apiBaseUrl,
100612
100663
  // Optional override props (will override fetched config if provided)
100613
100664
  darkMode,
@@ -100658,14 +100709,14 @@
100658
100709
  // Fetch agent configuration on mount
100659
100710
  reactExports.useEffect(() => {
100660
100711
  const loadAgentConfig = async () => {
100661
- if (!orgId || !apiBaseUrl) {
100662
- setConfigError("orgId and apiBaseUrl are required");
100712
+ if (!apiBaseUrl) {
100713
+ setConfigError("apiBaseUrl is required");
100663
100714
  setIsLoadingConfig(false);
100664
100715
  return;
100665
100716
  }
100666
100717
  try {
100667
100718
  setIsLoadingConfig(true);
100668
- const config = await fetchAgentConfig(apiBaseUrl, orgId);
100719
+ const config = await fetchAgentConfig(apiBaseUrl, getAuthToken);
100669
100720
  setAgentConfig(config);
100670
100721
  setConfigError(null);
100671
100722
  } catch (error) {
@@ -100676,11 +100727,11 @@
100676
100727
  }
100677
100728
  };
100678
100729
  loadAgentConfig();
100679
- }, [orgId, apiBaseUrl]);
100730
+ }, [apiBaseUrl, getAuthToken]);
100680
100731
 
100681
100732
  // Map API response to widget props (use props as overrides if provided)
100682
100733
  const widgetConfig = reactExports.useMemo(() => {
100683
- 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;
100684
100735
  if (!agentConfig) {
100685
100736
  return null;
100686
100737
  }
@@ -100739,7 +100790,6 @@
100739
100790
  fontFamily: (_ref1 = fontFamily !== null && fontFamily !== void 0 ? fontFamily : typography.agentMessageBubbleFontFamily) !== null && _ref1 !== void 0 ? _ref1 : "Inter",
100740
100791
  fontSize: (_ref10 = fontSize !== null && fontSize !== void 0 ? fontSize : typography.agentMessageBubbleFontSize) !== null && _ref10 !== void 0 ? _ref10 : "14px",
100741
100792
  defaultLanguage: locale,
100742
- organizationId: orgId,
100743
100793
  headerTextBold: (_ref11 = headerTextBold !== null && headerTextBold !== void 0 ? headerTextBold : typography.headerTextBold) !== null && _ref11 !== void 0 ? _ref11 : false,
100744
100794
  headerTextItalic: (_ref12 = headerTextItalic !== null && headerTextItalic !== void 0 ? headerTextItalic : typography.headerTextItalic) !== null && _ref12 !== void 0 ? _ref12 : false,
100745
100795
  headerTextColor: (_typography$headerTex = typography.headerTextColor) !== null && _typography$headerTex !== void 0 ? _typography$headerTex : "#FFFFFF",
@@ -100776,9 +100826,12 @@
100776
100826
  // every message and tell the backend to skip citation generation.
100777
100827
  // Defaults to true so agents that haven't set the field keep showing
100778
100828
  // citations (matches the testing-lab / chatbot-preview default).
100779
- 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
100780
100833
  };
100781
- }, [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
100782
100835
  // CSAT dependencies
100783
100836
  // (kept minimal because these come from agentConfig)
100784
100837
  ]);
@@ -100797,7 +100850,7 @@
100797
100850
  quickQuestions: [],
100798
100851
  apiBaseUrl: apiBaseUrl || "",
100799
100852
  apiKey: apiKey || "",
100800
- organizationId: orgId || "",
100853
+ getAuthToken,
100801
100854
  autoOpen: false,
100802
100855
  openDelay: 0,
100803
100856
  darkMode: false,
@@ -100805,7 +100858,7 @@
100805
100858
  onboardingQuestions: [],
100806
100859
  onboardingEnabled: false,
100807
100860
  collectionPrompt: undefined
100808
- }), [apiBaseUrl, apiKey, orgId]);
100861
+ }), [apiBaseUrl, apiKey, getAuthToken]);
100809
100862
 
100810
100863
  // ALWAYS call hooks before any early returns (Rules of Hooks)
100811
100864
  const {
@@ -100854,6 +100907,7 @@
100854
100907
  // knowledge-api LoadBalancer.
100855
100908
  apiKey,
100856
100909
  organizationId: widgetConfig.organizationId,
100910
+ getAuthToken,
100857
100911
  autoOpen,
100858
100912
  openDelay,
100859
100913
  darkMode: widgetConfig.darkMode,
@@ -100880,24 +100934,33 @@
100880
100934
  // navigation (but not a page refresh — feedback is not written to
100881
100935
  // localStorage so it resets on reload as intended).
100882
100936
  updateMessageFeedback(messageContent, feedback);
100883
- if (!orgId || !conversationId || !apiBaseUrl) return;
100937
+ if (!conversationId || !apiBaseUrl) return;
100884
100938
  const base = apiBaseUrl.replace(/\/$/, "");
100885
- fetch("".concat(base, "/api/v1/support/feedback"), {
100886
- method: "POST",
100887
- headers: {
100888
- "Content-Type": "application/json",
100889
- "x-eshal-org": orgId
100890
- },
100891
- credentials: "include",
100892
- body: JSON.stringify({
100893
- messageContent,
100894
- conversationId,
100895
- organizationId: orgId,
100896
- feedback,
100897
- feedbackText
100898
- })
100899
- }).catch(err => console.error("Feedback submission error:", err));
100900
- }, [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]);
100901
100964
 
100902
100965
  // Refresh chat handler — reset conversation
100903
100966
  const handleRefreshChat = reactExports.useCallback(() => {
@@ -100913,7 +100976,7 @@
100913
100976
  const {
100914
100977
  categories: filterCategories,
100915
100978
  hasFilters: showFilterStep
100916
- } = useFilterCatalog(apiBaseUrl, orgId);
100979
+ } = useFilterCatalog(apiBaseUrl, getAuthToken);
100917
100980
 
100918
100981
  // Copy chat handler — copy all messages to clipboard
100919
100982
  const handleCopyChat = reactExports.useCallback(() => {
@@ -101018,7 +101081,7 @@
101018
101081
  showPoweredBy: widgetConfig.showPoweredBy,
101019
101082
  apiBaseUrl: apiBaseUrl,
101020
101083
  apiKey: apiKey,
101021
- organizationId: widgetConfig.organizationId,
101084
+ getAuthToken: getAuthToken,
101022
101085
  conversationId: conversationId,
101023
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,
101024
101087
  onboardingQuestions: widgetConfig.onboardingQuestions,
@@ -101166,11 +101229,10 @@
101166
101229
  }
101167
101230
 
101168
101231
  // Render component
101169
- // If orgId and apiBaseUrl are provided, use dynamic config
101170
- // Otherwise, use the old static config approach for backward compatibility
101171
- const widgetProps = config.orgId && config.apiBaseUrl ? _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({
101172
- // Required props
101173
- orgId: config.orgId,
101232
+ // Mode B proxy: apiBaseUrl alone is enough — no key or orgId in browser
101233
+ // In-app JWT: getAuthToken fn passed via config
101234
+ const hasNewAuth = config.apiBaseUrl;
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({
101174
101236
  apiBaseUrl: config.apiBaseUrl
101175
101237
  }, options.wsBaseUrl !== undefined && {
101176
101238
  wsBaseUrl: config.wsBaseUrl