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 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
+ });
@@ -15,7 +15,7 @@ function initMain(name, version) {
15
15
  }
16
16
 
17
17
  const SDK_NAME = "javascript_native_web";
18
- const SDK_VERSION = "25.4.5";
18
+ const SDK_VERSION = "26.1.1";
19
19
 
20
20
  // tests
21
21
  describe("Bridged SDK Utilities Tests", () => {