@eshal-bot/chat-widget 0.1.67 → 0.1.68

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',
@@ -8790,17 +8791,17 @@
8790
8791
  * @param {string} apiBaseUrl - Frontend base URL, e.g. https://demo.eshal.ai
8791
8792
  * @returns {string} Voice WS base, e.g. wss://demo.eshal.ai/voice/ws
8792
8793
  */
8793
- const getWebSocketUrl = apiBaseUrl => {
8794
+ const getWebSocketUrl = (apiBaseUrl, wsBaseUrl) => {
8795
+ // If the backend returned a wsBaseUrl in agentConfig (or customer passed one
8796
+ // explicitly), use it directly — it already points at the correct host.
8797
+ if (wsBaseUrl) return wsBaseUrl.replace(/\/$/, '');
8794
8798
  if (!apiBaseUrl) {
8795
- // No base URL yet (config still loading) — safe public default.
8796
8799
  return 'wss://dev.eshal.ai/voice/ws';
8797
8800
  }
8798
8801
  try {
8799
8802
  const url = new URL(apiBaseUrl);
8800
- // http→ws, https→wss; point at the frontend voice-proxy path.
8801
8803
  url.protocol = url.protocol.replace(/^http/, 'ws');
8802
8804
  url.pathname = '/voice/ws';
8803
- // Clean URL without a trailing slash.
8804
8805
  return url.toString().replace(/\/$/, '');
8805
8806
  } catch (error) {
8806
8807
  console.error("Invalid API base URL provided for WebSocket:", error);
@@ -9144,9 +9145,10 @@
9144
9145
  welcomeMessage,
9145
9146
  quickQuestions = [],
9146
9147
  apiBaseUrl,
9148
+ wsBaseUrl,
9147
9149
  apiKey,
9148
9150
  organizationId,
9149
- secretKey,
9151
+ getAuthToken,
9150
9152
  autoOpen,
9151
9153
  openDelay,
9152
9154
  darkMode,
@@ -9314,11 +9316,9 @@
9314
9316
  const visitorProfileRef = reactExports.useRef(null);
9315
9317
  const websocketUrl = reactExports.useMemo(() => {
9316
9318
  if (!organizationId) return null;
9317
- // Voice WS base is always derived from apiBaseUrl (the frontend origin
9318
- // the widget is embedded with) → wss://{host}/voice/ws. No override.
9319
- const resolvedWsBaseUrl = getWebSocketUrl(apiBaseUrl);
9319
+ const resolvedWsBaseUrl = getWebSocketUrl(apiBaseUrl, wsBaseUrl);
9320
9320
  return "".concat(resolvedWsBaseUrl, "/").concat(organizationId, "/").concat(bidiSessionId);
9321
- }, [apiBaseUrl, organizationId, bidiSessionId]);
9321
+ }, [apiBaseUrl, wsBaseUrl, organizationId, bidiSessionId]);
9322
9322
  const getNextMessageId = reactExports.useCallback(() => {
9323
9323
  const id = "msg-".concat(messageIdRef.current);
9324
9324
  messageIdRef.current += 1;
@@ -9783,7 +9783,7 @@
9783
9783
  if (apiType === 'userName' || apiType === 'email' || apiType === 'phone') {
9784
9784
  // For onboarding fields, just consume the stream without processing.
9785
9785
  // Single attempt — no retry, no user-visible partial state to clean up.
9786
- const stream = await sendStreamingRequest(chatRequest, apiBaseUrl, organizationId, secretKey);
9786
+ const stream = await sendStreamingRequest(chatRequest, apiBaseUrl, getAuthToken);
9787
9787
  const reader = stream.getReader();
9788
9788
  const decoder = new TextDecoder();
9789
9789
  while (true) {
@@ -9802,7 +9802,7 @@
9802
9802
  let lastErr;
9803
9803
  for (let attempt = 0; attempt <= STREAM_RETRY_DELAYS_MS.length; attempt += 1) {
9804
9804
  try {
9805
- const stream = await sendStreamingRequest(chatRequest, apiBaseUrl, organizationId, secretKey);
9805
+ const stream = await sendStreamingRequest(chatRequest, apiBaseUrl, getAuthToken);
9806
9806
  await processStream(stream, setMessages);
9807
9807
  updateActivity(organizationId);
9808
9808
  return {
@@ -9830,7 +9830,7 @@
9830
9830
 
9831
9831
  // Non-chat, non-onboarding apiType (shouldn't currently happen):
9832
9832
  // fire and forget, single attempt.
9833
- await sendStreamingRequest(chatRequest, apiBaseUrl, organizationId);
9833
+ await sendStreamingRequest(chatRequest, apiBaseUrl, getAuthToken);
9834
9834
  updateActivity(organizationId);
9835
9835
  return {
9836
9836
  success: true
@@ -9866,7 +9866,7 @@
9866
9866
  // UX-M09: release the concurrency guard so the next send can proceed.
9867
9867
  isSendingRef.current = false;
9868
9868
  }
9869
- }, [apiBaseUrl, apiKey, getNextMessageId, organizationId, messages, bidiSessionId, routingChoice, widgetFilter, provideCitations]);
9869
+ }, [apiBaseUrl, apiKey, getNextMessageId, getAuthToken, messages, bidiSessionId, routingChoice, widgetFilter, provideCitations]);
9870
9870
  const handleOnboardingFormSubmit = reactExports.useCallback(async answers => {
9871
9871
  const questions = getOnboardingQuestions(onboardingQuestions);
9872
9872
  if (questions.length === 0) return;
@@ -94384,7 +94384,7 @@
94384
94384
  let {
94385
94385
  sources = [],
94386
94386
  apiBaseUrl,
94387
- organizationId,
94387
+ getAuthToken,
94388
94388
  isDark,
94389
94389
  textColor,
94390
94390
  fontFamily,
@@ -94410,7 +94410,7 @@
94410
94410
  // corpora that have none of the above.
94411
94411
  const _downloadKey = s => s.url || s.source_id || s.source_name || "";
94412
94412
  const handleFileDownload = async source => {
94413
- if (!apiBaseUrl || !organizationId) return;
94413
+ if (!apiBaseUrl) return;
94414
94414
  // SharePoint proxy URLs are GET endpoints that stream the file
94415
94415
  // directly via Microsoft Graph using the stored OAuth credential.
94416
94416
  // Bypass the legacy POST /file-download lookup (which expects an
@@ -94436,11 +94436,14 @@
94436
94436
  // set, or future deployments that set it) pass through
94437
94437
  // unchanged.
94438
94438
  const spUrl = source.url.startsWith("http") ? source.url : "".concat(apiBaseUrl.replace(/\/$/, "")).concat(source.url);
94439
+ const spHeaders = {};
94440
+ if (typeof getAuthToken === 'function') {
94441
+ const token = await getAuthToken();
94442
+ if (token) spHeaders['Authorization'] = "Bearer ".concat(token);
94443
+ }
94439
94444
  res = await fetch(spUrl, {
94440
94445
  method: "GET",
94441
- headers: {
94442
- "x-eshal-org": organizationId
94443
- },
94446
+ headers: spHeaders,
94444
94447
  credentials: "include"
94445
94448
  });
94446
94449
  } else {
@@ -94456,15 +94459,18 @@
94456
94459
  // - source.source_name (the user's original filename)
94457
94460
  // Sending both lets the server skip the Milvus resolution
94458
94461
  // AND advertise the readable name in Content-Disposition.
94462
+ const dlHeaders = {
94463
+ "Content-Type": "application/json"
94464
+ };
94465
+ if (typeof getAuthToken === 'function') {
94466
+ const token = await getAuthToken();
94467
+ if (token) dlHeaders['Authorization'] = "Bearer ".concat(token);
94468
+ }
94459
94469
  res = await fetch(url, {
94460
94470
  method: "POST",
94461
- headers: {
94462
- "Content-Type": "application/json",
94463
- "x-eshal-org": organizationId
94464
- },
94471
+ headers: dlHeaders,
94465
94472
  credentials: "include",
94466
94473
  body: JSON.stringify({
94467
- org_id: organizationId,
94468
94474
  source_id: source.source_id,
94469
94475
  file_name: source.url || "",
94470
94476
  source_name: source.source_name || ""
@@ -94649,7 +94655,7 @@
94649
94655
  conciergeName,
94650
94656
  companyName,
94651
94657
  apiBaseUrl,
94652
- organizationId,
94658
+ getAuthToken,
94653
94659
  onPromptSuggestionClick,
94654
94660
  allowFeedback = true,
94655
94661
  provideCitations = true,
@@ -94821,7 +94827,7 @@
94821
94827
  }), /*#__PURE__*/jsxRuntimeExports.jsx(MessageSources, {
94822
94828
  sources: message.sources,
94823
94829
  apiBaseUrl: apiBaseUrl,
94824
- organizationId: organizationId,
94830
+ getAuthToken: getAuthToken,
94825
94831
  isDark: isDark,
94826
94832
  textColor: messageTextColor,
94827
94833
  fontFamily: messageFontFamily,
@@ -94847,7 +94853,7 @@
94847
94853
  }), /*#__PURE__*/jsxRuntimeExports.jsx(MessageSources, {
94848
94854
  sources: message.sources,
94849
94855
  apiBaseUrl: apiBaseUrl,
94850
- organizationId: organizationId,
94856
+ getAuthToken: getAuthToken,
94851
94857
  isDark: isDark,
94852
94858
  textColor: messageTextColor,
94853
94859
  fontFamily: messageFontFamily,
@@ -95027,7 +95033,7 @@
95027
95033
  conciergeName,
95028
95034
  companyName,
95029
95035
  apiBaseUrl,
95030
- organizationId,
95036
+ getAuthToken,
95031
95037
  onPromptSuggestionClick,
95032
95038
  allowFeedback,
95033
95039
  provideCitations,
@@ -95071,7 +95077,7 @@
95071
95077
  conciergeName: conciergeName,
95072
95078
  companyName: companyName,
95073
95079
  apiBaseUrl: apiBaseUrl,
95074
- organizationId: organizationId,
95080
+ getAuthToken: getAuthToken,
95075
95081
  onPromptSuggestionClick: onPromptSuggestionClick,
95076
95082
  allowFeedback: allowFeedback,
95077
95083
  provideCitations: provideCitations,
@@ -95916,7 +95922,7 @@
95916
95922
  conciergeName,
95917
95923
  companyName,
95918
95924
  apiBaseUrl,
95919
- organizationId,
95925
+ getAuthToken,
95920
95926
  onPromptSuggestionClick,
95921
95927
  conversationResetKey,
95922
95928
  allowFeedback,
@@ -96098,7 +96104,7 @@
96098
96104
  conciergeName: conciergeName,
96099
96105
  companyName: companyName,
96100
96106
  apiBaseUrl: apiBaseUrl,
96101
- organizationId: organizationId,
96107
+ getAuthToken: getAuthToken,
96102
96108
  allowFeedback: allowFeedback,
96103
96109
  provideCitations: provideCitations,
96104
96110
  onFeedback: onFeedback,
@@ -96130,7 +96136,7 @@
96130
96136
  conciergeName: conciergeName,
96131
96137
  companyName: companyName,
96132
96138
  apiBaseUrl: apiBaseUrl,
96133
- organizationId: organizationId,
96139
+ getAuthToken: getAuthToken,
96134
96140
  headerTextColor: headerTextColor
96135
96141
  }, "skeleton-waiting");
96136
96142
  }
@@ -99645,8 +99651,7 @@
99645
99651
  showPoweredBy = true,
99646
99652
  apiBaseUrl,
99647
99653
  apiKey,
99648
- organizationId,
99649
- secretKey,
99654
+ getAuthToken,
99650
99655
  conversationId,
99651
99656
  conciergeId,
99652
99657
  onboardingQuestions = [],
@@ -99794,7 +99799,7 @@
99794
99799
  setCsatSubmitted(submitted);
99795
99800
  if (submitted) setShowCsat(false);
99796
99801
  } catch (_unused4) {}
99797
- }, [conversationId, organizationId]);
99802
+ }, [conversationId]);
99798
99803
 
99799
99804
  // Auto-scroll hook
99800
99805
  useAutoScroll(messagesContainerRef, [messages, isLoading], {
@@ -99932,14 +99937,15 @@
99932
99937
  const handleCsatRating = async (rating, format) => {
99933
99938
  setCsatSubmitted(true); // optimistic UI
99934
99939
  setShowCsat(false);
99935
- markCsatSubmitted(organizationId); // persist so page refresh doesn't re-show CSAT
99940
+ markCsatSubmitted(undefined); // persist so page refresh doesn't re-show CSAT
99936
99941
 
99937
99942
  try {
99938
99943
  const headers = {
99939
99944
  "Content-Type": "application/json"
99940
99945
  };
99941
- if (organizationId) {
99942
- headers["x-eshal-org"] = organizationId;
99946
+ if (typeof getAuthToken === 'function') {
99947
+ const token = await getAuthToken();
99948
+ if (token) headers['Authorization'] = "Bearer ".concat(token);
99943
99949
  }
99944
99950
  const response = await fetch("".concat(apiBaseUrl.replace(/\/$/, ""), "/api/v1/support/csat"), {
99945
99951
  method: "POST",
@@ -99949,7 +99955,6 @@
99949
99955
  conversationId,
99950
99956
  rating,
99951
99957
  ratingFormat: format,
99952
- organizationId,
99953
99958
  conciergeId
99954
99959
  })
99955
99960
  });
@@ -99973,8 +99978,9 @@
99973
99978
  const headers = {
99974
99979
  "Content-Type": "application/json"
99975
99980
  };
99976
- if (organizationId) {
99977
- headers["x-eshal-org"] = organizationId;
99981
+ if (typeof getAuthToken === 'function') {
99982
+ const token = await getAuthToken();
99983
+ if (token) headers['Authorization'] = "Bearer ".concat(token);
99978
99984
  }
99979
99985
  const ratingFormat = normalizeCsatFormat(csatFormat);
99980
99986
  const response = await fetch("".concat(apiBaseUrl.replace(/\/$/, ""), "/api/v1/support/csat"), {
@@ -99985,7 +99991,6 @@
99985
99991
  conversationId,
99986
99992
  rating: String(resolveRating),
99987
99993
  ratingFormat,
99988
- organizationId,
99989
99994
  conciergeId,
99990
99995
  feedback: resolveFeedbackText || undefined
99991
99996
  })
@@ -100019,7 +100024,7 @@
100019
100024
  } finally {
100020
100025
  setIsSubmittingResolve(false);
100021
100026
  }
100022
- }, [isCsatEnabled, isSubmittingResolve, isConversationResolved, resolveRating, resolveFeedbackText, conversationId, organizationId, conciergeId, apiBaseUrl, csatFormat, onResetConversation, onRefreshChat, onWidgetFilterChange]);
100027
+ }, [isCsatEnabled, isSubmittingResolve, isConversationResolved, resolveRating, resolveFeedbackText, conversationId, getAuthToken, conciergeId, apiBaseUrl, csatFormat, onResetConversation, onRefreshChat, onWidgetFilterChange]);
100023
100028
 
100024
100029
  // Handle X button: first click shows form, second click closes
100025
100030
  const handleCloseClick = reactExports.useCallback(() => {
@@ -100357,7 +100362,7 @@
100357
100362
  conciergeName: conciergeName,
100358
100363
  companyName: companyName,
100359
100364
  apiBaseUrl: apiBaseUrl,
100360
- organizationId: organizationId,
100365
+ getAuthToken: getAuthToken,
100361
100366
  conversationResetKey: conversationId,
100362
100367
  allowFeedback: allowFeedback,
100363
100368
  provideCitations: provideCitations,
@@ -100442,8 +100447,7 @@
100442
100447
 
100443
100448
  /**
100444
100449
  * @param {string|undefined|null} apiBaseUrl
100445
- * @param {string|undefined|null} orgId
100446
- * @param {string|undefined|null} secretKey — Mode A/B: orgId resolved server-side
100450
+ * @param {Function|undefined|null} getAuthToken - async fn returning a Bearer token (in-app JWT path), or undefined/null (Mode B proxy)
100447
100451
  * @returns {{
100448
100452
  * categories: Array<{id:string,label:string,count:number,tags:string[]}>,
100449
100453
  * loading: boolean,
@@ -100451,17 +100455,15 @@
100451
100455
  * hasFilters: boolean,
100452
100456
  * }}
100453
100457
  */
100454
- function useFilterCatalog(apiBaseUrl, orgId, secretKey) {
100455
- // Mode A: secretKey present, orgId null → key-based endpoint
100456
- // Mode C: orgId present (legacy) → orgId-in-path endpoint
100457
- const hasAuth = !!(apiBaseUrl && (orgId || secretKey));
100458
+ function useFilterCatalog(apiBaseUrl, getAuthToken) {
100459
+ const hasAuth = !!apiBaseUrl;
100458
100460
  const [state, setState] = reactExports.useState({
100459
100461
  categories: [],
100460
100462
  loading: hasAuth,
100461
100463
  error: null
100462
100464
  });
100463
100465
  reactExports.useEffect(() => {
100464
- if (!apiBaseUrl || !orgId && !secretKey) {
100466
+ if (!apiBaseUrl) {
100465
100467
  setState({
100466
100468
  categories: [],
100467
100469
  loading: false,
@@ -100469,7 +100471,7 @@
100469
100471
  });
100470
100472
  return;
100471
100473
  }
100472
- const cacheKey = secretKey ? "".concat(apiBaseUrl, "|__key__") : "".concat(apiBaseUrl, "|").concat(orgId);
100474
+ const cacheKey = getAuthToken ? "".concat(apiBaseUrl, "|__session__") : "".concat(apiBaseUrl, "|__proxy__");
100473
100475
  const cached = _PROCESS_CACHE.get(cacheKey);
100474
100476
  if (cached) {
100475
100477
  setState({
@@ -100485,45 +100487,54 @@
100485
100487
  error: null
100486
100488
  }));
100487
100489
  const base = apiBaseUrl.replace(/\/$/, "");
100488
- const url = secretKey ? "".concat(base, "/api/v1/widget/filters") : "".concat(base, "/api/v1/widget/filters/").concat(encodeURIComponent(orgId));
100489
- const headers = {
100490
- Accept: "application/json"
100491
- };
100492
- if (secretKey) headers["x-eshal-key"] = secretKey;
100493
- fetch(url, {
100494
- method: "GET",
100495
- headers
100496
- }).then(async res => {
100497
- if (!res.ok) {
100498
- throw new Error("filter catalog HTTP ".concat(res.status));
100490
+ const url = "".concat(base, "/api/v1/widget/filters");
100491
+ (async () => {
100492
+ try {
100493
+ const headers = {
100494
+ Accept: "application/json"
100495
+ };
100496
+ // In-app JWT path: set Authorization header
100497
+ // Mode B proxy: no auth header; proxy handles org resolution
100498
+ if (typeof getAuthToken === 'function') {
100499
+ const token = await getAuthToken();
100500
+ if (token) {
100501
+ headers['Authorization'] = "Bearer ".concat(token);
100502
+ }
100503
+ }
100504
+ const res = await fetch(url, {
100505
+ method: "GET",
100506
+ headers
100507
+ });
100508
+ if (!res.ok) {
100509
+ throw new Error("filter catalog HTTP ".concat(res.status));
100510
+ }
100511
+ const body = await res.json();
100512
+ const categories = Array.isArray(body === null || body === void 0 ? void 0 : body.categories) ? body.categories : [];
100513
+ if (cancelled) return;
100514
+ _PROCESS_CACHE.set(cacheKey, categories);
100515
+ setState({
100516
+ categories,
100517
+ loading: false,
100518
+ error: null
100519
+ });
100520
+ } catch (err) {
100521
+ if (cancelled) return;
100522
+ // Fail-soft: log and present empty so the UI hides itself.
100523
+ // We do NOT bubble the error to the caller because chat
100524
+ // should keep working even if the catalog is unreachable.
100525
+ // eslint-disable-next-line no-console
100526
+ console.warn("[useFilterCatalog] catalog fetch failed:", err);
100527
+ setState({
100528
+ categories: [],
100529
+ loading: false,
100530
+ error: err
100531
+ });
100499
100532
  }
100500
- const body = await res.json();
100501
- return Array.isArray(body === null || body === void 0 ? void 0 : body.categories) ? body.categories : [];
100502
- }).then(categories => {
100503
- if (cancelled) return;
100504
- _PROCESS_CACHE.set(cacheKey, categories);
100505
- setState({
100506
- categories,
100507
- loading: false,
100508
- error: null
100509
- });
100510
- }).catch(err => {
100511
- if (cancelled) return;
100512
- // Fail-soft: log and present empty so the UI hides itself.
100513
- // We do NOT bubble the error to the caller because chat
100514
- // should keep working even if the catalog is unreachable.
100515
- // eslint-disable-next-line no-console
100516
- console.warn("[useFilterCatalog] catalog fetch failed:", err);
100517
- setState({
100518
- categories: [],
100519
- loading: false,
100520
- error: err
100521
- });
100522
- });
100533
+ })();
100523
100534
  return () => {
100524
100535
  cancelled = true;
100525
100536
  };
100526
- }, [apiBaseUrl, orgId, secretKey]);
100537
+ }, [apiBaseUrl, getAuthToken]);
100527
100538
  const hasFilters = reactExports.useMemo(() => Array.isArray(state.categories) && state.categories.length > 0, [state.categories]);
100528
100539
  return _objectSpread2(_objectSpread2({}, state), {}, {
100529
100540
  hasFilters
@@ -100602,24 +100613,28 @@
100602
100613
  /**
100603
100614
  * Fetches agent configuration from the deploy-agent endpoint
100604
100615
  * @param {string} apiBaseUrl - Base URL for the API
100605
- * @param {string} orgId - Organization ID
100616
+ * @param {Function|undefined} getAuthToken - async fn returning a Bearer token (in-app JWT path), or undefined (Mode B proxy)
100606
100617
  * @returns {Promise<object>} Agent configuration object
100607
100618
  */
100608
- const fetchAgentConfig = async (apiBaseUrl, orgId, secretKey) => {
100619
+ const fetchAgentConfig = async (apiBaseUrl, getAuthToken) => {
100609
100620
  if (!apiBaseUrl) {
100610
100621
  throw new Error("apiBaseUrl is required");
100611
100622
  }
100612
- // Mode A: secretKey in header, no orgId in URL
100613
- // Mode B (proxy): no secretKey, no orgId — proxy intercepts and signs the request
100614
- // Mode C legacy: orgId in URL path
100615
-
100616
100623
  try {
100617
100624
  const base = apiBaseUrl.replace(/\/$/, '');
100618
- const url = orgId ? "".concat(base, "/api/v1/deploy-agent/").concat(orgId) : "".concat(base, "/api/v1/deploy-agent");
100625
+ const url = "".concat(base, "/api/v1/deploy-agent");
100619
100626
  const headers = {
100620
100627
  "Content-Type": "application/json"
100621
100628
  };
100622
- if (secretKey) headers['x-eshal-key'] = secretKey;
100629
+
100630
+ // In-app JWT path: set Authorization header
100631
+ // Mode B proxy: no auth header; proxy handles org resolution
100632
+ if (typeof getAuthToken === 'function') {
100633
+ const token = await getAuthToken();
100634
+ if (token) {
100635
+ headers['Authorization'] = "Bearer ".concat(token);
100636
+ }
100637
+ }
100623
100638
  const response = await fetch(url, {
100624
100639
  method: "GET",
100625
100640
  headers,
@@ -100639,11 +100654,10 @@
100639
100654
  const ChatWidget = _ref => {
100640
100655
  var _agentConfig$concierg;
100641
100656
  let {
100642
- // Auth — at least one of secretKey (Mode A/B) or orgId (Mode C legacy) is needed.
100643
- secretKey,
100644
- // sk_live_... from ChatWidget.init({ secretKey, apiBaseUrl })
100645
- orgId,
100646
- // legacy — kept for backward compatibility
100657
+ /**
100658
+ * @param {Function|undefined} getAuthToken - async fn returning a Keycloak Bearer token (in-app JWT path)
100659
+ */
100660
+ getAuthToken,
100647
100661
  apiBaseUrl,
100648
100662
  // Optional override props (will override fetched config if provided)
100649
100663
  darkMode,
@@ -100699,13 +100713,9 @@
100699
100713
  setIsLoadingConfig(false);
100700
100714
  return;
100701
100715
  }
100702
- // Mode B proxy: neither secretKey nor orgId in browser — proxy handles auth
100703
-
100704
100716
  try {
100705
100717
  setIsLoadingConfig(true);
100706
- // Pass secretKey for Mode A/B — fetchAgentConfig will send it as a
100707
- // header and omit the orgId path segment. Pass orgId for Mode C legacy.
100708
- const config = await fetchAgentConfig(apiBaseUrl, orgId, secretKey);
100718
+ const config = await fetchAgentConfig(apiBaseUrl, getAuthToken);
100709
100719
  setAgentConfig(config);
100710
100720
  setConfigError(null);
100711
100721
  } catch (error) {
@@ -100716,11 +100726,11 @@
100716
100726
  }
100717
100727
  };
100718
100728
  loadAgentConfig();
100719
- }, [orgId, secretKey, apiBaseUrl]);
100729
+ }, [apiBaseUrl, getAuthToken]);
100720
100730
 
100721
100731
  // Map API response to widget props (use props as overrides if provided)
100722
100732
  const widgetConfig = reactExports.useMemo(() => {
100723
- 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;
100733
+ 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, _agentConfig$wsBaseUr;
100724
100734
  if (!agentConfig) {
100725
100735
  return null;
100726
100736
  }
@@ -100779,7 +100789,6 @@
100779
100789
  fontFamily: (_ref1 = fontFamily !== null && fontFamily !== void 0 ? fontFamily : typography.agentMessageBubbleFontFamily) !== null && _ref1 !== void 0 ? _ref1 : "Inter",
100780
100790
  fontSize: (_ref10 = fontSize !== null && fontSize !== void 0 ? fontSize : typography.agentMessageBubbleFontSize) !== null && _ref10 !== void 0 ? _ref10 : "14px",
100781
100791
  defaultLanguage: locale,
100782
- organizationId: orgId,
100783
100792
  headerTextBold: (_ref11 = headerTextBold !== null && headerTextBold !== void 0 ? headerTextBold : typography.headerTextBold) !== null && _ref11 !== void 0 ? _ref11 : false,
100784
100793
  headerTextItalic: (_ref12 = headerTextItalic !== null && headerTextItalic !== void 0 ? headerTextItalic : typography.headerTextItalic) !== null && _ref12 !== void 0 ? _ref12 : false,
100785
100794
  headerTextColor: (_typography$headerTex = typography.headerTextColor) !== null && _typography$headerTex !== void 0 ? _typography$headerTex : "#FFFFFF",
@@ -100816,9 +100825,13 @@
100816
100825
  // every message and tell the backend to skip citation generation.
100817
100826
  // Defaults to true so agents that haven't set the field keep showing
100818
100827
  // citations (matches the testing-lab / chatbot-preview default).
100819
- provideCitations: (_behavior$provideCita = behavior.provideCitations) !== null && _behavior$provideCita !== void 0 ? _behavior$provideCita : true
100828
+ provideCitations: (_behavior$provideCita = behavior.provideCitations) !== null && _behavior$provideCita !== void 0 ? _behavior$provideCita : true,
100829
+ // organizationId from the backend response — used internally for
100830
+ // session isolation (localStorage keys) and WS paths. Not a user prop.
100831
+ organizationId: (_agentConfig$organiza = agentConfig.organizationId) !== null && _agentConfig$organiza !== void 0 ? _agentConfig$organiza : null,
100832
+ wsBaseUrl: (_agentConfig$wsBaseUr = agentConfig.wsBaseUrl) !== null && _agentConfig$wsBaseUr !== void 0 ? _agentConfig$wsBaseUr : null
100820
100833
  };
100821
- }, [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
100822
100835
  // CSAT dependencies
100823
100836
  // (kept minimal because these come from agentConfig)
100824
100837
  ]);
@@ -100837,7 +100850,7 @@
100837
100850
  quickQuestions: [],
100838
100851
  apiBaseUrl: apiBaseUrl || "",
100839
100852
  apiKey: apiKey || "",
100840
- organizationId: orgId || "",
100853
+ getAuthToken,
100841
100854
  autoOpen: false,
100842
100855
  openDelay: 0,
100843
100856
  darkMode: false,
@@ -100845,7 +100858,7 @@
100845
100858
  onboardingQuestions: [],
100846
100859
  onboardingEnabled: false,
100847
100860
  collectionPrompt: undefined
100848
- }), [apiBaseUrl, apiKey, orgId]);
100861
+ }), [apiBaseUrl, apiKey, getAuthToken]);
100849
100862
 
100850
100863
  // ALWAYS call hooks before any early returns (Rules of Hooks)
100851
100864
  const {
@@ -100887,14 +100900,10 @@
100887
100900
  welcomeMessage: widgetConfig.welcomeMessage,
100888
100901
  quickQuestions: widgetConfig.quickQuestions,
100889
100902
  apiBaseUrl,
100890
- // Voice WS endpoint is derived entirely from apiBaseUrl inside
100891
- // useChatState (→ wss://{host}/voice/ws, the frontend voice proxy).
100892
- // Nothing is forwarded/overridable here, so a stale backend or embed
100893
- // wsBaseUrl can't point voice at the old (now non-public)
100894
- // knowledge-api LoadBalancer.
100903
+ wsBaseUrl: widgetConfig.wsBaseUrl,
100895
100904
  apiKey,
100896
100905
  organizationId: widgetConfig.organizationId,
100897
- secretKey,
100906
+ getAuthToken,
100898
100907
  autoOpen,
100899
100908
  openDelay,
100900
100909
  darkMode: widgetConfig.darkMode,
@@ -100921,25 +100930,33 @@
100921
100930
  // navigation (but not a page refresh — feedback is not written to
100922
100931
  // localStorage so it resets on reload as intended).
100923
100932
  updateMessageFeedback(messageContent, feedback);
100924
- if (!orgId && !secretKey || !conversationId || !apiBaseUrl) return;
100933
+ if (!conversationId || !apiBaseUrl) return;
100925
100934
  const base = apiBaseUrl.replace(/\/$/, "");
100926
- const feedbackHeaders = {
100927
- "Content-Type": "application/json"
100928
- };
100929
- if (secretKey) feedbackHeaders['x-eshal-key'] = secretKey;else if (orgId) feedbackHeaders['x-eshal-org'] = orgId;
100930
- fetch("".concat(base, "/api/v1/support/feedback"), {
100931
- method: "POST",
100932
- headers: feedbackHeaders,
100933
- credentials: "include",
100934
- body: JSON.stringify({
100935
- messageContent,
100936
- conversationId,
100937
- organizationId: orgId,
100938
- feedback,
100939
- feedbackText
100940
- })
100941
- }).catch(err => console.error("Feedback submission error:", err));
100942
- }, [orgId, conversationId, apiBaseUrl, updateMessageFeedback]);
100935
+ (async () => {
100936
+ try {
100937
+ const feedbackHeaders = {
100938
+ "Content-Type": "application/json"
100939
+ };
100940
+ if (typeof getAuthToken === 'function') {
100941
+ const token = await getAuthToken();
100942
+ if (token) feedbackHeaders['Authorization'] = "Bearer ".concat(token);
100943
+ }
100944
+ await fetch("".concat(base, "/api/v1/support/feedback"), {
100945
+ method: "POST",
100946
+ headers: feedbackHeaders,
100947
+ credentials: "include",
100948
+ body: JSON.stringify({
100949
+ messageContent,
100950
+ conversationId,
100951
+ feedback,
100952
+ feedbackText
100953
+ })
100954
+ });
100955
+ } catch (err) {
100956
+ console.error("Feedback submission error:", err);
100957
+ }
100958
+ })();
100959
+ }, [conversationId, apiBaseUrl, getAuthToken, updateMessageFeedback]);
100943
100960
 
