Skip to content

Commit 5dfe7c7

Browse files
authored
Merge pull request #17 from Countly/4.2-fixes
Plenty of fixes (Rating, offline mode and 24.11 update)
2 parents cf5c572 + 450c9fe commit 5dfe7c7

File tree

11 files changed

+2048
-6123
lines changed

11 files changed

+2048
-6123
lines changed

CHANGELOG.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,24 @@
1+
## 24.11.0
2+
3+
* Mitigated an issue where SDK could try to send old stored offline mode data during init if `clear_stored_id` was true
4+
* Mitigated an issue where the SDK could stayed on offline mode after the first init with `offline_mode` set to true
5+
* Mitigated an issue where old Rating widget stickers were not cleared when a new one was presented
6+
7+
* Improved view tracking logic
8+
* Default request method is now set to "POST"
9+
* Healtchecks won't be sent in offline mode anymore
10+
* Added a new interface 'feedback' which includes convenience methods to show feedback widgets:
11+
* showNPS([String nameIDorTag]) - for displaying the first available NPS widget or one with the given name, Tag or ID value
12+
* showSurvey([String nameIDorTag]) - for displaying the first available Survey widget or one with the given name, Tag or ID value
13+
* showRating([String nameIDorTag]) - for displaying the first available Rating widget or one with the given name, Tag or ID value
14+
115
## 24.4.1
216

317
* Added a new method `set_id(newDeviceId)` for managing device id changes according to the device ID Type.
418

519
## 24.4.0
620

7-
! Minor breaking change ! For implementations using `salt` the browser compatability is tied to SubtleCrypto's `digest` method support
21+
! Minor breaking change ! For implementations using `salt` the browser compatibility is tied to SubtleCrypto's `digest` method support
822

923
* Added the `salt` init config flag to add checksums to requests (for secure contexts only)
1024
* Added support for Feedback Widgets' terms and conditions

cypress/e2e/device_id_init_scenarios.cy.js

