@askdialog/dialog-sdk 2.3.0 → 2.4.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 +63 -5
- package/dist/Dialog.d.ts +14 -16
- package/dist/Dialog.d.ts.map +1 -1
- package/dist/Dialog.js +29 -23
- package/dist/DialogSearchError.d.ts +19 -0
- package/dist/DialogSearchError.d.ts.map +1 -0
- package/dist/DialogSearchError.js +17 -0
- package/dist/__tests__/dialogAddToCart.spec.d.ts +2 -0
- package/dist/__tests__/dialogAddToCart.spec.d.ts.map +1 -0
- package/dist/__tests__/dialogAddToCart.spec.js +59 -0
- package/dist/__tests__/dialogCallbacks.spec.d.ts +2 -0
- package/dist/__tests__/dialogCallbacks.spec.d.ts.map +1 -0
- package/dist/__tests__/dialogCallbacks.spec.js +40 -0
- package/dist/__tests__/publicTypes.spec.d.ts +2 -0
- package/dist/__tests__/publicTypes.spec.d.ts.map +1 -0
- package/dist/__tests__/publicTypes.spec.js +21 -0
- package/dist/__tests__/searchService.spec.d.ts +2 -0
- package/dist/__tests__/searchService.spec.d.ts.map +1 -0
- package/dist/__tests__/searchService.spec.js +111 -0
- package/dist/config/config.development.d.ts +1 -0
- package/dist/config/config.development.d.ts.map +1 -1
- package/dist/config/config.development.js +2 -0
- package/dist/config/config.production.d.ts +1 -0
- package/dist/config/config.production.d.ts.map +1 -1
- package/dist/config/config.production.js +2 -0
- package/dist/config/index.d.ts +1 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/services/search.d.ts +7 -0
- package/dist/services/search.d.ts.map +1 -0
- package/dist/services/search.js +37 -0
- package/dist/types/assistantEvent.d.ts +8 -0
- package/dist/types/assistantEvent.d.ts.map +1 -1
- package/dist/types/constructor.d.ts +11 -10
- package/dist/types/constructor.d.ts.map +1 -1
- package/dist/types/events.d.ts +15 -1
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/search.d.ts +50 -0
- package/dist/types/search.d.ts.map +1 -0
- package/dist/types/search.js +4 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -21,11 +21,11 @@ Before using the Dialog SDK, you need:
|
|
|
21
21
|
### Installation
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
npm install @
|
|
24
|
+
npm install @askdialog/dialog-sdk
|
|
25
25
|
# or
|
|
26
|
-
pnpm add @
|
|
26
|
+
pnpm add @askdialog/dialog-sdk
|
|
27
27
|
# or
|
|
28
|
-
yarn add @
|
|
28
|
+
yarn add @askdialog/dialog-sdk
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
You can also use our CDN link if you’re not using a package manager.
|
|
@@ -47,7 +47,7 @@ const client = new window.DialogSDK.Dialog({
|
|
|
47
47
|
### Instantiate the client
|
|
48
48
|
|
|
49
49
|
```typescript
|
|
50
|
-
import { Dialog } from '@
|
|
50
|
+
import { Dialog } from '@askdialog/dialog-sdk';
|
|
51
51
|
|
|
52
52
|
const client = new Dialog({
|
|
53
53
|
apiKey: 'YOUR_API_KEY', // required
|
|
@@ -87,7 +87,9 @@ The optional `countryCode` (ISO 3166 alpha-2, e.g. `'FR'`, `'US'`) sets the regi
|
|
|
87
87
|
The addToCart function is triggered when a user clicks the AddToCart button.
|
|
88
88
|
The getProduct function is used to display product information in the assistant.
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
`callbacks` is **optional**: a search-only integration can construct the client with just `apiKey` and `locale`. Only `getProduct()` and `addToCart()` require their callback — they throw an explicit configuration error when it is absent; every other feature works without callbacks.
|
|
91
|
+
|
|
92
|
+
When the client is instantiated, it will automatically insert into the DOM the Dialog Assistant script, so you can interact with the assistant using `sendProductMessage` or `sendGenericMessage`. This assistant runtime always loads — it owns the shopper identity, consent handling and analytics bridge — while the heavy assistant UI stays lazy-loaded and is not fetched eagerly.
|
|
91
93
|
|
|
92
94
|
### Getters
|
|
93
95
|
|
|
@@ -163,6 +165,62 @@ Example of expected result:
|
|
|
163
165
|
```
|
|
164
166
|
|
|
165
167
|
|
|
168
|
+
- Search products
|
|
169
|
+
|
|
170
|
+
`client.search()` performs a typed product search through Dialog's public API, with no framework and no commerce callbacks required.
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
import { Dialog, DialogSearchError } from '@askdialog/dialog-sdk';
|
|
174
|
+
import type { SearchResponse } from '@askdialog/dialog-sdk';
|
|
175
|
+
|
|
176
|
+
const client = new Dialog({ apiKey: 'YOUR_API_KEY', locale: 'fr' });
|
|
177
|
+
|
|
178
|
+
const response: SearchResponse = await client.search({
|
|
179
|
+
query: 'shampoo',
|
|
180
|
+
page: 0, // optional, zero-indexed (default 0)
|
|
181
|
+
hitsPerPage: 20, // optional, 1-100 (default 20)
|
|
182
|
+
});
|
|
183
|
+
// response.hits[n].product: { id, title?, url?, imageUrl?, priceRange?, inStock? }
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
With the IIFE bundle the results are plain runtime JSON (same shape, no types):
|
|
187
|
+
|
|
188
|
+
```html
|
|
189
|
+
<script src="https://d2m6yt8rnm4dos.cloudfront.net/dialog-sdk.X.Y.Z.min.js"></script>
|
|
190
|
+
<script>
|
|
191
|
+
const client = new window.DialogSDK.Dialog({ apiKey: 'YOUR_API_KEY', locale: 'fr' });
|
|
192
|
+
client.search({ query: 'shampoo' }).then((response) => console.log(response.hits));
|
|
193
|
+
</script>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
A non-2xx answer rejects with `DialogSearchError` — stable `name`, HTTP `status`, optional machine-readable `code` (e.g. `SEARCH_INDEX_NOT_FOUND`) and `message`. Aborting rejects with the native `AbortError`, and network failures keep their native errors.
|
|
197
|
+
|
|
198
|
+
The SDK is stateless: no debounce, no cache, no automatic cancellation of previous searches. For search-as-you-type, own those in the integration:
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
let controller: AbortController | undefined;
|
|
202
|
+
let latestRequestId = 0;
|
|
203
|
+
let debounceTimer: number | undefined;
|
|
204
|
+
|
|
205
|
+
const onInput = (query: string) => {
|
|
206
|
+
window.clearTimeout(debounceTimer);
|
|
207
|
+
debounceTimer = window.setTimeout(async () => {
|
|
208
|
+
controller?.abort(); // cancel the in-flight request
|
|
209
|
+
controller = new AbortController();
|
|
210
|
+
const requestId = ++latestRequestId;
|
|
211
|
+
try {
|
|
212
|
+
const response = await client.search({ query }, { signal: controller.signal });
|
|
213
|
+
if (requestId !== latestRequestId) return; // stale response, ignore
|
|
214
|
+
render(response.hits);
|
|
215
|
+
} catch (error) {
|
|
216
|
+
if (error instanceof DOMException && error.name === 'AbortError') return;
|
|
217
|
+
if (error instanceof DialogSearchError) renderError(error.message);
|
|
218
|
+
else throw error;
|
|
219
|
+
}
|
|
220
|
+
}, 200);
|
|
221
|
+
};
|
|
222
|
+
```
|
|
223
|
+
|
|
166
224
|
- Handler for fetch product
|
|
167
225
|
|
|
168
226
|
The `getProduct` callback is called by the assistant to display product information. You must return an object matching the `SimplifiedProduct` interface.
|
package/dist/Dialog.d.ts
CHANGED
|
@@ -2,16 +2,17 @@ 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 { GenericQuestionPayload, LegacyCheckoutParams, OpenAssistantPayload, ProductQuestionPayload, SubmitCheckoutParams } from "./types/events";
|
|
5
|
+
import { AddToCartInput, GenericQuestionPayload, LegacyCheckoutParams, OpenAssistantPayload, ProductQuestionPayload, SubmitCheckoutParams } from "./types/events";
|
|
6
6
|
import { SimplifiedProduct } from "./types/product";
|
|
7
7
|
import { EventsHandler } from "./EventsHandler";
|
|
8
|
+
import { SearchOptions, SearchRequest, SearchResponse } from "./types/search";
|
|
8
9
|
import { AssistantEvent } from "./types/assistantEvent";
|
|
9
10
|
export declare class Dialog {
|
|
10
11
|
static readonly VERSION: string;
|
|
11
12
|
private _apiKey;
|
|
12
13
|
private _locale;
|
|
13
14
|
private _countryCode?;
|
|
14
|
-
private _callbacks
|
|
15
|
+
private _callbacks?;
|
|
15
16
|
private _theme;
|
|
16
17
|
private _userId;
|
|
17
18
|
private _eventsHandler;
|
|
@@ -24,6 +25,14 @@ export declare class Dialog {
|
|
|
24
25
|
getLocalizationInformations(): DetailedLocaleInfo | null;
|
|
25
26
|
private _createOrRetrieveUserId;
|
|
26
27
|
getSuggestions(productId: string): Promise<Suggestion>;
|
|
28
|
+
/**
|
|
29
|
+
* Typed product search through the Nest public endpoint. Stateless: one
|
|
30
|
+
* request per call, cancellation belongs to the caller via
|
|
31
|
+
* `options.signal` (previous searches are never cancelled automatically).
|
|
32
|
+
* Rejects with `DialogSearchError` on a non-2xx answer, and with the
|
|
33
|
+
* native AbortError / network error otherwise.
|
|
34
|
+
*/
|
|
35
|
+
search(request: SearchRequest, options?: SearchOptions): Promise<SearchResponse>;
|
|
27
36
|
openAssistant(params: OpenAssistantPayload): void;
|
|
28
37
|
closeAssistant(): void;
|
|
29
38
|
sendProductMessage(params: ProductQuestionPayload): void;
|
|
@@ -31,20 +40,9 @@ export declare class Dialog {
|
|
|
31
40
|
onAssistantEvent(listener: (event: AssistantEvent) => void): void;
|
|
32
41
|
dispatchAssistantEvent(event: AssistantEvent): void;
|
|
33
42
|
getProduct(productId: string, variantId?: string): Promise<SimplifiedProduct>;
|
|
34
|
-
addToCart(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
price?: string;
|
|
38
|
-
currency?: string;
|
|
39
|
-
variantId?: string;
|
|
40
|
-
}): Promise<void>;
|
|
41
|
-
registerAddToCartEvent({ productId, quantity, currency, variantId, price, }: {
|
|
42
|
-
productId: string;
|
|
43
|
-
quantity: number;
|
|
44
|
-
price?: string;
|
|
45
|
-
currency?: string;
|
|
46
|
-
variantId?: string;
|
|
47
|
-
}): void;
|
|
43
|
+
addToCart(input: AddToCartInput): Promise<void>;
|
|
44
|
+
private _getCallbacksOrThrow;
|
|
45
|
+
registerAddToCartEvent(input: AddToCartInput): void;
|
|
48
46
|
registerSubmitCheckoutEvent(params: SubmitCheckoutParams | LegacyCheckoutParams): void;
|
|
49
47
|
private _loadAssistant;
|
|
50
48
|
}
|
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,
|
|
1
|
+
{"version":3,"file":"Dialog.d.ts","sourceRoot":"","sources":["../src/Dialog.ts"],"names":[],"mappings":"AAIA,OAAO,EAAmB,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACzE,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,EAEd,sBAAsB,EACtB,oBAAoB,EACpB,oBAAoB,EACpB,sBAAsB,EACtB,oBAAoB,EACrB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,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,YAAY,CAAC,CAAS;IAE9B,OAAO,CAAC,UAAU,CAAC,CAAkB;IACrC,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,cAAc,CAAgB;gBAE1B,EACV,MAAM,EACN,MAAM,EACN,WAAW,EACX,SAAS,EACT,KAAK,EACL,MAAM,GACP,EAAE,iBAAiB;IAYpB,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,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;;;;;;OAMG;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;IAItB,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;IAUhB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAS5D,OAAO,CAAC,oBAAoB;IAUrB,sBAAsB,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI;IAcnD,2BAA2B,CAChC,MAAM,EAAE,oBAAoB,GAAG,oBAAoB,GAClD,IAAI;IA0BP,OAAO,CAAC,cAAc;CA4BvB"}
|
package/dist/Dialog.js
CHANGED
|
@@ -7,6 +7,7 @@ import { ANONYMOUS_CUSTOMER_ID, CUSTOMER_ID } from "./constants/user";
|
|
|
7
7
|
import { DialogEvents, } from "./types/events";
|
|
8
8
|
import { EventsHandler } from "./EventsHandler";
|
|
9
9
|
import { loadSuggestions } from "./services/suggestions";
|
|
10
|
+
import { searchProducts } from "./services/search";
|
|
10
11
|
import { config } from "./config";
|
|
11
12
|
import { exposeSdkOnWindowDialog } from "./windowAudit";
|
|
12
13
|
export class Dialog {
|
|
@@ -63,6 +64,16 @@ export class Dialog {
|
|
|
63
64
|
async getSuggestions(productId) {
|
|
64
65
|
return loadSuggestions(this._apiKey, this._locale, productId);
|
|
65
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Typed product search through the Nest public endpoint. Stateless: one
|
|
69
|
+
* request per call, cancellation belongs to the caller via
|
|
70
|
+
* `options.signal` (previous searches are never cancelled automatically).
|
|
71
|
+
* Rejects with `DialogSearchError` on a non-2xx answer, and with the
|
|
72
|
+
* native AbortError / network error otherwise.
|
|
73
|
+
*/
|
|
74
|
+
search(request, options) {
|
|
75
|
+
return searchProducts(this._apiKey, request, options);
|
|
76
|
+
}
|
|
66
77
|
// TODO: Not yet implemented on assistant
|
|
67
78
|
openAssistant(params) {
|
|
68
79
|
this._eventsHandler.emitExternalEvent(DialogEvents.OPEN_ASSISTANT, params);
|
|
@@ -84,33 +95,28 @@ export class Dialog {
|
|
|
84
95
|
this._eventsHandler.emitAssistantEvent(event.type, event.payload);
|
|
85
96
|
}
|
|
86
97
|
getProduct(productId, variantId) {
|
|
87
|
-
return this.
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
price,
|
|
96
|
-
});
|
|
97
|
-
this.registerAddToCartEvent({
|
|
98
|
-
productId,
|
|
99
|
-
variantId,
|
|
100
|
-
quantity,
|
|
101
|
-
currency,
|
|
102
|
-
price,
|
|
103
|
-
});
|
|
98
|
+
return this._getCallbacksOrThrow("getProduct").getProduct(productId, variantId);
|
|
99
|
+
}
|
|
100
|
+
// The full input (including the optional enriched product fields) is
|
|
101
|
+
// forwarded to the merchant callback and to the tracking event, so
|
|
102
|
+
// integrations can consume the added-product data wherever they hook in.
|
|
103
|
+
async addToCart(input) {
|
|
104
|
+
await this._getCallbacksOrThrow("addToCart").addToCart(input);
|
|
105
|
+
this.registerAddToCartEvent(input);
|
|
104
106
|
return;
|
|
105
107
|
}
|
|
106
|
-
|
|
108
|
+
// Callbacks are optional at construction; the two commerce methods assert
|
|
109
|
+
// theirs at call time with an integration-facing configuration error.
|
|
110
|
+
_getCallbacksOrThrow(name) {
|
|
111
|
+
if (this._callbacks?.[name] === undefined) {
|
|
112
|
+
throw new Error(`Dialog: \`callbacks.${name}\` was not provided to the constructor; ${name}() is unavailable on this instance.`);
|
|
113
|
+
}
|
|
114
|
+
return this._callbacks;
|
|
115
|
+
}
|
|
116
|
+
registerAddToCartEvent(input) {
|
|
107
117
|
this._eventsHandler.emitExternalEvent(DialogEvents.TRACK_ADD_TO_CART, {
|
|
108
118
|
userId: this._userId,
|
|
109
|
-
|
|
110
|
-
variantId,
|
|
111
|
-
quantity,
|
|
112
|
-
price,
|
|
113
|
-
currency,
|
|
119
|
+
...input,
|
|
114
120
|
});
|
|
115
121
|
}
|
|
116
122
|
// Order-level: call ONCE per completed order with the order total.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
interface DialogSearchErrorParams {
|
|
2
|
+
status: number;
|
|
3
|
+
code?: string;
|
|
4
|
+
message: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Non-2xx answer from the search endpoint. `status` is the HTTP status,
|
|
8
|
+
* `code` the stable machine-readable error code the API carries when the
|
|
9
|
+
* body is JSON (e.g. "SEARCH_INDEX_NOT_FOUND"), `message` the API message
|
|
10
|
+
* or the HTTP statusText when the body is not JSON. Aborts and network
|
|
11
|
+
* failures are NOT wrapped: they reject with their native errors.
|
|
12
|
+
*/
|
|
13
|
+
export declare class DialogSearchError extends Error {
|
|
14
|
+
readonly status: number;
|
|
15
|
+
readonly code?: string;
|
|
16
|
+
constructor({ status, code, message }: DialogSearchErrorParams);
|
|
17
|
+
}
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=DialogSearchError.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DialogSearchError.d.ts","sourceRoot":"","sources":["../src/DialogSearchError.ts"],"names":[],"mappings":"AAAA,UAAU,uBAAuB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,SAAgB,MAAM,EAAE,MAAM,CAAC;IAC/B,SAAgB,IAAI,CAAC,EAAE,MAAM,CAAC;gBAElB,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,uBAAuB;CAM/D"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Non-2xx answer from the search endpoint. `status` is the HTTP status,
|
|
3
|
+
* `code` the stable machine-readable error code the API carries when the
|
|
4
|
+
* body is JSON (e.g. "SEARCH_INDEX_NOT_FOUND"), `message` the API message
|
|
5
|
+
* or the HTTP statusText when the body is not JSON. Aborts and network
|
|
6
|
+
* failures are NOT wrapped: they reject with their native errors.
|
|
7
|
+
*/
|
|
8
|
+
export class DialogSearchError extends Error {
|
|
9
|
+
status;
|
|
10
|
+
code;
|
|
11
|
+
constructor({ status, code, message }) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "DialogSearchError";
|
|
14
|
+
this.status = status;
|
|
15
|
+
this.code = code;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialogAddToCart.spec.d.ts","sourceRoot":"","sources":["../../src/__tests__/dialogAddToCart.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { Dialog } from "../Dialog";
|
|
3
|
+
import { EventsHandler } from "../EventsHandler";
|
|
4
|
+
import { DialogEvents } from "../types/events";
|
|
5
|
+
// The full Dialog constructor loads the assistant into the DOM; these tests
|
|
6
|
+
// only exercise the add-to-cart pass-through, so the instance is built from
|
|
7
|
+
// the prototype with just the fields addToCart/registerAddToCartEvent read.
|
|
8
|
+
const buildDialog = (addToCartCallback) => {
|
|
9
|
+
const eventsHandler = new EventsHandler("fr", "user-1");
|
|
10
|
+
const emitExternalEvent = vi.fn();
|
|
11
|
+
eventsHandler.emitExternalEvent =
|
|
12
|
+
emitExternalEvent;
|
|
13
|
+
const dialog = Object.create(Dialog.prototype);
|
|
14
|
+
Object.assign(dialog, {
|
|
15
|
+
_callbacks: { addToCart: addToCartCallback, getProduct: vi.fn() },
|
|
16
|
+
_eventsHandler: eventsHandler,
|
|
17
|
+
_userId: "user-1",
|
|
18
|
+
});
|
|
19
|
+
return { dialog, emitExternalEvent };
|
|
20
|
+
};
|
|
21
|
+
const enrichedInput = {
|
|
22
|
+
productId: "gid://shopify/Product/42",
|
|
23
|
+
variantId: "gid://shopify/ProductVariant/4242",
|
|
24
|
+
quantity: 2,
|
|
25
|
+
price: "19.90",
|
|
26
|
+
currency: "EUR",
|
|
27
|
+
productTitle: "Suggested product",
|
|
28
|
+
variantTitle: "50ml",
|
|
29
|
+
productUrl: "https://shop.example/products/suggested",
|
|
30
|
+
pageProductId: "gid://shopify/Product/1",
|
|
31
|
+
pageVariantId: "gid://shopify/ProductVariant/11",
|
|
32
|
+
};
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
vi.clearAllMocks();
|
|
35
|
+
});
|
|
36
|
+
describe("Dialog.addToCart", () => {
|
|
37
|
+
it("forwards the full enriched input to the merchant callback", async () => {
|
|
38
|
+
const addToCartCallback = vi.fn().mockResolvedValue(undefined);
|
|
39
|
+
const { dialog } = buildDialog(addToCartCallback);
|
|
40
|
+
await dialog.addToCart(enrichedInput);
|
|
41
|
+
expect(addToCartCallback).toHaveBeenCalledWith(enrichedInput);
|
|
42
|
+
});
|
|
43
|
+
it("emits TRACK_ADD_TO_CART with the enriched fields and the userId", async () => {
|
|
44
|
+
const { dialog, emitExternalEvent } = buildDialog(vi.fn().mockResolvedValue(undefined));
|
|
45
|
+
await dialog.addToCart(enrichedInput);
|
|
46
|
+
expect(emitExternalEvent).toHaveBeenCalledWith(DialogEvents.TRACK_ADD_TO_CART, { userId: "user-1", ...enrichedInput });
|
|
47
|
+
});
|
|
48
|
+
it("keeps working with the legacy minimal input", async () => {
|
|
49
|
+
const addToCartCallback = vi.fn().mockResolvedValue(undefined);
|
|
50
|
+
const { dialog, emitExternalEvent } = buildDialog(addToCartCallback);
|
|
51
|
+
const legacyInput = {
|
|
52
|
+
productId: "gid://shopify/Product/42",
|
|
53
|
+
quantity: 1,
|
|
54
|
+
};
|
|
55
|
+
await dialog.addToCart(legacyInput);
|
|
56
|
+
expect(addToCartCallback).toHaveBeenCalledWith(legacyInput);
|
|
57
|
+
expect(emitExternalEvent).toHaveBeenCalledWith(DialogEvents.TRACK_ADD_TO_CART, { userId: "user-1", ...legacyInput });
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialogCallbacks.spec.d.ts","sourceRoot":"","sources":["../../src/__tests__/dialogCallbacks.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { Dialog } from "../Dialog";
|
|
3
|
+
// The full Dialog constructor loads the assistant into the DOM; these tests
|
|
4
|
+
// only exercise the callback plumbing, so the instance is built from the
|
|
5
|
+
// prototype with just the fields the tested methods read.
|
|
6
|
+
const buildDialog = (callbacks) => {
|
|
7
|
+
const dialog = Object.create(Dialog.prototype);
|
|
8
|
+
Object.assign(dialog, { _callbacks: callbacks });
|
|
9
|
+
return dialog;
|
|
10
|
+
};
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
vi.clearAllMocks();
|
|
13
|
+
});
|
|
14
|
+
describe("Dialog without commerce callbacks", () => {
|
|
15
|
+
it("throws an explicit configuration error from getProduct()", async () => {
|
|
16
|
+
const dialog = buildDialog(undefined);
|
|
17
|
+
expect(() => dialog.getProduct("product-1")).toThrowError(/callbacks\.getProduct/);
|
|
18
|
+
});
|
|
19
|
+
it("throws an explicit configuration error from addToCart()", async () => {
|
|
20
|
+
const dialog = buildDialog(undefined);
|
|
21
|
+
await expect(dialog.addToCart({
|
|
22
|
+
productId: "product-1",
|
|
23
|
+
variantId: "variant-1",
|
|
24
|
+
quantity: 1,
|
|
25
|
+
})).rejects.toThrowError(/callbacks\.addToCart/);
|
|
26
|
+
});
|
|
27
|
+
it("throws when only the other callback was provided", () => {
|
|
28
|
+
const dialog = buildDialog({ addToCart: vi.fn() });
|
|
29
|
+
expect(() => dialog.getProduct("product-1")).toThrowError(/callbacks\.getProduct/);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
describe("Dialog with commerce callbacks (historical construction)", () => {
|
|
33
|
+
it("keeps forwarding getProduct() to the merchant callback", async () => {
|
|
34
|
+
const product = { id: "product-1" };
|
|
35
|
+
const getProduct = vi.fn().mockResolvedValue(product);
|
|
36
|
+
const dialog = buildDialog({ getProduct });
|
|
37
|
+
await expect(dialog.getProduct("product-1", "variant-1")).resolves.toBe(product);
|
|
38
|
+
expect(getProduct).toHaveBeenCalledWith("product-1", "variant-1");
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publicTypes.spec.d.ts","sourceRoot":"","sources":["../../src/__tests__/publicTypes.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Compile-time contract of the public API: these assertions are checked by
|
|
2
|
+
// `tsc` (test-type), the runtime test only anchors the file in the suite.
|
|
3
|
+
import { describe, expect, expectTypeOf, it } from "vitest";
|
|
4
|
+
import { DialogSearchError, } from "../index";
|
|
5
|
+
describe("public API types", () => {
|
|
6
|
+
it("accepts construction with and without commerce callbacks", () => {
|
|
7
|
+
expectTypeOf().toMatchTypeOf();
|
|
8
|
+
expectTypeOf().toMatchTypeOf();
|
|
9
|
+
// Invalid callback values must be rejected.
|
|
10
|
+
expectTypeOf().not.toMatchTypeOf();
|
|
11
|
+
expectTypeOf().parameters.toMatchTypeOf();
|
|
12
|
+
expectTypeOf().returns.resolves.toEqualTypeOf();
|
|
13
|
+
expectTypeOf().toEqualTypeOf();
|
|
14
|
+
expectTypeOf().toEqualTypeOf();
|
|
15
|
+
expectTypeOf().toMatchTypeOf();
|
|
16
|
+
const error = new DialogSearchError({ status: 404, message: "not found" });
|
|
17
|
+
expectTypeOf(error.status).toEqualTypeOf();
|
|
18
|
+
expectTypeOf(error.code).toEqualTypeOf();
|
|
19
|
+
expect(error.name).toBe("DialogSearchError");
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"searchService.spec.d.ts","sourceRoot":"","sources":["../../src/__tests__/searchService.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { DialogSearchError } from "../DialogSearchError";
|
|
3
|
+
import { searchProducts } from "../services/search";
|
|
4
|
+
const API_KEY = "pk_test_abcdef";
|
|
5
|
+
const emptyResponse = {
|
|
6
|
+
queryId: "0198c3f2-0000-7000-8000-000000000000",
|
|
7
|
+
hits: [],
|
|
8
|
+
nbHits: 0,
|
|
9
|
+
page: 0,
|
|
10
|
+
nbPages: 0,
|
|
11
|
+
hitsPerPage: 20,
|
|
12
|
+
processingTimeMs: 3,
|
|
13
|
+
query: "running shoes",
|
|
14
|
+
};
|
|
15
|
+
const jsonResponse = (body, status = 200) => new Response(JSON.stringify(body), {
|
|
16
|
+
status,
|
|
17
|
+
headers: { "Content-Type": "application/json" },
|
|
18
|
+
});
|
|
19
|
+
const fetchMock = vi.fn();
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
22
|
+
});
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
vi.unstubAllGlobals();
|
|
25
|
+
vi.clearAllMocks();
|
|
26
|
+
});
|
|
27
|
+
describe("searchProducts", () => {
|
|
28
|
+
it("POSTs the exact request to /public/search with only the api key header", async () => {
|
|
29
|
+
fetchMock.mockResolvedValue(jsonResponse(emptyResponse));
|
|
30
|
+
await searchProducts(API_KEY, {
|
|
31
|
+
query: "running shoes",
|
|
32
|
+
page: 2,
|
|
33
|
+
hitsPerPage: 50,
|
|
34
|
+
});
|
|
35
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
36
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
37
|
+
expect(url).toMatch(/\/public\/search$/);
|
|
38
|
+
expect(init.method).toBe("POST");
|
|
39
|
+
expect(init.headers).toEqual({
|
|
40
|
+
"Content-Type": "application/json",
|
|
41
|
+
"x-dialog-api-key": API_KEY,
|
|
42
|
+
});
|
|
43
|
+
expect(JSON.parse(init.body)).toEqual({
|
|
44
|
+
query: "running shoes",
|
|
45
|
+
page: 2,
|
|
46
|
+
hitsPerPage: 50,
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
it("omits pagination fields the caller did not provide", async () => {
|
|
50
|
+
fetchMock.mockResolvedValue(jsonResponse(emptyResponse));
|
|
51
|
+
await searchProducts(API_KEY, { query: "running shoes" });
|
|
52
|
+
const [, init] = fetchMock.mock.calls[0];
|
|
53
|
+
expect(JSON.parse(init.body)).toEqual({
|
|
54
|
+
query: "running shoes",
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
it("resolves the typed response, including empty results", async () => {
|
|
58
|
+
fetchMock.mockResolvedValue(jsonResponse(emptyResponse));
|
|
59
|
+
await expect(searchProducts(API_KEY, { query: "running shoes" })).resolves.toEqual(emptyResponse);
|
|
60
|
+
});
|
|
61
|
+
it.each([
|
|
62
|
+
[401, "INVALID_WIDGET_API_KEY", "Invalid widget API key"],
|
|
63
|
+
[
|
|
64
|
+
422,
|
|
65
|
+
"VALIDATION_ERROR",
|
|
66
|
+
"query must contain at least 2 visible characters",
|
|
67
|
+
],
|
|
68
|
+
[429, "THROTTLED", "Too many requests"],
|
|
69
|
+
[503, "STOREFRONT_SEARCH_UNAVAILABLE", "Storefront search is unavailable"],
|
|
70
|
+
])("rejects a %i answer with a DialogSearchError carrying status, code and message", async (status, code, message) => {
|
|
71
|
+
fetchMock.mockResolvedValue(jsonResponse({ statusCode: status, error: code, message }, status));
|
|
72
|
+
const error = await searchProducts(API_KEY, {
|
|
73
|
+
query: "running shoes",
|
|
74
|
+
}).catch((caught) => caught);
|
|
75
|
+
expect(error).toBeInstanceOf(DialogSearchError);
|
|
76
|
+
expect(error).toMatchObject({
|
|
77
|
+
name: "DialogSearchError",
|
|
78
|
+
status,
|
|
79
|
+
code,
|
|
80
|
+
message,
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
it("falls back to statusText when the error body is not JSON", async () => {
|
|
84
|
+
fetchMock.mockResolvedValue(new Response("<html>Bad Gateway</html>", {
|
|
85
|
+
status: 502,
|
|
86
|
+
statusText: "Bad Gateway",
|
|
87
|
+
}));
|
|
88
|
+
const error = await searchProducts(API_KEY, {
|
|
89
|
+
query: "running shoes",
|
|
90
|
+
}).catch((caught) => caught);
|
|
91
|
+
expect(error).toBeInstanceOf(DialogSearchError);
|
|
92
|
+
expect(error).toMatchObject({
|
|
93
|
+
status: 502,
|
|
94
|
+
code: undefined,
|
|
95
|
+
message: "Bad Gateway",
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
it("propagates network failures untouched", async () => {
|
|
99
|
+
const networkError = new TypeError("Failed to fetch");
|
|
100
|
+
fetchMock.mockRejectedValue(networkError);
|
|
101
|
+
await expect(searchProducts(API_KEY, { query: "running shoes" })).rejects.toBe(networkError);
|
|
102
|
+
});
|
|
103
|
+
it("forwards the AbortSignal and preserves the native AbortError", async () => {
|
|
104
|
+
const abortError = new DOMException("The operation was aborted.", "AbortError");
|
|
105
|
+
fetchMock.mockRejectedValue(abortError);
|
|
106
|
+
const controller = new AbortController();
|
|
107
|
+
await expect(searchProducts(API_KEY, { query: "running shoes" }, { signal: controller.signal })).rejects.toBe(abortError);
|
|
108
|
+
const [, init] = fetchMock.mock.calls[0];
|
|
109
|
+
expect(init.signal).toBe(controller.signal);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.development.d.ts","sourceRoot":"","sources":["../../src/config/config.development.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,MAAM
|
|
1
|
+
{"version":3,"file":"config.development.d.ts","sourceRoot":"","sources":["../../src/config/config.development.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,MAAM;;;;CAKlB,CAAC;AACF,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export const config = {
|
|
2
2
|
baseApiUrl: "https://abkjr5ukvi.execute-api.eu-west-1.amazonaws.com",
|
|
3
|
+
// Nest monolith (staging) — serves POST /public/search.
|
|
4
|
+
monolithApiUrl: "https://fvcphlqyle.execute-api.eu-west-1.amazonaws.com",
|
|
3
5
|
assistantUrl: "https://d2bycosa71tnxv.cloudfront.net/assets/index.js",
|
|
4
6
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.production.d.ts","sourceRoot":"","sources":["../../src/config/config.production.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,MAAM
|
|
1
|
+
{"version":3,"file":"config.production.d.ts","sourceRoot":"","sources":["../../src/config/config.production.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,MAAM;;;;CAKlB,CAAC;AACF,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export const config = {
|
|
2
2
|
baseApiUrl: "https://rtbzcxkmwj.execute-api.eu-west-1.amazonaws.com",
|
|
3
|
+
// Nest monolith (production) — serves POST /public/search.
|
|
4
|
+
monolithApiUrl: "https://n5e8pa4r9a.execute-api.eu-west-1.amazonaws.com",
|
|
3
5
|
assistantUrl: "https://d2zm7i5bmzo6ze.cloudfront.net/assets/index.js",
|
|
4
6
|
};
|
package/dist/config/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,MAAM
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,MAAM;;;;CAKlB,CAAC;AACF,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC"}
|
package/dist/config/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export const config = {
|
|
2
2
|
baseApiUrl: "https://rtbzcxkmwj.execute-api.eu-west-1.amazonaws.com",
|
|
3
|
+
// Nest monolith (production) — serves POST /public/search.
|
|
4
|
+
monolithApiUrl: "https://n5e8pa4r9a.execute-api.eu-west-1.amazonaws.com",
|
|
3
5
|
assistantUrl: "https://d2zm7i5bmzo6ze.cloudfront.net/assets/index.js",
|
|
4
6
|
};
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,iBAAiB,CAAC;AAChC,cAAc,SAAS,CAAC;AACxB,cAAc,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,SAAS,CAAC;AACxB,cAAc,eAAe,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { SearchOptions, SearchRequest, SearchResponse } from "../types/search";
|
|
2
|
+
/**
|
|
3
|
+
* One POST per invocation — no debounce, cache, retry or request state; the
|
|
4
|
+
* caller owns cancellation through `options.signal`.
|
|
5
|
+
*/
|
|
6
|
+
export declare const searchProducts: (apiKey: string, request: SearchRequest, options?: SearchOptions) => Promise<SearchResponse>;
|
|
7
|
+
//# sourceMappingURL=search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/services/search.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAuB/E;;;GAGG;AACH,eAAO,MAAM,cAAc,GACzB,QAAQ,MAAM,EACd,SAAS,aAAa,EACtB,UAAU,aAAa,KACtB,OAAO,CAAC,cAAc,CAgBxB,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { config } from "../config";
|
|
2
|
+
import { DialogSearchError } from "../DialogSearchError";
|
|
3
|
+
const SEARCH_PATH = "/public/search";
|
|
4
|
+
const API_KEY_HEADER = "x-dialog-api-key";
|
|
5
|
+
const toSearchError = async (response) => {
|
|
6
|
+
let body;
|
|
7
|
+
try {
|
|
8
|
+
body = (await response.json());
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
body = undefined;
|
|
12
|
+
}
|
|
13
|
+
const code = typeof body?.error === "string" ? body.error : undefined;
|
|
14
|
+
const message = typeof body?.message === "string" && body.message.length > 0
|
|
15
|
+
? body.message
|
|
16
|
+
: response.statusText;
|
|
17
|
+
return new DialogSearchError({ status: response.status, code, message });
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* One POST per invocation — no debounce, cache, retry or request state; the
|
|
21
|
+
* caller owns cancellation through `options.signal`.
|
|
22
|
+
*/
|
|
23
|
+
export const searchProducts = async (apiKey, request, options) => {
|
|
24
|
+
const response = await fetch(`${config.monolithApiUrl}${SEARCH_PATH}`, {
|
|
25
|
+
method: "POST",
|
|
26
|
+
headers: {
|
|
27
|
+
"Content-Type": "application/json",
|
|
28
|
+
[API_KEY_HEADER]: apiKey,
|
|
29
|
+
},
|
|
30
|
+
body: JSON.stringify(request),
|
|
31
|
+
signal: options?.signal,
|
|
32
|
+
});
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
throw await toSearchError(response);
|
|
35
|
+
}
|
|
36
|
+
return (await response.json());
|
|
37
|
+
};
|
|
@@ -18,6 +18,14 @@ export interface GenericAssistantEventPayload {
|
|
|
18
18
|
userId?: string;
|
|
19
19
|
productId?: string;
|
|
20
20
|
variantId?: string;
|
|
21
|
+
quantity?: number;
|
|
22
|
+
price?: string;
|
|
23
|
+
currency?: string;
|
|
24
|
+
productTitle?: string;
|
|
25
|
+
variantTitle?: string;
|
|
26
|
+
productUrl?: string;
|
|
27
|
+
pageProductId?: string;
|
|
28
|
+
pageVariantId?: string;
|
|
21
29
|
}
|
|
22
30
|
export type AssistantEventPayload = GenericAssistantEventPayload;
|
|
23
31
|
export interface AssistantEvent<T = AssistantEventPayload> {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assistantEvent.d.ts","sourceRoot":"","sources":["../../src/types/assistantEvent.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,yBAAyB,CAAC;AAE7D,oBAAY,eAAe;IACzB,qBAAqB,wBAAwB;IAC7C,qBAAqB,wBAAwB;IAC7C,iBAAiB,oBAAoB;IACrC,4BAA4B,6BAA6B;IACzD,0BAA0B,6BAA6B;IACvD,kBAAkB,oBAAoB;IACtC,2BAA2B,6BAA6B;IACxD,2BAA2B,6BAA6B;CACzD;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;CACb;
|
|
1
|
+
{"version":3,"file":"assistantEvent.d.ts","sourceRoot":"","sources":["../../src/types/assistantEvent.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,yBAAyB,CAAC;AAE7D,oBAAY,eAAe;IACzB,qBAAqB,wBAAwB;IAC7C,qBAAqB,wBAAwB;IAC7C,iBAAiB,oBAAoB;IACrC,4BAA4B,6BAA6B;IACzD,0BAA0B,6BAA6B;IACvD,kBAAkB,oBAAoB;IACtC,2BAA2B,6BAA6B;IACxD,2BAA2B,6BAA6B;CACzD;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;CACb;AAKD,MAAM,WAAW,4BAA4B;IAC3C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,qBAAqB,GAAG,4BAA4B,CAAC;AAEjE,MAAM,WAAW,cAAc,CAAC,CAAC,GAAG,qBAAqB;IACvD,IAAI,EAAE,eAAe,CAAC;IACtB,OAAO,EAAE,CAAC,CAAC;CACZ"}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import { AddToCartInput } from "./events";
|
|
1
2
|
import { SimplifiedProduct } from "./product";
|
|
2
3
|
import { Theme } from "./theme";
|
|
4
|
+
export interface DialogCallbacks {
|
|
5
|
+
addToCart: (input: AddToCartInput) => Promise<void>;
|
|
6
|
+
getProduct: (productId: string, variantId?: string) => Promise<SimplifiedProduct>;
|
|
7
|
+
}
|
|
3
8
|
export interface DialogConstructor {
|
|
4
9
|
apiKey: string;
|
|
5
10
|
locale: string;
|
|
@@ -9,16 +14,12 @@ export interface DialogConstructor {
|
|
|
9
14
|
* Omit it to keep deriving the country from the locale (backward compatible).
|
|
10
15
|
*/
|
|
11
16
|
countryCode?: string;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
currency?: string;
|
|
19
|
-
}) => Promise<void>;
|
|
20
|
-
getProduct: (productId: string, variantId?: string) => Promise<SimplifiedProduct>;
|
|
21
|
-
};
|
|
17
|
+
/**
|
|
18
|
+
* Commerce callbacks. Optional: a search-only integration can omit them —
|
|
19
|
+
* the assistant runtime still loads, and only `getProduct()` / `addToCart()`
|
|
20
|
+
* throw a configuration error when their callback is absent.
|
|
21
|
+
*/
|
|
22
|
+
callbacks?: DialogCallbacks;
|
|
22
23
|
theme?: Partial<Theme>;
|
|
23
24
|
userId?: string;
|
|
24
25
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constructor.d.ts","sourceRoot":"","sources":["../../src/types/constructor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"constructor.d.ts","sourceRoot":"","sources":["../../src/types/constructor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,UAAU,EAAE,CACV,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,KACf,OAAO,CAAC,iBAAiB,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,KAAK,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
|
package/dist/types/events.d.ts
CHANGED
|
@@ -34,7 +34,21 @@ export interface DiagnosticPayload {
|
|
|
34
34
|
buttonType: DiagnosticButtonType;
|
|
35
35
|
url: string;
|
|
36
36
|
}
|
|
37
|
-
export interface
|
|
37
|
+
export interface AddToCartProductDetails {
|
|
38
|
+
productTitle?: string;
|
|
39
|
+
variantTitle?: string;
|
|
40
|
+
productUrl?: string;
|
|
41
|
+
pageProductId?: string;
|
|
42
|
+
pageVariantId?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface AddToCartInput extends AddToCartProductDetails {
|
|
45
|
+
productId: string;
|
|
46
|
+
quantity: number;
|
|
47
|
+
price?: string;
|
|
48
|
+
currency?: string;
|
|
49
|
+
variantId?: string;
|
|
50
|
+
}
|
|
51
|
+
export interface TrackEventPayload extends AddToCartProductDetails {
|
|
38
52
|
userId?: string;
|
|
39
53
|
productId: string;
|
|
40
54
|
variantId?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/types/events.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,+BAA+B,CAAC;AAEhE,oBAAY,YAAY;IACtB,cAAc,mBAAmB;IACjC,eAAe,oBAAoB;IACnC,YAAY,qBAAqB;IACjC,qBAAqB,qBAAqB;IAC1C,iBAAiB,sBAAsB;IACvC,qBAAqB,0BAA0B;CAChD;AACD,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;CAClB;AACD,MAAM,WAAW,sBAAuB,SAAQ,sBAAsB;IACpE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,MAAM,oBAAoB,GAAG,mBAAmB,GAAG,cAAc,CAAC;AAExE,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,UAAU,EAAE,oBAAoB,CAAC;IACjC,GAAG,EAAE,MAAM,CAAC;CACb;AAQD,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/types/events.ts"],"names":[],"mappings":"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"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,SAAS,CAAC;AACxB,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,SAAS,CAAC;AACxB,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,kBAAkB,CAAC"}
|
package/dist/types/index.js
CHANGED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export interface SearchRequest {
|
|
2
|
+
/** Trimmed server-side; must keep at least two visible characters (code points). */
|
|
3
|
+
query: string;
|
|
4
|
+
/** Zero-indexed results page. Defaults to 0 server-side. */
|
|
5
|
+
page?: number;
|
|
6
|
+
/** Between 1 and 100. Defaults to 20 server-side. */
|
|
7
|
+
hitsPerPage?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface SearchOptions {
|
|
10
|
+
/** Forwarded to fetch untouched: aborting rejects with the native AbortError. */
|
|
11
|
+
signal?: AbortSignal;
|
|
12
|
+
}
|
|
13
|
+
export interface SearchPrice {
|
|
14
|
+
/** Decimal amount as a string, exactly as indexed (e.g. "24.90"). */
|
|
15
|
+
amount: string;
|
|
16
|
+
currencyCode: string;
|
|
17
|
+
}
|
|
18
|
+
export interface SearchPriceRange {
|
|
19
|
+
min: SearchPrice;
|
|
20
|
+
max: SearchPrice;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Storefront-ready projection of a search hit. Every display field is
|
|
24
|
+
* best-effort: a product may carry only its id.
|
|
25
|
+
*/
|
|
26
|
+
export interface SearchProduct {
|
|
27
|
+
id: string;
|
|
28
|
+
title?: string;
|
|
29
|
+
url?: string;
|
|
30
|
+
imageUrl?: string;
|
|
31
|
+
priceRange?: SearchPriceRange;
|
|
32
|
+
inStock?: boolean;
|
|
33
|
+
}
|
|
34
|
+
export interface SearchHit {
|
|
35
|
+
id: string;
|
|
36
|
+
score: number;
|
|
37
|
+
product: SearchProduct;
|
|
38
|
+
}
|
|
39
|
+
export interface SearchResponse {
|
|
40
|
+
/** Nest-generated id for search attribution analytics. */
|
|
41
|
+
queryId: string;
|
|
42
|
+
hits: SearchHit[];
|
|
43
|
+
nbHits: number;
|
|
44
|
+
page: number;
|
|
45
|
+
nbPages: number;
|
|
46
|
+
hitsPerPage: number;
|
|
47
|
+
processingTimeMs: number;
|
|
48
|
+
query: string;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/types/search.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,oFAAoF;IACpF,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,iFAAiF;IACjF,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,WAAW,CAAC;IACjB,GAAG,EAAE,WAAW,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,aAAa,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,0DAA0D;IAC1D,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,SAAS,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;CACf"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askdialog/dialog-sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Dialog SDK",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"author": "Dialog",
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"devDependencies": {
|
|
29
|
+
"@vitest/coverage-v8": "^3.0.0",
|
|
29
30
|
"esbuild": "^0.25.12",
|
|
30
31
|
"eslint-import-resolver-typescript": "^4.4.4",
|
|
31
32
|
"eslint-plugin-import": "^2.32.0",
|
|
@@ -46,7 +47,10 @@
|
|
|
46
47
|
"lint": "eslint .",
|
|
47
48
|
"lint:fix": "eslint . --fix",
|
|
48
49
|
"test": "vitest run",
|
|
50
|
+
"test-coverage": "vitest run --coverage",
|
|
51
|
+
"test-linter": "eslint .",
|
|
49
52
|
"test-type": "tsc --noEmit",
|
|
53
|
+
"smoke": "chmod +x scripts/smoke.sh && ./scripts/smoke.sh",
|
|
50
54
|
"link": "pnpm link --global",
|
|
51
55
|
"unlink": "pnpm unlink",
|
|
52
56
|
"watch": "tsc --watch",
|