@eshal-bot/chat-widget 0.1.64 → 0.1.65

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.
@@ -8011,14 +8011,18 @@
8011
8011
  * @param {object} request - Chat request object
8012
8012
  * @param {string} apiBaseUrl - Base URL for the API
8013
8013
  */
8014
- const sendStreamingRequest = async (request, apiBaseUrl, organizationId) => {
8014
+ const sendStreamingRequest = async (request, apiBaseUrl, organizationId, secretKey) => {
8015
8015
  const headers = {
8016
8016
  'Content-Type': 'application/json',
8017
8017
  Accept: 'text/event-stream'
8018
8018
  };
8019
8019
 
8020
- // Add organization ID header if provided
8021
- if (organizationId) {
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) {
8022
8026
  headers['x-eshal-org'] = organizationId;
8023
8027
  }
8024
8028
  const response = await fetch("".concat(apiBaseUrl.replace(/\/$/, ''), "/api/v1/support"), {
@@ -9129,6 +9133,7 @@
9129
9133
  apiBaseUrl,
9130
9134
  apiKey,
9131
9135
  organizationId,
9136
+ secretKey,
9132
9137
  autoOpen,
9133
9138
  openDelay,
9134
9139
  darkMode,
@@ -9765,7 +9770,7 @@
9765
9770
  if (apiType === 'userName' || apiType === 'email' || apiType === 'phone') {
9766
9771
  // For onboarding fields, just consume the stream without processing.
9767
9772
  // Single attempt — no retry, no user-visible partial state to clean up.
9768
- const stream = await sendStreamingRequest(chatRequest, apiBaseUrl, organizationId);
9773
+ const stream = await sendStreamingRequest(chatRequest, apiBaseUrl, organizationId, secretKey);
9769
9774
  const reader = stream.getReader();
9770
9775
  const decoder = new TextDecoder();
9771
9776
  while (true) {
@@ -9784,7 +9789,7 @@
9784
9789
  let lastErr;
9785
9790
  for (let attempt = 0; attempt <= STREAM_RETRY_DELAYS_MS.length; attempt += 1) {
9786
9791
  try {
9787
- const stream = await sendStreamingRequest(chatRequest, apiBaseUrl, organizationId);
9792
+ const stream = await sendStreamingRequest(chatRequest, apiBaseUrl, organizationId, secretKey);
9788
9793
  await processStream(stream, setMessages);
9789
9794
  updateActivity(organizationId);
9790
9795
  return {
@@ -99628,6 +99633,7 @@
99628
99633
  apiBaseUrl,
99629
99634
  apiKey,
99630
99635
  organizationId,
99636
+ secretKey,
99631
99637
  conversationId,
99632
99638
  conciergeId,
99633
99639
  onboardingQuestions = [],
@@ -100424,6 +100430,7 @@
100424
100430
  /**
100425
100431
  * @param {string|undefined|null} apiBaseUrl
100426
100432
  * @param {string|undefined|null} orgId
100433
+ * @param {string|undefined|null} secretKey — Mode A/B: orgId resolved server-side
100427
100434
  * @returns {{
100428
100435
  * categories: Array<{id:string,label:string,count:number,tags:string[]}>,
100429
100436
  * loading: boolean,
@@ -100431,14 +100438,17 @@
100431
100438
  * hasFilters: boolean,
100432
100439
  * }}
100433
100440
  */
100434
- function useFilterCatalog(apiBaseUrl, orgId) {
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));
100435
100445
  const [state, setState] = reactExports.useState({
100436
100446
  categories: [],
100437
- loading: !!(apiBaseUrl && orgId),
100447
+ loading: hasAuth,
100438
100448
  error: null
100439
100449
  });
100440
100450
  reactExports.useEffect(() => {
100441
- if (!apiBaseUrl || !orgId) {
100451
+ if (!apiBaseUrl || !orgId && !secretKey) {
100442
100452
  setState({
100443
100453
  categories: [],
100444
100454
  loading: false,
@@ -100446,8 +100456,8 @@
100446
100456
  });
100447
100457
  return;
100448
100458
  }
100449
- const key = "".concat(apiBaseUrl, "|").concat(orgId);
100450
- const cached = _PROCESS_CACHE.get(key);
100459
+ const cacheKey = secretKey ? "".concat(apiBaseUrl, "|__key__") : "".concat(apiBaseUrl, "|").concat(orgId);
100460
+ const cached = _PROCESS_CACHE.get(cacheKey);
100451
100461
  if (cached) {
100452
100462
  setState({
100453
100463
  categories: cached,
@@ -100461,12 +100471,15 @@
100461
100471
  loading: true,
100462
100472
  error: null
100463
100473
  }));
100464
- const url = "".concat(apiBaseUrl.replace(/\/$/, ""), "/api/v1/widget/filters/").concat(encodeURIComponent(orgId));
100474
+ 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;
100465
100480
  fetch(url, {
100466
100481
  method: "GET",
100467
- headers: {
100468
- Accept: "application/json"
100469
- }
100482
+ headers
100470
100483
  }).then(async res => {
100471
100484
  if (!res.ok) {
100472
100485
  throw new Error("filter catalog HTTP ".concat(res.status));
@@ -100475,7 +100488,7 @@
100475
100488
  return Array.isArray(body === null || body === void 0 ? void 0 : body.categories) ? body.categories : [];
100476
100489
  }).then(categories => {
100477
100490
  if (cancelled) return;
100478
- _PROCESS_CACHE.set(key, categories);
100491
+ _PROCESS_CACHE.set(cacheKey, categories);
100479
100492
  setState({
100480
100493
  categories,
100481
100494
  loading: false,
@@ -100497,7 +100510,7 @@
100497
100510
  return () => {
100498
100511
  cancelled = true;
100499
100512
  };
100500
- }, [apiBaseUrl, orgId]);
100513
+ }, [apiBaseUrl, orgId, secretKey]);
100501
100514
  const hasFilters = reactExports.useMemo(() => Array.isArray(state.categories) && state.categories.length > 0, [state.categories]);
100502
100515
  return _objectSpread2(_objectSpread2({}, state), {}, {
100503
100516
  hasFilters
@@ -100579,17 +100592,24 @@
100579
100592
  * @param {string} orgId - Organization ID
100580
100593
  * @returns {Promise<object>} Agent configuration object
100581
100594
  */
100582
- const fetchAgentConfig = async (apiBaseUrl, orgId) => {
100583
- if (!apiBaseUrl || !orgId) {
100584
- throw new Error("apiBaseUrl and orgId are required");
100595
+ const fetchAgentConfig = async (apiBaseUrl, orgId, secretKey) => {
100596
+ if (!apiBaseUrl) {
100597
+ throw new Error("apiBaseUrl is required");
100585
100598
  }
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
+
100586
100603
  try {
100587
- const url = "".concat(apiBaseUrl.replace(/\/$/, ''), "/api/v1/deploy-agent/").concat(orgId);
100604
+ const base = apiBaseUrl.replace(/\/$/, '');
100605
+ const url = orgId ? "".concat(base, "/api/v1/deploy-agent/").concat(orgId) : "".concat(base, "/api/v1/deploy-agent");
100606
+ const headers = {
100607
+ "Content-Type": "application/json"
100608
+ };
100609
+ if (secretKey) headers['x-eshal-key'] = secretKey;
100588
100610
  const response = await fetch(url, {
100589
100611
  method: "GET",
100590
- headers: {
100591
- "Content-Type": "application/json"
100592
- },
100612
+ headers,
100593
100613
  credentials: "include"
100594
100614
  });
100595
100615
  if (!response.ok) {
@@ -100606,8 +100626,11 @@
100606
100626
  const ChatWidget = _ref => {
100607
100627
  var _agentConfig$concierg;
100608
100628
  let {
100609
- // Required props for dynamic config
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 })
100610
100632
  orgId,
100633
+ // legacy — kept for backward compatibility
100611
100634
  apiBaseUrl,
100612
100635
  // Optional override props (will override fetched config if provided)
100613
100636
  darkMode,
@@ -100658,14 +100681,18 @@
100658
100681
  // Fetch agent configuration on mount
100659
100682
  reactExports.useEffect(() => {
100660
100683
  const loadAgentConfig = async () => {
100661
- if (!orgId || !apiBaseUrl) {
100662
- setConfigError("orgId and apiBaseUrl are required");
100684
+ if (!apiBaseUrl) {
100685
+ setConfigError("apiBaseUrl is required");
100663
100686
  setIsLoadingConfig(false);
100664
100687
  return;
100665
100688
  }
100689
+ // Mode B proxy: neither secretKey nor orgId in browser — proxy handles auth
100690
+
100666
100691
  try {
100667
100692
  setIsLoadingConfig(true);
100668
- const config = await fetchAgentConfig(apiBaseUrl, orgId);
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);
100669
100696
  setAgentConfig(config);
100670
100697
  setConfigError(null);
100671
100698
  } catch (error) {
@@ -100676,7 +100703,7 @@
100676
100703
  }
100677
100704
  };
100678
100705
  loadAgentConfig();
100679
- }, [orgId, apiBaseUrl]);
100706
+ }, [orgId, secretKey, apiBaseUrl]);
100680
100707
 
100681
100708
  // Map API response to widget props (use props as overrides if provided)
100682
100709
  const widgetConfig = reactExports.useMemo(() => {
@@ -100854,6 +100881,7 @@
100854
100881
  // knowledge-api LoadBalancer.
100855
100882
  apiKey,
100856
100883
  organizationId: widgetConfig.organizationId,
100884
+ secretKey,
100857
100885
  autoOpen,
100858
100886
  openDelay,
100859
100887
  darkMode: widgetConfig.darkMode,
@@ -100880,14 +100908,15 @@
100880
100908
  // navigation (but not a page refresh — feedback is not written to
100881
100909
  // localStorage so it resets on reload as intended).
100882
100910
  updateMessageFeedback(messageContent, feedback);
100883
- if (!orgId || !conversationId || !apiBaseUrl) return;
100911
+ if (!orgId && !secretKey || !conversationId || !apiBaseUrl) return;
100884
100912
  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;
100885
100917
  fetch("".concat(base, "/api/v1/support/feedback"), {
100886
100918
  method: "POST",
100887
- headers: {
100888
- "Content-Type": "application/json",
100889
- "x-eshal-org": orgId
100890
- },
100919
+ headers: feedbackHeaders,
100891
100920
  credentials: "include",
100892
100921
  body: JSON.stringify({
100893
100922
  messageContent,
@@ -100913,7 +100942,7 @@
100913
100942
  const {
100914
100943
  categories: filterCategories,
100915
100944
  hasFilters: showFilterStep
100916
- } = useFilterCatalog(apiBaseUrl, orgId);
100945
+ } = useFilterCatalog(apiBaseUrl, orgId, secretKey);
100917
100946
 
100918
100947
  // Copy chat handler — copy all messages to clipboard
100919
100948
  const handleCopyChat = reactExports.useCallback(() => {
@@ -101019,6 +101048,7 @@
101019
101048
  apiBaseUrl: apiBaseUrl,
101020
101049
  apiKey: apiKey,
101021
101050
  organizationId: widgetConfig.organizationId,
101051
+ secretKey: secretKey,
101022
101052
  conversationId: conversationId,
101023
101053
  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
101054
  onboardingQuestions: widgetConfig.onboardingQuestions,
@@ -101166,11 +101196,15 @@
101166
101196
  }
101167
101197
 
101168
101198
  // 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,
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
101203
+ const hasNewAuth = config.apiBaseUrl;
101204
+ 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,
101174
101208
  apiBaseUrl: config.apiBaseUrl
101175
101209
  }, options.wsBaseUrl !== undefined && {
101176
101210
  wsBaseUrl: config.wsBaseUrl