Lines changed: 112 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,21 @@
88
* | device ID | generated | mode was | device ID | device ID | | not | |
99
* | was set | ID | enabled | provided | enabled | | set | set |
1010
* +--------------------------------------------------+------------------------------------+----------------------+
11-
* | First init | - | - | - | 1 | - |
11+
* | First init | - | - | - | 1 | 65 |
1212
* +--------------------------------------------------+------------------------------------+----------------------+
13-
* | First init | x | - | - | 2 | - |
13+
* | First init | x | - | - | 2 | 66 |
1414
* +--------------------------------------------------+------------------------------------+----------------------+
15-
* | First init | - | x | - | 3 | - |
15+
* | First init | - | x | - | 3 | 67 |
1616
* +--------------------------------------------------+------------------------------------+----------------------+
17-
* | First init | - | - | x | 4 | - |
17+
* | First init | - | - | x | 4 | 68 |
1818
* +--------------------------------------------------+------------------------------------+----------------------+
19-
* | First init | x | x | - | 5 | - |
19+
* | First init | x | x | - | 5 | 69 |
2020
* +--------------------------------------------------+------------------------------------+----------------------+
21-
* | First init | x | - | x | 6 | - |
21+
* | First init | x | - | x | 6 | 70 |
2222
* +--------------------------------------------------+------------------------------------+----------------------+
23-
* | First init | - | x | x | 7 | - |
23+
* | First init | - | x | x | 7 | 71 |
2424
* +--------------------------------------------------+------------------------------------+----------------------+
25-
* | First init | x | x | x | 8 | - |
25+
* | First init | x | x | x | 8 | 72 |
2626
* +--------------------------------------------------+------------------------------------+----------------------+
2727
* | x | - | - | - | - | - | 17 | 33 |
2828
* +--------------------------------------------------+------------------------------------+----------------------+
@@ -720,12 +720,12 @@ describe("Device Id tests during first init", ()=>{
720720
initMain("[CLY]_temp_id", false, undefined);
721721
Countly.halt();
722722
initMain(undefined, false, undefined, true);
723-
expect(Countly.get_device_id_type()).to.equal(Countly.DeviceIdType.SDK_GENERATED);
724-
validateSdkGeneratedId(Countly.get_device_id());
725-
validateInternalDeviceIdType(DeviceIdTypeInternalEnumsTest.SDK_GENERATED);
723+
expect(Countly.get_device_id_type()).to.equal(Countly.DeviceIdType.TEMPORARY_ID);
724+
expect(Countly.get_device_id()).to.eq("[CLY]_temp_id");
725+
validateInternalDeviceIdType(DeviceIdTypeInternalEnumsTest.TEMPORARY_ID);
726726
Countly.begin_session();
727727
cy.fetch_local_request_queue().then((eq) => {
728-
checkRequestsForT(eq, DeviceIdTypeInternalEnumsTest.SDK_GENERATED);
728+
checkRequestsForT(eq, DeviceIdTypeInternalEnumsTest.TEMPORARY_ID);
729729
});
730730
});
731731
});
@@ -1080,4 +1080,104 @@ describe("Device Id tests during first init", ()=>{
10801080
});
10811081
});
10821082
});
1083+
1084+
// testing first 8 tests with clear_stored_id flag set to true
1085+
it("65-SDK is initialized without custom device id, without offline mode, without utm device id", () => {
1086+
hp.haltAndClearStorage(() => {
1087+
initMain(undefined, false, undefined, true);
1088+
expect(Countly.get_device_id_type()).to.eq(Countly.DeviceIdType.SDK_GENERATED);
1089+
validateSdkGeneratedId(Countly.get_device_id());
1090+
validateInternalDeviceIdType(DeviceIdTypeInternalEnumsTest.SDK_GENERATED);
1091+
Countly.begin_session();
1092+
cy.fetch_local_request_queue().then((eq) => {
1093+
checkRequestsForT(eq, DeviceIdTypeInternalEnumsTest.SDK_GENERATED);
1094+
});
1095+
});
1096+
});
1097+
// we provide device id information sdk should use it
1098+
it("66-SDK is initialized with custom device id, without offline mode, without utm device id", () => {
1099+
hp.haltAndClearStorage(() => {
1100+
initMain("gerwutztreimer", false, undefined, true);
1101+
expect(Countly.get_device_id_type()).to.equal(Countly.DeviceIdType.DEVELOPER_SUPPLIED);
1102+
expect(Countly.get_device_id()).to.eq("gerwutztreimer");
1103+
validateInternalDeviceIdType(DeviceIdTypeInternalEnumsTest.DEVELOPER_SUPPLIED);
1104+
Countly.begin_session();
1105+
cy.fetch_local_request_queue().then((eq) => {
1106+
checkRequestsForT(eq, DeviceIdTypeInternalEnumsTest.DEVELOPER_SUPPLIED);
1107+
});
1108+
});
1109+
});
1110+
// we provide no device id information sdk should generate the id
1111+
it("67-SDK is initialized without custom device id, with offline mode, without utm device id", () => {
1112+
hp.haltAndClearStorage(() => {
1113+
initMain(undefined, true, undefined, true);
1114+
expect(Countly.get_device_id_type()).to.equal(Countly.DeviceIdType.TEMPORARY_ID);
1115+
expect(Countly.get_device_id()).to.eq("[CLY]_temp_id");
1116+
validateInternalDeviceIdType(DeviceIdTypeInternalEnumsTest.TEMPORARY_ID);
1117+
Countly.begin_session();
1118+
cy.fetch_local_request_queue().then((eq) => {
1119+
checkRequestsForT(eq, DeviceIdTypeInternalEnumsTest.TEMPORARY_ID);
1120+
});
1121+
});
1122+
});
1123+
it("68-SDK is initialized without custom device id, without offline mode, with utm device id", () => {
1124+
hp.haltAndClearStorage(() => {
1125+
initMain(undefined, false, "?cly_device_id=abab", true);
1126+
expect(Countly.get_device_id_type()).to.equal(Countly.DeviceIdType.DEVELOPER_SUPPLIED);
1127+
expect(Countly.get_device_id()).to.eq("abab");
1128+
validateInternalDeviceIdType(DeviceIdTypeInternalEnumsTest.URL_PROVIDED);
1129+
Countly.begin_session();
1130+
cy.fetch_local_request_queue().then((eq) => {
1131+
checkRequestsForT(eq, DeviceIdTypeInternalEnumsTest.URL_PROVIDED);
1132+
});
1133+
});
1134+
});
1135+
it("69-SDK is initialized with custom device id, with offline mode, without utm device id", () => {
1136+
hp.haltAndClearStorage(() => {
1137+
initMain("customID", true, undefined, true);
1138+
expect(Countly.get_device_id_type()).to.equal(Countly.DeviceIdType.DEVELOPER_SUPPLIED);
1139+
expect(Countly.get_device_id()).to.eq("customID");
1140+
validateInternalDeviceIdType(DeviceIdTypeInternalEnumsTest.DEVELOPER_SUPPLIED);
1141+
Countly.begin_session();
1142+
cy.fetch_local_request_queue().then((eq) => {
1143+
checkRequestsForT(eq, DeviceIdTypeInternalEnumsTest.DEVELOPER_SUPPLIED);
1144+
});
1145+
});
1146+
});
1147+
it("70-SDK is initialized with custom device id, without offline mode, with utm device id", () => {
1148+
hp.haltAndClearStorage(() => {
1149+
initMain("customID2", false, "?cly_device_id=someID", true);
1150+
expect(Countly.get_device_id_type()).to.equal(Countly.DeviceIdType.DEVELOPER_SUPPLIED);
1151+
expect(Countly.get_device_id()).to.eq("someID");
1152+
validateInternalDeviceIdType(DeviceIdTypeInternalEnumsTest.URL_PROVIDED);
1153+
Countly.begin_session();
1154+
cy.fetch_local_request_queue().then((eq) => {
1155+
checkRequestsForT(eq, DeviceIdTypeInternalEnumsTest.URL_PROVIDED);
1156+
});
1157+
});
1158+
});
1159+
it("71-SDK is initialized without custom device id, with offline mode, with utm device id", () => {
1160+
hp.haltAndClearStorage(() => {
1161+
initMain(undefined, true, "?cly_device_id=someID", true);
1162+
expect(Countly.get_device_id_type()).to.equal(Countly.DeviceIdType.DEVELOPER_SUPPLIED);
1163+
expect(Countly.get_device_id()).to.eq("someID");
1164+
validateInternalDeviceIdType(DeviceIdTypeInternalEnumsTest.URL_PROVIDED);
1165+
Countly.begin_session();
1166+
cy.fetch_local_request_queue().then((eq) => {
1167+
checkRequestsForT(eq, DeviceIdTypeInternalEnumsTest.URL_PROVIDED);
1168+
});
1169+
});
1170+
});
1171+
it("72-SDK is initialized with custom device id, with offline mode, with utm device id", () => {
1172+
hp.haltAndClearStorage(() => {
1173+
initMain("customID3", true, "?cly_device_id=someID2", true);
1174+
expect(Countly.get_device_id_type()).to.equal(Countly.DeviceIdType.DEVELOPER_SUPPLIED);
1175+
expect(Countly.get_device_id()).to.eq("someID2");
1176+
validateInternalDeviceIdType(DeviceIdTypeInternalEnumsTest.URL_PROVIDED);
1177+
Countly.begin_session();
1178+
cy.fetch_local_request_queue().then((eq) => {
1179+
checkRequestsForT(eq, DeviceIdTypeInternalEnumsTest.URL_PROVIDED);
1180+
});
1181+
});
1182+
});
10831183
});

