Skip to content

Commit 17a0b47

Browse files
authored
Merge pull request #42 from Countly/content_filter
feat: content filter callback
2 parents 0e23180 + e64b4c4 commit 17a0b47

File tree

9 files changed

+2420
-2478
lines changed

9 files changed

+2420
-2478
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
## X.X.X
1+
## 25.4.3
2+
3+
* Added filtering capability to `content` interface through `enterContentZone(contentFilterCallback)`.
4+
5+
## 25.4.2
26

37
* Mitigated an issue where manual feedback reporting could have failed
48
* Mitigated a possible issue with request timeouts in IE11

cypress.config.js

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,32 @@ export default defineConfig({
3333
req.on('data', chunk => { body += chunk; });
3434
req.on('end', () => {
3535
requests.push({ method: req.method, url: req.url, headers: req.headers, body });
36+
37+
let payload = { result: 'Success' };
38+
39+
if (req.url && req.url.indexOf('/o/sdk/content') !== -1) {
40+
payload = {
41+
html: 'http://test/_external/content?id=111&uid=tester&app_id=222',
42+
geo: {
43+
l: {
44+
x: 282,
45+
y: 56,
46+
w: 303,
47+
h: 300
48+
},
49+
p: {
50+
x: 62,
51+
y: 325.5,
52+
w: 288,
53+
h: 216
54+
}
55+
}
56+
};
57+
}
58+
3659
setTimeout(() => {
3760
res.writeHead(200, { 'Content-Type': 'application/json' });
38-
res.end(JSON.stringify({ result: 'Success' }));
61+
res.end(JSON.stringify(payload));
3962
}, responseDelay);
4063
});
4164
});

cypress/e2e/content_filter.cy.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/* eslint-disable require-jsdoc */
2+
const Countly = require("../../Countly.js");
3+
const hp = require("../support/helper");
4+
5+
function initMain() {
6+
Countly.init({
7+
app_key: "YOUR_APP_KEY",
8+
url: "http://localhost:9000",
9+
debug: true
10+
});
11+
}
12+
13+
describe("Content Filter Tests", () => {
14+
beforeEach(() => {
15+
cy.wait(1000);
16+
});
17+
18+
afterEach(() => {
19+
cy.task("stopServer");
20+
});
21+
22+
it("Correctly provides params", () => {
23+
hp.haltAndClearStorage(() => {
24+
cy.task("setResponseDelay", 0);
25+
cy.task("startServer");
26+
const filterParams = [];
27+
initMain();
28+
29+
function filter(params) {
30+
filterParams.push(params);
31+
return false;
32+
}
33+
34+
Countly.content.enterContentZone(filter);
35+
36+
cy.wait(5000).then(() => {
37+
expect(filterParams.length).to.be.greaterThan(0);
38+
expect(filterParams[0]).to.have.property("id");
39+
expect(filterParams[0]["id"]).to.equal("111");
40+
expect(filterParams[0]).to.have.property("uid");
41+
expect(filterParams[0]["uid"]).to.equal("tester");
42+
expect(filterParams[0]).to.have.property("app_id");
43+
expect(filterParams[0]["app_id"]).to.equal("222");
44+
cy.task("stopServer");
45+
});
46+
});
47+
});
48+
it("Correctly provides params_async", () => {
49+
hp.haltAndClearStorage(() => {
50+
cy.task("setResponseDelay", 0);
51+
cy.task("startServer");
52+
const filterParams = [];
53+
Countly.q = Countly.q || [];
54+
initMain();
55+
56+
function filter(params) {
57+
filterParams.push(params);
58+
return false;
59+
}
60+
61+
Countly.q.push(["content.enterContentZone", filter]);
62+
63+
cy.wait(5000).then(() => {
64+
expect(filterParams.length).to.be.greaterThan(0);
65+
expect(filterParams[0]).to.have.property("id");
66+
expect(filterParams[0]["id"]).to.equal("111");
67+
expect(filterParams[0]).to.have.property("uid");
68+
expect(filterParams[0]["uid"]).to.equal("tester");
69+
expect(filterParams[0]).to.have.property("app_id");
70+
expect(filterParams[0]["app_id"]).to.equal("222");
71+
cy.task("stopServer");
72+
});
73+
});
74+
});
75+
});

examples/example_async.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,17 @@
2424
//track sessions automatically
2525
Countly.q.push(['track_sessions']);
2626

27+
//content filter example
28+
function filter(params) {
29+
var shouldContentBeShown = true;
30+
console.log("Content filter called with params:", params);
31+
// Add your content filtering logic here and determine whether to show content
32+
return shouldContentBeShown;
33+
}
34+
35+
//enter content zone with filter
36+
Countly.q.push(['content.enterContentZone', filter]);
37+
2738
//track performance automatically
2839
Countly.q.push(["track_performance"]);
2940

examples/example_sync.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,17 @@
2727
Countly.track_pageview();
2828
Countly.track_errors();
2929

