@joryio/web-sdk 0.0.0-stage → 1.3.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/CHANGELOG.md +5 -0
- package/LICENSE +54 -0
- package/README.md +497 -2
- package/dist/core/config.d.ts +101 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/device.d.ts +14 -0
- package/dist/core/device.d.ts.map +1 -0
- package/dist/core/ecommerce.d.ts +67 -0
- package/dist/core/ecommerce.d.ts.map +1 -0
- package/dist/core/identity.d.ts +36 -0
- package/dist/core/identity.d.ts.map +1 -0
- package/dist/core/inapp.d.ts +169 -0
- package/dist/core/inapp.d.ts.map +1 -0
- package/dist/core/network.d.ts +158 -0
- package/dist/core/network.d.ts.map +1 -0
- package/dist/core/outbox.d.ts +79 -0
- package/dist/core/outbox.d.ts.map +1 -0
- package/dist/core/pending-attributes.d.ts +43 -0
- package/dist/core/pending-attributes.d.ts.map +1 -0
- package/dist/core/push.d.ts +59 -0
- package/dist/core/push.d.ts.map +1 -0
- package/dist/core/queue.d.ts +107 -0
- package/dist/core/queue.d.ts.map +1 -0
- package/dist/core/sdk.d.ts +169 -0
- package/dist/core/sdk.d.ts.map +1 -0
- package/dist/core/session.d.ts +36 -0
- package/dist/core/session.d.ts.map +1 -0
- package/dist/core/storage.d.ts +17 -0
- package/dist/core/storage.d.ts.map +1 -0
- package/dist/core/user.d.ts +28 -0
- package/dist/core/user.d.ts.map +1 -0
- package/dist/core/utm.d.ts +39 -0
- package/dist/core/utm.d.ts.map +1 -0
- package/dist/core/values.d.ts +3 -0
- package/dist/core/values.d.ts.map +1 -0
- package/dist/core/write-retry.d.ts +66 -0
- package/dist/core/write-retry.d.ts.map +1 -0
- package/dist/index.d.mts +711 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/joryio-sw.js +6 -0
- package/dist/joryio.cjs.js +7265 -0
- package/dist/joryio.min.js +7 -0
- package/dist/joryio.min.js.map +1 -0
- package/dist/joryio.mjs +7254 -0
- package/dist/joryio.umd.js +7271 -0
- package/dist/utils/bucket-hash.d.ts +2 -0
- package/dist/utils/bucket-hash.d.ts.map +1 -0
- package/dist/utils/logger.d.ts +14 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/uuid.d.ts +3 -0
- package/dist/utils/uuid.d.ts.map +1 -0
- package/package.json +90 -4
- package/src/core/config.ts +362 -0
- package/src/core/device.ts +50 -0
- package/src/core/ecommerce.ts +482 -0
- package/src/core/identity.ts +251 -0
- package/src/core/inapp.ts +2419 -0
- package/src/core/network.ts +1261 -0
- package/src/core/outbox.ts +707 -0
- package/src/core/pending-attributes.ts +350 -0
- package/src/core/push.ts +667 -0
- package/src/core/queue.ts +894 -0
- package/src/core/sdk.ts +3108 -0
- package/src/core/session.ts +311 -0
- package/src/core/storage.ts +157 -0
- package/src/core/user.ts +203 -0
- package/src/core/utm.ts +304 -0
- package/src/core/values.ts +85 -0
- package/src/core/write-retry.ts +136 -0
- package/src/index.ts +305 -0
- package/src/snippet/loader.js +134 -0
- package/src/utils/bucket-hash.ts +44 -0
- package/src/utils/logger.ts +58 -0
- package/src/utils/uuid.ts +29 -0
- package/src/worker/joryio-sw.js +218 -0
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E-Commerce tracking helpers for Joryio SDK
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { JoryioSDK } from './sdk';
|
|
6
|
+
import { Logger } from '../utils/logger';
|
|
7
|
+
|
|
8
|
+
export interface EcommerceProduct {
|
|
9
|
+
productId: string;
|
|
10
|
+
name: string;
|
|
11
|
+
price: number;
|
|
12
|
+
quantity?: number;
|
|
13
|
+
category?: string;
|
|
14
|
+
brand?: string;
|
|
15
|
+
variant?: string;
|
|
16
|
+
variantId?: string;
|
|
17
|
+
sku?: string;
|
|
18
|
+
imageUrl?: string;
|
|
19
|
+
url?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface EcommerceCartItem extends EcommerceProduct {
|
|
23
|
+
quantity: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface EcommerceOrder {
|
|
27
|
+
orderId: string;
|
|
28
|
+
items: EcommerceCartItem[];
|
|
29
|
+
value: number;
|
|
30
|
+
currency?: string;
|
|
31
|
+
shipping?: number;
|
|
32
|
+
tax?: number;
|
|
33
|
+
discount?: number;
|
|
34
|
+
coupon?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface EcommerceConfig {
|
|
38
|
+
/** Default currency for every event (ISO 4217). Default `USD`. */
|
|
39
|
+
currency?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** A required id or label: a string that is not empty or only whitespace. */
|
|
43
|
+
const isText = (v: unknown): v is string => typeof v === 'string' && v.trim().length > 0;
|
|
44
|
+
const isFiniteNumber = (v: unknown): v is number => typeof v === 'number' && Number.isFinite(v);
|
|
45
|
+
/** A finite number, or undefined - never NaN or Infinity on the wire. */
|
|
46
|
+
const finiteOrUndefined = (v: unknown): number | undefined => (isFiniteNumber(v) ? v : undefined);
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* E-Commerce tracking helper class
|
|
50
|
+
* Provides standardized e-commerce event tracking
|
|
51
|
+
*
|
|
52
|
+
* Every helper checks its required arguments and, when one is missing, logs
|
|
53
|
+
* a warning and sends NOTHING (SDK 1.3.0 contract item 42). ONE set on every
|
|
54
|
+
* Joryio SDK and wrapper - web, iOS, Android, React Native, Unity - pinned by
|
|
55
|
+
* the backend's ecommerce-guard-parity spec:
|
|
56
|
+
* - a product (and every cart/order line): a productId and a finite price;
|
|
57
|
+
* - cart calls: an items array and a finite cartValue;
|
|
58
|
+
* - purchase: an orderId, an items array (may be empty) and a finite value;
|
|
59
|
+
* - every other id or label the event is about (categoryId and
|
|
60
|
+
* categoryName, query, paymentMethod, shareMethod, orderId, couponCode);
|
|
61
|
+
* - an optional amount (cartValue on addToCart, shipping, tax, discount,
|
|
62
|
+
* refundAmount, discountAmount) may be left out, but one given must be
|
|
63
|
+
* finite - it is refused, not silently dropped.
|
|
64
|
+
* An id that is empty or only whitespace counts as missing. It used to throw into the host page on a missing `items` or a
|
|
65
|
+
* null product, and to send an "Order Completed" with no order id and a total
|
|
66
|
+
* of 0 that reporting counted as a real order. No helper ever throws.
|
|
67
|
+
*/
|
|
68
|
+
export class EcommerceTracker {
|
|
69
|
+
private sdk: JoryioSDK;
|
|
70
|
+
private config: { currency: string };
|
|
71
|
+
private logger: Logger;
|
|
72
|
+
|
|
73
|
+
constructor(sdk: JoryioSDK, config?: EcommerceConfig | null, logger?: Logger) {
|
|
74
|
+
this.sdk = sdk;
|
|
75
|
+
this.config = {
|
|
76
|
+
currency: (config && typeof config.currency === 'string' && config.currency) || 'USD',
|
|
77
|
+
};
|
|
78
|
+
this.logger = logger || new Logger(false);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Run one helper: a missing argument is a warning and no event, never a throw. */
|
|
82
|
+
private guard(method: string, problem: string | null, send: () => void): void {
|
|
83
|
+
if (problem) {
|
|
84
|
+
this.logger.warn(`ecommerce.${method}() not tracked: ${problem}`);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
send();
|
|
89
|
+
} catch (error) {
|
|
90
|
+
this.logger.error(`ecommerce.${method}() failed:`, error);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private productProblem(product: unknown, what = 'product'): string | null {
|
|
95
|
+
if (!product || typeof product !== 'object') return `a ${what} object is required`;
|
|
96
|
+
if (!isText((product as any).productId)) return `${what}.productId is required`;
|
|
97
|
+
if (!isFiniteNumber((product as any).price)) return `${what}.price must be a finite number`;
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** An items array whose every line (a null entry is skipped) is a valid product. */
|
|
102
|
+
private itemsProblem(items: unknown): string | null {
|
|
103
|
+
if (!Array.isArray(items)) return 'an items array is required';
|
|
104
|
+
for (const item of items) {
|
|
105
|
+
if (item == null) continue;
|
|
106
|
+
const problem = this.productProblem(item, 'item');
|
|
107
|
+
if (problem) return problem;
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** A cart: valid lines and a finite total. */
|
|
113
|
+
private cartProblem(items: unknown, cartValue: unknown): string | null {
|
|
114
|
+
return this.itemsProblem(items) ?? (isFiniteNumber(cartValue) ? null : 'cartValue must be a finite number');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Text arguments, each required: the first one missing, or null. */
|
|
118
|
+
private textProblem(args: Record<string, unknown>): string | null {
|
|
119
|
+
for (const [name, value] of Object.entries(args)) {
|
|
120
|
+
if (!isText(value)) return `a ${name} is required`;
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Optional amounts: absent is fine, present must be finite. */
|
|
126
|
+
private amountsProblem(args: Record<string, unknown>): string | null {
|
|
127
|
+
for (const [name, value] of Object.entries(args)) {
|
|
128
|
+
if (value !== undefined && value !== null && !isFiniteNumber(value)) return `${name} must be a finite number`;
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** The lines to describe (null entries skipped; the rest passed the guard). */
|
|
134
|
+
private validItems(items: EcommerceCartItem[]): EcommerceCartItem[] {
|
|
135
|
+
return items.filter((item) => item && typeof item === 'object');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private itemCount(items: EcommerceCartItem[]): number {
|
|
139
|
+
return items.reduce((sum, item) => sum + (isFiniteNumber(item.quantity) ? item.quantity : 0), 0);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Track when a user views a product
|
|
144
|
+
*/
|
|
145
|
+
viewProduct(product: EcommerceProduct): void {
|
|
146
|
+
this.guard('viewProduct', this.productProblem(product), () => this.sdk.track('Product Viewed', {
|
|
147
|
+
product_id: product.productId,
|
|
148
|
+
name: product.name,
|
|
149
|
+
price: product.price,
|
|
150
|
+
category: product.category,
|
|
151
|
+
brand: product.brand,
|
|
152
|
+
variant: product.variant,
|
|
153
|
+
variant_id: product.variantId,
|
|
154
|
+
sku: product.sku,
|
|
155
|
+
image_url: product.imageUrl,
|
|
156
|
+
url: product.url,
|
|
157
|
+
currency: this.config.currency,
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Track when a user views a category/collection page
|
|
163
|
+
*/
|
|
164
|
+
viewCategory(categoryId: string, categoryName: string, properties?: Record<string, any>): void {
|
|
165
|
+
this.guard('viewCategory', this.textProblem({ categoryId, categoryName }), () =>
|
|
166
|
+
this.sdk.track('Product List Viewed', {
|
|
167
|
+
category_id: categoryId,
|
|
168
|
+
category_name: categoryName,
|
|
169
|
+
...(properties && typeof properties === 'object' ? properties : {}),
|
|
170
|
+
}));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Track when a user searches for products
|
|
175
|
+
*/
|
|
176
|
+
search(query: string, resultCount?: number, properties?: Record<string, any>): void {
|
|
177
|
+
this.guard('search', this.textProblem({ query }), () =>
|
|
178
|
+
this.sdk.track('Products Searched', {
|
|
179
|
+
query,
|
|
180
|
+
result_count: finiteOrUndefined(resultCount),
|
|
181
|
+
...(properties && typeof properties === 'object' ? properties : {}),
|
|
182
|
+
}));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Track when a user adds an item to cart
|
|
187
|
+
*/
|
|
188
|
+
addToCart(item: EcommerceCartItem, cartValue?: number): void {
|
|
189
|
+
const problem = this.productProblem(item, 'item') ?? this.amountsProblem({ cartValue });
|
|
190
|
+
this.guard('addToCart', problem, () => this.sdk.track('Product Added', {
|
|
191
|
+
product_id: item.productId,
|
|
192
|
+
name: item.name,
|
|
193
|
+
price: item.price,
|
|
194
|
+
quantity: item.quantity,
|
|
195
|
+
category: item.category,
|
|
196
|
+
brand: item.brand,
|
|
197
|
+
variant: item.variant,
|
|
198
|
+
variant_id: item.variantId,
|
|
199
|
+
sku: item.sku,
|
|
200
|
+
image_url: item.imageUrl,
|
|
201
|
+
url: item.url,
|
|
202
|
+
item_total: finiteOrUndefined(item.price * item.quantity),
|
|
203
|
+
cart_value: finiteOrUndefined(cartValue),
|
|
204
|
+
currency: this.config.currency,
|
|
205
|
+
}));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Track when a user removes an item from cart
|
|
210
|
+
*/
|
|
211
|
+
removeFromCart(item: EcommerceProduct, quantity: number = 1): void {
|
|
212
|
+
this.guard('removeFromCart', this.productProblem(item, 'item'), () =>
|
|
213
|
+
this.sdk.track('Product Removed', {
|
|
214
|
+
product_id: item.productId,
|
|
215
|
+
name: item.name,
|
|
216
|
+
price: item.price,
|
|
217
|
+
quantity: isFiniteNumber(quantity) ? quantity : 1,
|
|
218
|
+
variant_id: item.variantId,
|
|
219
|
+
sku: item.sku,
|
|
220
|
+
currency: this.config.currency,
|
|
221
|
+
}));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Track when a user updates their cart
|
|
226
|
+
*/
|
|
227
|
+
/**
|
|
228
|
+
* Track the cart being viewed.
|
|
229
|
+
*
|
|
230
|
+
* `Cart Viewed` has been in the canonical event list all along, but no
|
|
231
|
+
* tracker had a method for it - so the one way to send it was to type the
|
|
232
|
+
* name by hand, which is the habit these trackers exist to remove. Same
|
|
233
|
+
* payload as updateCart, since the cart is the same thing either way.
|
|
234
|
+
*/
|
|
235
|
+
cartViewed(items: EcommerceCartItem[], cartValue: number): void {
|
|
236
|
+
this.guard('cartViewed', this.cartProblem(items, cartValue), () => {
|
|
237
|
+
const valid = this.validItems(items);
|
|
238
|
+
this.sdk.track('Cart Viewed', {
|
|
239
|
+
items: valid.map((item) => ({
|
|
240
|
+
product_id: item.productId,
|
|
241
|
+
name: item.name,
|
|
242
|
+
price: item.price,
|
|
243
|
+
quantity: item.quantity,
|
|
244
|
+
variant_id: item.variantId,
|
|
245
|
+
sku: item.sku,
|
|
246
|
+
})),
|
|
247
|
+
item_count: this.itemCount(valid),
|
|
248
|
+
cart_value: finiteOrUndefined(cartValue),
|
|
249
|
+
currency: this.config.currency,
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
updateCart(items: EcommerceCartItem[], cartValue: number): void {
|
|
255
|
+
this.guard('updateCart', this.cartProblem(items, cartValue), () => {
|
|
256
|
+
const valid = this.validItems(items);
|
|
257
|
+
this.sdk.track('Cart Updated', {
|
|
258
|
+
items: valid.map((item) => ({
|
|
259
|
+
product_id: item.productId,
|
|
260
|
+
name: item.name,
|
|
261
|
+
price: item.price,
|
|
262
|
+
quantity: item.quantity,
|
|
263
|
+
variant_id: item.variantId,
|
|
264
|
+
sku: item.sku,
|
|
265
|
+
})),
|
|
266
|
+
item_count: this.itemCount(valid),
|
|
267
|
+
cart_value: finiteOrUndefined(cartValue),
|
|
268
|
+
currency: this.config.currency,
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Track when a user starts checkout
|
|
275
|
+
*/
|
|
276
|
+
startCheckout(items: EcommerceCartItem[], cartValue: number, properties?: Record<string, any>): void {
|
|
277
|
+
this.guard('startCheckout', this.cartProblem(items, cartValue), () => {
|
|
278
|
+
const valid = this.validItems(items);
|
|
279
|
+
this.sdk.track('Checkout Started', {
|
|
280
|
+
items: valid.map((item) => ({
|
|
281
|
+
product_id: item.productId,
|
|
282
|
+
name: item.name,
|
|
283
|
+
price: item.price,
|
|
284
|
+
quantity: item.quantity,
|
|
285
|
+
variant_id: item.variantId,
|
|
286
|
+
sku: item.sku,
|
|
287
|
+
})),
|
|
288
|
+
item_count: this.itemCount(valid),
|
|
289
|
+
value: finiteOrUndefined(cartValue),
|
|
290
|
+
currency: this.config.currency,
|
|
291
|
+
...(properties && typeof properties === 'object' ? properties : {}),
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Track when a user adds payment info
|
|
298
|
+
*/
|
|
299
|
+
addPaymentInfo(paymentMethod: string, properties?: Record<string, any>): void {
|
|
300
|
+
this.guard('addPaymentInfo', this.textProblem({ paymentMethod }), () =>
|
|
301
|
+
this.sdk.track('Payment Info Entered', {
|
|
302
|
+
payment_method: paymentMethod,
|
|
303
|
+
...(properties && typeof properties === 'object' ? properties : {}),
|
|
304
|
+
}));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Track when an order is placed
|
|
309
|
+
*/
|
|
310
|
+
purchase(order: EcommerceOrder): void {
|
|
311
|
+
// Never a zero or empty order: no order id, no finite value or no items
|
|
312
|
+
// array is a warning and nothing sent (contract item 42).
|
|
313
|
+
const problem = !order || typeof order !== 'object'
|
|
314
|
+
? 'an order object is required'
|
|
315
|
+
: !isText(order.orderId)
|
|
316
|
+
? 'order.orderId is required'
|
|
317
|
+
: !isFiniteNumber(order.value)
|
|
318
|
+
? 'order.value must be a finite number'
|
|
319
|
+
: this.itemsProblem(order.items) ??
|
|
320
|
+
this.amountsProblem({ shipping: order.shipping, tax: order.tax, discount: order.discount });
|
|
321
|
+
this.guard('purchase', problem, () => {
|
|
322
|
+
const items = this.validItems(order.items);
|
|
323
|
+
this.sdk.track('Order Completed', {
|
|
324
|
+
// `orderId` and `total`, NOT `order_id` and `value`.
|
|
325
|
+
//
|
|
326
|
+
// The revenue pipeline reads exactly these two keys - see
|
|
327
|
+
// purchase-vocabulary.ts, which calls `value`/`revenue` "somebody else's
|
|
328
|
+
// convention" and warns that documenting `order_id` "would have told
|
|
329
|
+
// customers to send a key nothing reads". Every purchase tracked through
|
|
330
|
+
// this tracker therefore landed as ZERO revenue with no order id for
|
|
331
|
+
// attribution, on all three SDKs, for as long as it has existed.
|
|
332
|
+
//
|
|
333
|
+
// Not a crash, not a warning: a $0 order that looks like an order.
|
|
334
|
+
orderId: order.orderId,
|
|
335
|
+
items: items.map((item) => ({
|
|
336
|
+
product_id: item.productId,
|
|
337
|
+
name: item.name,
|
|
338
|
+
price: item.price,
|
|
339
|
+
quantity: item.quantity,
|
|
340
|
+
category: item.category,
|
|
341
|
+
brand: item.brand,
|
|
342
|
+
variant_id: item.variantId,
|
|
343
|
+
sku: item.sku,
|
|
344
|
+
})),
|
|
345
|
+
item_count: this.itemCount(items),
|
|
346
|
+
total: order.value,
|
|
347
|
+
currency: order.currency || this.config.currency,
|
|
348
|
+
shipping: finiteOrUndefined(order.shipping),
|
|
349
|
+
tax: finiteOrUndefined(order.tax),
|
|
350
|
+
discount: finiteOrUndefined(order.discount),
|
|
351
|
+
coupon: order.coupon,
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Track order fulfillment (shipped)
|
|
358
|
+
*/
|
|
359
|
+
orderFulfilled(orderId: string, trackingNumber?: string, carrier?: string): void {
|
|
360
|
+
this.guard('orderFulfilled', isText(orderId) ? null : 'an orderId is required', () =>
|
|
361
|
+
this.sdk.track('Order Fulfilled', {
|
|
362
|
+
order_id: orderId,
|
|
363
|
+
tracking_number: trackingNumber,
|
|
364
|
+
carrier,
|
|
365
|
+
}));
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Track order delivery
|
|
370
|
+
*/
|
|
371
|
+
orderDelivered(orderId: string): void {
|
|
372
|
+
this.guard('orderDelivered', isText(orderId) ? null : 'an orderId is required', () =>
|
|
373
|
+
this.sdk.track('Order Delivered', {
|
|
374
|
+
order_id: orderId,
|
|
375
|
+
}));
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Track order cancellation
|
|
380
|
+
*/
|
|
381
|
+
orderCancelled(orderId: string, reason?: string): void {
|
|
382
|
+
this.guard('orderCancelled', isText(orderId) ? null : 'an orderId is required', () =>
|
|
383
|
+
this.sdk.track('Order Cancelled', {
|
|
384
|
+
order_id: orderId,
|
|
385
|
+
reason,
|
|
386
|
+
}));
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Track order refund
|
|
391
|
+
*/
|
|
392
|
+
orderRefunded(orderId: string, refundAmount?: number, reason?: string): void {
|
|
393
|
+
const problem = this.textProblem({ orderId }) ?? this.amountsProblem({ refundAmount });
|
|
394
|
+
this.guard('orderRefunded', problem, () =>
|
|
395
|
+
this.sdk.track('Order Refunded', {
|
|
396
|
+
order_id: orderId,
|
|
397
|
+
refund_amount: finiteOrUndefined(refundAmount),
|
|
398
|
+
reason,
|
|
399
|
+
}));
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Track product added to wishlist
|
|
404
|
+
*/
|
|
405
|
+
addToWishlist(product: EcommerceProduct): void {
|
|
406
|
+
this.guard('addToWishlist', this.productProblem(product), () =>
|
|
407
|
+
this.sdk.track('Product Added to Wishlist', {
|
|
408
|
+
product_id: product.productId,
|
|
409
|
+
name: product.name,
|
|
410
|
+
price: product.price,
|
|
411
|
+
category: product.category,
|
|
412
|
+
brand: product.brand,
|
|
413
|
+
currency: this.config.currency,
|
|
414
|
+
}));
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Track product removed from wishlist
|
|
419
|
+
*/
|
|
420
|
+
removeFromWishlist(product: EcommerceProduct): void {
|
|
421
|
+
this.guard('removeFromWishlist', this.productProblem(product), () =>
|
|
422
|
+
this.sdk.track('Product Removed from Wishlist', {
|
|
423
|
+
product_id: product.productId,
|
|
424
|
+
name: product.name,
|
|
425
|
+
}));
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Track product shared
|
|
430
|
+
*/
|
|
431
|
+
shareProduct(product: EcommerceProduct, shareMethod: string): void {
|
|
432
|
+
this.guard('shareProduct', this.productProblem(product) ?? this.textProblem({ shareMethod }), () =>
|
|
433
|
+
this.sdk.track('Product Shared', {
|
|
434
|
+
product_id: product.productId,
|
|
435
|
+
name: product.name,
|
|
436
|
+
share_method: shareMethod,
|
|
437
|
+
}));
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Track coupon applied
|
|
442
|
+
*/
|
|
443
|
+
applyCoupon(couponCode: string, discountAmount?: number, discountType?: 'fixed' | 'percentage'): void {
|
|
444
|
+
const problem = this.textProblem({ couponCode }) ?? this.amountsProblem({ discountAmount });
|
|
445
|
+
this.guard('applyCoupon', problem, () =>
|
|
446
|
+
this.sdk.track('Coupon Applied', {
|
|
447
|
+
coupon_code: couponCode,
|
|
448
|
+
discount_amount: finiteOrUndefined(discountAmount),
|
|
449
|
+
discount_type: discountType,
|
|
450
|
+
}));
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Track coupon removed
|
|
455
|
+
*/
|
|
456
|
+
removeCoupon(couponCode: string): void {
|
|
457
|
+
this.guard('removeCoupon', isText(couponCode) ? null : 'a couponCode is required', () =>
|
|
458
|
+
this.sdk.track('Coupon Removed', {
|
|
459
|
+
coupon_code: couponCode,
|
|
460
|
+
}));
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Track product review submitted
|
|
465
|
+
*/
|
|
466
|
+
submitReview(productId: string, rating: number, reviewText?: string): void {
|
|
467
|
+
const problem = !isText(productId) ? 'a productId is required' : !isFiniteNumber(rating) ? 'rating must be a finite number' : null;
|
|
468
|
+
this.guard('submitReview', problem, () =>
|
|
469
|
+
this.sdk.track('Product Reviewed', {
|
|
470
|
+
product_id: productId,
|
|
471
|
+
rating,
|
|
472
|
+
review_text: reviewText,
|
|
473
|
+
}));
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Create an e-commerce tracker instance
|
|
479
|
+
*/
|
|
480
|
+
export function createEcommerceTracker(sdk: JoryioSDK, config?: EcommerceConfig | null, logger?: Logger): EcommerceTracker {
|
|
481
|
+
return new EcommerceTracker(sdk, config, logger);
|
|
482
|
+
}
|