cypress/e2e/health_check.cy.js

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,28 +10,42 @@ function initMain() {
1010
});
1111
}
1212

13-
describe("Health Check tests ", () => {
13+
describe("Health Check tests", () => {
1414
it("Check if health check is sent at the beginning", () => {
1515
hp.haltAndClearStorage(() => {
1616
initMain();
17-
cy.intercept("https://your.domain.count.ly/i?*").as("getXhr");
18-
cy.wait("@getXhr").then((xhr) => {
19-
const url = new URL(xhr.request.url);
17+
cy.intercept("POST", "https://your.domain.count.ly/i").as("postXhr");
18+
cy.wait("@postXhr").then((xhr) => {
19+
const body = xhr.request.body;
20+
const params = new URLSearchParams(body);
2021

21-
// Test the 'hc' parameter
22-
const hcParam = url.searchParams.get("hc");
23-
const hcParamObj = JSON.parse(hcParam);
22+
const hcParam = params.get("hc");
23+
const hcParamObj = JSON.parse(decodeURIComponent(hcParam));
2424
expect(hcParamObj).to.eql({ el: 0, wl: 0, sc: -1, em: "" });
2525

26-
// Test the 'metrics' parameter
27-
const metricsParam = url.searchParams.get("metrics");
26+
const metricsParam = params.get("metrics");
2827
expect(metricsParam).to.equal("{\"_app_version\":\"0.0\",\"_ua\":\"abcd\"}");
2928

30-
// check nothing in the request queue
3129
cy.fetch_local_request_queue().then((rq) => {
3230
expect(rq.length).to.equal(0);
3331
});
3432
});
3533
});
3634
});
35+
it("Check no health check is sent in offline mode", () => {
36+
hp.haltAndClearStorage(() => {
37+
Countly.init({
38+
app_key: "YOUR_APP_KEY",
39+
url: "https://your.domain.count.ly",
40+
test_mode: true,
41+
offline_mode: true
42+
});
43+
44+
cy.intercept("POST", "https://your.domain.count.ly/i").as("postXhr");
45+
cy.get('@postXhr').should('not.exist');
46+
cy.fetch_local_request_queue().then((rq) => {
47+
expect(rq.length).to.equal(0);
48+
});
49+
});
50+
});
3751
});

cypress/e2e/remaining_requests.cy.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ describe("Remaining requests tests ", () => {
1818
initMain(false);
1919

2020
// We will expect 4 requests: health check, begin_session, end_session, orientation
21-
hp.interceptAndCheckRequests(undefined, undefined, undefined, "?hc=*", "hc", (requestParams) => {
21+
hp.interceptAndCheckRequests("POST", undefined, undefined, "?hc=*", "hc", (requestParams) => {
2222
const params = JSON.parse(requestParams.get("hc"));
2323
assert.isTrue(typeof params.el === "number");
2424
assert.isTrue(typeof params.wl === "number");
@@ -29,19 +29,19 @@ describe("Remaining requests tests ", () => {
2929
cy.wait(1000).then(() => {
3030
// Create a session
3131
Countly.begin_session();
32-
hp.interceptAndCheckRequests(undefined, undefined, undefined, "?begin_session=*", "begin_session", (requestParams) => {
32+
hp.interceptAndCheckRequests("POST", undefined, undefined, "?begin_session=*", "begin_session", (requestParams) => {
3333
expect(requestParams.get("begin_session")).to.equal("1");
3434
expect(requestParams.get("rr")).to.equal("3");
3535
expect(requestParams.get("av")).to.equal(av);
3636
});
3737
// End the session
3838
Countly.end_session(undefined, true);
39-
hp.interceptAndCheckRequests(undefined, undefined, undefined, "?end_session=*", "end", (requestParams) => {
39+
hp.interceptAndCheckRequests("POST", undefined, undefined, "?end_session=*", "end", (requestParams) => {
4040
expect(requestParams.get("end_session")).to.equal("1");
4141
expect(requestParams.get("rr")).to.equal("2");
4242
expect(requestParams.get("av")).to.equal(av);
4343
});
44-
hp.interceptAndCheckRequests(undefined, undefined, undefined, undefined, "orientation", (requestParams) => {
44+
hp.interceptAndCheckRequests("POST", undefined, undefined, undefined, "orientation", (requestParams) => {
4545
expect(JSON.parse(requestParams.get("events"))[0].key).to.equal("[CLY]_orientation");
4646
expect(requestParams.get("rr")).to.equal("1");
4747
expect(requestParams.get("av")).to.equal(av);

cypress/support/helper.js

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -68,31 +68,43 @@ var waitFunction = function(startTime, waitTime, waitIncrement, continueCallback
6868
};
6969

7070
/**
71-
* This intercepts the request the SDK makes and returns the request parameters to the callback function
72-
* @param {String} requestType - GET, POST, PUT, DELETE
73-
* @param {String} requestUrl - request url (https://your.domain.count.ly)
74-
* @param {String} endPoint - endpoint (/i)
75-
* @param {String} requestParams - request parameters (?begin_session=**)
71+
* Intercepts SDK requests and returns request parameters to the callback function.
72+
* @param {String} requestType - GET or POST
73+
* @param {String} requestUrl - base URL (e.g., https://your.domain.count.ly)
74+
* @param {String} endPoint - endpoint (e.g., /i)
75+
* @param {String} aliasParam - parameter to match in requests (e.g., "hc", "begin_session")
7676
* @param {String} alias - alias for the request
77-
* @param {Function} callback - callback function
77+
* @param {Function} callback - callback function for parsed parameters
7878
*/
79-
function interceptAndCheckRequests(requestType, requestUrl, endPoint, requestParams, alias, callback) {
79+
function interceptAndCheckRequests(requestType, requestUrl, endPoint, aliasParam, alias, callback) {
8080
requestType = requestType || "GET";
81-
requestUrl = requestUrl || "https://your.domain.count.ly"; // TODO: might be needed in the future but not yet
81+
requestUrl = requestUrl || "https://your.domain.count.ly";
8282
endPoint = endPoint || "/i";
83-
requestParams = requestParams || "?*";
8483
alias = alias || "getXhr";
8584

86-
cy.intercept(requestUrl + endPoint + requestParams, (req) => {
87-
const { url } = req;
88-
req.reply(200, {result: "Success"}, {
85+
// Intercept requests
86+
cy.intercept(requestType, requestUrl + endPoint + "*", (req) => {
87+
if (requestType === "POST" && req.body) {
88+
// Parse URL-encoded body for POST requests
89+
const params = new URLSearchParams(req.body);
90+
callback(params);
91+
} else {
92+
// Parse URL parameters for GET requests
93+
const url = new URL(req.url);
94+
const params = url.searchParams;
95+
callback(params);
96+
}
97+
req.reply(200, { result: "Success" }, {
8998
"x-countly-rr": "2"
9099
});
91100
}).as(alias);
101+
102+
// Wait for the request alias to be triggered
92103
cy.wait("@" + alias).then((xhr) => {
93-
const url = new URL(xhr.request.url);
94-
const searchParams = url.searchParams;
95-
callback(searchParams);
104+
const params = requestType === "POST" && xhr.request.body
105+
? new URLSearchParams(xhr.request.body)
106+
: new URL(xhr.request.url).searchParams;
107+
callback(params);
96108
});
97109
}
98110

examples/Angular/main.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
import { enableProdMode } from '@angular/core';
2-
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3-
4-
import { AppModule } from './app/app.module';
5-
import { environment } from './environments/environment';
1+
import { bootstrapApplication } from '@angular/platform-browser';
2+
import { appConfig } from './app/app.config';
3+
import { AppComponent } from './app/app.component';
64

75
import Countly from 'countly-sdk-web';
86

@@ -22,10 +20,6 @@ Countly.init({
2220
});
2321
Countly.track_sessions();
2422

25-
if (environment.production) {
26-
enableProdMode();
27-
28-
}
23+
bootstrapApplication(AppComponent, appConfig)
24+
.catch((err) => console.error(err));
2925

30-
platformBrowserDynamic().bootstrapModule(AppModule)
31-
.catch(err => console.error(err));

0 commit comments

Comments
 (0)