30+
//content filter example
31+
function filter(params) {
32+
var shouldContentBeShown = true;
33+
console.log("Content filter called with params:", params);
34+
// Add your content filtering logic here and determine whether to show content
35+
return shouldContentBeShown;
36+
}
37+
38+
//enter content zone with filter
39+
Countly.content.enterContentZone(filter);
40+
3041
document.getElementById("testButton").addEventListener("click", function() {
3142
Countly.add_event({
3243
key: "buttonClick",

modules/Constants.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ var healthCheckCounterEnum = Object.freeze({
106106
consecutiveBackoffCount: "cly_hc_consecutive_backoff_count",
107107
});
108108

109-
var SDK_VERSION = "25.4.1";
109+
var SDK_VERSION = "25.4.3";
110110
var SDK_NAME = "javascript_native_web";
111111

112112
// Using this on document.referrer would return an array with 17 elements in it. The 12th element (array[11]) would be the path we are looking for. Others would be things like password and such (use https://regex101.com/ to check more)

modules/CountlyClass.js

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ class CountlyClass {
125125
#lastRequestWasBackoff;
126126
#testModeTime;
127127
#requestTimeoutDuration;
128+
#contentFilterCallback;
128129
constructor(ob) {
129130
this.#self = this;
130131
this.#global = !Countly.i;
@@ -196,6 +197,7 @@ class CountlyClass {
196197
this.#SCBackoffRequestAge = 24; // 24 hours
197198
this.#SCBackoffDuration = 60; // 60 seconds
198199
this.#requestTimeoutDuration = 30000; // 30 seconds
200+
this.#contentFilterCallback = null;
199201
this.app_key = getConfig("app_key", ob, null);
200202
this.url = stripTrailingSlash(getConfig("url", ob, ""));
201203
this.serialize = getConfig("serialize", ob, Countly.serialize);
@@ -3972,8 +3974,8 @@ class CountlyClass {
39723974
}
39733975

39743976
content = {
3975-
enterContentZone: () => {
3976-
this.#enterContentZoneInternal();
3977+
enterContentZone: (filter_callback) => {
3978+
this.#enterContentZoneInternal(false, filter_callback);
39773979
},
39783980
refreshContentZone: () => {
39793981
this.#refreshContentZoneInternal();
@@ -3983,7 +3985,7 @@ class CountlyClass {
39833985
},
39843986
};
39853987

3986-
#enterContentZoneInternal = (forced) => {
3988+
#enterContentZoneInternal = (forced, filter_callback) => {
39873989
if (!isBrowser) {
39883990
this.#log(logLevelEnums.WARNING, "content.enterContentZone, window object is not available. Not entering content zone.");
39893991
return;
@@ -3992,11 +3994,16 @@ class CountlyClass {
39923994
this.#log(logLevelEnums.DEBUG, "content.enterContentZone, Already in content zone");
39933995
return;
39943996
}
3997+
if (filter_callback && typeof filter_callback == "function") {
3998+
this.#log(logLevelEnums.DEBUG, "content.enterContentZone, Content filter callback is provided");
3999+
this.#contentFilterCallback = filter_callback;
4000+
}
4001+
39954002
if (!this.#initTimestamp || (getMsTimestamp() - this.#initTimestamp) < 4000 ) {
39964003
// settimeout
39974004
this.#log(logLevelEnums.DEBUG, "content.enterContentZone, Not enough time passed since initialization");
39984005
setTimeout(() => {
3999-
this.#enterContentZoneInternal();
4006+
this.#enterContentZoneInternal(false);
40004007
}, 4001);
40014008
return;
40024009
}
@@ -4020,7 +4027,7 @@ class CountlyClass {
40204027
this.#processAsyncQueue();
40214028
this.#sendEventsForced();
40224029
setTimeout(() => {
4023-
this.#enterContentZoneInternal();
4030+
this.#enterContentZoneInternal(false);
40244031
}, 1000);
40254032
};
40264033

@@ -4087,6 +4094,22 @@ class CountlyClass {
40874094
return;
40884095
}
40894096

4097+
// Build query params
4098+
const queryParams = {};
4099+
const qIndex = response.html.indexOf("?");
4100+
if (qIndex !== -1) {
4101+
const search = response.html.slice(qIndex + 1);
4102+
new URLSearchParams(search).forEach((v, k) => {
4103+
queryParams[k] = v;
4104+
});
4105+
}
4106+
4107+
// Filter check
4108+
if (this.#contentFilterCallback && this.#contentFilterCallback(queryParams) === false) {
4109+
this.#log(logLevelEnums.VERBOSE, "sendContentRequest, Content was filtered out by the content filter");
4110+
return;
4111+
}
4112+
40904113
this.#displayContent(response);
40914114
clearInterval(this.#contentZoneTimer); // prevent multiple content requests while one is on
40924115
window.addEventListener('message', (event) => {

0 commit comments

Comments
 (0)