@askdialog/dialog-sdk 2.11.0 → 2.13.0
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/README.md +31 -20
- package/dist/Dialog.d.ts +16 -22
- package/dist/Dialog.d.ts.map +1 -1
- package/dist/Dialog.js +44 -56
- package/dist/__tests__/dialogChangeCartQuantity.spec.d.ts +2 -0
- package/dist/__tests__/dialogChangeCartQuantity.spec.d.ts.map +1 -0
- package/dist/__tests__/dialogChangeCartQuantity.spec.js +46 -0
- package/dist/__tests__/dialogSearchAnalytics.spec.js +2 -4
- package/dist/__tests__/publicTypes.spec.js +3 -0
- package/dist/__tests__/searchController.spec.js +46 -20
- package/dist/__tests__/searchControllerAttribution.spec.js +8 -6
- package/dist/__tests__/searchImpressions.spec.js +2 -3
- package/dist/__tests__/searchService.spec.js +13 -11
- package/dist/searchController.d.ts +1 -1
- package/dist/searchController.d.ts.map +1 -1
- package/dist/searchController.js +41 -64
- package/dist/services/search.d.ts +3 -5
- package/dist/services/search.d.ts.map +1 -1
- package/dist/services/search.js +11 -6
- package/dist/types/constructor.d.ts +9 -3
- package/dist/types/constructor.d.ts.map +1 -1
- package/dist/types/events.d.ts +5 -0
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/search.d.ts +14 -14
- package/dist/types/search.d.ts.map +1 -1
- package/dist/types/search.js +1 -1
- package/dist/types/searchAnalytics.d.ts +12 -6
- package/dist/types/searchAnalytics.d.ts.map +1 -1
- package/dist/types/searchAnalytics.js +1 -4
- package/dist/types/searchController.d.ts +22 -51
- package/dist/types/searchController.d.ts.map +1 -1
- package/dist/types/searchController.js +1 -1
- package/dist/utils/localization.d.ts +0 -1
- package/dist/utils/localization.d.ts.map +1 -1
- package/dist/utils/localization.js +0 -11
- package/dist/utils/searchControllerAnalytics.d.ts +1 -4
- package/dist/utils/searchControllerAnalytics.d.ts.map +1 -1
- package/dist/utils/searchControllerAnalytics.js +5 -8
- package/dist/utils/searchImpressions.d.ts +4 -4
- package/dist/utils/searchImpressions.d.ts.map +1 -1
- package/dist/utils/searchImpressions.js +4 -11
- package/dist/utils/searchRequests.d.ts +17 -0
- package/dist/utils/searchRequests.d.ts.map +1 -0
- package/dist/utils/searchRequests.js +36 -0
- package/package.json +1 -1
- package/dist/utils/searchSections.d.ts +0 -5
- package/dist/utils/searchSections.d.ts.map +0 -1
- package/dist/utils/searchSections.js +0 -21
package/README.md
CHANGED
|
@@ -51,7 +51,8 @@ import { Dialog } from '@askdialog/dialog-sdk';
|
|
|
51
51
|
|
|
52
52
|
const client = new Dialog({
|
|
53
53
|
apiKey: 'YOUR_API_KEY', // required
|
|
54
|
-
locale: '
|
|
54
|
+
locale: 'fr-FR', currency: 'EUR', // ISO 639-1 language
|
|
55
|
+
currency: 'EUR', // required ISO 4217 currency
|
|
55
56
|
countryCode: 'FR', // optional, ISO 3166 alpha-2
|
|
56
57
|
callbacks: {
|
|
57
58
|
addToCart: async ({
|
|
@@ -100,7 +101,7 @@ Declare it at construction on product pages:
|
|
|
100
101
|
```ts
|
|
101
102
|
new Dialog({
|
|
102
103
|
apiKey: 'YOUR_API_KEY',
|
|
103
|
-
locale: 'fr',
|
|
104
|
+
locale: 'fr-FR', currency: 'EUR',
|
|
104
105
|
product: { id: 'PRODUCT_ID', variantId: 'VARIANT_ID' }, // variantId optional
|
|
105
106
|
});
|
|
106
107
|
```
|
|
@@ -130,7 +131,7 @@ Some sessions must hide purchasing actions — for example a B2B storefront that
|
|
|
130
131
|
```ts
|
|
131
132
|
new Dialog({
|
|
132
133
|
apiKey: 'YOUR_API_KEY',
|
|
133
|
-
locale: 'fr',
|
|
134
|
+
locale: 'fr-FR', currency: 'EUR',
|
|
134
135
|
disableAddToCart: true, // hide the add-to-cart CTA for this session
|
|
135
136
|
});
|
|
136
137
|
```
|
|
@@ -219,18 +220,20 @@ Example of expected result:
|
|
|
219
220
|
|
|
220
221
|
- Search products
|
|
221
222
|
|
|
222
|
-
`client.search()`
|
|
223
|
+
`client.search()` sends a multi-index request to the public search API. Build names with `searchIndexName(index, language, currency)`: `<index>_<lang>_<currency>`, e.g. `products_fr_eur`. Supported indices: `products`, `collections`, `articles`, `pages`. Language must be a lowercase ISO 639-1 code (`fr`, `en`). Regional locales such as `fr-FR` are rejected. The ISO 4217 currency is lowercased in the index name.
|
|
224
|
+
|
|
225
|
+
Currency is required and independent of language: `fr` with `USD` produces `products_fr_usd`. Names without a currency suffix return 404.
|
|
223
226
|
|
|
224
227
|
```typescript
|
|
225
228
|
import { Dialog, DialogSearchError, searchIndexName } from '@askdialog/dialog-sdk';
|
|
226
229
|
import type { SearchResponse } from '@askdialog/dialog-sdk';
|
|
227
230
|
|
|
228
|
-
const client = new Dialog({ apiKey: 'YOUR_API_KEY', locale: 'fr' });
|
|
231
|
+
const client = new Dialog({ apiKey: 'YOUR_API_KEY', locale: 'fr-FR', currency: 'EUR' });
|
|
229
232
|
|
|
230
233
|
const response: SearchResponse = await client.search({
|
|
231
234
|
requests: [
|
|
232
235
|
{
|
|
233
|
-
indexName: searchIndexName('products', client.
|
|
236
|
+
indexName: searchIndexName('products', 'fr', client.currency), // "products_fr_eur"
|
|
234
237
|
query: 'shampoo',
|
|
235
238
|
page: 0, // optional, zero-indexed (default 0)
|
|
236
239
|
hitsPerPage: 20, // optional, 1-100 (default 20)
|
|
@@ -248,29 +251,32 @@ With the IIFE bundle the results are plain runtime JSON (same shape, no types):
|
|
|
248
251
|
```html
|
|
249
252
|
<script src="https://d2m6yt8rnm4dos.cloudfront.net/dialog-sdk.X.Y.Z.min.js"></script>
|
|
250
253
|
<script>
|
|
251
|
-
const client = new window.DialogSDK.Dialog({ apiKey: 'YOUR_API_KEY', locale: 'fr' });
|
|
254
|
+
const client = new window.DialogSDK.Dialog({ apiKey: 'YOUR_API_KEY', locale: 'fr-FR', currency: 'EUR' });
|
|
252
255
|
client
|
|
253
|
-
.search({ requests: [{ indexName: '
|
|
256
|
+
.search({ requests: [{ indexName: 'products_fr_eur', query: 'shampoo' }] })
|
|
254
257
|
.then((response) => console.log(response.results[0].hits));
|
|
255
258
|
</script>
|
|
256
259
|
```
|
|
257
260
|
|
|
258
261
|
A non-2xx answer rejects with `DialogSearchError` — stable `name`, HTTP `status` and `message` (e.g. `404 Index products_xx does not exist`, `400 Unknown parameter: foo`). Aborting rejects with the native `AbortError`, and network failures keep their native errors.
|
|
259
262
|
|
|
260
|
-
`client.search()`
|
|
263
|
+
`client.search()` sends requests unchanged, without debounce, caching or automatic cancellation. Use `createSearchController()` for interactive search.
|
|
261
264
|
|
|
262
265
|
- Search controller
|
|
263
266
|
|
|
264
|
-
`createSearchController()`
|
|
267
|
+
`createSearchController()` handles debounce, cancellation, stale responses, pagination, retries and search analytics. New queries reset pagination. State is `idle`, `loading`, `success`, `empty` or `error`.
|
|
268
|
+
|
|
269
|
+
Products are available in `state.response`. Optional `sections` query additional indexes in the same request and expose results in `state.sections`.
|
|
265
270
|
|
|
266
271
|
```typescript
|
|
267
272
|
import { createSearchController, Dialog, SearchStatus } from '@askdialog/dialog-sdk';
|
|
268
273
|
|
|
269
|
-
const client = new Dialog({ apiKey: 'YOUR_API_KEY', locale: 'fr' });
|
|
274
|
+
const client = new Dialog({ apiKey: 'YOUR_API_KEY', locale: 'fr-FR', currency: 'EUR' });
|
|
270
275
|
|
|
271
276
|
const controller = createSearchController({
|
|
272
277
|
search: (request, options) => client.search(request, options),
|
|
273
|
-
|
|
278
|
+
language: 'fr',
|
|
279
|
+
currency: client.currency,
|
|
274
280
|
analytics: {
|
|
275
281
|
surface: 'search_page', // where results are displayed
|
|
276
282
|
trackViewSearchResults: (params) => client.trackViewSearchResults(params),
|
|
@@ -283,7 +289,7 @@ const controller = createSearchController({
|
|
|
283
289
|
});
|
|
284
290
|
|
|
285
291
|
const unsubscribe = controller.subscribe((state) => {
|
|
286
|
-
// state: { status, query, page, response?, error? }
|
|
292
|
+
// state: { status, query, page, response?, sections?, error? }
|
|
287
293
|
if (state.status === SearchStatus.SUCCESS) {
|
|
288
294
|
renderCards(state.response.hits).forEach((element, index) => {
|
|
289
295
|
controller.observeResult(element, index); // viewport impression
|
|
@@ -296,16 +302,20 @@ input.oninput = () => controller.setQuery(input.value); // debounced
|
|
|
296
302
|
form.onsubmit = () => controller.submit(input.value); // immediate
|
|
297
303
|
nextButton.onclick = () => controller.setPage(controller.getState().page + 1);
|
|
298
304
|
retryButton.onclick = () => controller.retry();
|
|
299
|
-
//
|
|
305
|
+
// Cancel requests and remove observers on unmount.
|
|
300
306
|
controller.dispose();
|
|
301
307
|
```
|
|
302
308
|
|
|
303
|
-
The
|
|
309
|
+
The client uses a BCP-47 `locale` such as `fr-FR` for assistant localization. Search controllers and React/Vue hooks require explicit `language` (ISO 639-1) and `currency` (ISO 4217), independently of the client locale. Pass `client.currency` to reuse its configured currency.
|
|
310
|
+
|
|
311
|
+
For Shopify, use `window.Shopify.currency.active` as the currency. Controller options are fixed at creation; recreate the controller to change language or currency.
|
|
312
|
+
|
|
313
|
+
Framework integrations:
|
|
304
314
|
|
|
305
|
-
-
|
|
306
|
-
-
|
|
307
|
-
-
|
|
308
|
-
-
|
|
315
|
+
- Subscribe to state changes and render results.
|
|
316
|
+
- Call `observeResult(element, index)` for each result and `selectResult(index)` on selection. Use `{ navigate: false }` for middle-clicks and modified clicks. Call `preventDefault()` only when `selectResult` returns true.
|
|
317
|
+
- Pass `navigate` for router navigation. The integration owns URL synchronization.
|
|
318
|
+
- Create one controller per search surface and call `dispose()` on unmount.
|
|
309
319
|
|
|
310
320
|
The raw JavaScript reference adapter lives in [`packages/search-example`](../search-example).
|
|
311
321
|
|
|
@@ -531,7 +541,8 @@ interface AssistantEvent {
|
|
|
531
541
|
payload: {
|
|
532
542
|
// Common fields (included in all events)
|
|
533
543
|
date: string; // ISO timestamp
|
|
534
|
-
locale: string; //
|
|
544
|
+
locale: string; // BCP-47 locale
|
|
545
|
+
currency: string; // Current currency
|
|
535
546
|
url: string; // Current page URL
|
|
536
547
|
userId?: string; // User ID if available
|
|
537
548
|
|
package/dist/Dialog.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { DialogConstructor } from "./types/constructor";
|
|
|
2
2
|
import { Theme } from "./types/theme";
|
|
3
3
|
import { DetailedLocaleInfo } from "./utils/localization";
|
|
4
4
|
import { Suggestion } from "./types/suggestion";
|
|
5
|
-
import { AddToCartInput, GenericQuestionPayload, LegacyCheckoutParams, OpenAssistantPayload, ProductQuestionPayload, SubmitCheckoutParams } from "./types/events";
|
|
5
|
+
import { AddToCartInput, ChangeCartQuantityInput, GenericQuestionPayload, LegacyCheckoutParams, OpenAssistantPayload, ProductQuestionPayload, SubmitCheckoutParams } from "./types/events";
|
|
6
6
|
import { SimplifiedProduct } from "./types/product";
|
|
7
7
|
import { SelectSearchResultParams, ViewSearchResultsParams } from "./types/searchAnalytics";
|
|
8
8
|
import { EventsHandler } from "./EventsHandler";
|
|
@@ -12,6 +12,7 @@ export declare class Dialog {
|
|
|
12
12
|
static readonly VERSION: string;
|
|
13
13
|
private _apiKey;
|
|
14
14
|
private _locale;
|
|
15
|
+
private _currency;
|
|
15
16
|
private _countryCode?;
|
|
16
17
|
private _callbacks?;
|
|
17
18
|
private _theme;
|
|
@@ -20,37 +21,31 @@ export declare class Dialog {
|
|
|
20
21
|
private _ignoreOneTrustAutoBlock;
|
|
21
22
|
private _disableAddToCart;
|
|
22
23
|
private _currentProduct?;
|
|
23
|
-
constructor({ apiKey, locale, countryCode, callbacks, theme, userId, ignoreOneTrustAutoBlock, disableAddToCart, product, }: DialogConstructor);
|
|
24
|
+
constructor({ apiKey, locale, currency, countryCode, callbacks, theme, userId, ignoreOneTrustAutoBlock, disableAddToCart, product, }: DialogConstructor);
|
|
24
25
|
get apiKey(): string;
|
|
25
26
|
get theme(): Theme;
|
|
26
27
|
get userId(): string;
|
|
27
28
|
get locale(): string;
|
|
29
|
+
get currency(): string;
|
|
28
30
|
get eventsHandler(): EventsHandler;
|
|
29
31
|
getLocalizationInformations(): DetailedLocaleInfo | null;
|
|
30
32
|
private _createOrRetrieveUserId;
|
|
31
33
|
getSuggestions(productId: string): Promise<Suggestion>;
|
|
32
34
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* call, cancellation belongs to the caller via `options.signal` (previous
|
|
37
|
-
* searches are never cancelled automatically). Rejects with
|
|
38
|
-
* `DialogSearchError` on a non-2xx answer, and with the native AbortError /
|
|
39
|
-
* network error otherwise.
|
|
35
|
+
* Send one search request with names built by `searchIndexName`.
|
|
36
|
+
* Cancel through `options.signal`. HTTP errors reject with `DialogSearchError`;
|
|
37
|
+
* network and cancellation errors propagate unchanged.
|
|
40
38
|
*/
|
|
41
39
|
search(request: SearchRequest, options?: SearchOptions): Promise<SearchResponse>;
|
|
42
40
|
openAssistant(params: OpenAssistantPayload): void;
|
|
43
41
|
closeAssistant(): void;
|
|
44
42
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* its own (floating bookmark, resume surface, free-text input). Call it on
|
|
48
|
-
* every PDP navigation on single-page storefronts; ids must match the
|
|
49
|
-
* Dialog product feed.
|
|
43
|
+
* Set the default product context for assistant questions.
|
|
44
|
+
* Call on each product-page navigation; IDs must match the Dialog product feed.
|
|
50
45
|
*/
|
|
51
46
|
setCurrentProduct(productId: string, variantId?: string): void;
|
|
52
47
|
private static _isValidProductId;
|
|
53
|
-
/**
|
|
48
|
+
/** Clear the product context on non-product pages. */
|
|
54
49
|
clearCurrentProduct(): void;
|
|
55
50
|
private _applyCurrentProductDataset;
|
|
56
51
|
sendProductMessage(params: ProductQuestionPayload): void;
|
|
@@ -59,19 +54,18 @@ export declare class Dialog {
|
|
|
59
54
|
dispatchAssistantEvent(event: AssistantEvent): void;
|
|
60
55
|
getProduct(productId: string, variantId?: string): Promise<SimplifiedProduct>;
|
|
61
56
|
addToCart(input: AddToCartInput): Promise<void>;
|
|
57
|
+
canChangeCartQuantity(): boolean;
|
|
58
|
+
changeCartQuantity(input: ChangeCartQuantityInput): Promise<void>;
|
|
59
|
+
private _isCartWriteDisabled;
|
|
62
60
|
private _getCallbacksOrThrow;
|
|
63
61
|
registerAddToCartEvent(input: AddToCartInput): void;
|
|
64
62
|
registerSubmitCheckoutEvent(params: SubmitCheckoutParams | LegacyCheckoutParams): void;
|
|
65
63
|
/**
|
|
66
|
-
* Emit
|
|
67
|
-
*
|
|
68
|
-
* directly with `items: []` for a rendered no-results state.
|
|
64
|
+
* Emit impressions batched by `createSearchImpressionTracker`.
|
|
65
|
+
* Use `items: []` for an empty result set.
|
|
69
66
|
*/
|
|
70
67
|
trackViewSearchResults(params: ViewSearchResultsParams): void;
|
|
71
|
-
/**
|
|
72
|
-
* Emit on a result click — including auxclick / cmd+click, not only before
|
|
73
|
-
* a same-tab navigation. Force the clicked item's impression first.
|
|
74
|
-
*/
|
|
68
|
+
/** Emit a selection after its impression, including middle-clicks and modified clicks. */
|
|
75
69
|
trackSelectSearchResult(params: SelectSearchResultParams): void;
|
|
76
70
|
private _loadAssistant;
|
|
77
71
|
}
|
package/dist/Dialog.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dialog.d.ts","sourceRoot":"","sources":["../src/Dialog.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,iBAAiB,EAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,EACL,kBAAkB,EAEnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EACL,cAAc,
|
|
1
|
+
{"version":3,"file":"Dialog.d.ts","sourceRoot":"","sources":["../src/Dialog.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,iBAAiB,EAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,EACL,kBAAkB,EAEnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EACL,cAAc,EACd,uBAAuB,EAEvB,sBAAsB,EACtB,oBAAoB,EACpB,oBAAoB,EACpB,sBAAsB,EACtB,oBAAoB,EACrB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EACL,wBAAwB,EACxB,uBAAuB,EACxB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGhD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAE9E,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,qBAAa,MAAM;IACjB,gBAAuB,OAAO,SAAuB;IAErD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,YAAY,CAAC,CAAS;IAE9B,OAAO,CAAC,UAAU,CAAC,CAAkB;IACrC,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,wBAAwB,CAAU;IAC1C,OAAO,CAAC,iBAAiB,CAAU;IACnC,OAAO,CAAC,eAAe,CAAC,CAAiB;gBAE7B,EACV,MAAM,EACN,MAAM,EACN,QAAQ,EACR,WAAW,EACX,SAAS,EACT,KAAK,EACL,MAAM,EACN,uBAAuB,EACvB,gBAAgB,EAChB,OAAO,GACR,EAAE,iBAAiB;IAwBpB,IAAW,MAAM,IAAI,MAAM,CAE1B;IACD,IAAW,KAAK,IAAI,KAAK,CAExB;IACD,IAAW,MAAM,IAAI,MAAM,CAE1B;IACD,IAAW,MAAM,IAAI,MAAM,CAE1B;IACD,IAAW,QAAQ,IAAI,MAAM,CAE5B;IACD,IAAW,aAAa,IAAI,aAAa,CAExC;IAEM,2BAA2B,IAAI,kBAAkB,GAAG,IAAI;IAI/D,OAAO,CAAC,uBAAuB;IAkBlB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAInE;;;;OAIG;IACI,MAAM,CACX,OAAO,EAAE,aAAa,EACtB,OAAO,CAAC,EAAE,aAAa,GACtB,OAAO,CAAC,cAAc,CAAC;IAKnB,aAAa,CAAC,MAAM,EAAE,oBAAoB,GAAG,IAAI;IAKjD,cAAc,IAAI,IAAI;IAI7B;;;OAGG;IACI,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAYrE,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAIhC,sDAAsD;IAC/C,mBAAmB,IAAI,IAAI;IAMlC,OAAO,CAAC,2BAA2B;IA2B5B,kBAAkB,CAAC,MAAM,EAAE,sBAAsB,GAAG,IAAI;IAIxD,kBAAkB,CAAC,MAAM,EAAE,sBAAsB,GAAG,IAAI;IAOxD,gBAAgB,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,IAAI;IAIjE,sBAAsB,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI;IAInD,UAAU,CACf,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,iBAAiB,CAAC;IAQhB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAUrD,qBAAqB,IAAI,OAAO;IAO1B,kBAAkB,CAC7B,KAAK,EAAE,uBAAuB,GAC7B,OAAO,CAAC,IAAI,CAAC;IAQhB,OAAO,CAAC,oBAAoB;IAY5B,OAAO,CAAC,oBAAoB;IAarB,sBAAsB,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI;IASnD,2BAA2B,CAChC,MAAM,EAAE,oBAAoB,GAAG,oBAAoB,GAClD,IAAI;IA0BP;;;OAGG;IACI,sBAAsB,CAAC,MAAM,EAAE,uBAAuB,GAAG,IAAI;IAUpE,0FAA0F;IACnF,uBAAuB,CAAC,MAAM,EAAE,wBAAwB,GAAG,IAAI;IAUtE,OAAO,CAAC,cAAc;CAsCvB"}
|
package/dist/Dialog.js
CHANGED
|
@@ -14,6 +14,7 @@ export class Dialog {
|
|
|
14
14
|
static VERSION = packageJson.version;
|
|
15
15
|
_apiKey;
|
|
16
16
|
_locale;
|
|
17
|
+
_currency;
|
|
17
18
|
_countryCode;
|
|
18
19
|
_callbacks;
|
|
19
20
|
_theme;
|
|
@@ -22,9 +23,10 @@ export class Dialog {
|
|
|
22
23
|
_ignoreOneTrustAutoBlock;
|
|
23
24
|
_disableAddToCart;
|
|
24
25
|
_currentProduct;
|
|
25
|
-
constructor({ apiKey, locale, countryCode, callbacks, theme, userId, ignoreOneTrustAutoBlock, disableAddToCart, product, }) {
|
|
26
|
+
constructor({ apiKey, locale, currency, countryCode, callbacks, theme, userId, ignoreOneTrustAutoBlock, disableAddToCart, product, }) {
|
|
26
27
|
this._apiKey = apiKey;
|
|
27
28
|
this._locale = locale;
|
|
29
|
+
this._currency = currency;
|
|
28
30
|
this._countryCode = countryCode;
|
|
29
31
|
this._callbacks = callbacks;
|
|
30
32
|
this._ignoreOneTrustAutoBlock = ignoreOneTrustAutoBlock ?? false;
|
|
@@ -54,6 +56,9 @@ export class Dialog {
|
|
|
54
56
|
get locale() {
|
|
55
57
|
return this._locale;
|
|
56
58
|
}
|
|
59
|
+
get currency() {
|
|
60
|
+
return this._currency;
|
|
61
|
+
}
|
|
57
62
|
get eventsHandler() {
|
|
58
63
|
return this._eventsHandler;
|
|
59
64
|
}
|
|
@@ -77,13 +82,9 @@ export class Dialog {
|
|
|
77
82
|
return loadSuggestions(this._apiKey, this._locale, productId);
|
|
78
83
|
}
|
|
79
84
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
* call, cancellation belongs to the caller via `options.signal` (previous
|
|
84
|
-
* searches are never cancelled automatically). Rejects with
|
|
85
|
-
* `DialogSearchError` on a non-2xx answer, and with the native AbortError /
|
|
86
|
-
* network error otherwise.
|
|
85
|
+
* Send one search request with names built by `searchIndexName`.
|
|
86
|
+
* Cancel through `options.signal`. HTTP errors reject with `DialogSearchError`;
|
|
87
|
+
* network and cancellation errors propagate unchanged.
|
|
87
88
|
*/
|
|
88
89
|
search(request, options) {
|
|
89
90
|
return searchLexical(this._apiKey, request, options);
|
|
@@ -97,11 +98,8 @@ export class Dialog {
|
|
|
97
98
|
this._eventsHandler.emitExternalEvent(DialogEvents.CLOSE_ASSISTANT);
|
|
98
99
|
}
|
|
99
100
|
/**
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
* its own (floating bookmark, resume surface, free-text input). Call it on
|
|
103
|
-
* every PDP navigation on single-page storefronts; ids must match the
|
|
104
|
-
* Dialog product feed.
|
|
101
|
+
* Set the default product context for assistant questions.
|
|
102
|
+
* Call on each product-page navigation; IDs must match the Dialog product feed.
|
|
105
103
|
*/
|
|
106
104
|
setCurrentProduct(productId, variantId) {
|
|
107
105
|
if (!Dialog._isValidProductId(productId)) {
|
|
@@ -114,23 +112,17 @@ export class Dialog {
|
|
|
114
112
|
static _isValidProductId(productId) {
|
|
115
113
|
return typeof productId === "string" && productId.trim() !== "";
|
|
116
114
|
}
|
|
117
|
-
/**
|
|
115
|
+
/** Clear the product context on non-product pages. */
|
|
118
116
|
clearCurrentProduct() {
|
|
119
117
|
this._currentProduct = undefined;
|
|
120
118
|
this._applyCurrentProductDataset();
|
|
121
119
|
}
|
|
122
|
-
// The
|
|
123
|
-
// the page product: the runtime observes data-product-id / data-variant-id
|
|
124
|
-
// and needs no event plumbing, so SDK and runtime versions can drift.
|
|
120
|
+
// The runtime reads product context from the mount node dataset.
|
|
125
121
|
_applyCurrentProductDataset() {
|
|
126
|
-
//
|
|
127
|
-
// assistant runtime reads the dataset the same way, so on a page where
|
|
128
|
-
// another integration already rendered #dialog-shopify-ai (duplicate-id
|
|
129
|
-
// case) writer and reader must land on the same — first — node.
|
|
122
|
+
// Match the runtime lookup: duplicate IDs must resolve to the same first node.
|
|
130
123
|
const mountNode = document.getElementById("dialog-shopify-ai");
|
|
131
124
|
if (mountNode === null) {
|
|
132
|
-
//
|
|
133
|
-
// removed it) — the declaration is kept but cannot reach the runtime.
|
|
125
|
+
// Keep the context until a mount node is available.
|
|
134
126
|
console.warn("Dialog: assistant mount node not found; the current product declaration has no effect.");
|
|
135
127
|
return;
|
|
136
128
|
}
|
|
@@ -162,27 +154,37 @@ export class Dialog {
|
|
|
162
154
|
getProduct(productId, variantId) {
|
|
163
155
|
return this._getCallbacksOrThrow("getProduct").getProduct(productId, variantId);
|
|
164
156
|
}
|
|
165
|
-
//
|
|
166
|
-
// forwarded to the merchant callback and to the tracking event, so
|
|
167
|
-
// integrations can consume the added-product data wherever they hook in.
|
|
157
|
+
// Forward all product fields to the commerce callback and analytics.
|
|
168
158
|
async addToCart(input) {
|
|
169
|
-
//
|
|
170
|
-
|
|
171
|
-
if (this._disableAddToCart) {
|
|
172
|
-
console.warn("Dialog: addToCart is disabled on this instance (disableAddToCart); ignoring the call.");
|
|
159
|
+
// Disabled instances must neither modify the cart nor emit analytics.
|
|
160
|
+
if (this._isCartWriteDisabled("addToCart"))
|
|
173
161
|
return;
|
|
174
|
-
}
|
|
175
162
|
await this._getCallbacksOrThrow("addToCart").addToCart(input);
|
|
176
163
|
this.registerAddToCartEvent(input);
|
|
177
164
|
return;
|
|
178
165
|
}
|
|
179
|
-
|
|
180
|
-
|
|
166
|
+
canChangeCartQuantity() {
|
|
167
|
+
return (!this._disableAddToCart &&
|
|
168
|
+
this._callbacks?.changeCartQuantity !== undefined);
|
|
169
|
+
}
|
|
170
|
+
async changeCartQuantity(input) {
|
|
171
|
+
if (this._isCartWriteDisabled("changeCartQuantity"))
|
|
172
|
+
return;
|
|
173
|
+
await this._getCallbacksOrThrow("changeCartQuantity").changeCartQuantity(input);
|
|
174
|
+
}
|
|
175
|
+
_isCartWriteDisabled(operation) {
|
|
176
|
+
if (!this._disableAddToCart)
|
|
177
|
+
return false;
|
|
178
|
+
console.warn(`Dialog: ${operation} is disabled on this instance (disableAddToCart); ignoring the call.`);
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
// Validate optional commerce callbacks when invoked.
|
|
181
182
|
_getCallbacksOrThrow(name) {
|
|
182
|
-
|
|
183
|
+
const callbacks = this._callbacks;
|
|
184
|
+
if (callbacks?.[name] === undefined) {
|
|
183
185
|
throw new Error(`Dialog: \`callbacks.${name}\` was not provided to the constructor; ${name}() is unavailable on this instance.`);
|
|
184
186
|
}
|
|
185
|
-
return
|
|
187
|
+
return callbacks;
|
|
186
188
|
}
|
|
187
189
|
registerAddToCartEvent(input) {
|
|
188
190
|
this._eventsHandler.emitExternalEvent(DialogEvents.TRACK_ADD_TO_CART, {
|
|
@@ -190,13 +192,8 @@ export class Dialog {
|
|
|
190
192
|
...input,
|
|
191
193
|
});
|
|
192
194
|
}
|
|
193
|
-
//
|
|
194
|
-
//
|
|
195
|
-
// call this per line item (that has no total and revenue resolves to 0).
|
|
196
|
-
//
|
|
197
|
-
// The legacy per-line signature (`{ productId, quantity, price }`) is still
|
|
198
|
-
// accepted for backward compatibility so existing installs keep working
|
|
199
|
-
// after an upgrade, but it is deprecated: it carries no order total.
|
|
195
|
+
// Call once per completed order; `orderValue` supplies the revenue total.
|
|
196
|
+
// The deprecated per-item payload remains accepted but has no order total.
|
|
200
197
|
registerSubmitCheckoutEvent(params) {
|
|
201
198
|
if ("orderValue" in params) {
|
|
202
199
|
this._eventsHandler.emitExternalEvent(DialogEvents.TRACK_SUBMIT_CHECKOUT, {
|
|
@@ -218,9 +215,8 @@ export class Dialog {
|
|
|
218
215
|
});
|
|
219
216
|
}
|
|
220
217
|
/**
|
|
221
|
-
* Emit
|
|
222
|
-
*
|
|
223
|
-
* directly with `items: []` for a rendered no-results state.
|
|
218
|
+
* Emit impressions batched by `createSearchImpressionTracker`.
|
|
219
|
+
* Use `items: []` for an empty result set.
|
|
224
220
|
*/
|
|
225
221
|
trackViewSearchResults(params) {
|
|
226
222
|
this._eventsHandler.emitExternalEvent(DialogEvents.TRACK_VIEW_SEARCH_RESULTS, {
|
|
@@ -228,10 +224,7 @@ export class Dialog {
|
|
|
228
224
|
...params,
|
|
229
225
|
});
|
|
230
226
|
}
|
|
231
|
-
/**
|
|
232
|
-
* Emit on a result click — including auxclick / cmd+click, not only before
|
|
233
|
-
* a same-tab navigation. Force the clicked item's impression first.
|
|
234
|
-
*/
|
|
227
|
+
/** Emit a selection after its impression, including middle-clicks and modified clicks. */
|
|
235
228
|
trackSelectSearchResult(params) {
|
|
236
229
|
this._eventsHandler.emitExternalEvent(DialogEvents.TRACK_SELECT_SEARCH_RESULT, {
|
|
237
230
|
userId: this._userId,
|
|
@@ -252,7 +245,7 @@ export class Dialog {
|
|
|
252
245
|
div.dataset.countryCode = localeInfo.countryCode;
|
|
253
246
|
div.dataset.language = localeInfo.language;
|
|
254
247
|
if (this._disableAddToCart) {
|
|
255
|
-
//
|
|
248
|
+
// Hide the add-to-cart button in the assistant.
|
|
256
249
|
div.dataset.disableAddToCart = "true";
|
|
257
250
|
}
|
|
258
251
|
document.body.appendChild(div);
|
|
@@ -262,12 +255,7 @@ export class Dialog {
|
|
|
262
255
|
setTimeout(() => {
|
|
263
256
|
const script = document.createElement("script");
|
|
264
257
|
if (this._ignoreOneTrustAutoBlock) {
|
|
265
|
-
//
|
|
266
|
-
// (by domain), so the merchant's data-ot-ignore on their own SDK tag
|
|
267
|
-
// cannot cover this one — it has to be set here. OneTrust's trap fires
|
|
268
|
-
// synchronously inside the `src` setter, so the attribute must be in
|
|
269
|
-
// place BEFORE src is assigned or the script is rewritten to
|
|
270
|
-
// type="text/plain" despite carrying the attribute.
|
|
258
|
+
// Set the bypass attribute before src: OneTrust intercepts the src setter.
|
|
271
259
|
script.setAttribute("data-ot-ignore", "");
|
|
272
260
|
}
|
|
273
261
|
script.defer = true;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialogChangeCartQuantity.spec.d.ts","sourceRoot":"","sources":["../../src/__tests__/dialogChangeCartQuantity.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { Dialog } from "../Dialog";
|
|
3
|
+
const buildDialog = (changeCartQuantity, disableAddToCart = false) => {
|
|
4
|
+
const dialog = Object.create(Dialog.prototype);
|
|
5
|
+
Object.assign(dialog, {
|
|
6
|
+
_callbacks: {
|
|
7
|
+
addToCart: vi.fn(),
|
|
8
|
+
getProduct: vi.fn(),
|
|
9
|
+
...(changeCartQuantity === undefined ? {} : { changeCartQuantity }),
|
|
10
|
+
},
|
|
11
|
+
_disableAddToCart: disableAddToCart,
|
|
12
|
+
});
|
|
13
|
+
return dialog;
|
|
14
|
+
};
|
|
15
|
+
const input = {
|
|
16
|
+
productId: "gid://shopify/Product/42",
|
|
17
|
+
variantId: "gid://shopify/ProductVariant/4242",
|
|
18
|
+
quantity: 2,
|
|
19
|
+
};
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
vi.clearAllMocks();
|
|
22
|
+
});
|
|
23
|
+
describe("Dialog.changeCartQuantity", () => {
|
|
24
|
+
it("forwards the input to the merchant callback", async () => {
|
|
25
|
+
const changeCartQuantity = vi.fn().mockResolvedValue(undefined);
|
|
26
|
+
const dialog = buildDialog(changeCartQuantity);
|
|
27
|
+
await dialog.changeCartQuantity(input);
|
|
28
|
+
expect(changeCartQuantity).toHaveBeenCalledWith(input);
|
|
29
|
+
expect(dialog.canChangeCartQuantity()).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
it("throws an explicit configuration error without the callback", async () => {
|
|
32
|
+
const dialog = buildDialog(undefined);
|
|
33
|
+
await expect(dialog.changeCartQuantity(input)).rejects.toThrowError(/callbacks\.changeCartQuantity/);
|
|
34
|
+
expect(dialog.canChangeCartQuantity()).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
it("is a no-op and reports no capability when disableAddToCart is set", async () => {
|
|
37
|
+
const changeCartQuantity = vi.fn().mockResolvedValue(undefined);
|
|
38
|
+
const dialog = buildDialog(changeCartQuantity, true);
|
|
39
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => undefined);
|
|
40
|
+
await expect(dialog.changeCartQuantity(input)).resolves.toBeUndefined();
|
|
41
|
+
expect(changeCartQuantity).not.toHaveBeenCalled();
|
|
42
|
+
expect(dialog.canChangeCartQuantity()).toBe(false);
|
|
43
|
+
expect(warn).toHaveBeenCalledOnce();
|
|
44
|
+
warn.mockRestore();
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -2,9 +2,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
|
|
|
2
2
|
import { Dialog } from "../Dialog";
|
|
3
3
|
import { EventsHandler } from "../EventsHandler";
|
|
4
4
|
import { DIALOG_CUSTOM_EVENT, DialogEvents } from "../types/events";
|
|
5
|
-
//
|
|
6
|
-
// exercise the search tracking methods, so the instance is built from the
|
|
7
|
-
// prototype with just the fields those methods read.
|
|
5
|
+
// Bypass constructor DOM setup; provide only the fields used by analytics.
|
|
8
6
|
const buildDialog = () => {
|
|
9
7
|
const eventsHandler = new EventsHandler("fr", "user-1");
|
|
10
8
|
const emitExternalEvent = vi.fn();
|
|
@@ -19,7 +17,7 @@ const buildDialog = () => {
|
|
|
19
17
|
};
|
|
20
18
|
const envelope = {
|
|
21
19
|
query_id: "query-1",
|
|
22
|
-
index: "
|
|
20
|
+
index: "products_fr_eur",
|
|
23
21
|
surface: "search_page",
|
|
24
22
|
search_type: "lexical",
|
|
25
23
|
page: 2,
|
|
@@ -8,6 +8,9 @@ describe("public API types", () => {
|
|
|
8
8
|
expectTypeOf().toMatchTypeOf();
|
|
9
9
|
// Invalid callback values must be rejected.
|
|
10
10
|
expectTypeOf().not.toMatchTypeOf();
|
|
11
|
+
expectTypeOf().toMatchTypeOf();
|
|
12
|
+
expectTypeOf().parameters.toMatchTypeOf();
|
|
13
|
+
expectTypeOf().toEqualTypeOf();
|
|
11
14
|
expectTypeOf().parameters.toMatchTypeOf();
|
|
12
15
|
expectTypeOf().toMatchTypeOf();
|
|
13
16
|
expectTypeOf().toMatchTypeOf();
|