@blotoutio/providers-evo-search-sdk 1.55.2 → 1.56.1
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/core.cjs.js +103 -42
- package/core.js +103 -42
- package/core.mjs +103 -42
- package/hooks.cjs.js +2 -1
- package/hooks.d.ts +13 -0
- package/hooks.mjs +2 -1
- package/index.cjs.js +2261 -771
- package/index.js +2262 -772
- package/index.mjs +2261 -771
- package/package.json +1 -1
package/core.cjs.js
CHANGED
|
@@ -511,6 +511,15 @@ new Set([
|
|
|
511
511
|
OFFLINE_TOUCH,
|
|
512
512
|
]);
|
|
513
513
|
|
|
514
|
+
const CURRENCY_CODE_PATTERN = /^[A-Z]{3}$/;
|
|
515
|
+
const parseCurrencyCode = (value) => {
|
|
516
|
+
if (typeof value !== 'string') {
|
|
517
|
+
return undefined;
|
|
518
|
+
}
|
|
519
|
+
const code = value.trim().toUpperCase();
|
|
520
|
+
return CURRENCY_CODE_PATTERN.test(code) ? code : undefined;
|
|
521
|
+
};
|
|
522
|
+
|
|
514
523
|
const uiActions = new Set([
|
|
515
524
|
'evoSearchProductRecommendationClicked',
|
|
516
525
|
'evoSearchProductClicked',
|
|
@@ -601,7 +610,24 @@ const notifyReady = () => {
|
|
|
601
610
|
notifySubscribers(registry.subscribers, 'notifyReady');
|
|
602
611
|
};
|
|
603
612
|
|
|
604
|
-
|
|
613
|
+
// Always defer to what the storefront itself is rendering — same source
|
|
614
|
+
// shop-gpt reads from. Two widgets on the same page would otherwise drift:
|
|
615
|
+
// shop-gpt picks Shopify, evo-search would pick its own override and show
|
|
616
|
+
// the same product in a different currency. `uiPreferences.currency` is
|
|
617
|
+
// kept as a last-resort fallback for non-Shopify hosts; 'USD' is the
|
|
618
|
+
// final default to match shop-gpt's behaviour.
|
|
619
|
+
const readShopifyCurrency = () => {
|
|
620
|
+
var _a;
|
|
621
|
+
try {
|
|
622
|
+
const shopify = window.Shopify;
|
|
623
|
+
return parseCurrencyCode((_a = shopify === null || shopify === void 0 ? void 0 : shopify.currency) === null || _a === void 0 ? void 0 : _a.active);
|
|
624
|
+
}
|
|
625
|
+
catch {
|
|
626
|
+
return undefined;
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
const resolveDisplayCurrency = (uiPreferences) => { var _a, _b; return (_b = (_a = readShopifyCurrency()) !== null && _a !== void 0 ? _a : parseCurrencyCode(uiPreferences === null || uiPreferences === void 0 ? void 0 : uiPreferences.currency)) !== null && _b !== void 0 ? _b : 'USD'; };
|
|
630
|
+
const createEvoSearchAPI = ({ fetch: fetchImpl = window.fetch, baseURL, userId, sessionId, sendTag, uiPreferences, }) => {
|
|
605
631
|
if (!baseURL) {
|
|
606
632
|
throw new Error('baseURL missing');
|
|
607
633
|
}
|
|
@@ -618,62 +644,82 @@ const createEvoSearchAPI = ({ fetch: fetchImpl = window.fetch, baseURL, userId,
|
|
|
618
644
|
}
|
|
619
645
|
return headers;
|
|
620
646
|
};
|
|
647
|
+
// Parsing `baseURL` once at API construction avoids re-parsing the host on
|
|
648
|
+
// every keystroke when `getURL` runs inside the autocomplete hot path.
|
|
649
|
+
const baseOrigin = new URL(baseURL).origin;
|
|
650
|
+
const PROVIDER_PATH_PREFIX = '/providers/evoSearch';
|
|
621
651
|
const getURL = (path) => {
|
|
622
|
-
|
|
652
|
+
const url = new URL(baseOrigin);
|
|
653
|
+
url.pathname = `${PROVIDER_PATH_PREFIX}${path}`;
|
|
654
|
+
url.searchParams.set('currency', resolveDisplayCurrency(uiPreferences));
|
|
655
|
+
return url;
|
|
623
656
|
};
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
657
|
+
// Only `/user/*` endpoints return per-user PII (recents, profile-bound
|
|
658
|
+
// data) and rely on the worker's HMAC-signed cookie. Sending
|
|
659
|
+
// `credentials: 'include'` on the public read endpoints (autocomplete,
|
|
660
|
+
// search, trending) is pointless and triggers the strict CORS contract
|
|
661
|
+
// (`Allow-Credentials: true` + non-wildcard origin echo) on every
|
|
662
|
+
// response — so default to `omit` and opt in below.
|
|
663
|
+
const needsCredentials = (path) => path.startsWith('/user/');
|
|
664
|
+
const getJSON = async (path, label, params, signal) => {
|
|
665
|
+
const url = getURL(path);
|
|
666
|
+
for (const [key, value] of Object.entries(params !== null && params !== void 0 ? params : {})) {
|
|
667
|
+
url.searchParams.set(key, value);
|
|
668
|
+
}
|
|
630
669
|
const response = await fetchImpl(url, {
|
|
631
670
|
method: 'GET',
|
|
632
|
-
headers,
|
|
633
|
-
credentials: 'include',
|
|
671
|
+
headers: getHeaders(),
|
|
672
|
+
credentials: needsCredentials(path) ? 'include' : 'omit',
|
|
634
673
|
signal,
|
|
635
674
|
});
|
|
636
675
|
if (!response.ok) {
|
|
637
|
-
const errorText = await response.text();
|
|
638
|
-
logger.error(`EvoSearch API:
|
|
639
|
-
throw new Error(
|
|
676
|
+
const errorText = await response.text().catch(() => '');
|
|
677
|
+
logger.error(`EvoSearch API: ${label} failed - ${response.status}: ${errorText}`);
|
|
678
|
+
throw new Error(`${label} failed - ${response.status}`);
|
|
640
679
|
}
|
|
641
|
-
|
|
680
|
+
return (await response.json());
|
|
681
|
+
};
|
|
682
|
+
const autocomplete = async (query, limit = 8, signal) => {
|
|
683
|
+
logger.info(`EvoSearch API: Autocomplete request - query length: ${query.length}, limit: ${limit}`);
|
|
684
|
+
const data = await getJSON('/autocomplete', 'Autocomplete', { q: query, limit: String(limit) }, signal);
|
|
642
685
|
logger.info(`EvoSearch API: Autocomplete success - ${data.llmSuggestions.length} suggestions, ${data.products.length} products`);
|
|
643
686
|
return data;
|
|
644
687
|
};
|
|
645
|
-
const trending =
|
|
646
|
-
const url = getURL('/trending');
|
|
688
|
+
const trending = (signal) => {
|
|
647
689
|
logger.info('EvoSearch API: Fetching trending searches');
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
690
|
+
return getJSON('/trending', 'Trending', undefined, signal);
|
|
691
|
+
};
|
|
692
|
+
const recents = (signal) => {
|
|
693
|
+
logger.info('EvoSearch API: Fetching recent searches');
|
|
694
|
+
return getJSON('/user/recents', 'Recents', undefined, signal);
|
|
695
|
+
};
|
|
696
|
+
const sendMutation = async (path, label, method, params) => {
|
|
697
|
+
const url = getURL(path);
|
|
698
|
+
for (const [key, value] of Object.entries(params !== null && params !== void 0 ? params : {})) {
|
|
699
|
+
url.searchParams.set(key, value);
|
|
700
|
+
}
|
|
701
|
+
try {
|
|
702
|
+
const response = await fetchImpl(url, {
|
|
703
|
+
method,
|
|
704
|
+
headers: getHeaders(),
|
|
705
|
+
credentials: 'include',
|
|
706
|
+
});
|
|
707
|
+
if (!response.ok) {
|
|
708
|
+
const errorText = await response.text().catch(() => '');
|
|
709
|
+
logger.error(`EvoSearch API: ${label} failed - ${response.status}: ${errorText}`);
|
|
710
|
+
throw new Error(`${label} failed - ${response.status}`);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
catch (error) {
|
|
714
|
+
logger.error(`EvoSearch API: ${label} threw`, error);
|
|
715
|
+
throw error;
|
|
656
716
|
}
|
|
657
|
-
return await response.json();
|
|
658
717
|
};
|
|
718
|
+
const deleteRecent = (value) => sendMutation('/user/recents', 'DeleteRecent', 'DELETE', { value });
|
|
719
|
+
const clearRecents = () => sendMutation('/user/recents', 'ClearRecents', 'DELETE');
|
|
659
720
|
const search = async (query, limit = 8, signal) => {
|
|
660
|
-
const url = getURL('/search');
|
|
661
|
-
url.searchParams.set('q', query);
|
|
662
|
-
url.searchParams.set('limit', limit.toString());
|
|
663
|
-
const headers = getHeaders();
|
|
664
721
|
logger.info(`EvoSearch API: Search request - query length: ${query.length}, limit: ${limit}`);
|
|
665
|
-
const
|
|
666
|
-
method: 'GET',
|
|
667
|
-
headers,
|
|
668
|
-
credentials: 'include',
|
|
669
|
-
signal,
|
|
670
|
-
});
|
|
671
|
-
if (!response.ok) {
|
|
672
|
-
const errorText = await response.text();
|
|
673
|
-
logger.error(`EvoSearch API: Search failed - ${response.status}: ${errorText}`);
|
|
674
|
-
throw new Error(`Search failed - ${response.status}`);
|
|
675
|
-
}
|
|
676
|
-
const data = (await response.json());
|
|
722
|
+
const data = await getJSON('/search', 'Search', { q: query, limit: String(limit) }, signal);
|
|
677
723
|
logger.info(`EvoSearch API: Search success - ${data.results.length} products`);
|
|
678
724
|
return data;
|
|
679
725
|
};
|
|
@@ -720,6 +766,11 @@ const createEvoSearchAPI = ({ fetch: fetchImpl = window.fetch, baseURL, userId,
|
|
|
720
766
|
return {
|
|
721
767
|
autocomplete,
|
|
722
768
|
trending,
|
|
769
|
+
user: {
|
|
770
|
+
recents,
|
|
771
|
+
deleteRecent,
|
|
772
|
+
clearRecents,
|
|
773
|
+
},
|
|
723
774
|
search,
|
|
724
775
|
sendEvent,
|
|
725
776
|
};
|
|
@@ -734,11 +785,20 @@ const init = (params) => {
|
|
|
734
785
|
try {
|
|
735
786
|
(_a = window[registryKey]) !== null && _a !== void 0 ? _a : (window[registryKey] = {});
|
|
736
787
|
logger.info('EvoSearch SDK: Initializing');
|
|
788
|
+
if (new URLSearchParams(window.location.search).get('evs') === 'preview') {
|
|
789
|
+
try {
|
|
790
|
+
sessionStorage.setItem(previewKeyName, '1');
|
|
791
|
+
logger.info('EvoSearch SDK: Preview mode enabled via ?evs=preview URL param');
|
|
792
|
+
}
|
|
793
|
+
catch {
|
|
794
|
+
error('EvoSearch SDK: Failed to set previewEvoSearch sessionStorage');
|
|
795
|
+
}
|
|
796
|
+
}
|
|
737
797
|
const { enabled, uiPreferences } = (_c = (_b = params.manifest) === null || _b === void 0 ? void 0 : _b.variables) !== null && _c !== void 0 ? _c : {};
|
|
738
798
|
const mode = uiPreferences === null || uiPreferences === void 0 ? void 0 : uiPreferences.mode;
|
|
739
799
|
const hasPreview = hasPreviewKey();
|
|
740
800
|
const shouldEnable = enabled || hasPreview;
|
|
741
|
-
if (!shouldEnable
|
|
801
|
+
if (!shouldEnable) {
|
|
742
802
|
logger.info('EvoSearch SDK: Mode is disabled, skipping initialization');
|
|
743
803
|
return;
|
|
744
804
|
}
|
|
@@ -750,6 +810,7 @@ const init = (params) => {
|
|
|
750
810
|
userId: params.userId,
|
|
751
811
|
sessionId: (_d = params.session) === null || _d === void 0 ? void 0 : _d.sessionId,
|
|
752
812
|
sendTag: params.sendTag,
|
|
813
|
+
uiPreferences,
|
|
753
814
|
});
|
|
754
815
|
window[registryKey].api = evoSearchAPI;
|
|
755
816
|
const uiImplementation = window[registryKey].ui;
|
package/core.js
CHANGED
|
@@ -512,6 +512,15 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
512
512
|
OFFLINE_TOUCH,
|
|
513
513
|
]);
|
|
514
514
|
|
|
515
|
+
const CURRENCY_CODE_PATTERN = /^[A-Z]{3}$/;
|
|
516
|
+
const parseCurrencyCode = (value) => {
|
|
517
|
+
if (typeof value !== 'string') {
|
|
518
|
+
return undefined;
|
|
519
|
+
}
|
|
520
|
+
const code = value.trim().toUpperCase();
|
|
521
|
+
return CURRENCY_CODE_PATTERN.test(code) ? code : undefined;
|
|
522
|
+
};
|
|
523
|
+
|
|
515
524
|
const uiActions = new Set([
|
|
516
525
|
'evoSearchProductRecommendationClicked',
|
|
517
526
|
'evoSearchProductClicked',
|
|
@@ -602,7 +611,24 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
602
611
|
notifySubscribers(registry.subscribers, 'notifyReady');
|
|
603
612
|
};
|
|
604
613
|
|
|
605
|
-
|
|
614
|
+
// Always defer to what the storefront itself is rendering — same source
|
|
615
|
+
// shop-gpt reads from. Two widgets on the same page would otherwise drift:
|
|
616
|
+
// shop-gpt picks Shopify, evo-search would pick its own override and show
|
|
617
|
+
// the same product in a different currency. `uiPreferences.currency` is
|
|
618
|
+
// kept as a last-resort fallback for non-Shopify hosts; 'USD' is the
|
|
619
|
+
// final default to match shop-gpt's behaviour.
|
|
620
|
+
const readShopifyCurrency = () => {
|
|
621
|
+
var _a;
|
|
622
|
+
try {
|
|
623
|
+
const shopify = window.Shopify;
|
|
624
|
+
return parseCurrencyCode((_a = shopify === null || shopify === void 0 ? void 0 : shopify.currency) === null || _a === void 0 ? void 0 : _a.active);
|
|
625
|
+
}
|
|
626
|
+
catch {
|
|
627
|
+
return undefined;
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
const resolveDisplayCurrency = (uiPreferences) => { var _a, _b; return (_b = (_a = readShopifyCurrency()) !== null && _a !== void 0 ? _a : parseCurrencyCode(uiPreferences === null || uiPreferences === void 0 ? void 0 : uiPreferences.currency)) !== null && _b !== void 0 ? _b : 'USD'; };
|
|
631
|
+
const createEvoSearchAPI = ({ fetch: fetchImpl = window.fetch, baseURL, userId, sessionId, sendTag, uiPreferences, }) => {
|
|
606
632
|
if (!baseURL) {
|
|
607
633
|
throw new Error('baseURL missing');
|
|
608
634
|
}
|
|
@@ -619,62 +645,82 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
619
645
|
}
|
|
620
646
|
return headers;
|
|
621
647
|
};
|
|
648
|
+
// Parsing `baseURL` once at API construction avoids re-parsing the host on
|
|
649
|
+
// every keystroke when `getURL` runs inside the autocomplete hot path.
|
|
650
|
+
const baseOrigin = new URL(baseURL).origin;
|
|
651
|
+
const PROVIDER_PATH_PREFIX = '/providers/evoSearch';
|
|
622
652
|
const getURL = (path) => {
|
|
623
|
-
|
|
653
|
+
const url = new URL(baseOrigin);
|
|
654
|
+
url.pathname = `${PROVIDER_PATH_PREFIX}${path}`;
|
|
655
|
+
url.searchParams.set('currency', resolveDisplayCurrency(uiPreferences));
|
|
656
|
+
return url;
|
|
624
657
|
};
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
658
|
+
// Only `/user/*` endpoints return per-user PII (recents, profile-bound
|
|
659
|
+
// data) and rely on the worker's HMAC-signed cookie. Sending
|
|
660
|
+
// `credentials: 'include'` on the public read endpoints (autocomplete,
|
|
661
|
+
// search, trending) is pointless and triggers the strict CORS contract
|
|
662
|
+
// (`Allow-Credentials: true` + non-wildcard origin echo) on every
|
|
663
|
+
// response — so default to `omit` and opt in below.
|
|
664
|
+
const needsCredentials = (path) => path.startsWith('/user/');
|
|
665
|
+
const getJSON = async (path, label, params, signal) => {
|
|
666
|
+
const url = getURL(path);
|
|
667
|
+
for (const [key, value] of Object.entries(params !== null && params !== void 0 ? params : {})) {
|
|
668
|
+
url.searchParams.set(key, value);
|
|
669
|
+
}
|
|
631
670
|
const response = await fetchImpl(url, {
|
|
632
671
|
method: 'GET',
|
|
633
|
-
headers,
|
|
634
|
-
credentials: 'include',
|
|
672
|
+
headers: getHeaders(),
|
|
673
|
+
credentials: needsCredentials(path) ? 'include' : 'omit',
|
|
635
674
|
signal,
|
|
636
675
|
});
|
|
637
676
|
if (!response.ok) {
|
|
638
|
-
const errorText = await response.text();
|
|
639
|
-
logger.error(`EvoSearch API:
|
|
640
|
-
throw new Error(
|
|
677
|
+
const errorText = await response.text().catch(() => '');
|
|
678
|
+
logger.error(`EvoSearch API: ${label} failed - ${response.status}: ${errorText}`);
|
|
679
|
+
throw new Error(`${label} failed - ${response.status}`);
|
|
641
680
|
}
|
|
642
|
-
|
|
681
|
+
return (await response.json());
|
|
682
|
+
};
|
|
683
|
+
const autocomplete = async (query, limit = 8, signal) => {
|
|
684
|
+
logger.info(`EvoSearch API: Autocomplete request - query length: ${query.length}, limit: ${limit}`);
|
|
685
|
+
const data = await getJSON('/autocomplete', 'Autocomplete', { q: query, limit: String(limit) }, signal);
|
|
643
686
|
logger.info(`EvoSearch API: Autocomplete success - ${data.llmSuggestions.length} suggestions, ${data.products.length} products`);
|
|
644
687
|
return data;
|
|
645
688
|
};
|
|
646
|
-
const trending =
|
|
647
|
-
const url = getURL('/trending');
|
|
689
|
+
const trending = (signal) => {
|
|
648
690
|
logger.info('EvoSearch API: Fetching trending searches');
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
691
|
+
return getJSON('/trending', 'Trending', undefined, signal);
|
|
692
|
+
};
|
|
693
|
+
const recents = (signal) => {
|
|
694
|
+
logger.info('EvoSearch API: Fetching recent searches');
|
|
695
|
+
return getJSON('/user/recents', 'Recents', undefined, signal);
|
|
696
|
+
};
|
|
697
|
+
const sendMutation = async (path, label, method, params) => {
|
|
698
|
+
const url = getURL(path);
|
|
699
|
+
for (const [key, value] of Object.entries(params !== null && params !== void 0 ? params : {})) {
|
|
700
|
+
url.searchParams.set(key, value);
|
|
701
|
+
}
|
|
702
|
+
try {
|
|
703
|
+
const response = await fetchImpl(url, {
|
|
704
|
+
method,
|
|
705
|
+
headers: getHeaders(),
|
|
706
|
+
credentials: 'include',
|
|
707
|
+
});
|
|
708
|
+
if (!response.ok) {
|
|
709
|
+
const errorText = await response.text().catch(() => '');
|
|
710
|
+
logger.error(`EvoSearch API: ${label} failed - ${response.status}: ${errorText}`);
|
|
711
|
+
throw new Error(`${label} failed - ${response.status}`);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
catch (error) {
|
|
715
|
+
logger.error(`EvoSearch API: ${label} threw`, error);
|
|
716
|
+
throw error;
|
|
657
717
|
}
|
|
658
|
-
return await response.json();
|
|
659
718
|
};
|
|
719
|
+
const deleteRecent = (value) => sendMutation('/user/recents', 'DeleteRecent', 'DELETE', { value });
|
|
720
|
+
const clearRecents = () => sendMutation('/user/recents', 'ClearRecents', 'DELETE');
|
|
660
721
|
const search = async (query, limit = 8, signal) => {
|
|
661
|
-
const url = getURL('/search');
|
|
662
|
-
url.searchParams.set('q', query);
|
|
663
|
-
url.searchParams.set('limit', limit.toString());
|
|
664
|
-
const headers = getHeaders();
|
|
665
722
|
logger.info(`EvoSearch API: Search request - query length: ${query.length}, limit: ${limit}`);
|
|
666
|
-
const
|
|
667
|
-
method: 'GET',
|
|
668
|
-
headers,
|
|
669
|
-
credentials: 'include',
|
|
670
|
-
signal,
|
|
671
|
-
});
|
|
672
|
-
if (!response.ok) {
|
|
673
|
-
const errorText = await response.text();
|
|
674
|
-
logger.error(`EvoSearch API: Search failed - ${response.status}: ${errorText}`);
|
|
675
|
-
throw new Error(`Search failed - ${response.status}`);
|
|
676
|
-
}
|
|
677
|
-
const data = (await response.json());
|
|
723
|
+
const data = await getJSON('/search', 'Search', { q: query, limit: String(limit) }, signal);
|
|
678
724
|
logger.info(`EvoSearch API: Search success - ${data.results.length} products`);
|
|
679
725
|
return data;
|
|
680
726
|
};
|
|
@@ -721,6 +767,11 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
721
767
|
return {
|
|
722
768
|
autocomplete,
|
|
723
769
|
trending,
|
|
770
|
+
user: {
|
|
771
|
+
recents,
|
|
772
|
+
deleteRecent,
|
|
773
|
+
clearRecents,
|
|
774
|
+
},
|
|
724
775
|
search,
|
|
725
776
|
sendEvent,
|
|
726
777
|
};
|
|
@@ -735,11 +786,20 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
735
786
|
try {
|
|
736
787
|
(_a = window[registryKey]) !== null && _a !== void 0 ? _a : (window[registryKey] = {});
|
|
737
788
|
logger.info('EvoSearch SDK: Initializing');
|
|
789
|
+
if (new URLSearchParams(window.location.search).get('evs') === 'preview') {
|
|
790
|
+
try {
|
|
791
|
+
sessionStorage.setItem(previewKeyName, '1');
|
|
792
|
+
logger.info('EvoSearch SDK: Preview mode enabled via ?evs=preview URL param');
|
|
793
|
+
}
|
|
794
|
+
catch {
|
|
795
|
+
error('EvoSearch SDK: Failed to set previewEvoSearch sessionStorage');
|
|
796
|
+
}
|
|
797
|
+
}
|
|
738
798
|
const { enabled, uiPreferences } = (_c = (_b = params.manifest) === null || _b === void 0 ? void 0 : _b.variables) !== null && _c !== void 0 ? _c : {};
|
|
739
799
|
const mode = uiPreferences === null || uiPreferences === void 0 ? void 0 : uiPreferences.mode;
|
|
740
800
|
const hasPreview = hasPreviewKey();
|
|
741
801
|
const shouldEnable = enabled || hasPreview;
|
|
742
|
-
if (!shouldEnable
|
|
802
|
+
if (!shouldEnable) {
|
|
743
803
|
logger.info('EvoSearch SDK: Mode is disabled, skipping initialization');
|
|
744
804
|
return;
|
|
745
805
|
}
|
|
@@ -751,6 +811,7 @@ var ProvidersEvoSearchSdk = (function () {
|
|
|
751
811
|
userId: params.userId,
|
|
752
812
|
sessionId: (_d = params.session) === null || _d === void 0 ? void 0 : _d.sessionId,
|
|
753
813
|
sendTag: params.sendTag,
|
|
814
|
+
uiPreferences,
|
|
754
815
|
});
|
|
755
816
|
window[registryKey].api = evoSearchAPI;
|
|
756
817
|
const uiImplementation = window[registryKey].ui;
|
package/core.mjs
CHANGED
|
@@ -509,6 +509,15 @@ new Set([
|
|
|
509
509
|
OFFLINE_TOUCH,
|
|
510
510
|
]);
|
|
511
511
|
|
|
512
|
+
const CURRENCY_CODE_PATTERN = /^[A-Z]{3}$/;
|
|
513
|
+
const parseCurrencyCode = (value) => {
|
|
514
|
+
if (typeof value !== 'string') {
|
|
515
|
+
return undefined;
|
|
516
|
+
}
|
|
517
|
+
const code = value.trim().toUpperCase();
|
|
518
|
+
return CURRENCY_CODE_PATTERN.test(code) ? code : undefined;
|
|
519
|
+
};
|
|
520
|
+
|
|
512
521
|
const uiActions = new Set([
|
|
513
522
|
'evoSearchProductRecommendationClicked',
|
|
514
523
|
'evoSearchProductClicked',
|
|
@@ -599,7 +608,24 @@ const notifyReady = () => {
|
|
|
599
608
|
notifySubscribers(registry.subscribers, 'notifyReady');
|
|
600
609
|
};
|
|
601
610
|
|
|
602
|
-
|
|
611
|
+
// Always defer to what the storefront itself is rendering — same source
|
|
612
|
+
// shop-gpt reads from. Two widgets on the same page would otherwise drift:
|
|
613
|
+
// shop-gpt picks Shopify, evo-search would pick its own override and show
|
|
614
|
+
// the same product in a different currency. `uiPreferences.currency` is
|
|
615
|
+
// kept as a last-resort fallback for non-Shopify hosts; 'USD' is the
|
|
616
|
+
// final default to match shop-gpt's behaviour.
|
|
617
|
+
const readShopifyCurrency = () => {
|
|
618
|
+
var _a;
|
|
619
|
+
try {
|
|
620
|
+
const shopify = window.Shopify;
|
|
621
|
+
return parseCurrencyCode((_a = shopify === null || shopify === void 0 ? void 0 : shopify.currency) === null || _a === void 0 ? void 0 : _a.active);
|
|
622
|
+
}
|
|
623
|
+
catch {
|
|
624
|
+
return undefined;
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
const resolveDisplayCurrency = (uiPreferences) => { var _a, _b; return (_b = (_a = readShopifyCurrency()) !== null && _a !== void 0 ? _a : parseCurrencyCode(uiPreferences === null || uiPreferences === void 0 ? void 0 : uiPreferences.currency)) !== null && _b !== void 0 ? _b : 'USD'; };
|
|
628
|
+
const createEvoSearchAPI = ({ fetch: fetchImpl = window.fetch, baseURL, userId, sessionId, sendTag, uiPreferences, }) => {
|
|
603
629
|
if (!baseURL) {
|
|
604
630
|
throw new Error('baseURL missing');
|
|
605
631
|
}
|
|
@@ -616,62 +642,82 @@ const createEvoSearchAPI = ({ fetch: fetchImpl = window.fetch, baseURL, userId,
|
|
|
616
642
|
}
|
|
617
643
|
return headers;
|
|
618
644
|
};
|
|
645
|
+
// Parsing `baseURL` once at API construction avoids re-parsing the host on
|
|
646
|
+
// every keystroke when `getURL` runs inside the autocomplete hot path.
|
|
647
|
+
const baseOrigin = new URL(baseURL).origin;
|
|
648
|
+
const PROVIDER_PATH_PREFIX = '/providers/evoSearch';
|
|
619
649
|
const getURL = (path) => {
|
|
620
|
-
|
|
650
|
+
const url = new URL(baseOrigin);
|
|
651
|
+
url.pathname = `${PROVIDER_PATH_PREFIX}${path}`;
|
|
652
|
+
url.searchParams.set('currency', resolveDisplayCurrency(uiPreferences));
|
|
653
|
+
return url;
|
|
621
654
|
};
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
655
|
+
// Only `/user/*` endpoints return per-user PII (recents, profile-bound
|
|
656
|
+
// data) and rely on the worker's HMAC-signed cookie. Sending
|
|
657
|
+
// `credentials: 'include'` on the public read endpoints (autocomplete,
|
|
658
|
+
// search, trending) is pointless and triggers the strict CORS contract
|
|
659
|
+
// (`Allow-Credentials: true` + non-wildcard origin echo) on every
|
|
660
|
+
// response — so default to `omit` and opt in below.
|
|
661
|
+
const needsCredentials = (path) => path.startsWith('/user/');
|
|
662
|
+
const getJSON = async (path, label, params, signal) => {
|
|
663
|
+
const url = getURL(path);
|
|
664
|
+
for (const [key, value] of Object.entries(params !== null && params !== void 0 ? params : {})) {
|
|
665
|
+
url.searchParams.set(key, value);
|
|
666
|
+
}
|
|
628
667
|
const response = await fetchImpl(url, {
|
|
629
668
|
method: 'GET',
|
|
630
|
-
headers,
|
|
631
|
-
credentials: 'include',
|
|
669
|
+
headers: getHeaders(),
|
|
670
|
+
credentials: needsCredentials(path) ? 'include' : 'omit',
|
|
632
671
|
signal,
|
|
633
672
|
});
|
|
634
673
|
if (!response.ok) {
|
|
635
|
-
const errorText = await response.text();
|
|
636
|
-
logger.error(`EvoSearch API:
|
|
637
|
-
throw new Error(
|
|
674
|
+
const errorText = await response.text().catch(() => '');
|
|
675
|
+
logger.error(`EvoSearch API: ${label} failed - ${response.status}: ${errorText}`);
|
|
676
|
+
throw new Error(`${label} failed - ${response.status}`);
|
|
638
677
|
}
|
|
639
|
-
|
|
678
|
+
return (await response.json());
|
|
679
|
+
};
|
|
680
|
+
const autocomplete = async (query, limit = 8, signal) => {
|
|
681
|
+
logger.info(`EvoSearch API: Autocomplete request - query length: ${query.length}, limit: ${limit}`);
|
|
682
|
+
const data = await getJSON('/autocomplete', 'Autocomplete', { q: query, limit: String(limit) }, signal);
|
|
640
683
|
logger.info(`EvoSearch API: Autocomplete success - ${data.llmSuggestions.length} suggestions, ${data.products.length} products`);
|
|
641
684
|
return data;
|
|
642
685
|
};
|
|
643
|
-
const trending =
|
|
644
|
-
const url = getURL('/trending');
|
|
686
|
+
const trending = (signal) => {
|
|
645
687
|
logger.info('EvoSearch API: Fetching trending searches');
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
688
|
+
return getJSON('/trending', 'Trending', undefined, signal);
|
|
689
|
+
};
|
|
690
|
+
const recents = (signal) => {
|
|
691
|
+
logger.info('EvoSearch API: Fetching recent searches');
|
|
692
|
+
return getJSON('/user/recents', 'Recents', undefined, signal);
|
|
693
|
+
};
|
|
694
|
+
const sendMutation = async (path, label, method, params) => {
|
|
695
|
+
const url = getURL(path);
|
|
696
|
+
for (const [key, value] of Object.entries(params !== null && params !== void 0 ? params : {})) {
|
|
697
|
+
url.searchParams.set(key, value);
|
|
698
|
+
}
|
|
699
|
+
try {
|
|
700
|
+
const response = await fetchImpl(url, {
|
|
701
|
+
method,
|
|
702
|
+
headers: getHeaders(),
|
|
703
|
+
credentials: 'include',
|
|
704
|
+
});
|
|
705
|
+
if (!response.ok) {
|
|
706
|
+
const errorText = await response.text().catch(() => '');
|
|
707
|
+
logger.error(`EvoSearch API: ${label} failed - ${response.status}: ${errorText}`);
|
|
708
|
+
throw new Error(`${label} failed - ${response.status}`);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
catch (error) {
|
|
712
|
+
logger.error(`EvoSearch API: ${label} threw`, error);
|
|
713
|
+
throw error;
|
|
654
714
|
}
|
|
655
|
-
return await response.json();
|
|
656
715
|
};
|
|
716
|
+
const deleteRecent = (value) => sendMutation('/user/recents', 'DeleteRecent', 'DELETE', { value });
|
|
717
|
+
const clearRecents = () => sendMutation('/user/recents', 'ClearRecents', 'DELETE');
|
|
657
718
|
const search = async (query, limit = 8, signal) => {
|
|
658
|
-
const url = getURL('/search');
|
|
659
|
-
url.searchParams.set('q', query);
|
|
660
|
-
url.searchParams.set('limit', limit.toString());
|
|
661
|
-
const headers = getHeaders();
|
|
662
719
|
logger.info(`EvoSearch API: Search request - query length: ${query.length}, limit: ${limit}`);
|
|
663
|
-
const
|
|
664
|
-
method: 'GET',
|
|
665
|
-
headers,
|
|
666
|
-
credentials: 'include',
|
|
667
|
-
signal,
|
|
668
|
-
});
|
|
669
|
-
if (!response.ok) {
|
|
670
|
-
const errorText = await response.text();
|
|
671
|
-
logger.error(`EvoSearch API: Search failed - ${response.status}: ${errorText}`);
|
|
672
|
-
throw new Error(`Search failed - ${response.status}`);
|
|
673
|
-
}
|
|
674
|
-
const data = (await response.json());
|
|
720
|
+
const data = await getJSON('/search', 'Search', { q: query, limit: String(limit) }, signal);
|
|
675
721
|
logger.info(`EvoSearch API: Search success - ${data.results.length} products`);
|
|
676
722
|
return data;
|
|
677
723
|
};
|
|
@@ -718,6 +764,11 @@ const createEvoSearchAPI = ({ fetch: fetchImpl = window.fetch, baseURL, userId,
|
|
|
718
764
|
return {
|
|
719
765
|
autocomplete,
|
|
720
766
|
trending,
|
|
767
|
+
user: {
|
|
768
|
+
recents,
|
|
769
|
+
deleteRecent,
|
|
770
|
+
clearRecents,
|
|
771
|
+
},
|
|
721
772
|
search,
|
|
722
773
|
sendEvent,
|
|
723
774
|
};
|
|
@@ -732,11 +783,20 @@ const init = (params) => {
|
|
|
732
783
|
try {
|
|
733
784
|
(_a = window[registryKey]) !== null && _a !== void 0 ? _a : (window[registryKey] = {});
|
|
734
785
|
logger.info('EvoSearch SDK: Initializing');
|
|
786
|
+
if (new URLSearchParams(window.location.search).get('evs') === 'preview') {
|
|
787
|
+
try {
|
|
788
|
+
sessionStorage.setItem(previewKeyName, '1');
|
|
789
|
+
logger.info('EvoSearch SDK: Preview mode enabled via ?evs=preview URL param');
|
|
790
|
+
}
|
|
791
|
+
catch {
|
|
792
|
+
error('EvoSearch SDK: Failed to set previewEvoSearch sessionStorage');
|
|
793
|
+
}
|
|
794
|
+
}
|
|
735
795
|
const { enabled, uiPreferences } = (_c = (_b = params.manifest) === null || _b === void 0 ? void 0 : _b.variables) !== null && _c !== void 0 ? _c : {};
|
|
736
796
|
const mode = uiPreferences === null || uiPreferences === void 0 ? void 0 : uiPreferences.mode;
|
|
737
797
|
const hasPreview = hasPreviewKey();
|
|
738
798
|
const shouldEnable = enabled || hasPreview;
|
|
739
|
-
if (!shouldEnable
|
|
799
|
+
if (!shouldEnable) {
|
|
740
800
|
logger.info('EvoSearch SDK: Mode is disabled, skipping initialization');
|
|
741
801
|
return;
|
|
742
802
|
}
|
|
@@ -748,6 +808,7 @@ const init = (params) => {
|
|
|
748
808
|
userId: params.userId,
|
|
749
809
|
sessionId: (_d = params.session) === null || _d === void 0 ? void 0 : _d.sessionId,
|
|
750
810
|
sendTag: params.sendTag,
|
|
811
|
+
uiPreferences,
|
|
751
812
|
});
|
|
752
813
|
window[registryKey].api = evoSearchAPI;
|
|
753
814
|
const uiImplementation = window[registryKey].ui;
|
package/hooks.cjs.js
CHANGED
|
@@ -635,6 +635,7 @@ const extractHandle = (url, fallback) => {
|
|
|
635
635
|
}
|
|
636
636
|
};
|
|
637
637
|
const mapAPIProductToSDKProduct = (apiProduct) => {
|
|
638
|
+
var _a;
|
|
638
639
|
const safeUrl = isHttpURL(apiProduct.url) ? apiProduct.url : '';
|
|
639
640
|
const safeImg = isHttpURL(apiProduct.img) ? apiProduct.img : '';
|
|
640
641
|
const handle = extractHandle(safeUrl, apiProduct.id);
|
|
@@ -652,7 +653,7 @@ const mapAPIProductToSDKProduct = (apiProduct) => {
|
|
|
652
653
|
{
|
|
653
654
|
price: apiProduct.price,
|
|
654
655
|
comparedAtPrice: apiProduct.compareAtPrice || null,
|
|
655
|
-
currencyCode:
|
|
656
|
+
currencyCode: (_a = apiProduct.currencyCode) !== null && _a !== void 0 ? _a : '',
|
|
656
657
|
},
|
|
657
658
|
],
|
|
658
659
|
};
|