@searchspring/snap-tracker 0.63.5 → 0.65.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 +1 -12
- package/dist/cjs/BeaconEvent.d.ts +2 -0
- package/dist/cjs/BeaconEvent.d.ts.map +1 -1
- package/dist/cjs/BeaconEvent.js +29 -0
- package/dist/cjs/Tracker.d.ts +6 -19
- package/dist/cjs/Tracker.d.ts.map +1 -1
- package/dist/cjs/Tracker.js +158 -485
- package/dist/cjs/types.d.ts +12 -42
- package/dist/cjs/types.d.ts.map +1 -1
- package/dist/cjs/types.js +2 -24
- package/dist/esm/BeaconEvent.d.ts +2 -0
- package/dist/esm/BeaconEvent.d.ts.map +1 -1
- package/dist/esm/BeaconEvent.js +17 -0
- package/dist/esm/Tracker.d.ts +6 -19
- package/dist/esm/Tracker.d.ts.map +1 -1
- package/dist/esm/Tracker.js +134 -462
- package/dist/esm/types.d.ts +12 -42
- package/dist/esm/types.d.ts.map +1 -1
- package/dist/esm/types.js +2 -24
- package/package.json +7 -6
- package/dist/cjs/PixelEvent.d.ts +0 -9
- package/dist/cjs/PixelEvent.d.ts.map +0 -1
- package/dist/cjs/PixelEvent.js +0 -69
- package/dist/cjs/TrackEvent.d.ts +0 -11
- package/dist/cjs/TrackEvent.d.ts.map +0 -1
- package/dist/cjs/TrackEvent.js +0 -31
- package/dist/esm/PixelEvent.d.ts +0 -9
- package/dist/esm/PixelEvent.d.ts.map +0 -1
- package/dist/esm/PixelEvent.js +0 -62
- package/dist/esm/TrackEvent.d.ts +0 -11
- package/dist/esm/TrackEvent.d.ts.map +0 -1
- package/dist/esm/TrackEvent.js +0 -25
package/dist/esm/Tracker.js
CHANGED
|
@@ -1,203 +1,86 @@
|
|
|
1
1
|
import deepmerge from 'deepmerge';
|
|
2
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
3
2
|
import { StorageStore } from '@searchspring/snap-store-mobx';
|
|
4
|
-
import {
|
|
3
|
+
import { version, DomTargeter, getContext } from '@searchspring/snap-toolbox';
|
|
5
4
|
import { AppMode } from '@searchspring/snap-toolbox';
|
|
6
|
-
import {
|
|
7
|
-
import { PixelEvent } from './PixelEvent';
|
|
5
|
+
import { Beacon } from '@searchspring/beacon';
|
|
8
6
|
import { BeaconEvent } from './BeaconEvent';
|
|
9
7
|
import { BeaconType, BeaconCategory, } from './types';
|
|
10
|
-
export const BATCH_TIMEOUT = 200;
|
|
11
|
-
const LEGACY_USERID_COOKIE_NAME = '_isuid';
|
|
12
|
-
const USERID_COOKIE_NAME = 'ssUserId';
|
|
13
|
-
const SHOPPERID_COOKIE_NAME = 'ssShopperId';
|
|
14
|
-
const COOKIE_EXPIRATION = 31536000000; // 1 year
|
|
15
|
-
const VIEWED_COOKIE_EXPIRATION = 220752000000; // 7 years
|
|
16
|
-
const COOKIE_SAMESITE = 'Lax';
|
|
17
|
-
const COOKIE_DOMAIN = (typeof window !== 'undefined' && window.location.hostname && '.' + window.location.hostname.replace(/^www\./, '')) || undefined;
|
|
18
|
-
const SESSIONID_STORAGE_NAME = 'ssSessionIdNamespace';
|
|
19
|
-
const LOCALSTORAGE_BEACON_POOL_NAME = 'ssBeaconPool';
|
|
20
|
-
const CART_PRODUCTS = 'ssCartProducts';
|
|
21
|
-
const VIEWED_PRODUCTS = 'ssViewedProducts';
|
|
22
|
-
export const MAX_VIEWED_COUNT = 20;
|
|
23
8
|
const MAX_PARENT_LEVELS = 3;
|
|
24
9
|
const defaultConfig = {
|
|
25
10
|
id: 'track',
|
|
26
11
|
framework: 'snap',
|
|
27
12
|
mode: AppMode.production,
|
|
28
13
|
};
|
|
29
|
-
export class Tracker {
|
|
14
|
+
export class Tracker extends Beacon {
|
|
30
15
|
constructor(globals, config) {
|
|
31
|
-
|
|
16
|
+
config = deepmerge(defaultConfig, config || {});
|
|
17
|
+
config.initiator = `searchspring/${config.framework}/${version}`;
|
|
18
|
+
super(globals, config);
|
|
32
19
|
this.targeters = [];
|
|
33
20
|
this.track = {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
category: payload?.category || BeaconCategory.CUSTOM,
|
|
38
|
-
context: payload?.context ? deepmerge(this.context, payload.context) : this.context,
|
|
39
|
-
event: payload.event,
|
|
40
|
-
pid: payload?.pid || undefined,
|
|
41
|
-
};
|
|
42
|
-
const doNotTrack = this.doNotTrack.find((entry) => entry.type === event.type && entry.category === event.category);
|
|
43
|
-
if (doNotTrack) {
|
|
21
|
+
// TODO: search where this is used and remove unwanted fields from type
|
|
22
|
+
error: (data, siteId) => {
|
|
23
|
+
if (this.doNotTrack?.includes('error') || this.mode === AppMode.development) {
|
|
44
24
|
return;
|
|
45
25
|
}
|
|
46
|
-
const beaconEvent = new BeaconEvent(event, this.config);
|
|
47
|
-
this.sendEvents([beaconEvent]);
|
|
48
|
-
return beaconEvent;
|
|
49
|
-
},
|
|
50
|
-
error: (data, siteId) => {
|
|
51
26
|
if (!data?.stack && !data?.message) {
|
|
52
27
|
// no console log
|
|
53
28
|
return;
|
|
54
29
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
trackingCode: siteId,
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
});
|
|
30
|
+
const { stack, message, details } = data;
|
|
31
|
+
const { pageUrl } = this.getContext();
|
|
32
|
+
// prevent sending of errors when on localhost or CDN
|
|
33
|
+
if (message?.includes('Profile is currently paused') || pageUrl.includes('//localhost') || pageUrl.includes('//snapui.searchspring.io/')) {
|
|
34
|
+
return;
|
|
64
35
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
category: BeaconCategory.RUNTIME,
|
|
69
|
-
context,
|
|
70
|
-
event: {
|
|
71
|
-
href: href || window.location.href,
|
|
72
|
-
filename,
|
|
36
|
+
this.events.error.snap({
|
|
37
|
+
data: {
|
|
38
|
+
message: message || 'unknown',
|
|
73
39
|
stack,
|
|
74
|
-
message,
|
|
75
|
-
colno,
|
|
76
|
-
lineno,
|
|
77
|
-
errortimestamp,
|
|
78
40
|
details,
|
|
79
|
-
context: data.context,
|
|
80
41
|
},
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (payload.event.message?.includes('Profile is currently paused') ||
|
|
84
|
-
!payload.event.href ||
|
|
85
|
-
payload.event.href.includes('//localhost') ||
|
|
86
|
-
payload.event.href.includes('//snapui.searchspring.io/')) {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
return this.track.event(payload);
|
|
42
|
+
siteId,
|
|
43
|
+
});
|
|
90
44
|
},
|
|
91
45
|
shopper: {
|
|
92
46
|
login: (data, siteId) => {
|
|
93
|
-
|
|
94
|
-
if (!getFlags().cookies()) {
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
if (!data.id) {
|
|
98
|
-
console.error('tracker.shopper.login event: requires a valid shopper ID parameter. Example: tracker.shopper.login({ id: "1234" })');
|
|
47
|
+
if (this.doNotTrack?.includes('shopper.login')) {
|
|
99
48
|
return;
|
|
100
49
|
}
|
|
101
|
-
|
|
102
|
-
let context = this.context;
|
|
103
|
-
if (siteId) {
|
|
104
|
-
context = deepmerge(context, {
|
|
105
|
-
context: {
|
|
106
|
-
website: {
|
|
107
|
-
trackingCode: siteId,
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
});
|
|
111
|
-
context.shopperId = data.id;
|
|
112
|
-
}
|
|
113
|
-
const storedShopperId = this.getShopperId();
|
|
114
|
-
if (storedShopperId != data.id) {
|
|
115
|
-
// user's logged in id has changed, update shopperId cookie send login event
|
|
116
|
-
cookies.set(SHOPPERID_COOKIE_NAME, data.id, COOKIE_SAMESITE, COOKIE_EXPIRATION, COOKIE_DOMAIN);
|
|
117
|
-
this.context.shopperId = data.id;
|
|
118
|
-
this.sendPreflight();
|
|
119
|
-
const payload = {
|
|
120
|
-
type: BeaconType.LOGIN,
|
|
121
|
-
category: BeaconCategory.PERSONALIZATION,
|
|
122
|
-
context,
|
|
123
|
-
event: {
|
|
124
|
-
userId: this.context.userId,
|
|
125
|
-
shopperId: data.id,
|
|
126
|
-
},
|
|
127
|
-
};
|
|
128
|
-
return this.track.event(payload);
|
|
129
|
-
}
|
|
50
|
+
this.events.shopper.login({ data: { id: data.id }, siteId });
|
|
130
51
|
},
|
|
131
52
|
},
|
|
132
53
|
product: {
|
|
133
54
|
view: (data, siteId) => {
|
|
134
|
-
if (
|
|
135
|
-
console.error('track.product.view event: requires a valid uid, sku and/or childUid, childSku. \nExample: track.product.view({ uid: "123", sku: "product123", childUid: "123_a", childSku: "product123_a" })');
|
|
55
|
+
if (this.doNotTrack?.includes('product.view')) {
|
|
136
56
|
return;
|
|
137
57
|
}
|
|
138
|
-
let
|
|
139
|
-
if (
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
},
|
|
145
|
-
},
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
const payload = {
|
|
149
|
-
type: BeaconType.PRODUCT,
|
|
150
|
-
category: BeaconCategory.PAGEVIEW,
|
|
151
|
-
context,
|
|
152
|
-
event: {
|
|
153
|
-
uid: data?.uid ? `${data.uid}` : undefined,
|
|
154
|
-
sku: data?.sku ? `${data.sku}` : undefined,
|
|
155
|
-
childUid: data?.childUid ? `${data.childUid}` : undefined,
|
|
156
|
-
childSku: data?.childSku ? `${data.childSku}` : undefined,
|
|
157
|
-
},
|
|
158
|
-
};
|
|
159
|
-
const event = this.track.event(payload);
|
|
160
|
-
if (event) {
|
|
161
|
-
// save recently viewed products to cookie
|
|
162
|
-
const sku = data?.childSku || data?.childUid || data?.sku || data?.uid;
|
|
163
|
-
if (sku) {
|
|
164
|
-
const lastViewedProducts = this.cookies.viewed.get();
|
|
165
|
-
const uniqueCartItems = Array.from(new Set([sku, ...lastViewedProducts])).map((item) => `${item}`.trim());
|
|
166
|
-
cookies.set(VIEWED_PRODUCTS, uniqueCartItems.slice(0, MAX_VIEWED_COUNT).join(','), COOKIE_SAMESITE, VIEWED_COOKIE_EXPIRATION, COOKIE_DOMAIN);
|
|
167
|
-
if (!lastViewedProducts.includes(sku)) {
|
|
168
|
-
this.sendPreflight();
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
// legacy tracking
|
|
172
|
-
if (data?.sku) {
|
|
173
|
-
// only send sku to pixel tracker if present (don't send childSku)
|
|
174
|
-
new PixelEvent({
|
|
175
|
-
...payload,
|
|
176
|
-
event: {
|
|
177
|
-
sku: data.sku,
|
|
178
|
-
id: data.uid,
|
|
179
|
-
},
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
return event;
|
|
58
|
+
let result = data;
|
|
59
|
+
if (!data.uid && data.sku) {
|
|
60
|
+
result = {
|
|
61
|
+
...data,
|
|
62
|
+
uid: data.sku,
|
|
63
|
+
};
|
|
183
64
|
}
|
|
65
|
+
this.events.product.pageView({ data: { result: result }, siteId });
|
|
184
66
|
},
|
|
67
|
+
/**
|
|
68
|
+
* @deprecated tracker.track.product.click() is deprecated and will be removed. Use tracker.events['search' | 'category'].clickThrough() instead
|
|
69
|
+
*/
|
|
185
70
|
click: (data, siteId) => {
|
|
71
|
+
// Controllers will send product click events through tracker.beacon
|
|
72
|
+
// For legacy support if someone calls this, continute to 1.0 beacon just like is.js
|
|
73
|
+
// TODO: remove after 1.0 deprecation period
|
|
74
|
+
if (this.doNotTrack?.includes('product.click')) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
186
77
|
if (!data?.intellisuggestData || !data?.intellisuggestSignature) {
|
|
187
78
|
console.error(`track.product.click event: object parameter requires a valid intellisuggestData and intellisuggestSignature. \nExample: track.click.product({ intellisuggestData: "eJwrTs4tNM9jYCjKTM8oYXDWdQ3TDTfUDbIwMDVjMARCYwMQSi_KTAEA9IQKWA", intellisuggestSignature: "9e46f9fd3253c267fefc298704e39084a6f8b8e47abefdee57277996b77d8e70" })`);
|
|
188
79
|
return;
|
|
189
80
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
context: {
|
|
194
|
-
website: {
|
|
195
|
-
trackingCode: siteId,
|
|
196
|
-
},
|
|
197
|
-
},
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
const payload = {
|
|
81
|
+
const beaconContext = this.getContext();
|
|
82
|
+
const context = transformToLegacyContext(beaconContext, siteId || this.globals.siteId);
|
|
83
|
+
const event = {
|
|
201
84
|
type: BeaconType.CLICK,
|
|
202
85
|
category: BeaconCategory.INTERACTION,
|
|
203
86
|
context,
|
|
@@ -207,359 +90,117 @@ export class Tracker {
|
|
|
207
90
|
href: data?.href ? `${data.href}` : undefined,
|
|
208
91
|
},
|
|
209
92
|
};
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
return
|
|
93
|
+
const beaconEvent = new BeaconEvent(event, this.config);
|
|
94
|
+
const beaconEventData = beaconEvent.send();
|
|
95
|
+
return beaconEventData;
|
|
213
96
|
},
|
|
214
97
|
},
|
|
215
98
|
cart: {
|
|
216
99
|
view: (data, siteId) => {
|
|
217
|
-
if (
|
|
218
|
-
console.error('track.view.cart event: parameter must be an array of cart items. \nExample: track.view.cart({ items: [{ id: "123", sku: "product123", childSku: "product123_a", qty: "1", price: "9.99" }] })');
|
|
100
|
+
if (this.doNotTrack?.includes('cart.view')) {
|
|
219
101
|
return;
|
|
220
102
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
const items = data.items.map((item, index) => {
|
|
232
|
-
if (!item?.qty || !item?.price || (!item?.uid && !item?.sku && !item?.childUid && !item?.childSku)) {
|
|
233
|
-
console.error(`track.view.cart event: item at index ${index} requires a valid qty, price, and (uid and/or sku and/or childUid and/or childSku.) \nExample: track.view.cart({ items: [{ uid: "123", sku: "product123", childUid: "123_a", childSku: "product123_a", qty: "1", price: "9.99" }] })`);
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
const product = {
|
|
237
|
-
qty: `${item.qty}`,
|
|
238
|
-
price: `${item.price}`,
|
|
239
|
-
};
|
|
240
|
-
if (item?.uid) {
|
|
241
|
-
product.uid = `${item.uid}`;
|
|
242
|
-
}
|
|
243
|
-
if (item?.sku) {
|
|
244
|
-
product.sku = `${item.sku}`;
|
|
245
|
-
}
|
|
246
|
-
if (item?.childUid) {
|
|
247
|
-
product.childUid = `${item.childUid}`;
|
|
103
|
+
// uid can be optional in legacy payload but required in 2.0 spec - use sku as fallback
|
|
104
|
+
const results = data.items
|
|
105
|
+
.map((item) => {
|
|
106
|
+
if (!item.uid && item.sku) {
|
|
107
|
+
return {
|
|
108
|
+
...item,
|
|
109
|
+
uid: item.sku,
|
|
110
|
+
};
|
|
248
111
|
}
|
|
249
|
-
|
|
250
|
-
|
|
112
|
+
else {
|
|
113
|
+
return item;
|
|
251
114
|
}
|
|
252
|
-
|
|
115
|
+
})
|
|
116
|
+
.map((item) => {
|
|
117
|
+
// convert to Product[] - ensure qty and price are numbers
|
|
118
|
+
return {
|
|
119
|
+
...item,
|
|
120
|
+
qty: Number(item.qty),
|
|
121
|
+
price: Number(item.price),
|
|
122
|
+
};
|
|
253
123
|
});
|
|
254
|
-
|
|
255
|
-
type: BeaconType.CART,
|
|
256
|
-
category: BeaconCategory.CARTVIEW,
|
|
257
|
-
context,
|
|
258
|
-
event: { items },
|
|
259
|
-
};
|
|
260
|
-
const event = this.track.event(payload);
|
|
261
|
-
if (event) {
|
|
262
|
-
// save cart items to cookie
|
|
263
|
-
if (items.length) {
|
|
264
|
-
const products = items.map((item) => item?.childSku || item?.childUid || item?.sku || item?.uid || '').filter((sku) => sku);
|
|
265
|
-
this.cookies.cart.add(products);
|
|
266
|
-
}
|
|
267
|
-
// legacy tracking
|
|
268
|
-
new PixelEvent(payload);
|
|
269
|
-
return event;
|
|
270
|
-
}
|
|
124
|
+
this.events.cart.view({ data: { results: results }, siteId });
|
|
271
125
|
},
|
|
272
126
|
},
|
|
273
127
|
order: {
|
|
274
128
|
transaction: (data, siteId) => {
|
|
275
|
-
if (
|
|
276
|
-
console.error('track.order.transaction event: object parameter must contain `items` array of cart items. \nExample: order.transaction({ order: { id: "1001", total: "10.71", transactionTotal: "9.99", city: "Los Angeles", state: "CA", country: "US" }, items: [{ uid: "123", sku: "product123", childUid: "123_a", childSku: "product123_a", qty: "1", price: "9.99" }] })');
|
|
129
|
+
if (this.doNotTrack?.includes('order.transaction')) {
|
|
277
130
|
return;
|
|
278
131
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
product.uid = `${item.uid}`;
|
|
300
|
-
}
|
|
301
|
-
if (item?.sku) {
|
|
302
|
-
product.sku = `${item.sku}`;
|
|
303
|
-
}
|
|
304
|
-
if (item?.childUid) {
|
|
305
|
-
product.childUid = `${item.childUid}`;
|
|
306
|
-
}
|
|
307
|
-
if (item?.childSku) {
|
|
308
|
-
product.childSku = `${item.childSku}`;
|
|
309
|
-
}
|
|
310
|
-
return product;
|
|
311
|
-
});
|
|
312
|
-
const eventPayload = {
|
|
313
|
-
orderId: data?.order?.id ? `${data.order.id}` : undefined,
|
|
314
|
-
total: data?.order?.total ? `${data.order.total}` : undefined,
|
|
315
|
-
transactionTotal: data?.order?.transactionTotal ? `${data.order.transactionTotal}` : undefined,
|
|
316
|
-
city: data?.order?.city ? `${data.order.city}` : undefined,
|
|
317
|
-
state: data?.order?.state ? `${data.order.state}` : undefined,
|
|
318
|
-
country: data?.order?.country ? `${data.order.country}` : undefined,
|
|
319
|
-
items,
|
|
320
|
-
};
|
|
321
|
-
const payload = {
|
|
322
|
-
type: BeaconType.ORDER,
|
|
323
|
-
category: BeaconCategory.ORDERVIEW,
|
|
324
|
-
context,
|
|
325
|
-
event: eventPayload,
|
|
132
|
+
const order = data.order;
|
|
133
|
+
const items = data.items;
|
|
134
|
+
const orderTransactionData = {
|
|
135
|
+
orderId: `${order?.id || ''}`,
|
|
136
|
+
transactionTotal: Number(order?.transactionTotal || 0),
|
|
137
|
+
total: Number(order?.total || 0),
|
|
138
|
+
city: order?.city,
|
|
139
|
+
state: order?.state,
|
|
140
|
+
country: order?.country,
|
|
141
|
+
results: items.map((item) => {
|
|
142
|
+
return {
|
|
143
|
+
// uid is required - fallback to get most relevant
|
|
144
|
+
uid: item.uid || item.sku || '',
|
|
145
|
+
childUid: item.childUid,
|
|
146
|
+
sku: item.sku,
|
|
147
|
+
childSku: item.childSku,
|
|
148
|
+
qty: Number(item.qty),
|
|
149
|
+
price: Number(item.price),
|
|
150
|
+
};
|
|
151
|
+
}),
|
|
326
152
|
};
|
|
327
|
-
|
|
328
|
-
if (event) {
|
|
329
|
-
// clear cart items from cookie when order is placed
|
|
330
|
-
this.cookies.cart.clear();
|
|
331
|
-
// legacy tracking
|
|
332
|
-
new PixelEvent(payload);
|
|
333
|
-
return event;
|
|
334
|
-
}
|
|
153
|
+
this.events.order.transaction({ data: orderTransactionData, siteId });
|
|
335
154
|
},
|
|
336
155
|
},
|
|
337
156
|
};
|
|
338
|
-
this.updateContext = (key, value) => {
|
|
339
|
-
if (value) {
|
|
340
|
-
this.context[key] = value;
|
|
341
|
-
}
|
|
342
|
-
};
|
|
343
|
-
this.setCurrency = (currency) => {
|
|
344
|
-
if (!currency?.code) {
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
this.context.currency = currency;
|
|
348
|
-
};
|
|
349
|
-
this.getUserId = () => {
|
|
350
|
-
let userId;
|
|
351
|
-
try {
|
|
352
|
-
// use cookies if available, fallback to localstorage
|
|
353
|
-
if (getFlags().cookies()) {
|
|
354
|
-
userId = cookies.get(LEGACY_USERID_COOKIE_NAME) || cookies.get(USERID_COOKIE_NAME) || uuidv4();
|
|
355
|
-
cookies.set(USERID_COOKIE_NAME, userId, COOKIE_SAMESITE, COOKIE_EXPIRATION, COOKIE_DOMAIN);
|
|
356
|
-
cookies.set(LEGACY_USERID_COOKIE_NAME, userId, COOKIE_SAMESITE, COOKIE_EXPIRATION, COOKIE_DOMAIN);
|
|
357
|
-
}
|
|
358
|
-
else if (getFlags().storage()) {
|
|
359
|
-
userId = window.localStorage.getItem(USERID_COOKIE_NAME) || uuidv4();
|
|
360
|
-
window.localStorage.setItem(USERID_COOKIE_NAME, userId);
|
|
361
|
-
}
|
|
362
|
-
else {
|
|
363
|
-
throw 'unsupported features';
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
catch (e) {
|
|
367
|
-
console.error('Failed to persist user id to cookie or local storage:', e);
|
|
368
|
-
}
|
|
369
|
-
return userId;
|
|
370
|
-
};
|
|
371
|
-
this.getSessionId = () => {
|
|
372
|
-
let sessionId;
|
|
373
|
-
if (getFlags().storage()) {
|
|
374
|
-
try {
|
|
375
|
-
sessionId = window.sessionStorage.getItem(SESSIONID_STORAGE_NAME) || uuidv4();
|
|
376
|
-
window.sessionStorage.setItem(SESSIONID_STORAGE_NAME, sessionId);
|
|
377
|
-
getFlags().cookies() && cookies.set(SESSIONID_STORAGE_NAME, sessionId, COOKIE_SAMESITE, 0, COOKIE_DOMAIN); //session cookie
|
|
378
|
-
}
|
|
379
|
-
catch (e) {
|
|
380
|
-
console.error('Failed to persist session id to session storage:', e);
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
else if (getFlags().cookies()) {
|
|
384
|
-
// use cookies if sessionStorage is not enabled and only reset cookie if new session to keep expiration
|
|
385
|
-
sessionId = cookies.get(SESSIONID_STORAGE_NAME);
|
|
386
|
-
if (!sessionId) {
|
|
387
|
-
sessionId = uuidv4();
|
|
388
|
-
cookies.set(SESSIONID_STORAGE_NAME, sessionId, COOKIE_SAMESITE, 0, COOKIE_DOMAIN);
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
return sessionId;
|
|
392
|
-
};
|
|
393
|
-
this.getShopperId = () => {
|
|
394
|
-
const shopperId = cookies.get(SHOPPERID_COOKIE_NAME);
|
|
395
|
-
if (!shopperId) {
|
|
396
|
-
return;
|
|
397
|
-
}
|
|
398
|
-
return shopperId;
|
|
399
|
-
};
|
|
400
|
-
this.sendPreflight = () => {
|
|
401
|
-
const userId = this.getUserId();
|
|
402
|
-
const siteId = this.context.website.trackingCode;
|
|
403
|
-
const shopper = this.getShopperId();
|
|
404
|
-
const cart = this.cookies.cart.get();
|
|
405
|
-
const lastViewed = this.cookies.viewed.get();
|
|
406
|
-
if (userId && typeof userId == 'string' && siteId && (shopper || cart.length || lastViewed.length)) {
|
|
407
|
-
const preflightParams = {
|
|
408
|
-
userId,
|
|
409
|
-
siteId,
|
|
410
|
-
};
|
|
411
|
-
let queryStringParams = `?userId=${encodeURIComponent(userId)}&siteId=${encodeURIComponent(siteId)}`;
|
|
412
|
-
if (shopper) {
|
|
413
|
-
preflightParams.shopper = shopper;
|
|
414
|
-
queryStringParams += `&shopper=${encodeURIComponent(shopper)}`;
|
|
415
|
-
}
|
|
416
|
-
if (cart.length) {
|
|
417
|
-
preflightParams.cart = cart;
|
|
418
|
-
queryStringParams += cart.map((item) => `&cart=${encodeURIComponent(item)}`).join('');
|
|
419
|
-
}
|
|
420
|
-
if (lastViewed.length) {
|
|
421
|
-
preflightParams.lastViewed = lastViewed;
|
|
422
|
-
queryStringParams += lastViewed.map((item) => `&lastViewed=${encodeURIComponent(item)}`).join('');
|
|
423
|
-
}
|
|
424
|
-
const origin = this.config.requesters?.personalization?.origin || `https://${siteId}.a.searchspring.io`;
|
|
425
|
-
const endpoint = `${origin}/api/personalization/preflightCache`;
|
|
426
|
-
const xhr = new XMLHttpRequest();
|
|
427
|
-
if (charsParams(preflightParams) > 1024) {
|
|
428
|
-
xhr.open('POST', endpoint);
|
|
429
|
-
xhr.setRequestHeader('Content-Type', 'application/json');
|
|
430
|
-
xhr.send(JSON.stringify(preflightParams));
|
|
431
|
-
}
|
|
432
|
-
else {
|
|
433
|
-
xhr.open('GET', endpoint + queryStringParams);
|
|
434
|
-
xhr.send();
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
};
|
|
438
157
|
this.cookies = {
|
|
439
158
|
cart: {
|
|
440
159
|
get: () => {
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
return [];
|
|
444
|
-
}
|
|
445
|
-
return items.split(',');
|
|
160
|
+
const data = this.storage.cart.get();
|
|
161
|
+
return data.map((item) => this.getProductId(item));
|
|
446
162
|
},
|
|
447
163
|
set: (items) => {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
cookies.set(CART_PRODUCTS, uniqueCartItems.join(','), COOKIE_SAMESITE, 0, COOKIE_DOMAIN);
|
|
452
|
-
const itemsHaveChanged = cartItems.filter((item) => items.includes(item)).length !== items.length;
|
|
453
|
-
if (itemsHaveChanged) {
|
|
454
|
-
this.sendPreflight();
|
|
455
|
-
}
|
|
456
|
-
}
|
|
164
|
+
const cartItems = items.map((item) => `${item}`.trim());
|
|
165
|
+
const uniqueCartItems = Array.from(new Set(cartItems)).map((uid) => ({ uid, sku: uid, price: 0, qty: 1 }));
|
|
166
|
+
this.storage.cart.set(uniqueCartItems);
|
|
457
167
|
},
|
|
458
168
|
add: (items) => {
|
|
459
169
|
if (items.length) {
|
|
460
|
-
const
|
|
461
|
-
|
|
462
|
-
const uniqueCartItems = Array.from(new Set([...currentCartItems, ...itemsToAdd]));
|
|
463
|
-
cookies.set(CART_PRODUCTS, uniqueCartItems.join(','), COOKIE_SAMESITE, 0, COOKIE_DOMAIN);
|
|
464
|
-
const itemsHaveChanged = currentCartItems.filter((item) => itemsToAdd.includes(item)).length !== itemsToAdd.length;
|
|
465
|
-
if (itemsHaveChanged) {
|
|
466
|
-
this.sendPreflight();
|
|
467
|
-
}
|
|
170
|
+
const itemsToAdd = items.map((item) => `${item}`.trim()).map((uid) => ({ uid, sku: uid, price: 0, qty: 1 }));
|
|
171
|
+
this.storage.cart.add(itemsToAdd);
|
|
468
172
|
}
|
|
469
173
|
},
|
|
470
174
|
remove: (items) => {
|
|
471
175
|
if (items.length) {
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
const updatedItems = currentCartItems.filter((item) => !itemsToRemove.includes(item));
|
|
475
|
-
cookies.set(CART_PRODUCTS, updatedItems.join(','), COOKIE_SAMESITE, 0, COOKIE_DOMAIN);
|
|
476
|
-
const itemsHaveChanged = currentCartItems.length !== updatedItems.length;
|
|
477
|
-
if (itemsHaveChanged) {
|
|
478
|
-
this.sendPreflight();
|
|
479
|
-
}
|
|
176
|
+
const itemsToRemove = items.map((item) => `${item}`.trim()).map((uid) => ({ uid, sku: uid, price: 0, qty: 1 }));
|
|
177
|
+
this.storage.cart.remove(itemsToRemove);
|
|
480
178
|
}
|
|
481
179
|
},
|
|
482
180
|
clear: () => {
|
|
483
|
-
|
|
484
|
-
cookies.unset(CART_PRODUCTS, COOKIE_DOMAIN);
|
|
485
|
-
this.sendPreflight();
|
|
486
|
-
}
|
|
181
|
+
this.storage.cart.clear();
|
|
487
182
|
},
|
|
488
183
|
},
|
|
489
184
|
viewed: {
|
|
490
185
|
get: () => {
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
return [];
|
|
494
|
-
}
|
|
495
|
-
return items.split(',');
|
|
186
|
+
const viewedItems = this.storage.viewed.get();
|
|
187
|
+
return viewedItems.map((item) => this.getProductId(item));
|
|
496
188
|
},
|
|
497
189
|
},
|
|
498
190
|
};
|
|
499
|
-
this.sendEvents = (eventsToSend) => {
|
|
500
|
-
if (this.mode !== AppMode.production) {
|
|
501
|
-
return;
|
|
502
|
-
}
|
|
503
|
-
const savedEvents = JSON.parse(this.localStorage.get(LOCALSTORAGE_BEACON_POOL_NAME) || '[]');
|
|
504
|
-
if (eventsToSend) {
|
|
505
|
-
const eventsClone = [];
|
|
506
|
-
savedEvents.forEach((_event, idx) => {
|
|
507
|
-
// using Object.assign since we are not modifying nested properties
|
|
508
|
-
eventsClone.push(Object.assign({}, _event));
|
|
509
|
-
delete eventsClone[idx].id;
|
|
510
|
-
delete eventsClone[idx].pid;
|
|
511
|
-
});
|
|
512
|
-
const stringyEventsClone = JSON.stringify(eventsClone);
|
|
513
|
-
// de-dupe events
|
|
514
|
-
eventsToSend.forEach((event, idx) => {
|
|
515
|
-
const newEvent = Object.assign({}, event);
|
|
516
|
-
delete newEvent.id;
|
|
517
|
-
delete newEvent.pid;
|
|
518
|
-
if (stringyEventsClone.indexOf(JSON.stringify(newEvent)) == -1) {
|
|
519
|
-
savedEvents.push({ ...eventsToSend[idx] });
|
|
520
|
-
}
|
|
521
|
-
});
|
|
522
|
-
// save the beacon pool with de-duped events
|
|
523
|
-
this.localStorage.set(LOCALSTORAGE_BEACON_POOL_NAME, JSON.stringify(savedEvents));
|
|
524
|
-
}
|
|
525
|
-
clearTimeout(this.isSending);
|
|
526
|
-
this.isSending = window.setTimeout(() => {
|
|
527
|
-
if (savedEvents.length) {
|
|
528
|
-
const xhr = new XMLHttpRequest();
|
|
529
|
-
const origin = this.config.requesters?.beacon?.origin || 'https://beacon.searchspring.io';
|
|
530
|
-
xhr.open('POST', `${origin}/beacon`);
|
|
531
|
-
xhr.setRequestHeader('Content-Type', 'application/json');
|
|
532
|
-
xhr.send(JSON.stringify(savedEvents.length == 1 ? savedEvents[0] : savedEvents));
|
|
533
|
-
}
|
|
534
|
-
this.localStorage.set(LOCALSTORAGE_BEACON_POOL_NAME, JSON.stringify([]));
|
|
535
|
-
}, BATCH_TIMEOUT);
|
|
536
|
-
};
|
|
537
191
|
if (typeof globals != 'object' || typeof globals.siteId != 'string') {
|
|
538
192
|
throw new Error(`Invalid config passed to tracker. The "siteId" attribute must be provided.`);
|
|
539
193
|
}
|
|
540
|
-
this.config =
|
|
194
|
+
this.config = config;
|
|
541
195
|
this.doNotTrack = this.config.doNotTrack || [];
|
|
542
196
|
if (Object.values(AppMode).includes(this.config.mode)) {
|
|
543
197
|
this.mode = this.config.mode;
|
|
544
198
|
}
|
|
545
|
-
this.globals = globals;
|
|
546
199
|
this.localStorage = new StorageStore({
|
|
547
200
|
type: 'local',
|
|
548
201
|
key: `ss-${this.config.id}`,
|
|
549
202
|
});
|
|
550
203
|
this.localStorage.set('siteId', this.globals.siteId);
|
|
551
|
-
this.context = {
|
|
552
|
-
userId: this.getUserId() || '',
|
|
553
|
-
sessionId: this.getSessionId(),
|
|
554
|
-
shopperId: this.getShopperId(),
|
|
555
|
-
pageLoadId: uuidv4(),
|
|
556
|
-
website: {
|
|
557
|
-
trackingCode: this.globals.siteId,
|
|
558
|
-
},
|
|
559
|
-
};
|
|
560
|
-
if (this.globals.currency?.code) {
|
|
561
|
-
this.context.currency = this.globals.currency;
|
|
562
|
-
}
|
|
563
204
|
if (!window.searchspring?.tracker) {
|
|
564
205
|
window.searchspring = window.searchspring || {};
|
|
565
206
|
window.searchspring.tracker = this;
|
|
@@ -660,17 +301,48 @@ export class Tracker {
|
|
|
660
301
|
});
|
|
661
302
|
}
|
|
662
303
|
});
|
|
663
|
-
this.sendEvents();
|
|
664
304
|
}
|
|
665
305
|
getGlobals() {
|
|
666
306
|
return JSON.parse(JSON.stringify(this.globals));
|
|
667
307
|
}
|
|
668
|
-
getContext() {
|
|
669
|
-
return JSON.parse(JSON.stringify(this.context));
|
|
670
|
-
}
|
|
671
308
|
retarget() {
|
|
672
309
|
this.targeters.forEach((target) => {
|
|
673
310
|
target.retarget();
|
|
674
311
|
});
|
|
675
312
|
}
|
|
676
313
|
}
|
|
314
|
+
function transformToLegacyContext(_context, siteId) {
|
|
315
|
+
const context = { ..._context };
|
|
316
|
+
if (context.userAgent) {
|
|
317
|
+
delete context.userAgent;
|
|
318
|
+
}
|
|
319
|
+
if (context.timestamp) {
|
|
320
|
+
// @ts-ignore - property not optional
|
|
321
|
+
delete context.timestamp;
|
|
322
|
+
}
|
|
323
|
+
if (context.initiator) {
|
|
324
|
+
// @ts-ignore - property not optional
|
|
325
|
+
delete context.initiator;
|
|
326
|
+
}
|
|
327
|
+
if (context.dev) {
|
|
328
|
+
delete context.dev;
|
|
329
|
+
}
|
|
330
|
+
let attribution;
|
|
331
|
+
if (context.attribution?.length) {
|
|
332
|
+
attribution = {
|
|
333
|
+
type: context.attribution[0].type,
|
|
334
|
+
id: context.attribution[0].id,
|
|
335
|
+
};
|
|
336
|
+
delete context.attribution;
|
|
337
|
+
}
|
|
338
|
+
const beaconContext = {
|
|
339
|
+
...context,
|
|
340
|
+
website: {
|
|
341
|
+
trackingCode: siteId,
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
if (attribution) {
|
|
345
|
+
beaconContext.attribution = attribution;
|
|
346
|
+
}
|
|
347
|
+
return beaconContext;
|
|
348
|
+
}
|