100944
100961
  // Refresh chat handler — reset conversation
100945
100962
  const handleRefreshChat = reactExports.useCallback(() => {
@@ -100955,7 +100972,7 @@
100955
100972
  const {
100956
100973
  categories: filterCategories,
100957
100974
  hasFilters: showFilterStep
100958
- } = useFilterCatalog(apiBaseUrl, orgId, secretKey);
100975
+ } = useFilterCatalog(apiBaseUrl, getAuthToken);
100959
100976
 
100960
100977
  // Copy chat handler — copy all messages to clipboard
100961
100978
  const handleCopyChat = reactExports.useCallback(() => {
@@ -101060,8 +101077,7 @@
101060
101077
  showPoweredBy: widgetConfig.showPoweredBy,
101061
101078
  apiBaseUrl: apiBaseUrl,
101062
101079
  apiKey: apiKey,
101063
- organizationId: widgetConfig.organizationId,
101064
- secretKey: secretKey,
101080
+ getAuthToken: getAuthToken,
101065
101081
  conversationId: conversationId,
101066
101082
  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,
101067
101083
  onboardingQuestions: widgetConfig.onboardingQuestions,
@@ -101209,15 +101225,10 @@
101209
101225
  }
101210
101226
 
101211
101227
  // Render component
101212
- // Mode A/B: secretKey present — orgId derived server-side from the key
101213
- // Mode C legacy: orgId in config — backward compatible
101214
- // Both: apiBaseUrl required
101215
- // Mode B proxy: apiBaseUrl alone is enough — no secretKey or orgId in browser
101228
+ // Mode B proxy: apiBaseUrl alone is enough — no key or orgId in browser
101229
+ // In-app JWT: getAuthToken fn passed via config
101216
101230
  const hasNewAuth = config.apiBaseUrl;
101217
101231
  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({
101218
- // Auth props — secretKey replaces orgId in Mode A/B
101219
- secretKey: config.secretKey || null,
101220
- orgId: config.orgId || null,
101221
101232
  apiBaseUrl: config.apiBaseUrl
101222
101233
  }, options.wsBaseUrl !== undefined && {
101223
101234
  wsBaseUrl: config.wsBaseUrl