@eshal-bot/chat-widget 0.1.62 → 0.1.63
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.
package/dist/chat-widget.js
CHANGED
|
@@ -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
|
-
//
|
|
8021
|
-
|
|
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 {
|
|
@@ -91865,6 +91870,9 @@
|
|
|
91865
91870
|
fontSize: 11,
|
|
91866
91871
|
fill: labelColor
|
|
91867
91872
|
},
|
|
91873
|
+
angle: 0,
|
|
91874
|
+
textAnchor: "middle",
|
|
91875
|
+
interval: 0,
|
|
91868
91876
|
label: props.xAxisLabel ? {
|
|
91869
91877
|
value: props.xAxisLabel,
|
|
91870
91878
|
position: 'insideBottom',
|
|
@@ -92143,6 +92151,9 @@
|
|
|
92143
92151
|
fontSize: 11,
|
|
92144
92152
|
fill: labelColor
|
|
92145
92153
|
},
|
|
92154
|
+
angle: 0,
|
|
92155
|
+
textAnchor: "middle",
|
|
92156
|
+
interval: 0,
|
|
92146
92157
|
label: props.xAxisLabel ? {
|
|
92147
92158
|
value: props.xAxisLabel,
|
|
92148
92159
|
position: 'insideBottom',
|
|
@@ -92713,10 +92724,12 @@
|
|
|
92713
92724
|
|
|
92714
92725
|
// ISO date / datetime prefix — matches the typical SQL output the
|
|
92715
92726
|
// agent's tools produce when GROUP BY-ing by month
|
|
92716
|
-
// (`DATE_TRUNC('month', col)` → `YYYY-MM-DD`)
|
|
92717
|
-
// below so a chart of monthly
|
|
92718
|
-
//
|
|
92719
|
-
|
|
92727
|
+
// (`DATE_TRUNC('month', col)` → `YYYY-MM-DD`, or `to_char(..,'YYYY-MM')`
|
|
92728
|
+
// → `YYYY-MM`). Used by formatAxisTick below so a chart of monthly
|
|
92729
|
+
// expenses shows "Jan '26" / "Feb '26" horizontally instead of
|
|
92730
|
+
// "2026 01 01" or "2026 01" slanted (EV-1187 follow-up + 2026-06-24
|
|
92731
|
+
// day-less variant fix).
|
|
92732
|
+
const ISO_DATE_RE = /^(\d{4})-(\d{2})(?:-\d{2})?(?:[T\s]|$)/;
|
|
92720
92733
|
function formatIsoDateAsMonthTick(value) {
|
|
92721
92734
|
if (typeof value !== 'string') return null;
|
|
92722
92735
|
const m = value.match(ISO_DATE_RE);
|
|
@@ -96219,7 +96232,18 @@
|
|
|
96219
96232
|
copyChat: "Copy Chat",
|
|
96220
96233
|
startChat: "Start Chat",
|
|
96221
96234
|
startingVoiceSession: "Starting voice session...",
|
|
96222
|
-
allowMicAccess: "Allow microphone access"
|
|
96235
|
+
allowMicAccess: "Allow microphone access",
|
|
96236
|
+
// Onboarding-form validation messages. These are the English fallback for
|
|
96237
|
+
// every locale (translate() falls back to EN[key]); without them the form
|
|
96238
|
+
// rendered the raw key strings ("emailInvalid", etc.). `fieldRequiredSuffix`
|
|
96239
|
+
// is appended after a field label: `${label} ${fieldRequiredSuffix}`.
|
|
96240
|
+
emailRequired: "Please enter your email address",
|
|
96241
|
+
nameRequired: "Please enter your name",
|
|
96242
|
+
phoneRequired: "Please enter your phone number",
|
|
96243
|
+
fieldRequiredSuffix: "is required",
|
|
96244
|
+
emailInvalid: "Please enter a valid email address",
|
|
96245
|
+
nameInvalid: "Please enter a valid name",
|
|
96246
|
+
phoneInvalid: "Please enter a valid phone number"
|
|
96223
96247
|
};
|
|
96224
96248
|
const DICTIONARIES = {
|
|
96225
96249
|
en: EN,
|
|
@@ -99609,6 +99633,7 @@
|
|
|
99609
99633
|
apiBaseUrl,
|
|
99610
99634
|
apiKey,
|
|
99611
99635
|
organizationId,
|
|
99636
|
+
secretKey,
|
|
99612
99637
|
conversationId,
|
|
99613
99638
|
conciergeId,
|
|
99614
99639
|
onboardingQuestions = [],
|
|
@@ -100405,6 +100430,7 @@
|
|
|
100405
100430
|
/**
|
|
100406
100431
|
* @param {string|undefined|null} apiBaseUrl
|
|
100407
100432
|
* @param {string|undefined|null} orgId
|
|
100433
|
+
* @param {string|undefined|null} secretKey — Mode A/B: orgId resolved server-side
|
|
100408
100434
|
* @returns {{
|
|
100409
100435
|
* categories: Array<{id:string,label:string,count:number,tags:string[]}>,
|
|
100410
100436
|
* loading: boolean,
|
|
@@ -100412,14 +100438,17 @@
|
|
|
100412
100438
|
* hasFilters: boolean,
|
|
100413
100439
|
* }}
|
|
100414
100440
|
*/
|
|
100415
|
-
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));
|
|
100416
100445
|
const [state, setState] = reactExports.useState({
|
|
100417
100446
|
categories: [],
|
|
100418
|
-
loading:
|
|
100447
|
+
loading: hasAuth,
|
|
100419
100448
|
error: null
|
|
100420
100449
|
});
|
|
100421
100450
|
reactExports.useEffect(() => {
|
|
100422
|
-
if (!apiBaseUrl || !orgId) {
|
|
100451
|
+
if (!apiBaseUrl || !orgId && !secretKey) {
|
|
100423
100452
|
setState({
|
|
100424
100453
|
categories: [],
|
|
100425
100454
|
loading: false,
|
|
@@ -100427,8 +100456,8 @@
|
|
|
100427
100456
|
});
|
|
100428
100457
|
return;
|
|
100429
100458
|
}
|
|
100430
|
-
const
|
|
100431
|
-
const cached = _PROCESS_CACHE.get(
|
|
100459
|
+
const cacheKey = secretKey ? "".concat(apiBaseUrl, "|__key__") : "".concat(apiBaseUrl, "|").concat(orgId);
|
|
100460
|
+
const cached = _PROCESS_CACHE.get(cacheKey);
|
|
100432
100461
|
if (cached) {
|
|
100433
100462
|
setState({
|
|
100434
100463
|
categories: cached,
|
|
@@ -100442,12 +100471,15 @@
|
|
|
100442
100471
|
loading: true,
|
|
100443
100472
|
error: null
|
|
100444
100473
|
}));
|
|
100445
|
-
const
|
|
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;
|
|
100446
100480
|
fetch(url, {
|
|
100447
100481
|
method: "GET",
|
|
100448
|
-
headers
|
|
100449
|
-
Accept: "application/json"
|
|
100450
|
-
}
|
|
100482
|
+
headers
|
|
100451
100483
|
}).then(async res => {
|
|
100452
100484
|
if (!res.ok) {
|
|
100453
100485
|
throw new Error("filter catalog HTTP ".concat(res.status));
|
|
@@ -100456,7 +100488,7 @@
|
|
|
100456
100488
|
return Array.isArray(body === null || body === void 0 ? void 0 : body.categories) ? body.categories : [];
|
|
100457
100489
|
}).then(categories => {
|
|
100458
100490
|
if (cancelled) return;
|
|
100459
|
-
_PROCESS_CACHE.set(
|
|
100491
|
+
_PROCESS_CACHE.set(cacheKey, categories);
|
|
100460
100492
|
setState({
|
|
100461
100493
|
categories,
|
|
100462
100494
|
loading: false,
|
|
@@ -100478,7 +100510,7 @@
|
|
|
100478
100510
|
return () => {
|
|
100479
100511
|
cancelled = true;
|
|
100480
100512
|
};
|
|
100481
|
-
}, [apiBaseUrl, orgId]);
|
|
100513
|
+
}, [apiBaseUrl, orgId, secretKey]);
|
|
100482
100514
|
const hasFilters = reactExports.useMemo(() => Array.isArray(state.categories) && state.categories.length > 0, [state.categories]);
|
|
100483
100515
|
return _objectSpread2(_objectSpread2({}, state), {}, {
|
|
100484
100516
|
hasFilters
|
|
@@ -100560,17 +100592,25 @@
|
|
|
100560
100592
|
* @param {string} orgId - Organization ID
|
|
100561
100593
|
* @returns {Promise<object>} Agent configuration object
|
|
100562
100594
|
*/
|
|
100563
|
-
const fetchAgentConfig = async (apiBaseUrl, orgId) => {
|
|
100564
|
-
if (!apiBaseUrl
|
|
100565
|
-
throw new Error("apiBaseUrl
|
|
100595
|
+
const fetchAgentConfig = async (apiBaseUrl, orgId, secretKey) => {
|
|
100596
|
+
if (!apiBaseUrl) {
|
|
100597
|
+
throw new Error("apiBaseUrl is required");
|
|
100598
|
+
}
|
|
100599
|
+
if (!secretKey && !orgId) {
|
|
100600
|
+
throw new Error("secretKey or orgId is required");
|
|
100566
100601
|
}
|
|
100567
100602
|
try {
|
|
100568
|
-
const
|
|
100603
|
+
const base = apiBaseUrl.replace(/\/$/, '');
|
|
100604
|
+
// Mode A/B: no orgId in URL — backend resolves org from x-eshal-key or HMAC sig
|
|
100605
|
+
// Mode C legacy: orgId in URL path, no header needed
|
|
100606
|
+
const url = secretKey ? "".concat(base, "/api/v1/deploy-agent") : "".concat(base, "/api/v1/deploy-agent/").concat(orgId);
|
|
100607
|
+
const headers = {
|
|
100608
|
+
"Content-Type": "application/json"
|
|
100609
|
+
};
|
|
100610
|
+
if (secretKey) headers['x-eshal-key'] = secretKey;
|
|
100569
100611
|
const response = await fetch(url, {
|
|
100570
100612
|
method: "GET",
|
|
100571
|
-
headers
|
|
100572
|
-
"Content-Type": "application/json"
|
|
100573
|
-
},
|
|
100613
|
+
headers,
|
|
100574
100614
|
credentials: "include"
|
|
100575
100615
|
});
|
|
100576
100616
|
if (!response.ok) {
|
|
@@ -100587,8 +100627,11 @@
|
|
|
100587
100627
|
const ChatWidget = _ref => {
|
|
100588
100628
|
var _agentConfig$concierg;
|
|
100589
100629
|
let {
|
|
100590
|
-
//
|
|
100630
|
+
// Auth — at least one of secretKey (Mode A/B) or orgId (Mode C legacy) is needed.
|
|
100631
|
+
secretKey,
|
|
100632
|
+
// sk_live_... from ChatWidget.init({ secretKey, apiBaseUrl })
|
|
100591
100633
|
orgId,
|
|
100634
|
+
// legacy — kept for backward compatibility
|
|
100592
100635
|
apiBaseUrl,
|
|
100593
100636
|
// Optional override props (will override fetched config if provided)
|
|
100594
100637
|
darkMode,
|
|
@@ -100639,14 +100682,21 @@
|
|
|
100639
100682
|
// Fetch agent configuration on mount
|
|
100640
100683
|
reactExports.useEffect(() => {
|
|
100641
100684
|
const loadAgentConfig = async () => {
|
|
100642
|
-
if (!
|
|
100643
|
-
setConfigError("
|
|
100685
|
+
if (!apiBaseUrl) {
|
|
100686
|
+
setConfigError("apiBaseUrl is required");
|
|
100687
|
+
setIsLoadingConfig(false);
|
|
100688
|
+
return;
|
|
100689
|
+
}
|
|
100690
|
+
if (!secretKey && !orgId) {
|
|
100691
|
+
setConfigError("secretKey or orgId is required");
|
|
100644
100692
|
setIsLoadingConfig(false);
|
|
100645
100693
|
return;
|
|
100646
100694
|
}
|
|
100647
100695
|
try {
|
|
100648
100696
|
setIsLoadingConfig(true);
|
|
100649
|
-
|
|
100697
|
+
// Pass secretKey for Mode A/B — fetchAgentConfig will send it as a
|
|
100698
|
+
// header and omit the orgId path segment. Pass orgId for Mode C legacy.
|
|
100699
|
+
const config = await fetchAgentConfig(apiBaseUrl, orgId, secretKey);
|
|
100650
100700
|
setAgentConfig(config);
|
|
100651
100701
|
setConfigError(null);
|
|
100652
100702
|
} catch (error) {
|
|
@@ -100657,7 +100707,7 @@
|
|
|
100657
100707
|
}
|
|
100658
100708
|
};
|
|
100659
100709
|
loadAgentConfig();
|
|
100660
|
-
}, [orgId, apiBaseUrl]);
|
|
100710
|
+
}, [orgId, secretKey, apiBaseUrl]);
|
|
100661
100711
|
|
|
100662
100712
|
// Map API response to widget props (use props as overrides if provided)
|
|
100663
100713
|
const widgetConfig = reactExports.useMemo(() => {
|
|
@@ -100835,6 +100885,7 @@
|
|
|
100835
100885
|
// knowledge-api LoadBalancer.
|
|
100836
100886
|
apiKey,
|
|
100837
100887
|
organizationId: widgetConfig.organizationId,
|
|
100888
|
+
secretKey,
|
|
100838
100889
|
autoOpen,
|
|
100839
100890
|
openDelay,
|
|
100840
100891
|
darkMode: widgetConfig.darkMode,
|
|
@@ -100861,14 +100912,15 @@
|
|
|
100861
100912
|
// navigation (but not a page refresh — feedback is not written to
|
|
100862
100913
|
// localStorage so it resets on reload as intended).
|
|
100863
100914
|
updateMessageFeedback(messageContent, feedback);
|
|
100864
|
-
if (!orgId || !conversationId || !apiBaseUrl) return;
|
|
100915
|
+
if (!orgId && !secretKey || !conversationId || !apiBaseUrl) return;
|
|
100865
100916
|
const base = apiBaseUrl.replace(/\/$/, "");
|
|
100917
|
+
const feedbackHeaders = {
|
|
100918
|
+
"Content-Type": "application/json"
|
|
100919
|
+
};
|
|
100920
|
+
if (secretKey) feedbackHeaders['x-eshal-key'] = secretKey;else if (orgId) feedbackHeaders['x-eshal-org'] = orgId;
|
|
100866
100921
|
fetch("".concat(base, "/api/v1/support/feedback"), {
|
|
100867
100922
|
method: "POST",
|
|
100868
|
-
headers:
|
|
100869
|
-
"Content-Type": "application/json",
|
|
100870
|
-
"x-eshal-org": orgId
|
|
100871
|
-
},
|
|
100923
|
+
headers: feedbackHeaders,
|
|
100872
100924
|
credentials: "include",
|
|
100873
100925
|
body: JSON.stringify({
|
|
100874
100926
|
messageContent,
|
|
@@ -100894,7 +100946,7 @@
|
|
|
100894
100946
|
const {
|
|
100895
100947
|
categories: filterCategories,
|
|
100896
100948
|
hasFilters: showFilterStep
|
|
100897
|
-
} = useFilterCatalog(apiBaseUrl, orgId);
|
|
100949
|
+
} = useFilterCatalog(apiBaseUrl, orgId, secretKey);
|
|
100898
100950
|
|
|
100899
100951
|
// Copy chat handler — copy all messages to clipboard
|
|
100900
100952
|
const handleCopyChat = reactExports.useCallback(() => {
|
|
@@ -101000,6 +101052,7 @@
|
|
|
101000
101052
|
apiBaseUrl: apiBaseUrl,
|
|
101001
101053
|
apiKey: apiKey,
|
|
101002
101054
|
organizationId: widgetConfig.organizationId,
|
|
101055
|
+
secretKey: secretKey,
|
|
101003
101056
|
conversationId: conversationId,
|
|
101004
101057
|
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,
|
|
101005
101058
|
onboardingQuestions: widgetConfig.onboardingQuestions,
|
|
@@ -101147,11 +101200,14 @@
|
|
|
101147
101200
|
}
|
|
101148
101201
|
|
|
101149
101202
|
// Render component
|
|
101150
|
-
//
|
|
101151
|
-
//
|
|
101152
|
-
|
|
101153
|
-
|
|
101154
|
-
|
|
101203
|
+
// Mode A/B: secretKey present — orgId derived server-side from the key
|
|
101204
|
+
// Mode C legacy: orgId in config — backward compatible
|
|
101205
|
+
// Both: apiBaseUrl required
|
|
101206
|
+
const hasNewAuth = (config.secretKey || config.orgId) && config.apiBaseUrl;
|
|
101207
|
+
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({
|
|
101208
|
+
// Auth props — secretKey replaces orgId in Mode A/B
|
|
101209
|
+
secretKey: config.secretKey || null,
|
|
101210
|
+
orgId: config.orgId || null,
|
|
101155
101211
|
apiBaseUrl: config.apiBaseUrl
|
|
101156
101212
|
}, options.wsBaseUrl !== undefined && {
|
|
101157
101213
|
wsBaseUrl: config.wsBaseUrl
|