coveo.analytics 2.30.56 → 2.32.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 +37 -43
- package/dist/browser.mjs +513 -357
- package/dist/coveoua.browser.js +1 -1
- package/dist/coveoua.browser.js.map +1 -1
- package/dist/coveoua.debug.js +542 -359
- package/dist/coveoua.debug.js.map +1 -1
- package/dist/coveoua.js +1 -1
- package/dist/coveoua.js.map +1 -1
- package/dist/definitions/client/analytics.d.ts +3 -2
- package/dist/definitions/client/runtimeEnvironment.d.ts +1 -1
- package/dist/definitions/donottrack.d.ts +1 -0
- package/dist/definitions/react-native/index.d.ts +1 -2
- package/dist/definitions/version.d.ts +1 -1
- package/dist/library.cjs +15350 -3699
- package/dist/library.es.js +513 -357
- package/dist/library.js +15350 -3699
- package/dist/library.mjs +15350 -3699
- package/dist/react-native.es.js +634 -460
- package/modules/package.json +9 -9
- package/package.json +60 -91
- package/react-native/package.json +7 -7
- package/src/caseAssist/caseAssistActions.ts +51 -50
- package/src/caseAssist/caseAssistClient.spec.ts +328 -297
- package/src/caseAssist/caseAssistClient.ts +201 -185
- package/src/client/analytics.spec.ts +724 -703
- package/src/client/analytics.ts +677 -602
- package/src/client/analyticsBeaconClient.spec.ts +195 -188
- package/src/client/analyticsBeaconClient.ts +99 -88
- package/src/client/analyticsFetchClient.ts +77 -61
- package/src/client/analyticsRequestClient.ts +17 -17
- package/src/client/location.ts +3 -3
- package/src/client/measurementProtocolMapper.ts +54 -45
- package/src/client/measurementProtocolMapping/baseMeasurementProtocolMapper.ts +48 -44
- package/src/client/measurementProtocolMapping/commerceMeasurementProtocolMapper.ts +133 -121
- package/src/client/measurementProtocolMapping/serviceMeasurementProtocolMapper.ts +17 -17
- package/src/client/noopAnalytics.ts +80 -68
- package/src/client/runtimeEnvironment.ts +50 -41
- package/src/client/utils.spec.ts +112 -97
- package/src/client/utils.ts +39 -39
- package/src/cookieutils.spec.ts +59 -0
- package/src/cookieutils.ts +40 -39
- package/src/coveoua/browser.ts +14 -12
- package/src/coveoua/library.ts +4 -4
- package/src/coveoua/plugins.ts +36 -34
- package/src/coveoua/simpleanalytics.spec.ts +467 -452
- package/src/coveoua/simpleanalytics.ts +146 -140
- package/src/detector.ts +17 -17
- package/src/donottrack.spec.ts +199 -121
- package/src/donottrack.ts +31 -8
- package/src/events.ts +86 -79
- package/src/formatting/format-array-for-coveo-custom-data.spec.ts +13 -12
- package/src/formatting/format-array-for-coveo-custom-data.ts +18 -18
- package/src/formatting/format-omnibox-metadata.ts +16 -12
- package/src/history.spec.ts +197 -195
- package/src/history.ts +143 -133
- package/src/hook/addDefaultValues.ts +13 -8
- package/src/hook/enhanceViewEvent.ts +17 -17
- package/src/insight/insightClient.spec.ts +1848 -1717
- package/src/insight/insightClient.ts +866 -761
- package/src/insight/insightEvents.ts +57 -56
- package/src/plugins/BasePlugin.ts +125 -121
- package/src/plugins/ec.spec.ts +457 -429
- package/src/plugins/ec.ts +202 -199
- package/src/plugins/link.spec.ts +183 -159
- package/src/plugins/link.ts +88 -85
- package/src/plugins/svc.spec.ts +161 -155
- package/src/plugins/svc.ts +93 -91
- package/src/react-native/index.ts +8 -1
- package/src/react-native/react-native-runtime.ts +33 -33
- package/src/react-native/react-native-utils.ts +1 -1
- package/src/react-native/react-native.spec.ts +31 -21
- package/src/searchPage/searchPageClient.spec.ts +1944 -1794
- package/src/searchPage/searchPageClient.ts +1261 -1040
- package/src/searchPage/searchPageEvents.ts +524 -511
- package/src/storage.spec.ts +51 -51
- package/src/storage.ts +47 -47
- package/dist/definitions/bundle/browser-fetch.d.ts +0 -1
- package/dist/definitions/src/coveoua/browser.d.ts +0 -6
- package/dist/definitions/src/coveoua/headless.d.ts +0 -6
- package/dist/definitions/src/coveoua/library.d.ts +0 -17
- package/dist/definitions/src/coveoua/plugins.d.ts +0 -10
- package/dist/definitions/src/coveoua/simpleanalytics.d.ts +0 -33
- package/dist/definitions/src/react-native/index.d.ts +0 -3
- package/dist/definitions/src/react-native/react-native-runtime.d.ts +0 -19
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import {vi} from 'vitest';
|
|
1
2
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
ClickEventRequest,
|
|
4
|
+
CustomEventRequest,
|
|
5
|
+
DefaultEventResponse,
|
|
6
|
+
EventType,
|
|
7
|
+
SearchEventRequest,
|
|
8
|
+
ViewEventRequest,
|
|
8
9
|
} from '../events';
|
|
9
10
|
import {CoveoAnalyticsClient} from './analytics';
|
|
10
11
|
import {IAnalyticsRequestOptions} from './analyticsRequestClient';
|
|
@@ -17,824 +18,844 @@ import {Cookie} from '../cookieutils';
|
|
|
17
18
|
import {CoveoLinkParam} from '../plugins/link';
|
|
18
19
|
|
|
19
20
|
const aVisitorId = '123';
|
|
20
|
-
|
|
21
|
+
vi.mock('uuid', async (importOriginal) => {
|
|
22
|
+
const actual = await importOriginal<typeof import('uuid')>();
|
|
23
|
+
return {
|
|
21
24
|
v4: () => aVisitorId,
|
|
22
|
-
validate:
|
|
23
|
-
v5:
|
|
24
|
-
}
|
|
25
|
+
validate: actual.validate,
|
|
26
|
+
v5: actual.v5,
|
|
27
|
+
};
|
|
28
|
+
});
|
|
25
29
|
|
|
26
30
|
const {fetchMock, fetchMockBeforeEach} = mockFetch();
|
|
27
31
|
|
|
28
32
|
describe('Analytics', () => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
});
|
|
33
|
+
const aToken = 'token';
|
|
34
|
+
const anEndpoint = 'http://bloup';
|
|
35
|
+
const A_VERSION = 'v1337';
|
|
36
|
+
|
|
37
|
+
const viewEvent: ViewEventRequest = {
|
|
38
|
+
location: 'here',
|
|
39
|
+
contentIdKey: 'key',
|
|
40
|
+
contentIdValue: 'value',
|
|
41
|
+
language: 'en',
|
|
42
|
+
};
|
|
43
|
+
const searchEvent: SearchEventRequest = {
|
|
44
|
+
searchQueryUid: 'sqUid',
|
|
45
|
+
actionCause: 'interfaceLoad',
|
|
46
|
+
queryText: 'test',
|
|
47
|
+
responseTime: 50,
|
|
48
|
+
};
|
|
49
|
+
const clickEvent: ClickEventRequest = {
|
|
50
|
+
searchQueryUid: 'sqUid',
|
|
51
|
+
actionCause: 'documentOpen',
|
|
52
|
+
documentPosition: 1,
|
|
53
|
+
documentUriHash: 'docUriHash',
|
|
54
|
+
sourceName: 'source',
|
|
55
|
+
};
|
|
56
|
+
const customEvent: CustomEventRequest = {
|
|
57
|
+
eventType: 'custom',
|
|
58
|
+
eventValue: 'value',
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const eventResponse: DefaultEventResponse = {
|
|
62
|
+
visitId: 'firsttimevisiting',
|
|
63
|
+
visitorId: aVisitorId,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const endpointForEventType = (eventType: EventType, endpoint: string = `${anEndpoint}/rest`) =>
|
|
67
|
+
`${endpoint}/${A_VERSION}/analytics/${eventType}?visitor=${aVisitorId}`;
|
|
68
|
+
const mockFetchRequestForEventType = (eventType: EventType) => {
|
|
69
|
+
const address = endpointForEventType(eventType);
|
|
70
|
+
fetchMock.post(address, eventResponse);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
let client: CoveoAnalyticsClient;
|
|
74
|
+
|
|
75
|
+
beforeEach(() => {
|
|
76
|
+
vi.resetAllMocks();
|
|
77
|
+
fetchMockBeforeEach();
|
|
78
|
+
|
|
79
|
+
new CookieStorage().removeItem('visitorId');
|
|
80
|
+
localStorage.clear();
|
|
81
|
+
fetchMock.reset();
|
|
82
|
+
|
|
83
|
+
client = new CoveoAnalyticsClient({
|
|
84
|
+
token: aToken,
|
|
85
|
+
endpoint: anEndpoint,
|
|
86
|
+
version: A_VERSION,
|
|
84
87
|
});
|
|
88
|
+
});
|
|
85
89
|
|
|
86
|
-
|
|
87
|
-
|
|
90
|
+
it('should add /rest if not present in endpoint', async () => {
|
|
91
|
+
const legacyEndpoint = 'https://usageanalytics.com';
|
|
88
92
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
fetchMock.post(endpointForEventType(EventType.custom, `${legacyEndpoint}/rest`), eventResponse);
|
|
96
|
-
const response = await client.sendEvent(EventType.custom);
|
|
97
|
-
expect(response).toEqual(eventResponse);
|
|
93
|
+
client = new CoveoAnalyticsClient({
|
|
94
|
+
token: aToken,
|
|
95
|
+
endpoint: legacyEndpoint,
|
|
96
|
+
version: A_VERSION,
|
|
98
97
|
});
|
|
99
98
|
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
fetchMock.post(endpointForEventType(EventType.custom, `${legacyEndpoint}/rest`), eventResponse);
|
|
100
|
+
const response = await client.sendEvent(EventType.custom);
|
|
101
|
+
expect(response).toEqual(eventResponse);
|
|
102
|
+
});
|
|
102
103
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
endpoint: legacyEndpoint,
|
|
106
|
-
version: A_VERSION,
|
|
107
|
-
});
|
|
104
|
+
it('should not add /rest if /rest present in endpoint', async () => {
|
|
105
|
+
const legacyEndpoint = 'https://usageanalytics.com/rest';
|
|
108
106
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
107
|
+
client = new CoveoAnalyticsClient({
|
|
108
|
+
token: aToken,
|
|
109
|
+
endpoint: legacyEndpoint,
|
|
110
|
+
version: A_VERSION,
|
|
112
111
|
});
|
|
113
112
|
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
fetchMock.post(endpointForEventType(EventType.custom, legacyEndpoint), eventResponse);
|
|
114
|
+
const response = await client.sendEvent(EventType.custom);
|
|
115
|
+
expect(response).toEqual(eventResponse);
|
|
116
|
+
});
|
|
116
117
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
endpoint: legacyEndpoint,
|
|
120
|
-
version: A_VERSION,
|
|
121
|
-
});
|
|
118
|
+
it('should not add /rest if /rest/ua present in endpoint', async () => {
|
|
119
|
+
const legacyEndpoint = 'https://usageanalytics.com/rest/ua';
|
|
122
120
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
121
|
+
client = new CoveoAnalyticsClient({
|
|
122
|
+
token: aToken,
|
|
123
|
+
endpoint: legacyEndpoint,
|
|
124
|
+
version: A_VERSION,
|
|
126
125
|
});
|
|
127
126
|
|
|
128
|
-
|
|
129
|
-
|
|
127
|
+
fetchMock.post(endpointForEventType(EventType.custom, legacyEndpoint), eventResponse);
|
|
128
|
+
const response = await client.sendEvent(EventType.custom);
|
|
129
|
+
expect(response).toEqual(eventResponse);
|
|
130
|
+
});
|
|
130
131
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
endpoint: legacyEndpoint + '/',
|
|
134
|
-
version: A_VERSION,
|
|
135
|
-
});
|
|
132
|
+
it('should remove trailing slash in endpoint', async () => {
|
|
133
|
+
const legacyEndpoint = 'https://usageanalytics.com';
|
|
136
134
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
135
|
+
client = new CoveoAnalyticsClient({
|
|
136
|
+
token: aToken,
|
|
137
|
+
endpoint: legacyEndpoint + '/',
|
|
138
|
+
version: A_VERSION,
|
|
140
139
|
});
|
|
141
140
|
|
|
142
|
-
|
|
143
|
-
|
|
141
|
+
fetchMock.post(endpointForEventType(EventType.custom, `${legacyEndpoint}/rest`), eventResponse);
|
|
142
|
+
const response = await client.sendEvent(EventType.custom);
|
|
143
|
+
expect(response).toEqual(eventResponse);
|
|
144
|
+
});
|
|
144
145
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
endpoint: aReverseProxyEndpoint,
|
|
148
|
-
isCustomEndpoint: true,
|
|
149
|
-
version: A_VERSION,
|
|
150
|
-
});
|
|
146
|
+
it('should allow reverse-proxy endpoint', async () => {
|
|
147
|
+
const aReverseProxyEndpoint = 'https://12364734.cloudfront.net/analytics';
|
|
151
148
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
149
|
+
client = new CoveoAnalyticsClient({
|
|
150
|
+
token: aToken,
|
|
151
|
+
endpoint: aReverseProxyEndpoint,
|
|
152
|
+
isCustomEndpoint: true,
|
|
153
|
+
version: A_VERSION,
|
|
155
154
|
});
|
|
156
155
|
|
|
157
|
-
|
|
158
|
-
|
|
156
|
+
fetchMock.post(endpointForEventType(EventType.custom, aReverseProxyEndpoint), eventResponse);
|
|
157
|
+
const response = await client.sendEvent(EventType.custom);
|
|
158
|
+
expect(response).toEqual(eventResponse);
|
|
159
|
+
});
|
|
159
160
|
|
|
160
|
-
|
|
161
|
-
|
|
161
|
+
it('should call fetch with the parameters', async () => {
|
|
162
|
+
mockFetchRequestForEventType(EventType.view);
|
|
162
163
|
|
|
163
|
-
|
|
164
|
+
const response = await client.sendViewEvent(viewEvent);
|
|
165
|
+
expect(response).toEqual(eventResponse);
|
|
164
166
|
|
|
165
|
-
|
|
166
|
-
expect(path).toBe(endpointForEventType(EventType.view));
|
|
167
|
+
expect(fetchMock.called()).toBe(true);
|
|
167
168
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
expect(h['Content-Type']).toBe('application/json');
|
|
169
|
+
const [path, {headers, body}]: any = fetchMock.lastCall();
|
|
170
|
+
expect(path).toBe(endpointForEventType(EventType.view));
|
|
171
171
|
|
|
172
|
-
|
|
172
|
+
const h = headers as Record<string, string>;
|
|
173
|
+
expect(h['Authorization']).toBe(`Bearer ${aToken}`);
|
|
174
|
+
expect(h['Content-Type']).toBe('application/json');
|
|
173
175
|
|
|
174
|
-
|
|
175
|
-
expect(parsedBody).toMatchObject(viewEvent);
|
|
176
|
-
});
|
|
176
|
+
expect(body).not.toBeUndefined();
|
|
177
177
|
|
|
178
|
-
|
|
179
|
-
|
|
178
|
+
const parsedBody = JSON.parse(body.toString());
|
|
179
|
+
expect(parsedBody).toMatchObject(viewEvent);
|
|
180
|
+
});
|
|
180
181
|
|
|
181
|
-
|
|
182
|
+
it('should append default values to the view event', async () => {
|
|
183
|
+
mockFetchRequestForEventType(EventType.view);
|
|
182
184
|
|
|
183
|
-
|
|
185
|
+
await client.sendViewEvent(viewEvent);
|
|
184
186
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
187
|
+
const [body] = getParsedBodyCalls();
|
|
188
|
+
|
|
189
|
+
expect(body).toMatchObject({
|
|
190
|
+
clientId: '123',
|
|
191
|
+
userAgent: navigator.userAgent,
|
|
189
192
|
});
|
|
193
|
+
});
|
|
190
194
|
|
|
191
|
-
|
|
192
|
-
|
|
195
|
+
it('should append default values to the search event', async () => {
|
|
196
|
+
mockFetchRequestForEventType(EventType.search);
|
|
193
197
|
|
|
194
|
-
|
|
198
|
+
await client.sendSearchEvent(searchEvent);
|
|
195
199
|
|
|
196
|
-
|
|
200
|
+
const [body] = getParsedBodyCalls();
|
|
197
201
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
});
|
|
202
|
+
expect(body).toMatchObject({
|
|
203
|
+
clientId: '123',
|
|
204
|
+
userAgent: navigator.userAgent,
|
|
202
205
|
});
|
|
206
|
+
});
|
|
203
207
|
|
|
204
|
-
|
|
205
|
-
|
|
208
|
+
it('should append default values to the click event', async () => {
|
|
209
|
+
mockFetchRequestForEventType(EventType.click);
|
|
206
210
|
|
|
207
|
-
|
|
211
|
+
await client.sendClickEvent(clickEvent);
|
|
208
212
|
|
|
209
|
-
|
|
213
|
+
const [body] = getParsedBodyCalls();
|
|
210
214
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
});
|
|
215
|
+
expect(body).toMatchObject({
|
|
216
|
+
clientId: '123',
|
|
217
|
+
userAgent: navigator.userAgent,
|
|
215
218
|
});
|
|
219
|
+
});
|
|
216
220
|
|
|
217
|
-
|
|
218
|
-
|
|
221
|
+
it('should append default values to the custom event', async () => {
|
|
222
|
+
mockFetchRequestForEventType(EventType.custom);
|
|
219
223
|
|
|
220
|
-
|
|
224
|
+
await client.sendCustomEvent(customEvent);
|
|
221
225
|
|
|
222
|
-
|
|
226
|
+
const [body] = getParsedBodyCalls();
|
|
223
227
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
});
|
|
228
|
+
expect(body).toMatchObject({
|
|
229
|
+
clientId: '123',
|
|
230
|
+
userAgent: navigator.userAgent,
|
|
228
231
|
});
|
|
232
|
+
});
|
|
229
233
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
await Promise.all([firstRequest, secondRequest, thirdRequest]);
|
|
234
|
+
it('should send the events in the order they were called', async () => {
|
|
235
|
+
mockFetchRequestForEventType(EventType.view);
|
|
236
|
+
const firstRequest = client.sendViewEvent({
|
|
237
|
+
...viewEvent,
|
|
238
|
+
customData: {
|
|
239
|
+
index: 0,
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
const secondRequest = client.sendViewEvent({
|
|
243
|
+
...viewEvent,
|
|
244
|
+
customData: {
|
|
245
|
+
index: 1,
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
const thirdRequest = client.sendViewEvent({
|
|
249
|
+
...viewEvent,
|
|
250
|
+
customData: {
|
|
251
|
+
index: 2,
|
|
252
|
+
},
|
|
253
|
+
});
|
|
252
254
|
|
|
253
|
-
|
|
254
|
-
const parsedBody = JSON.parse(response!.body!.toString());
|
|
255
|
-
expect(parsedBody.customData.index).toBe(index);
|
|
256
|
-
};
|
|
257
|
-
const [[, firstResponse], [, secondResponse], [, thirdResponse]] = fetchMock.calls();
|
|
258
|
-
assertResponseHasCustomDataWithIndex(firstResponse, 0);
|
|
259
|
-
assertResponseHasCustomDataWithIndex(secondResponse, 1);
|
|
260
|
-
assertResponseHasCustomDataWithIndex(thirdResponse, 2);
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
it('should should only clean null, undefined, and empty string values', async () => {
|
|
264
|
-
mockFetchRequestForEventType(EventType.view);
|
|
265
|
-
await client.sendEvent(EventType.view, {
|
|
266
|
-
fine: 1,
|
|
267
|
-
ok: 0,
|
|
268
|
-
notok: '',
|
|
269
|
-
invalid: null,
|
|
270
|
-
ohno: undefined,
|
|
271
|
-
});
|
|
255
|
+
await Promise.all([firstRequest, secondRequest, thirdRequest]);
|
|
272
256
|
|
|
273
|
-
|
|
257
|
+
const assertResponseHasCustomDataWithIndex = (
|
|
258
|
+
response: RequestInit | undefined,
|
|
259
|
+
index: number
|
|
260
|
+
) => {
|
|
261
|
+
const parsedBody = JSON.parse(response!.body!.toString());
|
|
262
|
+
expect(parsedBody.customData.index).toBe(index);
|
|
263
|
+
};
|
|
264
|
+
const [[, firstResponse], [, secondResponse], [, thirdResponse]] = fetchMock.calls();
|
|
265
|
+
assertResponseHasCustomDataWithIndex(firstResponse, 0);
|
|
266
|
+
assertResponseHasCustomDataWithIndex(secondResponse, 1);
|
|
267
|
+
assertResponseHasCustomDataWithIndex(thirdResponse, 2);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('should should only clean null, undefined, and empty string values', async () => {
|
|
271
|
+
mockFetchRequestForEventType(EventType.view);
|
|
272
|
+
await client.sendEvent(EventType.view, {
|
|
273
|
+
fine: 1,
|
|
274
|
+
ok: 0,
|
|
275
|
+
emptyField: '',
|
|
276
|
+
invalid: null,
|
|
277
|
+
undefinedField: undefined,
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
const [body] = getParsedBodyCalls();
|
|
281
|
+
|
|
282
|
+
expect(body).toMatchObject({
|
|
283
|
+
fine: 1,
|
|
284
|
+
ok: 0,
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
describe('should truncate the maxlength for URL parameters at 1024 characters for ua events', () => {
|
|
289
|
+
const desiredMax: number = 1024;
|
|
290
|
+
// Craft the URL so the truncation point falls in the %20 sequence
|
|
291
|
+
const longUrl: string = 'http://coveo.com/?q=' + 'a'.repeat(desiredMax - 22) + '%20b';
|
|
292
|
+
const expectedTruncatedLength = longUrl.lastIndexOf('%', desiredMax);
|
|
293
|
+
expect(longUrl.length).toBeGreaterThan(desiredMax);
|
|
294
|
+
async function testEventType(type: EventType, url: string) {
|
|
295
|
+
mockFetchRequestForEventType(type);
|
|
296
|
+
await client.sendEvent(type, {
|
|
297
|
+
location: type == EventType.view ? url : undefined,
|
|
298
|
+
originLevel3: url,
|
|
299
|
+
});
|
|
300
|
+
const [body] = getParsedBodyCalls();
|
|
301
|
+
if (type == EventType.view) {
|
|
302
|
+
expect(body.location).toHaveLength(expectedTruncatedLength);
|
|
303
|
+
}
|
|
304
|
+
expect(body.originLevel3).toHaveLength(expectedTruncatedLength);
|
|
305
|
+
}
|
|
306
|
+
it('for view events', () => testEventType(EventType.view, longUrl));
|
|
307
|
+
it('for click events', () => testEventType(EventType.click, longUrl));
|
|
308
|
+
it('for search events', () => testEventType(EventType.search, longUrl));
|
|
309
|
+
it('for custom events', () => testEventType(EventType.custom, longUrl));
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
describe('url truncation is null safe', () => {
|
|
313
|
+
async function testAttributeTruncation(url: any) {
|
|
314
|
+
mockFetchRequestForEventType(EventType.view);
|
|
315
|
+
await client.sendEvent(EventType.view, {
|
|
316
|
+
location: url,
|
|
317
|
+
originLevel3: url,
|
|
318
|
+
});
|
|
319
|
+
const [body] = getParsedBodyCalls();
|
|
320
|
+
expect(body.location).toBe(url == null ? undefined : url);
|
|
321
|
+
expect(body.originLevel3).toBe(url == null ? undefined : url);
|
|
322
|
+
}
|
|
323
|
+
it('for undefined urls', () => testAttributeTruncation(undefined));
|
|
324
|
+
it('for null urls', () => testAttributeTruncation(null));
|
|
325
|
+
it('for non-string urls', () => testAttributeTruncation(12345));
|
|
326
|
+
});
|
|
274
327
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
328
|
+
it('should not remove #queryText for search events even if empty', async () => {
|
|
329
|
+
mockFetchRequestForEventType(EventType.search);
|
|
330
|
+
await client.sendEvent(EventType.search, {
|
|
331
|
+
queryText: '',
|
|
279
332
|
});
|
|
280
333
|
|
|
281
|
-
|
|
282
|
-
const desiredMax: number = 1024;
|
|
283
|
-
// Craft the URL so the truncation point falls in the %20 sequence
|
|
284
|
-
const longUrl: string = 'http://coveo.com/?q=' + 'a'.repeat(desiredMax - 22) + '%20b';
|
|
285
|
-
const expectedTruncatedLength = longUrl.lastIndexOf('%', desiredMax);
|
|
286
|
-
expect(longUrl.length).toBeGreaterThan(desiredMax);
|
|
287
|
-
async function testEventType(type: EventType, url: string) {
|
|
288
|
-
mockFetchRequestForEventType(type);
|
|
289
|
-
await client.sendEvent(type, {
|
|
290
|
-
location: type == EventType.view ? url : undefined,
|
|
291
|
-
originLevel3: url,
|
|
292
|
-
});
|
|
293
|
-
const [body] = getParsedBodyCalls();
|
|
294
|
-
if (type == EventType.view) {
|
|
295
|
-
expect(body.location).toHaveLength(expectedTruncatedLength);
|
|
296
|
-
}
|
|
297
|
-
expect(body.originLevel3).toHaveLength(expectedTruncatedLength);
|
|
298
|
-
}
|
|
299
|
-
it('for view events', () => testEventType(EventType.view, longUrl));
|
|
300
|
-
it('for click events', () => testEventType(EventType.click, longUrl));
|
|
301
|
-
it('for search events', () => testEventType(EventType.search, longUrl));
|
|
302
|
-
it('for custom events', () => testEventType(EventType.custom, longUrl));
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
describe('url truncation is null safe', () => {
|
|
306
|
-
async function testAttributeTruncation(url: any) {
|
|
307
|
-
mockFetchRequestForEventType(EventType.view);
|
|
308
|
-
await client.sendEvent(EventType.view, {
|
|
309
|
-
location: url,
|
|
310
|
-
originLevel3: url,
|
|
311
|
-
});
|
|
312
|
-
const [body] = getParsedBodyCalls();
|
|
313
|
-
expect(body.location).toBe(url == null ? undefined : url);
|
|
314
|
-
expect(body.originLevel3).toBe(url == null ? undefined : url);
|
|
315
|
-
}
|
|
316
|
-
it('for undefined urls', () => testAttributeTruncation(undefined));
|
|
317
|
-
it('for null urls', () => testAttributeTruncation(null));
|
|
318
|
-
it('for non-string urls', () => testAttributeTruncation(12345));
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
it('should not remove #queryText for search events even if empty', async () => {
|
|
322
|
-
mockFetchRequestForEventType(EventType.search);
|
|
323
|
-
await client.sendEvent(EventType.search, {
|
|
324
|
-
queryText: '',
|
|
325
|
-
});
|
|
334
|
+
const [body] = getParsedBodyCalls();
|
|
326
335
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
expect(body).toMatchObject({
|
|
330
|
-
queryText: '',
|
|
331
|
-
});
|
|
336
|
+
expect(body).toMatchObject({
|
|
337
|
+
queryText: '',
|
|
332
338
|
});
|
|
339
|
+
});
|
|
333
340
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
341
|
+
describe('with event type mapping with variable arguments', () => {
|
|
342
|
+
const specialEventType = '🌟special🌟';
|
|
343
|
+
const argumentNames = ['eventCategory', 'eventAction', 'eventLabel', 'eventValue'];
|
|
344
|
+
beforeEach(() => {
|
|
345
|
+
mockFetchRequestForEventType(EventType.custom);
|
|
346
|
+
client.addEventTypeMapping(specialEventType, {
|
|
347
|
+
newEventType: EventType.custom,
|
|
348
|
+
variableLengthArgumentsNames: argumentNames,
|
|
349
|
+
});
|
|
350
|
+
});
|
|
344
351
|
|
|
345
|
-
|
|
346
|
-
|
|
352
|
+
it('should properly parse empty arguments', async () => {
|
|
353
|
+
await client.sendEvent(specialEventType);
|
|
347
354
|
|
|
348
|
-
|
|
349
|
-
|
|
355
|
+
const [path, {body}]: any = fetchMock.lastCall();
|
|
356
|
+
expect(path).toBe(endpointForEventType(EventType.custom));
|
|
350
357
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
358
|
+
const parsedBody = JSON.parse(body.toString());
|
|
359
|
+
expect(parsedBody).toEqual({});
|
|
360
|
+
});
|
|
354
361
|
|
|
355
|
-
|
|
356
|
-
|
|
362
|
+
it('should properly parse 1 argument', async () => {
|
|
363
|
+
await client.sendEvent(specialEventType, 'Video');
|
|
357
364
|
|
|
358
|
-
|
|
365
|
+
const [body] = getParsedBodyCalls();
|
|
359
366
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
367
|
+
expect(body).toEqual({
|
|
368
|
+
[argumentNames[0]]: 'Video',
|
|
369
|
+
});
|
|
370
|
+
});
|
|
364
371
|
|
|
365
|
-
|
|
366
|
-
|
|
372
|
+
it('should properly parse all arguments', async () => {
|
|
373
|
+
await client.sendEvent(specialEventType, 'Video', 'play', 'campaign');
|
|
367
374
|
|
|
368
|
-
|
|
375
|
+
const [body] = getParsedBodyCalls();
|
|
369
376
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
377
|
+
expect(body).toEqual({
|
|
378
|
+
[argumentNames[0]]: 'Video',
|
|
379
|
+
[argumentNames[1]]: 'play',
|
|
380
|
+
[argumentNames[2]]: 'campaign',
|
|
381
|
+
});
|
|
382
|
+
});
|
|
376
383
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
384
|
+
it('should properly handle a finished object argument', async () => {
|
|
385
|
+
await client.sendEvent(specialEventType, 'Video', {
|
|
386
|
+
nonInteraction: true,
|
|
387
|
+
});
|
|
381
388
|
|
|
382
|
-
|
|
389
|
+
const [body] = getParsedBodyCalls();
|
|
383
390
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
});
|
|
391
|
+
expect(body).toEqual({
|
|
392
|
+
[argumentNames[0]]: 'Video',
|
|
393
|
+
nonInteraction: true,
|
|
394
|
+
});
|
|
389
395
|
});
|
|
396
|
+
});
|
|
390
397
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
398
|
+
describe('with event type mapping activating context values', () => {
|
|
399
|
+
const specialEventType = '🌟special🌟';
|
|
400
|
+
beforeEach(() => {
|
|
401
|
+
mockFetchRequestForEventType(EventType.custom);
|
|
402
|
+
client.addEventTypeMapping(specialEventType, {
|
|
403
|
+
newEventType: EventType.custom,
|
|
404
|
+
addVisitorIdParameter: true,
|
|
405
|
+
});
|
|
406
|
+
});
|
|
400
407
|
|
|
401
|
-
|
|
402
|
-
|
|
408
|
+
it('should properly add visitorId key', async () => {
|
|
409
|
+
await client.sendEvent(specialEventType);
|
|
403
410
|
|
|
404
|
-
|
|
411
|
+
const [body] = getParsedBodyCalls();
|
|
405
412
|
|
|
406
|
-
|
|
407
|
-
});
|
|
413
|
+
expect(body).toHaveProperty('visitorId');
|
|
408
414
|
});
|
|
415
|
+
});
|
|
409
416
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
fetchMock.post(endpointForEventType(EventType.custom, endpointWithRest), eventResponse);
|
|
420
|
-
});
|
|
417
|
+
describe('with an endpoint set to the coveo platform proxy', () => {
|
|
418
|
+
const endpointWithRest = 'http://someendpoint.cloud.coveo.com/rest/ua';
|
|
419
|
+
beforeEach(() => {
|
|
420
|
+
client = new CoveoAnalyticsClient({
|
|
421
|
+
token: aToken,
|
|
422
|
+
endpoint: endpointWithRest,
|
|
423
|
+
version: A_VERSION,
|
|
424
|
+
});
|
|
421
425
|
|
|
422
|
-
|
|
423
|
-
const response = await client.sendEvent(EventType.custom);
|
|
424
|
-
expect(response).toEqual(eventResponse);
|
|
425
|
-
});
|
|
426
|
+
fetchMock.post(endpointForEventType(EventType.custom, endpointWithRest), eventResponse);
|
|
426
427
|
});
|
|
427
428
|
|
|
428
|
-
|
|
429
|
-
|
|
429
|
+
it('should properly send the event without appending an extra /rest', async () => {
|
|
430
|
+
const response = await client.sendEvent(EventType.custom);
|
|
431
|
+
expect(response).toEqual(eventResponse);
|
|
432
|
+
});
|
|
433
|
+
});
|
|
430
434
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
expect(body).toMatchObject(userId);
|
|
434
|
-
};
|
|
435
|
+
describe('with userId auto-detection', () => {
|
|
436
|
+
const eventType = '🛂';
|
|
435
437
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
endpoint: anEndpoint,
|
|
441
|
-
version: A_VERSION,
|
|
442
|
-
});
|
|
443
|
-
mockFetchRequestForEventType(EventType.custom);
|
|
444
|
-
});
|
|
445
|
-
describe('with measurement protocol', () => {
|
|
446
|
-
beforeEach(() => {
|
|
447
|
-
client.addEventTypeMapping(eventType, {
|
|
448
|
-
newEventType: EventType.custom,
|
|
449
|
-
usesMeasurementProtocol: true,
|
|
450
|
-
});
|
|
451
|
-
});
|
|
452
|
-
|
|
453
|
-
it('should set the absent userId to anonymous', async () => {
|
|
454
|
-
await client.sendEvent(eventType);
|
|
455
|
-
expectUserId({uid: 'anonymous'});
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
it('should leave existing userIds', async () => {
|
|
459
|
-
await client.sendEvent(eventType, {userId: 'bob'});
|
|
460
|
-
expectUserId({uid: 'bob'});
|
|
461
|
-
});
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
describe('without measurement protocol', () => {
|
|
465
|
-
beforeEach(() => {
|
|
466
|
-
client.addEventTypeMapping(eventType, {
|
|
467
|
-
newEventType: EventType.custom,
|
|
468
|
-
usesMeasurementProtocol: false,
|
|
469
|
-
});
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
it('should do nothing with absent userId', async () => {
|
|
473
|
-
await client.sendEvent(eventType);
|
|
474
|
-
expectUserId({});
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
it('should leave existing userIds', async () => {
|
|
478
|
-
await client.sendEvent(eventType, {userId: 'bob'});
|
|
479
|
-
expectUserId({userId: 'bob'});
|
|
480
|
-
});
|
|
481
|
-
});
|
|
482
|
-
});
|
|
438
|
+
const expectUserId = (userId: {[key: string]: string}) => {
|
|
439
|
+
const [body] = getParsedBodyCalls();
|
|
440
|
+
expect(body).toMatchObject(userId);
|
|
441
|
+
};
|
|
483
442
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
});
|
|
491
|
-
mockFetchRequestForEventType(EventType.custom);
|
|
492
|
-
client.addEventTypeMapping(eventType, {
|
|
493
|
-
newEventType: EventType.custom,
|
|
494
|
-
usesMeasurementProtocol: true,
|
|
495
|
-
});
|
|
496
|
-
});
|
|
497
|
-
|
|
498
|
-
it('should do nothing with absent userId', async () => {
|
|
499
|
-
await client.sendEvent(eventType);
|
|
500
|
-
expectUserId({});
|
|
501
|
-
});
|
|
502
|
-
|
|
503
|
-
it('should leave existing userIds', async () => {
|
|
504
|
-
await client.sendEvent(eventType, {userId: 'bob'});
|
|
505
|
-
expectUserId({uid: 'bob'});
|
|
506
|
-
});
|
|
443
|
+
describe('for API keys', () => {
|
|
444
|
+
beforeEach(() => {
|
|
445
|
+
client = new CoveoAnalyticsClient({
|
|
446
|
+
token: 'xxTestApiKey',
|
|
447
|
+
endpoint: anEndpoint,
|
|
448
|
+
version: A_VERSION,
|
|
507
449
|
});
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
const contextWebsite = 'yourbestfriend.com';
|
|
512
|
-
const trackingId = 'yourfavoritefood.ca ';
|
|
450
|
+
mockFetchRequestForEventType(EventType.custom);
|
|
451
|
+
});
|
|
452
|
+
describe('with measurement protocol', () => {
|
|
513
453
|
beforeEach(() => {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
});
|
|
519
|
-
mockFetchRequestForEventType(EventType.view);
|
|
454
|
+
client.addEventTypeMapping(eventType, {
|
|
455
|
+
newEventType: EventType.custom,
|
|
456
|
+
usesMeasurementProtocol: true,
|
|
457
|
+
});
|
|
520
458
|
});
|
|
521
459
|
|
|
522
|
-
it('should set
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
expect(body.trackingId).toBe(contextWebsite);
|
|
460
|
+
it('should set the absent userId to anonymous', async () => {
|
|
461
|
+
await client.sendEvent(eventType);
|
|
462
|
+
expectUserId({uid: 'anonymous'});
|
|
526
463
|
});
|
|
527
464
|
|
|
528
|
-
it('should
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
customData: {context_website: contextWebsite},
|
|
532
|
-
});
|
|
533
|
-
const [body] = getParsedBodyCalls();
|
|
534
|
-
expect(body.trackingId).toBe(trackingId);
|
|
465
|
+
it('should leave existing userIds', async () => {
|
|
466
|
+
await client.sendEvent(eventType, {userId: 'bob'});
|
|
467
|
+
expectUserId({uid: 'bob'});
|
|
535
468
|
});
|
|
536
|
-
|
|
469
|
+
});
|
|
537
470
|
|
|
538
|
-
|
|
539
|
-
const website = 'yourbestfriend.com';
|
|
540
|
-
const trackingId = 'yourfavoritefood.ca ';
|
|
471
|
+
describe('without measurement protocol', () => {
|
|
541
472
|
beforeEach(() => {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
});
|
|
547
|
-
mockFetchRequestForEventType(EventType.view);
|
|
473
|
+
client.addEventTypeMapping(eventType, {
|
|
474
|
+
newEventType: EventType.custom,
|
|
475
|
+
usesMeasurementProtocol: false,
|
|
476
|
+
});
|
|
548
477
|
});
|
|
549
478
|
|
|
550
|
-
it('should
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
expect(body.trackingId).toBe(website);
|
|
479
|
+
it('should do nothing with absent userId', async () => {
|
|
480
|
+
await client.sendEvent(eventType);
|
|
481
|
+
expectUserId({});
|
|
554
482
|
});
|
|
555
483
|
|
|
556
|
-
it('should
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
expect(body.trackingId).toBe(trackingId);
|
|
484
|
+
it('should leave existing userIds', async () => {
|
|
485
|
+
await client.sendEvent(eventType, {userId: 'bob'});
|
|
486
|
+
expectUserId({userId: 'bob'});
|
|
560
487
|
});
|
|
488
|
+
});
|
|
561
489
|
});
|
|
562
490
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
client.currentVisitorId = visitorId;
|
|
570
|
-
historyStore.addElement(history);
|
|
571
|
-
|
|
572
|
-
expect(storage.getItem('visitorId')).toBe('foo');
|
|
573
|
-
expect(historyStore.getMostRecentElement()).toEqual(history);
|
|
574
|
-
|
|
575
|
-
client.clear();
|
|
576
|
-
expect(storage.getItem('visitorId')).toBeNull();
|
|
577
|
-
expect(historyStore.getMostRecentElement()).toBeUndefined();
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
it('should support clearing cookies for visitorId and historyStore async', async () => {
|
|
581
|
-
const visitorId = 'foo';
|
|
582
|
-
const history = {name: 'foo', time: '123', value: 'bar'};
|
|
583
|
-
const storage = new CookieStorage();
|
|
584
|
-
const historyStore = new HistoryStore();
|
|
585
|
-
|
|
586
|
-
await client.setCurrentVisitorId(visitorId);
|
|
587
|
-
await historyStore.addElementAsync(history);
|
|
588
|
-
|
|
589
|
-
expect(await storage.getItem('visitorId')).toBe('foo');
|
|
590
|
-
expect(historyStore.getMostRecentElement()).toEqual(history);
|
|
591
|
-
|
|
592
|
-
client.clear();
|
|
593
|
-
expect(storage.getItem('visitorId')).toBeNull();
|
|
594
|
-
expect(historyStore.getMostRecentElement()).toBeUndefined();
|
|
595
|
-
});
|
|
596
|
-
|
|
597
|
-
it('should execute before send hooks passed as option', async () => {
|
|
598
|
-
mockFetchRequestForEventType(EventType.search);
|
|
599
|
-
const spy = jest.fn((_, p) => p);
|
|
600
|
-
const searchEventPayload = {queryText: 'potato'};
|
|
601
|
-
await new CoveoAnalyticsClient({
|
|
602
|
-
token: aToken,
|
|
603
|
-
endpoint: anEndpoint,
|
|
604
|
-
version: A_VERSION,
|
|
605
|
-
beforeSendHooks: [spy],
|
|
606
|
-
}).sendEvent(EventType.search, searchEventPayload);
|
|
607
|
-
expect(spy).toHaveBeenLastCalledWith(EventType.search, expect.objectContaining(searchEventPayload));
|
|
608
|
-
});
|
|
609
|
-
|
|
610
|
-
it('should preprocess the request with the preprocessRequest', async () => {
|
|
611
|
-
let clientOrigin: string;
|
|
612
|
-
let processedRequest: IAnalyticsRequestOptions = {
|
|
613
|
-
url: 'https://www.myownanalytics.com/endpoint',
|
|
614
|
-
headers: {
|
|
615
|
-
Age: '24',
|
|
616
|
-
},
|
|
617
|
-
method: 'PUT',
|
|
618
|
-
body: JSON.stringify({
|
|
619
|
-
test: 'custom',
|
|
620
|
-
}),
|
|
621
|
-
};
|
|
622
|
-
fetchMock.put(processedRequest.url, eventResponse);
|
|
623
|
-
const searchEventPayload = {queryText: 'potato'};
|
|
624
|
-
await new CoveoAnalyticsClient({
|
|
625
|
-
token: aToken,
|
|
626
|
-
endpoint: anEndpoint,
|
|
627
|
-
version: A_VERSION,
|
|
628
|
-
preprocessRequest: (request, type) => {
|
|
629
|
-
clientOrigin = type;
|
|
630
|
-
processedRequest = {
|
|
631
|
-
...request,
|
|
632
|
-
...processedRequest,
|
|
633
|
-
};
|
|
634
|
-
return processedRequest;
|
|
635
|
-
},
|
|
636
|
-
}).sendEvent(EventType.search, searchEventPayload);
|
|
637
|
-
|
|
638
|
-
const call = fetchMock.calls()[0];
|
|
639
|
-
const url = call[0];
|
|
640
|
-
const options: RequestInit | undefined = call[1];
|
|
641
|
-
|
|
642
|
-
const {url: expectedUrl, ...expectedOptions} = processedRequest;
|
|
643
|
-
expect(clientOrigin!).toBe('analyticsFetch');
|
|
644
|
-
expect(url).toBe(expectedUrl);
|
|
645
|
-
expect(options).toEqual(expectedOptions);
|
|
646
|
-
});
|
|
647
|
-
|
|
648
|
-
const getParsedBodyCalls = (): any[] => {
|
|
649
|
-
return fetchMock.calls().map(([, {body}]: any) => {
|
|
650
|
-
return JSON.parse(body.toString());
|
|
491
|
+
describe('for OAuth Tokens', () => {
|
|
492
|
+
beforeEach(() => {
|
|
493
|
+
client = new CoveoAnalyticsClient({
|
|
494
|
+
token: 'testToken',
|
|
495
|
+
endpoint: anEndpoint,
|
|
496
|
+
version: A_VERSION,
|
|
651
497
|
});
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
let client = new CoveoAnalyticsClient({});
|
|
660
|
-
|
|
661
|
-
expect(client.runtime).toBeInstanceOf(BrowserRuntime);
|
|
662
|
-
expect(client.runtime.storage).toBeInstanceOf(CookieAndLocalStorage);
|
|
663
|
-
});
|
|
664
|
-
|
|
665
|
-
it('should honor doNotTrack', () => {
|
|
666
|
-
jest.spyOn(doNotTrack, 'doNotTrack').mockImplementation(() => true);
|
|
498
|
+
mockFetchRequestForEventType(EventType.custom);
|
|
499
|
+
client.addEventTypeMapping(eventType, {
|
|
500
|
+
newEventType: EventType.custom,
|
|
501
|
+
usesMeasurementProtocol: true,
|
|
502
|
+
});
|
|
503
|
+
});
|
|
667
504
|
|
|
668
|
-
|
|
505
|
+
it('should do nothing with absent userId', async () => {
|
|
506
|
+
await client.sendEvent(eventType);
|
|
507
|
+
expectUserId({});
|
|
508
|
+
});
|
|
669
509
|
|
|
670
|
-
|
|
671
|
-
|
|
510
|
+
it('should leave existing userIds', async () => {
|
|
511
|
+
await client.sendEvent(eventType, {userId: 'bob'});
|
|
512
|
+
expectUserId({uid: 'bob'});
|
|
513
|
+
});
|
|
672
514
|
});
|
|
515
|
+
});
|
|
673
516
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
517
|
+
describe('with context_website is set in customData', () => {
|
|
518
|
+
const contextWebsite = 'yourbestfriend.com';
|
|
519
|
+
const trackingId = 'yourfavoritefood.ca ';
|
|
520
|
+
beforeEach(() => {
|
|
521
|
+
client = new CoveoAnalyticsClient({
|
|
522
|
+
token: 'testToken',
|
|
523
|
+
endpoint: anEndpoint,
|
|
524
|
+
version: A_VERSION,
|
|
525
|
+
});
|
|
526
|
+
mockFetchRequestForEventType(EventType.view);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
it('should set trackingId when trackingId is not specified', async () => {
|
|
530
|
+
await client.sendEvent(EventType.view, {
|
|
531
|
+
customData: {context_website: contextWebsite},
|
|
532
|
+
});
|
|
533
|
+
const [body] = getParsedBodyCalls();
|
|
534
|
+
expect(body.trackingId).toBe(contextWebsite);
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
it('should not overwrite trackingId when trackingId is specified', async () => {
|
|
538
|
+
await client.sendEvent(EventType.view, {
|
|
539
|
+
trackingId: trackingId,
|
|
540
|
+
customData: {context_website: contextWebsite},
|
|
541
|
+
});
|
|
542
|
+
const [body] = getParsedBodyCalls();
|
|
543
|
+
expect(body.trackingId).toBe(trackingId);
|
|
544
|
+
});
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
describe('with siteName is set in customData', () => {
|
|
548
|
+
const website = 'yourbestfriend.com';
|
|
549
|
+
const trackingId = 'yourfavoritefood.ca ';
|
|
550
|
+
beforeEach(() => {
|
|
551
|
+
client = new CoveoAnalyticsClient({
|
|
552
|
+
token: 'testToken',
|
|
553
|
+
endpoint: anEndpoint,
|
|
554
|
+
version: A_VERSION,
|
|
555
|
+
});
|
|
556
|
+
mockFetchRequestForEventType(EventType.view);
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
it('should set trackingId when trackingId is not specified', async () => {
|
|
560
|
+
await client.sendEvent(EventType.view, {customData: {siteName: website}});
|
|
561
|
+
const [body] = getParsedBodyCalls();
|
|
562
|
+
expect(body.trackingId).toBe(website);
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
it('should not overwrite trackingId when trackingId is specified', async () => {
|
|
566
|
+
await client.sendEvent(EventType.view, {
|
|
567
|
+
trackingId: trackingId,
|
|
568
|
+
customData: {siteName: website},
|
|
569
|
+
});
|
|
570
|
+
const [body] = getParsedBodyCalls();
|
|
571
|
+
expect(body.trackingId).toBe(trackingId);
|
|
572
|
+
});
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
it('should support clearing cookies for visitorId and historyStore', () => {
|
|
576
|
+
const visitorId = 'foo';
|
|
577
|
+
const history = {name: 'foo', time: '123', value: 'bar'};
|
|
578
|
+
const storage = new CookieStorage();
|
|
579
|
+
const historyStore = new HistoryStore();
|
|
580
|
+
|
|
581
|
+
client.currentVisitorId = visitorId;
|
|
582
|
+
historyStore.addElement(history);
|
|
583
|
+
|
|
584
|
+
expect(storage.getItem('visitorId')).toBe('foo');
|
|
585
|
+
expect(historyStore.getMostRecentElement()).toEqual(history);
|
|
586
|
+
|
|
587
|
+
client.clear();
|
|
588
|
+
expect(storage.getItem('visitorId')).toBeNull();
|
|
589
|
+
expect(historyStore.getMostRecentElement()).toBeUndefined();
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
it('should support clearing cookies for visitorId and historyStore async', async () => {
|
|
593
|
+
const visitorId = 'foo';
|
|
594
|
+
const history = {name: 'foo', time: '123', value: 'bar'};
|
|
595
|
+
const storage = new CookieStorage();
|
|
596
|
+
const historyStore = new HistoryStore();
|
|
597
|
+
|
|
598
|
+
await client.setCurrentVisitorId(visitorId);
|
|
599
|
+
await historyStore.addElementAsync(history);
|
|
600
|
+
|
|
601
|
+
expect(await storage.getItem('visitorId')).toBe('foo');
|
|
602
|
+
expect(historyStore.getMostRecentElement()).toEqual(history);
|
|
603
|
+
|
|
604
|
+
client.clear();
|
|
605
|
+
expect(storage.getItem('visitorId')).toBeNull();
|
|
606
|
+
expect(historyStore.getMostRecentElement()).toBeUndefined();
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
it('should execute before send hooks passed as option', async () => {
|
|
610
|
+
mockFetchRequestForEventType(EventType.search);
|
|
611
|
+
const spy = vi.fn((_, p) => p);
|
|
612
|
+
const searchEventPayload = {queryText: 'potato'};
|
|
613
|
+
await new CoveoAnalyticsClient({
|
|
614
|
+
token: aToken,
|
|
615
|
+
endpoint: anEndpoint,
|
|
616
|
+
version: A_VERSION,
|
|
617
|
+
beforeSendHooks: [spy],
|
|
618
|
+
}).sendEvent(EventType.search, searchEventPayload);
|
|
619
|
+
expect(spy).toHaveBeenLastCalledWith(
|
|
620
|
+
EventType.search,
|
|
621
|
+
expect.objectContaining(searchEventPayload)
|
|
622
|
+
);
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
it('should preprocess the request with the preprocessRequest', async () => {
|
|
626
|
+
let clientOrigin: string;
|
|
627
|
+
let processedRequest: IAnalyticsRequestOptions = {
|
|
628
|
+
url: 'https://www.myownanalytics.com/endpoint',
|
|
629
|
+
headers: {
|
|
630
|
+
Age: '24',
|
|
631
|
+
},
|
|
632
|
+
method: 'PUT',
|
|
633
|
+
body: JSON.stringify({
|
|
634
|
+
test: 'custom',
|
|
635
|
+
}),
|
|
636
|
+
};
|
|
637
|
+
fetchMock.put(processedRequest.url, eventResponse);
|
|
638
|
+
const searchEventPayload = {queryText: 'potato'};
|
|
639
|
+
await new CoveoAnalyticsClient({
|
|
640
|
+
token: aToken,
|
|
641
|
+
endpoint: anEndpoint,
|
|
642
|
+
version: A_VERSION,
|
|
643
|
+
preprocessRequest: (request, type) => {
|
|
644
|
+
clientOrigin = type;
|
|
645
|
+
processedRequest = {
|
|
646
|
+
...request,
|
|
647
|
+
...processedRequest,
|
|
648
|
+
};
|
|
649
|
+
return processedRequest;
|
|
650
|
+
},
|
|
651
|
+
}).sendEvent(EventType.search, searchEventPayload);
|
|
678
652
|
|
|
679
|
-
|
|
653
|
+
const call = fetchMock.calls()[0];
|
|
654
|
+
const url = call[0];
|
|
655
|
+
const options: RequestInit | undefined = call[1];
|
|
680
656
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
657
|
+
const {url: expectedUrl, ...expectedOptions} = processedRequest;
|
|
658
|
+
expect(clientOrigin!).toBe('analyticsFetch');
|
|
659
|
+
expect(url).toBe(expectedUrl);
|
|
660
|
+
expect(options).toEqual(expectedOptions);
|
|
661
|
+
});
|
|
684
662
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
client.setClientId('c7d57b22-4aa8-487a-a106-be5243885f0a');
|
|
689
|
-
expect(await client.getCurrentVisitorId()).toBe('c7d57b22-4aa8-487a-a106-be5243885f0a');
|
|
690
|
-
});
|
|
691
|
-
|
|
692
|
-
it('persists clientId in lowercase', async () => {
|
|
693
|
-
const client = new CoveoAnalyticsClient({});
|
|
694
|
-
client.setClientId('C7D57B22-4AA8-487A-A106-BE5243885F0A');
|
|
695
|
-
expect(await client.getCurrentVisitorId()).toBe('c7d57b22-4aa8-487a-a106-be5243885f0a');
|
|
696
|
-
});
|
|
697
|
-
|
|
698
|
-
it('allows setting a custom consistent clientId given a string', async () => {
|
|
699
|
-
const client = new CoveoAnalyticsClient({});
|
|
700
|
-
client.setClientId('somestring', 'testNameSpace');
|
|
701
|
-
//uuid v5 specific uuid generation
|
|
702
|
-
expect(await client.getCurrentVisitorId()).toBe('2c356915-8223-5773-acb8-e2a34404a559');
|
|
703
|
-
//check for consistent ids
|
|
704
|
-
client.setClientId('somestring', 'testNameSpace');
|
|
705
|
-
expect(await client.getCurrentVisitorId()).toBe('2c356915-8223-5773-acb8-e2a34404a559');
|
|
706
|
-
client.setClientId('otherstring', 'testNameSpace');
|
|
707
|
-
expect(await client.getCurrentVisitorId()).not.toBe('2c356915-8223-5773-acb8-e2a34404a559');
|
|
708
|
-
client.setClientId('somestring', 'otherNameSpace');
|
|
709
|
-
expect(await client.getCurrentVisitorId()).not.toBe('2c356915-8223-5773-acb8-e2a34404a559');
|
|
710
|
-
});
|
|
711
|
-
|
|
712
|
-
it('errors when not providing a namespace', async () => {
|
|
713
|
-
const client = new CoveoAnalyticsClient({});
|
|
714
|
-
expect.assertions(1);
|
|
715
|
-
await expect(client.setClientId('somestring')).rejects.toEqual(
|
|
716
|
-
Error('Cannot generate uuid client id without a specific namespace string.'),
|
|
717
|
-
);
|
|
718
|
-
//uuid v5 specific uuid generation
|
|
663
|
+
const getParsedBodyCalls = (): any[] => {
|
|
664
|
+
return fetchMock.calls().map(([, {body}]: any) => {
|
|
665
|
+
return JSON.parse(body.toString());
|
|
719
666
|
});
|
|
667
|
+
};
|
|
720
668
|
});
|
|
721
669
|
|
|
722
|
-
describe('
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
const forcedUUID: string = 'c0b48880-743e-484f-8044-d7c37910c55b';
|
|
726
|
-
|
|
727
|
-
function navigateTo(url: string) {
|
|
728
|
-
// @ts-ignore
|
|
729
|
-
delete window.location;
|
|
730
|
-
// @ts-ignore
|
|
731
|
-
window.location = new URL(url);
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
beforeEach(() => {
|
|
735
|
-
client = new CoveoAnalyticsClient({});
|
|
736
|
-
// need to clear existing clientIds
|
|
737
|
-
client.clear();
|
|
738
|
-
jest.spyOn(doNotTrack, 'doNotTrack').mockImplementation(() => false);
|
|
739
|
-
});
|
|
740
|
-
|
|
741
|
-
it('will extract a clientId from a query param if the referrer matches all and it is not expired', async () => {
|
|
742
|
-
client.setAcceptedLinkReferrers(['*']);
|
|
743
|
-
const linkString = new CoveoLinkParam(forcedUUID, Date.now());
|
|
744
|
-
navigateTo('http://my.receivingdomain.com/?cvo_cid=' + linkString.toString());
|
|
745
|
-
expect(await client.getCurrentVisitorId()).toBe(forcedUUID);
|
|
746
|
-
});
|
|
747
|
-
|
|
748
|
-
it('will extract a clientId from a query param if the referrer matches the current referrer exactly and it is not expired', async () => {
|
|
749
|
-
client.setAcceptedLinkReferrers(['somewhere.over']);
|
|
750
|
-
const linkString = new CoveoLinkParam(forcedUUID, Date.now());
|
|
751
|
-
navigateTo('http://my.receivingdomain.com/?cvo_cid=' + linkString.toString());
|
|
752
|
-
expect(await client.getCurrentVisitorId()).toBe(forcedUUID);
|
|
753
|
-
});
|
|
754
|
-
|
|
755
|
-
it('will extract a clientId from a query param if the referrer matches the current referrer with wildcard and it is not expired', async () => {
|
|
756
|
-
client.setAcceptedLinkReferrers(['*.over']);
|
|
757
|
-
const linkString = new CoveoLinkParam(forcedUUID, Date.now());
|
|
758
|
-
navigateTo('http://my.receivingdomain.com/?cvo_cid=' + linkString.toString());
|
|
759
|
-
expect(await client.getCurrentVisitorId()).toBe(forcedUUID);
|
|
760
|
-
});
|
|
670
|
+
describe('doNotTrack', () => {
|
|
671
|
+
it('should do business as usual if doNotTrack returns false', () => {
|
|
672
|
+
vi.spyOn(doNotTrack, 'shouldDisableAnalyticsForPrivacy').mockImplementation(() => false);
|
|
761
673
|
|
|
762
|
-
|
|
763
|
-
client.setAcceptedLinkReferrers(['*.mydomain.com', '*.over']);
|
|
764
|
-
const linkString = new CoveoLinkParam(forcedUUID, Date.now());
|
|
765
|
-
navigateTo('http://my.receivingdomain.com/?cvo_cid=' + linkString.toString());
|
|
766
|
-
expect(await client.getCurrentVisitorId()).toBe(forcedUUID);
|
|
767
|
-
});
|
|
674
|
+
let client = new CoveoAnalyticsClient({});
|
|
768
675
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
navigateTo('http://my.receivingdomain.com/?cvo_cid=' + linkString.toString());
|
|
773
|
-
expect(await client.getCurrentVisitorId()).not.toBe(null);
|
|
774
|
-
expect(await client.getCurrentVisitorId()).toBe(aVisitorId);
|
|
775
|
-
});
|
|
676
|
+
expect(client.runtime).toBeInstanceOf(BrowserRuntime);
|
|
677
|
+
expect(client.runtime.storage).toBeInstanceOf(CookieAndLocalStorage);
|
|
678
|
+
});
|
|
776
679
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
navigateTo('http://my.receivingdomain.com/?cvo_cid=' + linkString.toString());
|
|
780
|
-
expect(await client.getCurrentVisitorId()).not.toBe(null);
|
|
781
|
-
expect(await client.getCurrentVisitorId()).toBe(aVisitorId);
|
|
782
|
-
});
|
|
680
|
+
it('should honor doNotTrack', () => {
|
|
681
|
+
vi.spyOn(doNotTrack, 'shouldDisableAnalyticsForPrivacy').mockImplementation(() => true);
|
|
783
682
|
|
|
784
|
-
|
|
785
|
-
client.setAcceptedLinkReferrers([]);
|
|
786
|
-
const linkString = new CoveoLinkParam(forcedUUID, Date.now());
|
|
787
|
-
navigateTo('http://my.receivingdomain.com/?cvo_cid=' + linkString.toString());
|
|
788
|
-
expect(await client.getCurrentVisitorId()).not.toBe(null);
|
|
789
|
-
expect(await client.getCurrentVisitorId()).toBe(aVisitorId);
|
|
790
|
-
});
|
|
683
|
+
let client = new CoveoAnalyticsClient({});
|
|
791
684
|
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
navigateTo('http://my.receivingdomain.com/?cvo_cid=' + linkString.toString());
|
|
796
|
-
expect(await client.getCurrentVisitorId()).not.toBe(null);
|
|
797
|
-
expect(await client.getCurrentVisitorId()).toBe(aVisitorId);
|
|
798
|
-
});
|
|
685
|
+
expect(client.runtime).toBeInstanceOf(NoopRuntime);
|
|
686
|
+
expect(client.runtime.storage).toBeInstanceOf(NullStorage);
|
|
687
|
+
});
|
|
799
688
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
expect(await client.getCurrentVisitorId()).not.toBe(null);
|
|
805
|
-
expect(await client.getCurrentVisitorId()).toBe(aVisitorId);
|
|
806
|
-
});
|
|
689
|
+
it('should not clear existing cookies', async () => {
|
|
690
|
+
vi.spyOn(doNotTrack, 'shouldDisableAnalyticsForPrivacy').mockImplementation(() => true);
|
|
691
|
+
Cookie.set('coveo_visitorId', aVisitorId);
|
|
692
|
+
expect(Cookie.get('coveo_visitorId')).toBe(aVisitorId);
|
|
807
693
|
|
|
808
|
-
|
|
809
|
-
client.setAcceptedLinkReferrers(['*.mydomain.com', 'www.example.com']);
|
|
810
|
-
const linkString = new CoveoLinkParam(forcedUUID, Date.now());
|
|
811
|
-
navigateTo('http://my.receivingdomain.com/?cvo_cid=' + linkString.toString());
|
|
812
|
-
expect(await client.getCurrentVisitorId()).not.toBe(null);
|
|
813
|
-
expect(await client.getCurrentVisitorId()).toBe(aVisitorId);
|
|
814
|
-
});
|
|
694
|
+
new CoveoAnalyticsClient({});
|
|
815
695
|
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
expect(await client.getCurrentVisitorId()).not.toBe(null);
|
|
820
|
-
expect(await client.getCurrentVisitorId()).toBe(aVisitorId);
|
|
821
|
-
});
|
|
696
|
+
expect(Cookie.get('coveo_visitorId')).toBe(aVisitorId);
|
|
697
|
+
});
|
|
698
|
+
});
|
|
822
699
|
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
700
|
+
describe('custom clientId', () => {
|
|
701
|
+
it('allows setting of a custom clientId', async () => {
|
|
702
|
+
const client = new CoveoAnalyticsClient({});
|
|
703
|
+
client.setClientId('c7d57b22-4aa8-487a-a106-be5243885f0a');
|
|
704
|
+
expect(await client.getCurrentVisitorId()).toBe('c7d57b22-4aa8-487a-a106-be5243885f0a');
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
it('persists clientId in lowercase', async () => {
|
|
708
|
+
const client = new CoveoAnalyticsClient({});
|
|
709
|
+
client.setClientId('C7D57B22-4AA8-487A-A106-BE5243885F0A');
|
|
710
|
+
expect(await client.getCurrentVisitorId()).toBe('c7d57b22-4aa8-487a-a106-be5243885f0a');
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
it('allows setting a custom consistent clientId given a string', async () => {
|
|
714
|
+
const client = new CoveoAnalyticsClient({});
|
|
715
|
+
client.setClientId('somestring', 'testNameSpace');
|
|
716
|
+
//uuid v5 specific uuid generation
|
|
717
|
+
expect(await client.getCurrentVisitorId()).toBe('2c356915-8223-5773-acb8-e2a34404a559');
|
|
718
|
+
//check for consistent ids
|
|
719
|
+
client.setClientId('somestring', 'testNameSpace');
|
|
720
|
+
expect(await client.getCurrentVisitorId()).toBe('2c356915-8223-5773-acb8-e2a34404a559');
|
|
721
|
+
client.setClientId('otherstring', 'testNameSpace');
|
|
722
|
+
expect(await client.getCurrentVisitorId()).not.toBe('2c356915-8223-5773-acb8-e2a34404a559');
|
|
723
|
+
client.setClientId('somestring', 'otherNameSpace');
|
|
724
|
+
expect(await client.getCurrentVisitorId()).not.toBe('2c356915-8223-5773-acb8-e2a34404a559');
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
it('errors when not providing a namespace', async () => {
|
|
728
|
+
const client = new CoveoAnalyticsClient({});
|
|
729
|
+
expect.assertions(1);
|
|
730
|
+
await expect(client.setClientId('somestring')).rejects.toEqual(
|
|
731
|
+
Error('Cannot generate uuid client id without a specific namespace string.')
|
|
732
|
+
);
|
|
733
|
+
//uuid v5 specific uuid generation
|
|
734
|
+
});
|
|
735
|
+
});
|
|
831
736
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
737
|
+
describe('clientId from link', () => {
|
|
738
|
+
// note: referrer is set as http://somewhere.over/thereferrer in setup.js
|
|
739
|
+
let client: CoveoAnalyticsClient;
|
|
740
|
+
const forcedUUID: string = 'c0b48880-743e-484f-8044-d7c37910c55b';
|
|
741
|
+
|
|
742
|
+
function navigateTo(url: string) {
|
|
743
|
+
// @ts-ignore
|
|
744
|
+
delete window.location;
|
|
745
|
+
// @ts-ignore
|
|
746
|
+
window.location = new URL(url);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
beforeEach(() => {
|
|
750
|
+
client = new CoveoAnalyticsClient({});
|
|
751
|
+
// need to clear existing clientIds
|
|
752
|
+
client.clear();
|
|
753
|
+
vi.spyOn(doNotTrack, 'shouldDisableAnalyticsForPrivacy').mockImplementation(() => false);
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
it('will extract a clientId from a query param if the referrer matches all and it is not expired', async () => {
|
|
757
|
+
client.setAcceptedLinkReferrers(['*']);
|
|
758
|
+
const linkString = new CoveoLinkParam(forcedUUID, Date.now());
|
|
759
|
+
navigateTo('http://my.receivingdomain.com/?cvo_cid=' + linkString.toString());
|
|
760
|
+
expect(await client.getCurrentVisitorId()).toBe(forcedUUID);
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
it('will extract a clientId from a query param if the referrer matches the current referrer exactly and it is not expired', async () => {
|
|
764
|
+
client.setAcceptedLinkReferrers(['somewhere.over']);
|
|
765
|
+
const linkString = new CoveoLinkParam(forcedUUID, Date.now());
|
|
766
|
+
navigateTo('http://my.receivingdomain.com/?cvo_cid=' + linkString.toString());
|
|
767
|
+
expect(await client.getCurrentVisitorId()).toBe(forcedUUID);
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
it('will extract a clientId from a query param if the referrer matches the current referrer with wildcard and it is not expired', async () => {
|
|
771
|
+
client.setAcceptedLinkReferrers(['*.over']);
|
|
772
|
+
const linkString = new CoveoLinkParam(forcedUUID, Date.now());
|
|
773
|
+
navigateTo('http://my.receivingdomain.com/?cvo_cid=' + linkString.toString());
|
|
774
|
+
expect(await client.getCurrentVisitorId()).toBe(forcedUUID);
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
it('will extract a clientId from a query param if one of the referrer matches the current referrer and it is not expired', async () => {
|
|
778
|
+
client.setAcceptedLinkReferrers(['*.mydomain.com', '*.over']);
|
|
779
|
+
const linkString = new CoveoLinkParam(forcedUUID, Date.now());
|
|
780
|
+
navigateTo('http://my.receivingdomain.com/?cvo_cid=' + linkString.toString());
|
|
781
|
+
expect(await client.getCurrentVisitorId()).toBe(forcedUUID);
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
it('will not extract a clientId from a query param if the referrer matches and it is expired', async () => {
|
|
785
|
+
client.setAcceptedLinkReferrers(['*']);
|
|
786
|
+
const linkString = new CoveoLinkParam(forcedUUID, Date.now() - 180000);
|
|
787
|
+
navigateTo('http://my.receivingdomain.com/?cvo_cid=' + linkString.toString());
|
|
788
|
+
expect(await client.getCurrentVisitorId()).not.toBe(null);
|
|
789
|
+
expect(await client.getCurrentVisitorId()).toBe(aVisitorId);
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
it('will not extract a clientId from a query param if there is no accept list specified', async () => {
|
|
793
|
+
const linkString = new CoveoLinkParam(forcedUUID, Date.now());
|
|
794
|
+
navigateTo('http://my.receivingdomain.com/?cvo_cid=' + linkString.toString());
|
|
795
|
+
expect(await client.getCurrentVisitorId()).not.toBe(null);
|
|
796
|
+
expect(await client.getCurrentVisitorId()).toBe(aVisitorId);
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
it('will not extract a clientId from a query param if there is an empty accept list', async () => {
|
|
800
|
+
client.setAcceptedLinkReferrers([]);
|
|
801
|
+
const linkString = new CoveoLinkParam(forcedUUID, Date.now());
|
|
802
|
+
navigateTo('http://my.receivingdomain.com/?cvo_cid=' + linkString.toString());
|
|
803
|
+
expect(await client.getCurrentVisitorId()).not.toBe(null);
|
|
804
|
+
expect(await client.getCurrentVisitorId()).toBe(aVisitorId);
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
it('will not extract a clientId from a query param if the referrer list does not match', async () => {
|
|
808
|
+
client.setAcceptedLinkReferrers(['*.mydomain.com']);
|
|
809
|
+
const linkString = new CoveoLinkParam(forcedUUID, Date.now());
|
|
810
|
+
navigateTo('http://my.receivingdomain.com/?cvo_cid=' + linkString.toString());
|
|
811
|
+
expect(await client.getCurrentVisitorId()).not.toBe(null);
|
|
812
|
+
expect(await client.getCurrentVisitorId()).toBe(aVisitorId);
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
it('will not extract a clientId from a query param if the referrer list does not match the exact port', async () => {
|
|
816
|
+
client.setAcceptedLinkReferrers(['*.over:9000']);
|
|
817
|
+
const linkString = new CoveoLinkParam(forcedUUID, Date.now());
|
|
818
|
+
navigateTo('http://my.receivingdomain.com/?cvo_cid=' + linkString.toString());
|
|
819
|
+
expect(await client.getCurrentVisitorId()).not.toBe(null);
|
|
820
|
+
expect(await client.getCurrentVisitorId()).toBe(aVisitorId);
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
it('will not extract a clientId from a query param if the multi referrer list does not match', async () => {
|
|
824
|
+
client.setAcceptedLinkReferrers(['*.mydomain.com', 'www.example.com']);
|
|
825
|
+
const linkString = new CoveoLinkParam(forcedUUID, Date.now());
|
|
826
|
+
navigateTo('http://my.receivingdomain.com/?cvo_cid=' + linkString.toString());
|
|
827
|
+
expect(await client.getCurrentVisitorId()).not.toBe(null);
|
|
828
|
+
expect(await client.getCurrentVisitorId()).toBe(aVisitorId);
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
it('will not extract a clientId from a query param if it is not a UUID', async () => {
|
|
832
|
+
client.setAcceptedLinkReferrers(['*']);
|
|
833
|
+
navigateTo('http://my.receivingdomain.com/?cvo_cid=notauuid.' + Math.floor(Date.now() / 1000));
|
|
834
|
+
expect(await client.getCurrentVisitorId()).not.toBe(null);
|
|
835
|
+
expect(await client.getCurrentVisitorId()).toBe(aVisitorId);
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
it('will not extract a clientId from a query param if DNT is enabled', async () => {
|
|
839
|
+
client.setAcceptedLinkReferrers(['*']);
|
|
840
|
+
vi.spyOn(doNotTrack, 'shouldDisableAnalyticsForPrivacy').mockImplementation(() => true);
|
|
841
|
+
const linkString = new CoveoLinkParam(forcedUUID, Date.now());
|
|
842
|
+
navigateTo('http://my.receivingdomain.com/?cvo_cid=' + linkString.toString());
|
|
843
|
+
expect(await client.getCurrentVisitorId()).not.toBe(null);
|
|
844
|
+
expect(await client.getCurrentVisitorId()).toBe(aVisitorId);
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
it('will throw when specifying invalid hosts list', async () => {
|
|
848
|
+
//@ts-ignore
|
|
849
|
+
expect(() => client.setAcceptedLinkReferrers('*')).toThrow(
|
|
850
|
+
'Parameter should be an array of domain strings'
|
|
851
|
+
);
|
|
852
|
+
//@ts-ignore
|
|
853
|
+
expect(() => client.setAcceptedLinkReferrers({})).toThrow(
|
|
854
|
+
'Parameter should be an array of domain strings'
|
|
855
|
+
);
|
|
856
|
+
//@ts-ignore
|
|
857
|
+
expect(() => client.setAcceptedLinkReferrers([{}])).toThrow(
|
|
858
|
+
'Parameter should be an array of domain strings'
|
|
859
|
+
);
|
|
860
|
+
});
|
|
840
861
|
});
|