countly-sdk-web 25.4.5 → 26.1.1
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 +16 -0
- package/cypress/e2e/behavior_settings.cy.js +578 -0
- package/cypress/e2e/bridged_utils.cy.js +1 -1
- package/cypress/e2e/fake_request_handler.cy.js +216 -0
- package/cypress/e2e/remaining_requests.cy.js +17 -36
- package/cypress/e2e/request_backoff.cy.js +7 -7
- package/cypress/e2e/sdk_info.cy.js +1 -1
- package/cypress/support/helper.js +41 -0
- package/examples/example_quick_async.html +63 -0
- package/examples/quick_loader/quick_async_loader.js +152 -0
- package/examples/quick_loader/quick_async_loader.min.js +4 -0
- package/lib/countly.js +1139 -46
- package/lib/countly.min.js +255 -221
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
## 26.1.1
|
|
2
|
+
|
|
3
|
+
- Improved device metric detection capabilities.
|
|
4
|
+
|
|
5
|
+
## 26.1.0
|
|
6
|
+
|
|
7
|
+
- Added support for SBS flags:
|
|
8
|
+
- Event whitelisting / blacklisting
|
|
9
|
+
- Segmentation whitelisting / blacklisting (global and per-event)
|
|
10
|
+
- User property whitelisting / blacklisting
|
|
11
|
+
- Journey trigger events
|
|
12
|
+
- Added support for Feedback Widget resizing logic (will need server update to benefit.)
|
|
13
|
+
- Improved testing consistency of queuing system
|
|
14
|
+
|
|
15
|
+
- Mitigated an issue where an unintended URL was opened when closing a feedback widget after a content block was closed.
|
|
16
|
+
|
|
1
17
|
## 25.4.5
|
|
2
18
|
|
|
3
19
|
- Added SDK info methods:
|
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
/* eslint-disable require-jsdoc */
|
|
2
|
+
var Countly = require("../../lib/countly");
|
|
3
|
+
var hp = require("../support/helper");
|
|
4
|
+
|
|
5
|
+
function initWithBehavior(settings) {
|
|
6
|
+
Countly.init({
|
|
7
|
+
app_key: hp.appKey,
|
|
8
|
+
url: "https://example.count.ly",
|
|
9
|
+
debug: true,
|
|
10
|
+
test_mode: true,
|
|
11
|
+
behavior_settings: settings
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function collectEventKeysFromQueues() {
|
|
16
|
+
return cy.fetch_local_request_queue().then((rq) => {
|
|
17
|
+
const reqEvents = rq
|
|
18
|
+
.filter((r) => r.events)
|
|
19
|
+
.flatMap((r) => JSON.parse(r.events));
|
|
20
|
+
return cy.fetch_local_event_queue().then((eq) => {
|
|
21
|
+
return reqEvents.concat(eq || []);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe("Behavior settings filters", () => {
|
|
27
|
+
it("event blacklist blocks", () => {
|
|
28
|
+
hp.haltAndClearStorage(() => {
|
|
29
|
+
initWithBehavior({ c: { eb: ["blocked"] } });
|
|
30
|
+
Countly.add_event({ key: "blocked", count: 1 });
|
|
31
|
+
Countly.add_event({ key: "other", count: 1 });
|
|
32
|
+
Countly.add_event({ key: "kept", count: 1 });
|
|
33
|
+
cy.wait(200).then(() => collectEventKeysFromQueues()).then((events) => {
|
|
34
|
+
const keys = events.map((e) => e.key);
|
|
35
|
+
expect(keys).to.include("other");
|
|
36
|
+
expect(keys).to.include("kept");
|
|
37
|
+
expect(keys).to.not.include("blocked");
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("event whitelist allows only listed", () => {
|
|
43
|
+
hp.haltAndClearStorage(() => {
|
|
44
|
+
initWithBehavior({ c: { ew: ["kept"] } });
|
|
45
|
+
Countly.add_event({ key: "blocked", count: 1 });
|
|
46
|
+
Countly.add_event({ key: "other", count: 1 });
|
|
47
|
+
Countly.add_event({ key: "kept", count: 1 });
|
|
48
|
+
cy.wait(200).then(() => collectEventKeysFromQueues()).then((events) => {
|
|
49
|
+
const keys = events.map((e) => e.key);
|
|
50
|
+
expect(keys).to.include("kept");
|
|
51
|
+
expect(keys).to.not.include("blocked");
|
|
52
|
+
expect(keys).to.not.include("other");
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("global segmentation blacklist/whitelist applied", () => {
|
|
58
|
+
hp.haltAndClearStorage(() => {
|
|
59
|
+
initWithBehavior({ c: { sb: ["secret"] } });
|
|
60
|
+
Countly.add_event({ key: "segtest", count: 1, segmentation: { secret: "x", keep: "y", other: "z" } });
|
|
61
|
+
cy.wait(200).then(() => collectEventKeysFromQueues()).then((events) => {
|
|
62
|
+
expect(events.length).to.be.greaterThan(0);
|
|
63
|
+
const seg = events[0].segmentation;
|
|
64
|
+
expect(seg).to.have.property("keep", "y");
|
|
65
|
+
expect(seg).to.not.have.property("secret");
|
|
66
|
+
expect(seg).to.have.property("other", "z");
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("global segmentation whitelist applied", () => {
|
|
72
|
+
hp.haltAndClearStorage(() => {
|
|
73
|
+
initWithBehavior({ c: { sw: ["keep"] } });
|
|
74
|
+
Countly.add_event({ key: "segtest", count: 1, segmentation: { secret: "x", keep: "y", other: "z" } });
|
|
75
|
+
cy.wait(200).then(() => collectEventKeysFromQueues()).then((events) => {
|
|
76
|
+
expect(events.length).to.be.greaterThan(0);
|
|
77
|
+
const seg = events[0].segmentation;
|
|
78
|
+
expect(seg).to.have.property("keep", "y");
|
|
79
|
+
expect(seg).to.not.have.property("secret");
|
|
80
|
+
expect(seg).to.not.have.property("other");
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("event-specific segmentation filters respected", () => {
|
|
86
|
+
hp.haltAndClearStorage(() => {
|
|
87
|
+
initWithBehavior({ c: { esb: { purchase: ["card"] } } });
|
|
88
|
+
Countly.add_event({ key: "purchase", count: 1, segmentation: { card: "1111", keep: "ok", drop: "no" } });
|
|
89
|
+
Countly.add_event({ key: "not-purchase", count: 1, segmentation: { card: "1111", keep: "ok", drop: "no" } });
|
|
90
|
+
cy.wait(200).then(() => collectEventKeysFromQueues()).then((events) => {
|
|
91
|
+
const seg = events.find((e) => e.key === "purchase").segmentation;
|
|
92
|
+
expect(seg).to.have.property("keep", "ok");
|
|
93
|
+
expect(seg).to.not.have.property("card");
|
|
94
|
+
expect(seg).to.have.property("drop", "no");
|
|
95
|
+
const otherSeg = events.find((e) => e.key === "not-purchase").segmentation;
|
|
96
|
+
expect(otherSeg).to.have.property("card", "1111");
|
|
97
|
+
expect(otherSeg).to.have.property("keep", "ok");
|
|
98
|
+
expect(otherSeg).to.have.property("drop", "no");
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("event-specific segmentation whitelist respected", () => {
|
|
104
|
+
hp.haltAndClearStorage(() => {
|
|
105
|
+
initWithBehavior({ c: { esw: { purchase: ["keep"] } } });
|
|
106
|
+
Countly.add_event({ key: "purchase", count: 1, segmentation: { card: "1111", keep: "ok", drop: "no" } });
|
|
107
|
+
Countly.add_event({ key: "not-purchase", count: 1, segmentation: { card: "1111", keep: "ok", drop: "no" } });
|
|
108
|
+
cy.wait(200).then(() => collectEventKeysFromQueues()).then((events) => {
|
|
109
|
+
const seg = events.find((e) => e.key === "purchase").segmentation;
|
|
110
|
+
expect(seg).to.have.property("keep", "ok");
|
|
111
|
+
expect(seg).to.not.have.property("card");
|
|
112
|
+
expect(seg).to.not.have.property("drop");
|
|
113
|
+
const otherSeg = events.find((e) => e.key === "not-purchase").segmentation;
|
|
114
|
+
expect(otherSeg).to.have.property("card", "1111");
|
|
115
|
+
expect(otherSeg).to.have.property("keep", "ok");
|
|
116
|
+
expect(otherSeg).to.have.property("drop", "no");
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("user property whitelist applied", () => {
|
|
122
|
+
hp.haltAndClearStorage(() => {
|
|
123
|
+
initWithBehavior({ c: { upw: ["allowed", "name"] } });
|
|
124
|
+
Countly.user_details({ custom: { blocked: "x", allowed: "y", other: "z" }, name: "John", age: 30 });
|
|
125
|
+
cy.wait(200).then(() => {
|
|
126
|
+
cy.fetch_local_request_queue().then((rq) => {
|
|
127
|
+
const userReq = rq.find((r) => r.user_details);
|
|
128
|
+
expect(userReq, "user_details request present").to.exist;
|
|
129
|
+
const details = JSON.parse(userReq.user_details);
|
|
130
|
+
expect(details.custom).to.have.property("allowed", "y");
|
|
131
|
+
expect(details.custom).to.not.have.property("blocked");
|
|
132
|
+
expect(details.custom).to.not.have.property("other");
|
|
133
|
+
expect(details).to.have.property("name", "John");
|
|
134
|
+
expect(details).to.not.have.property("age");
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
it("user property blacklist applied", () => {
|
|
140
|
+
hp.haltAndClearStorage(() => {
|
|
141
|
+
initWithBehavior({ c: { upb: ["blocked", "age"] } });
|
|
142
|
+
Countly.user_details({ custom: { blocked: "x", allowed: "y", other: "z" }, name: "John", age: 30 });
|
|
143
|
+
cy.wait(200).then(() => {
|
|
144
|
+
cy.fetch_local_request_queue().then((rq) => {
|
|
145
|
+
const userReq = rq.find((r) => r.user_details);
|
|
146
|
+
expect(userReq, "user_details request present").to.exist;
|
|
147
|
+
const details = JSON.parse(userReq.user_details);
|
|
148
|
+
expect(details.custom).to.have.property("allowed", "y");
|
|
149
|
+
expect(details.custom).to.have.property("other", "z");
|
|
150
|
+
expect(details.custom).to.not.have.property("blocked");
|
|
151
|
+
expect(details).to.have.property("name", "John");
|
|
152
|
+
expect(details).to.not.have.property("age");
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe("Journey trigger behavior settings", () => {
|
|
160
|
+
it("journey trigger flushes event without heartbeat", () => {
|
|
161
|
+
hp.haltAndClearStorage(() => {
|
|
162
|
+
Countly.noHeartBeat = true;
|
|
163
|
+
initWithBehavior({ c: { jte: ["journey"] } });
|
|
164
|
+
Countly.add_event({ key: "journey", count: 1 });
|
|
165
|
+
cy.fetch_local_event_queue().then((eq) => {
|
|
166
|
+
expect(eq.length).to.equal(0); // moved out by journey trigger
|
|
167
|
+
});
|
|
168
|
+
cy.fetch_local_request_queue().then((rq) => {
|
|
169
|
+
const eventReq = rq.find((r) => r.events);
|
|
170
|
+
expect(eventReq).to.exist;
|
|
171
|
+
const events = JSON.parse(eventReq.events);
|
|
172
|
+
expect(events.map((e) => e.key)).to.include("journey");
|
|
173
|
+
cy.wait(2000).then(() => {
|
|
174
|
+
var requests = Countly._internals.testingGetRequests();
|
|
175
|
+
expect(requests.length).to.equal(3); // hc and sc and journey event
|
|
176
|
+
const journeyReq = requests.find((r) => r.functionName.includes("journey_trigger_send_request")); // journey event
|
|
177
|
+
const healthReq = requests.find((r) => r.functionName.includes("[healthCheck]"));
|
|
178
|
+
const sbsReq = requests.find((r) => r.functionName.includes("server_config"));
|
|
179
|
+
const contentReq = requests.find((r) => r.functionName.includes("sendContentRequest"));
|
|
180
|
+
expect(healthReq).to.exist;
|
|
181
|
+
expect(sbsReq).to.exist;
|
|
182
|
+
expect(journeyReq).to.exist;
|
|
183
|
+
expect(contentReq).to.be.undefined; // as journey request will fail
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("non-journey event stays queued when heartbeat disabled", () => {
|
|
190
|
+
hp.haltAndClearStorage(() => {
|
|
191
|
+
Countly.noHeartBeat = true;
|
|
192
|
+
initWithBehavior({});
|
|
193
|
+
Countly.add_event({ key: "regular", count: 1 });
|
|
194
|
+
cy.fetch_local_event_queue().then((eq) => {
|
|
195
|
+
expect(eq.map((e) => e.key)).to.include("regular");
|
|
196
|
+
});
|
|
197
|
+
cy.fetch_local_request_queue().then((rq) => {
|
|
198
|
+
const eventReq = rq.find((r) => r.events);
|
|
199
|
+
expect(eventReq).to.be.undefined;
|
|
200
|
+
cy.wait(1500).then(() => {
|
|
201
|
+
var requests = Countly._internals.testingGetRequests();
|
|
202
|
+
expect(requests.length).to.equal(2);
|
|
203
|
+
const journeyReq = requests.find((r) => r.functionName.includes("journey_trigger_send_request")); // no trigger event
|
|
204
|
+
const contentReq = requests.find((r) => r.functionName.includes("sendContentRequest"));
|
|
205
|
+
expect(journeyReq).to.be.undefined;
|
|
206
|
+
expect(contentReq).to.be.undefined;
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("journey trigger sends content request after event request succeeds", () => {
|
|
213
|
+
hp.haltAndClearStorage(() => {
|
|
214
|
+
var fakeServer = hp.createFakeRequestHandler({
|
|
215
|
+
onRequest: function(req) {
|
|
216
|
+
// Return success for all requests
|
|
217
|
+
if (req.url.includes("/o/sdk/content")) {
|
|
218
|
+
return { status: 200, responseText: '{"html":"", "geo":null}' }; // will be seen as failure
|
|
219
|
+
}
|
|
220
|
+
return { status: 200, responseText: '{"result":"Success"}' };
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
Countly.noHeartBeat = true;
|
|
225
|
+
Countly.init({
|
|
226
|
+
app_key: hp.appKey,
|
|
227
|
+
url: "https://test.count.ly",
|
|
228
|
+
debug: true,
|
|
229
|
+
behavior_settings: { c: { jte: ["journey_test"]} },
|
|
230
|
+
fake_request_handler: fakeServer.handler
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
Countly.add_event({ key: "journey_test", count: 1 });
|
|
234
|
+
|
|
235
|
+
cy.wait(4000).then(() => {
|
|
236
|
+
var requests = fakeServer.requests;
|
|
237
|
+
expect(requests.length).to.equal(6); // hc and sc and journey event
|
|
238
|
+
const journeyReq = requests.find((r) => r.functionName.includes("journey_trigger_send_request")); // journey event
|
|
239
|
+
const healthReq = requests.find((r) => r.functionName.includes("[healthCheck]"));
|
|
240
|
+
const sbsReq = requests.find((r) => r.functionName.includes("server_config"));
|
|
241
|
+
const contentReq = requests.find((r) => r.functionName.includes("sendContentRequest"));
|
|
242
|
+
const contentReqs = requests.filter((r) => r.functionName.includes("sendContentRequest"));
|
|
243
|
+
expect(contentReqs.length).to.equal(3); // 3 attempts due to failure (empty content)
|
|
244
|
+
expect(healthReq).to.exist;
|
|
245
|
+
expect(sbsReq).to.exist;
|
|
246
|
+
expect(journeyReq).to.exist;
|
|
247
|
+
expect(contentReq).to.exist;
|
|
248
|
+
expect(requests.indexOf(contentReq)).to.be.greaterThan(requests.indexOf(journeyReq));
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("journey trigger retries content request up to 3 times on failure", () => {
|
|
254
|
+
hp.haltAndClearStorage(() => {
|
|
255
|
+
var fakeServer = hp.createFakeRequestHandler({
|
|
256
|
+
onRequest: function(req) {
|
|
257
|
+
if (req.url.includes("/o/sdk/content")) {
|
|
258
|
+
// Always fail content requests with 500
|
|
259
|
+
return { status: 500, responseText: 'Server Error' };
|
|
260
|
+
}
|
|
261
|
+
return { status: 200, responseText: '{"result":"Success"}' };
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
Countly.noHeartBeat = true;
|
|
266
|
+
Countly.init({
|
|
267
|
+
app_key: hp.appKey,
|
|
268
|
+
url: "https://test.count.ly",
|
|
269
|
+
debug: true,
|
|
270
|
+
behavior_settings: { c: { jte: ["retry_test"]} },
|
|
271
|
+
fake_request_handler: fakeServer.handler
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
Countly.add_event({ key: "retry_test", count: 1 });
|
|
275
|
+
|
|
276
|
+
// Wait for retries (3 attempts with 1 second between = ~2 seconds, plus buffer)
|
|
277
|
+
cy.wait(3000).then(() => {
|
|
278
|
+
var requests = fakeServer.requests;
|
|
279
|
+
// Expected: hc + sc + journey_event + 3 content retries = 6
|
|
280
|
+
expect(requests.length).to.equal(6);
|
|
281
|
+
const healthReq = requests.find((r) => r.functionName.includes("[healthCheck]"));
|
|
282
|
+
const sbsReq = requests.find((r) => r.functionName.includes("server_config"));
|
|
283
|
+
const journeyReq = requests.find((r) => r.functionName.includes("journey_trigger_send_request"));
|
|
284
|
+
const contentReqs = requests.filter((r) => r.functionName.includes("sendContentRequest"));
|
|
285
|
+
expect(healthReq).to.exist;
|
|
286
|
+
expect(sbsReq).to.exist;
|
|
287
|
+
expect(journeyReq).to.exist;
|
|
288
|
+
expect(contentReqs.length).to.equal(3); // initial + 2 retries
|
|
289
|
+
// Verify content requests come after journey request
|
|
290
|
+
expect(requests.indexOf(contentReqs[0])).to.be.greaterThan(requests.indexOf(journeyReq));
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it("journey trigger stops retrying after successful content response", () => {
|
|
296
|
+
hp.haltAndClearStorage(() => {
|
|
297
|
+
var contentRequestCount = 0;
|
|
298
|
+
var fakeServer = hp.createFakeRequestHandler({
|
|
299
|
+
onRequest: function(req) {
|
|
300
|
+
if (req.url.includes("/o/sdk/content")) {
|
|
301
|
+
contentRequestCount++;
|
|
302
|
+
// Fail first request, succeed on second
|
|
303
|
+
if (contentRequestCount === 1) {
|
|
304
|
+
return { status: 500, responseText: 'Server Error' };
|
|
305
|
+
}
|
|
306
|
+
// Return valid content on second attempt
|
|
307
|
+
return {
|
|
308
|
+
status: 200,
|
|
309
|
+
responseText: JSON.stringify({
|
|
310
|
+
html: "https://content.test/page?param=1",
|
|
311
|
+
geo: { p: { x: 0, y: 0, w: 100, h: 100 }, l: { x: 0, y: 0, w: 100, h: 100 } }
|
|
312
|
+
})
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
return { status: 200, responseText: '{"result":"Success"}' };
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
Countly.noHeartBeat = true;
|
|
320
|
+
Countly.init({
|
|
321
|
+
app_key: hp.appKey,
|
|
322
|
+
url: "https://test.count.ly",
|
|
323
|
+
debug: true,
|
|
324
|
+
behavior_settings: { c: { jte: ["success_retry"]} },
|
|
325
|
+
fake_request_handler: fakeServer.handler
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
Countly.add_event({ key: "success_retry", count: 1 });
|
|
329
|
+
|
|
330
|
+
// Wait enough time that 3 retries would have happened (1 second between each)
|
|
331
|
+
cy.wait(3000).then(() => {
|
|
332
|
+
var requests = fakeServer.requests;
|
|
333
|
+
// Expected: hc + sc + journey_event + 2 content requests (1 fail + 1 success) = 5
|
|
334
|
+
expect(requests.length).to.equal(5);
|
|
335
|
+
const healthReq = requests.find((r) => r.functionName.includes("[healthCheck]"));
|
|
336
|
+
const sbsReq = requests.find((r) => r.functionName.includes("server_config"));
|
|
337
|
+
const journeyReq = requests.find((r) => r.functionName.includes("journey_trigger_send_request"));
|
|
338
|
+
const contentReqs = requests.filter((r) => r.functionName.includes("sendContentRequest"));
|
|
339
|
+
expect(healthReq).to.exist;
|
|
340
|
+
expect(sbsReq).to.exist;
|
|
341
|
+
expect(journeyReq).to.exist;
|
|
342
|
+
expect(contentReqs.length).to.equal(2); // first failed, second succeeded, no more retries
|
|
343
|
+
// Verify content requests come after journey request
|
|
344
|
+
expect(requests.indexOf(contentReqs[0])).to.be.greaterThan(requests.indexOf(journeyReq));
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it("journey trigger does not send content request if event request fails", () => {
|
|
350
|
+
hp.haltAndClearStorage(() => {
|
|
351
|
+
var fakeServer = hp.createFakeRequestHandler({
|
|
352
|
+
onRequest: function(req) {
|
|
353
|
+
if (req.url.includes("/o/sdk/content")) {
|
|
354
|
+
return { status: 200, responseText: '{"html":"https://test.com", "geo":{"p":{"x":0,"y":0,"w":100,"h":100},"l":{"x":0,"y":0,"w":100,"h":100}}}' };
|
|
355
|
+
}
|
|
356
|
+
if (req.functionName === "journey_trigger_send_request") {
|
|
357
|
+
// Fail journey event request
|
|
358
|
+
return { status: 500, responseText: 'Server Error' };
|
|
359
|
+
}
|
|
360
|
+
return { status: 200, responseText: '{"result":"Success"}' };
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
Countly.noHeartBeat = true;
|
|
365
|
+
Countly.init({
|
|
366
|
+
app_key: hp.appKey,
|
|
367
|
+
url: "https://test.count.ly",
|
|
368
|
+
debug: true,
|
|
369
|
+
behavior_settings: { c: { jte: ["fail_event"]} },
|
|
370
|
+
fake_request_handler: fakeServer.handler
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
Countly.add_event({ key: "fail_event", count: 1 });
|
|
374
|
+
|
|
375
|
+
cy.wait(2000).then(() => {
|
|
376
|
+
var requests = fakeServer.requests;
|
|
377
|
+
// Expected: hc + sc + journey_event (failed) = 3, no content requests
|
|
378
|
+
expect(requests.length).to.equal(3);
|
|
379
|
+
const healthReq = requests.find((r) => r.functionName.includes("[healthCheck]"));
|
|
380
|
+
const sbsReq = requests.find((r) => r.functionName.includes("server_config"));
|
|
381
|
+
const journeyReq = requests.find((r) => r.functionName.includes("journey_trigger_send_request"));
|
|
382
|
+
const contentReqs = requests.filter((r) => r.functionName.includes("sendContentRequest"));
|
|
383
|
+
expect(healthReq).to.exist;
|
|
384
|
+
expect(sbsReq).to.exist;
|
|
385
|
+
expect(journeyReq).to.exist;
|
|
386
|
+
expect(contentReqs.length).to.equal(0); // no content request since event failed
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it("journey trigger with empty response retries correctly", () => {
|
|
392
|
+
hp.haltAndClearStorage(() => {
|
|
393
|
+
var fakeServer = hp.createFakeRequestHandler({
|
|
394
|
+
onRequest: function(req) {
|
|
395
|
+
if (req.url.includes("/o/sdk/content")) {
|
|
396
|
+
// Return valid JSON but no html/geo content (triggers retry)
|
|
397
|
+
return { status: 200, responseText: '{}' };
|
|
398
|
+
}
|
|
399
|
+
return { status: 200, responseText: '{"result":"Success"}' };
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
Countly.noHeartBeat = true;
|
|
404
|
+
Countly.init({
|
|
405
|
+
app_key: hp.appKey,
|
|
406
|
+
url: "https://test.count.ly",
|
|
407
|
+
debug: true,
|
|
408
|
+
behavior_settings: { c: { jte: ["empty_content"]} },
|
|
409
|
+
fake_request_handler: fakeServer.handler
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
Countly.add_event({ key: "empty_content", count: 1 });
|
|
413
|
+
|
|
414
|
+
// Wait for retries (3 attempts with 1 second between = ~2 seconds, plus buffer)
|
|
415
|
+
cy.wait(3000).then(() => {
|
|
416
|
+
var requests = fakeServer.requests;
|
|
417
|
+
cy.log(requests);
|
|
418
|
+
// Expected: hc + sc + journey_event + 3 content retries = 6
|
|
419
|
+
expect(requests.length).to.equal(6);
|
|
420
|
+
const healthReq = requests.find((r) => r.functionName.includes("[healthCheck]"));
|
|
421
|
+
const sbsReq = requests.find((r) => r.functionName.includes("server_config"));
|
|
422
|
+
const journeyReq = requests.find((r) => r.functionName.includes("journey_trigger_send_request"));
|
|
423
|
+
const contentReqs = requests.filter((r) => r.functionName.includes("sendContentRequest"));
|
|
424
|
+
expect(healthReq).to.exist;
|
|
425
|
+
expect(sbsReq).to.exist;
|
|
426
|
+
expect(journeyReq).to.exist;
|
|
427
|
+
expect(contentReqs.length).to.equal(3); // 3 retries due to empty content
|
|
428
|
+
// Verify content requests come after journey request
|
|
429
|
+
expect(requests.indexOf(contentReqs[0])).to.be.greaterThan(requests.indexOf(journeyReq));
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
it("journey trigger validates request order and timing", () => {
|
|
435
|
+
hp.haltAndClearStorage(() => {
|
|
436
|
+
var fakeServer = hp.createFakeRequestHandler({
|
|
437
|
+
onRequest: function(req) {
|
|
438
|
+
if (req.url.includes("/o/sdk/content")) {
|
|
439
|
+
return { status: 200, responseText: '{"html":"https://test.com", "geo":{"p":{"x":0,"y":0,"w":100,"h":100},"l":{"x":0,"y":0,"w":100,"h":100}}}' };
|
|
440
|
+
}
|
|
441
|
+
return { status: 200, responseText: '{"result":"Success"}' };
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
Countly.noHeartBeat = true;
|
|
446
|
+
Countly.init({
|
|
447
|
+
app_key: hp.appKey,
|
|
448
|
+
url: "https://test.count.ly",
|
|
449
|
+
debug: true,
|
|
450
|
+
behavior_settings: { c: { jte: ["order_test"]} },
|
|
451
|
+
fake_request_handler: fakeServer.handler
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
Countly.add_event({ key: "order_test", count: 1 });
|
|
455
|
+
|
|
456
|
+
// Wait for event and content requests (content is sent immediately after event succeeds)
|
|
457
|
+
cy.wait(1000).then(() => {
|
|
458
|
+
var requests = fakeServer.requests;
|
|
459
|
+
// Expected: hc + sc + journey_event + 1 content (success) = 4
|
|
460
|
+
expect(requests.length).to.equal(4);
|
|
461
|
+
const healthReq = requests.find((r) => r.functionName.includes("[healthCheck]"));
|
|
462
|
+
const sbsReq = requests.find((r) => r.functionName.includes("server_config"));
|
|
463
|
+
const journeyReq = requests.find((r) => r.functionName.includes("journey_trigger_send_request"));
|
|
464
|
+
const contentReq = requests.find((r) => r.functionName.includes("sendContentRequest"));
|
|
465
|
+
expect(healthReq).to.exist;
|
|
466
|
+
expect(sbsReq).to.exist;
|
|
467
|
+
expect(journeyReq).to.exist;
|
|
468
|
+
expect(contentReq).to.exist;
|
|
469
|
+
// Verify order: journey event must come before content request
|
|
470
|
+
expect(requests.indexOf(contentReq)).to.be.greaterThan(requests.indexOf(journeyReq));
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it("multiple journey events trigger content request each time", () => {
|
|
476
|
+
hp.haltAndClearStorage(() => {
|
|
477
|
+
var fakeServer = hp.createFakeRequestHandler({
|
|
478
|
+
onRequest: function(req) {
|
|
479
|
+
if (req.url.includes("/o/sdk/content")) {
|
|
480
|
+
return { status: 200, responseText: '{"html":"https://test.com", "geo":{"p":{"x":0,"y":0,"w":100,"h":100},"l":{"x":0,"y":0,"w":100,"h":100}}}' };
|
|
481
|
+
}
|
|
482
|
+
if (req.functionName === "server_config") {
|
|
483
|
+
return { status: 200, responseText: '{"c": {"jte": ["multi_journey"]}}' };
|
|
484
|
+
}
|
|
485
|
+
return { status: 200, responseText: '{"result":"Success"}' };
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
Countly.noHeartBeat = true;
|
|
490
|
+
Countly.init({
|
|
491
|
+
app_key: hp.appKey,
|
|
492
|
+
url: "https://test.count.ly",
|
|
493
|
+
debug: true,
|
|
494
|
+
behavior_settings: { c: { jte: ["multi_journey"]} },
|
|
495
|
+
fake_request_handler: fakeServer.handler
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
// Add multiple journey events quickly
|
|
499
|
+
Countly.add_event({ key: "multi_journey", count: 1 });
|
|
500
|
+
Countly.add_event({ key: "multi_journey", count: 1 });
|
|
501
|
+
Countly.add_event({ key: "multi_journey", count: 1 });
|
|
502
|
+
|
|
503
|
+
// Wait for processing
|
|
504
|
+
cy.wait(4000).then(() => {
|
|
505
|
+
var requests = fakeServer.requests;
|
|
506
|
+
cy.log(requests);
|
|
507
|
+
// Expected: hc + sc + journey_event (with all 3 events separate) + 1 content = 6
|
|
508
|
+
expect(requests.length).to.equal(6);
|
|
509
|
+
const healthReq = requests.find((r) => r.functionName.includes("[healthCheck]"));
|
|
510
|
+
const sbsReq = requests.find((r) => r.functionName.includes("server_config"));
|
|
511
|
+
const journeyReq = requests.find((r) => r.functionName.includes("journey_trigger_send_request"));
|
|
512
|
+
const journeyReqs = requests.filter((r) => r.functionName.includes("journey_trigger_send_request"));
|
|
513
|
+
expect(journeyReqs.length).to.equal(3);
|
|
514
|
+
const contentReqs = requests.filter((r) => r.functionName.includes("sendContentRequest"));
|
|
515
|
+
expect(healthReq).to.exist;
|
|
516
|
+
expect(sbsReq).to.exist;
|
|
517
|
+
expect(journeyReq).to.exist;
|
|
518
|
+
expect(contentReqs.length).to.equal(1); // only 1 content request despite 3 journey events
|
|
519
|
+
// Verify content request comes after journey request
|
|
520
|
+
expect(requests.indexOf(contentReqs[0])).to.be.greaterThan(requests.indexOf(journeyReq));
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it("multiple journey events trigger content request but while content displayed ignored", () => {
|
|
526
|
+
hp.haltAndClearStorage(() => {
|
|
527
|
+
var fakeServer = hp.createFakeRequestHandler({
|
|
528
|
+
onRequest: function(req) {
|
|
529
|
+
if (req.url.includes("/o/sdk/content")) {
|
|
530
|
+
return { status: 200, responseText: '{"html":"https://test.com", "geo":{"p":{"x":0,"y":0,"w":100,"h":100},"l":{"x":0,"y":0,"w":100,"h":100}}}' };
|
|
531
|
+
}
|
|
532
|
+
if (req.functionName === "server_config") {
|
|
533
|
+
return { status: 200, responseText: '{"c": {"jte": ["journey_1", "journey_2"]}}' };
|
|
534
|
+
}
|
|
535
|
+
return { status: 200, responseText: '{"result":"Success"}' };
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
Countly.noHeartBeat = true;
|
|
540
|
+
Countly.init({
|
|
541
|
+
app_key: hp.appKey,
|
|
542
|
+
url: "https://test.count.ly",
|
|
543
|
+
debug: true,
|
|
544
|
+
behavior_settings: { c: { jte: ["journey_1", "journey_2"]} },
|
|
545
|
+
fake_request_handler: fakeServer.handler
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
// Add multiple journey events
|
|
549
|
+
Countly.add_event({ key: "journey_1", count: 1 });
|
|
550
|
+
Countly.add_event({ key: "random", count: 1 });
|
|
551
|
+
// content is being displayed so this trigger should be ignored
|
|
552
|
+
cy.wait(1000).then(() => {
|
|
553
|
+
Countly.add_event({ key: "random", count: 1 });
|
|
554
|
+
Countly.add_event({ key: "journey_2", count: 1 });
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
// Wait for processing
|
|
558
|
+
cy.wait(4000).then(() => {
|
|
559
|
+
var requests = fakeServer.requests;
|
|
560
|
+
cy.log(requests);
|
|
561
|
+
// Expected: hc + sc + 2 journey_events + 1 content = 5
|
|
562
|
+
expect(requests.length).to.equal(5);
|
|
563
|
+
const healthReq = requests.find((r) => r.functionName.includes("[healthCheck]"));
|
|
564
|
+
const sbsReq = requests.find((r) => r.functionName.includes("server_config"));
|
|
565
|
+
const journeyReq = requests.find((r) => r.functionName.includes("journey_trigger_send_request"));
|
|
566
|
+
const journeyReqs = requests.filter((r) => r.functionName.includes("journey_trigger_send_request"));
|
|
567
|
+
expect(journeyReqs.length).to.equal(2);
|
|
568
|
+
const contentReqs = requests.filter((r) => r.functionName.includes("sendContentRequest"));
|
|
569
|
+
expect(healthReq).to.exist;
|
|
570
|
+
expect(sbsReq).to.exist;
|
|
571
|
+
expect(journeyReq).to.exist;
|
|
572
|
+
expect(contentReqs.length).to.equal(1); // only 1 content request for 2 journey events
|
|
573
|
+
expect(requests[3]).to.equal(contentReqs[0]); // one before last request
|
|
574
|
+
expect(requests[4]).to.equal(journeyReqs[1]); // last request
|
|
575
|
+
});
|
|
576
|
+
});
|
|
577
|
+
});
|
|
578
|
+
});
|