Skip to content

Commit

Permalink
Merge pull request #17 from Countly/4.2-fixes
Browse files Browse the repository at this point in the history
Plenty of fixes (Rating, offline mode and 24.11 update)
  • Loading branch information
turtledreams authored Nov 12, 2024
2 parents cf5c572 + 450c9fe commit 5dfe7c7
Show file tree
Hide file tree
Showing 11 changed files with 2,048 additions and 6,123 deletions.
16 changes: 15 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
## 24.11.0

* Mitigated an issue where SDK could try to send old stored offline mode data during init if `clear_stored_id` was true
* Mitigated an issue where the SDK could stayed on offline mode after the first init with `offline_mode` set to true
* Mitigated an issue where old Rating widget stickers were not cleared when a new one was presented

* Improved view tracking logic
* Default request method is now set to "POST"
* Healtchecks won't be sent in offline mode anymore
* Added a new interface 'feedback' which includes convenience methods to show feedback widgets:
* showNPS([String nameIDorTag]) - for displaying the first available NPS widget or one with the given name, Tag or ID value
* showSurvey([String nameIDorTag]) - for displaying the first available Survey widget or one with the given name, Tag or ID value
* showRating([String nameIDorTag]) - for displaying the first available Rating widget or one with the given name, Tag or ID value

## 24.4.1

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

## 24.4.0

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

* Added the `salt` init config flag to add checksums to requests (for secure contexts only)
* Added support for Feedback Widgets' terms and conditions
Expand Down
124 changes: 112 additions & 12 deletions cypress/e2e/device_id_init_scenarios.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,21 @@
* | device ID | generated | mode was | device ID | device ID | | not | |
* | was set | ID | enabled | provided | enabled | | set | set |
* +--------------------------------------------------+------------------------------------+----------------------+
* | First init | - | - | - | 1 | - |
* | First init | - | - | - | 1 | 65 |
* +--------------------------------------------------+------------------------------------+----------------------+
* | First init | x | - | - | 2 | - |
* | First init | x | - | - | 2 | 66 |
* +--------------------------------------------------+------------------------------------+----------------------+
* | First init | - | x | - | 3 | - |
* | First init | - | x | - | 3 | 67 |
* +--------------------------------------------------+------------------------------------+----------------------+
* | First init | - | - | x | 4 | - |
* | First init | - | - | x | 4 | 68 |
* +--------------------------------------------------+------------------------------------+----------------------+
* | First init | x | x | - | 5 | - |
* | First init | x | x | - | 5 | 69 |
* +--------------------------------------------------+------------------------------------+----------------------+
* | First init | x | - | x | 6 | - |
* | First init | x | - | x | 6 | 70 |
* +--------------------------------------------------+------------------------------------+----------------------+
* | First init | - | x | x | 7 | - |
* | First init | - | x | x | 7 | 71 |
* +--------------------------------------------------+------------------------------------+----------------------+
* | First init | x | x | x | 8 | - |
* | First init | x | x | x | 8 | 72 |
* +--------------------------------------------------+------------------------------------+----------------------+
* | x | - | - | - | - | - | 17 | 33 |
* +--------------------------------------------------+------------------------------------+----------------------+
Expand Down Expand Up @@ -720,12 +720,12 @@ describe("Device Id tests during first init", ()=>{
initMain("[CLY]_temp_id", false, undefined);
Countly.halt();
initMain(undefined, false, undefined, true);
expect(Countly.get_device_id_type()).to.equal(Countly.DeviceIdType.SDK_GENERATED);
validateSdkGeneratedId(Countly.get_device_id());
validateInternalDeviceIdType(DeviceIdTypeInternalEnumsTest.SDK_GENERATED);
expect(Countly.get_device_id_type()).to.equal(Countly.DeviceIdType.TEMPORARY_ID);
expect(Countly.get_device_id()).to.eq("[CLY]_temp_id");
validateInternalDeviceIdType(DeviceIdTypeInternalEnumsTest.TEMPORARY_ID);
Countly.begin_session();
cy.fetch_local_request_queue().then((eq) => {
checkRequestsForT(eq, DeviceIdTypeInternalEnumsTest.SDK_GENERATED);
checkRequestsForT(eq, DeviceIdTypeInternalEnumsTest.TEMPORARY_ID);
});
});
});
Expand Down Expand Up @@ -1080,4 +1080,104 @@ describe("Device Id tests during first init", ()=>{
});
});
});

// testing first 8 tests with clear_stored_id flag set to true
it("65-SDK is initialized without custom device id, without offline mode, without utm device id", () => {
hp.haltAndClearStorage(() => {
initMain(undefined, false, undefined, true);
expect(Countly.get_device_id_type()).to.eq(Countly.DeviceIdType.SDK_GENERATED);
validateSdkGeneratedId(Countly.get_device_id());
validateInternalDeviceIdType(DeviceIdTypeInternalEnumsTest.SDK_GENERATED);
Countly.begin_session();
cy.fetch_local_request_queue().then((eq) => {
checkRequestsForT(eq, DeviceIdTypeInternalEnumsTest.SDK_GENERATED);
});
});
});
// we provide device id information sdk should use it
it("66-SDK is initialized with custom device id, without offline mode, without utm device id", () => {
hp.haltAndClearStorage(() => {
initMain("gerwutztreimer", false, undefined, true);
expect(Countly.get_device_id_type()).to.equal(Countly.DeviceIdType.DEVELOPER_SUPPLIED);
expect(Countly.get_device_id()).to.eq("gerwutztreimer");
validateInternalDeviceIdType(DeviceIdTypeInternalEnumsTest.DEVELOPER_SUPPLIED);
Countly.begin_session();
cy.fetch_local_request_queue().then((eq) => {
checkRequestsForT(eq, DeviceIdTypeInternalEnumsTest.DEVELOPER_SUPPLIED);
});
});
});
// we provide no device id information sdk should generate the id
it("67-SDK is initialized without custom device id, with offline mode, without utm device id", () => {
hp.haltAndClearStorage(() => {
initMain(undefined, true, undefined, true);
expect(Countly.get_device_id_type()).to.equal(Countly.DeviceIdType.TEMPORARY_ID);
expect(Countly.get_device_id()).to.eq("[CLY]_temp_id");
validateInternalDeviceIdType(DeviceIdTypeInternalEnumsTest.TEMPORARY_ID);
Countly.begin_session();
cy.fetch_local_request_queue().then((eq) => {
checkRequestsForT(eq, DeviceIdTypeInternalEnumsTest.TEMPORARY_ID);
});
});
});
it("68-SDK is initialized without custom device id, without offline mode, with utm device id", () => {
hp.haltAndClearStorage(() => {
initMain(undefined, false, "?cly_device_id=abab", true);
expect(Countly.get_device_id_type()).to.equal(Countly.DeviceIdType.DEVELOPER_SUPPLIED);
expect(Countly.get_device_id()).to.eq("abab");
validateInternalDeviceIdType(DeviceIdTypeInternalEnumsTest.URL_PROVIDED);
Countly.begin_session();
cy.fetch_local_request_queue().then((eq) => {
checkRequestsForT(eq, DeviceIdTypeInternalEnumsTest.URL_PROVIDED);
});
});
});
it("69-SDK is initialized with custom device id, with offline mode, without utm device id", () => {
hp.haltAndClearStorage(() => {
initMain("customID", true, undefined, true);
expect(Countly.get_device_id_type()).to.equal(Countly.DeviceIdType.DEVELOPER_SUPPLIED);
expect(Countly.get_device_id()).to.eq("customID");
validateInternalDeviceIdType(DeviceIdTypeInternalEnumsTest.DEVELOPER_SUPPLIED);
Countly.begin_session();
cy.fetch_local_request_queue().then((eq) => {
checkRequestsForT(eq, DeviceIdTypeInternalEnumsTest.DEVELOPER_SUPPLIED);
});
});
});
it("70-SDK is initialized with custom device id, without offline mode, with utm device id", () => {
hp.haltAndClearStorage(() => {
initMain("customID2", false, "?cly_device_id=someID", true);
expect(Countly.get_device_id_type()).to.equal(Countly.DeviceIdType.DEVELOPER_SUPPLIED);
expect(Countly.get_device_id()).to.eq("someID");
validateInternalDeviceIdType(DeviceIdTypeInternalEnumsTest.URL_PROVIDED);
Countly.begin_session();
cy.fetch_local_request_queue().then((eq) => {
checkRequestsForT(eq, DeviceIdTypeInternalEnumsTest.URL_PROVIDED);
});
});
});
it("71-SDK is initialized without custom device id, with offline mode, with utm device id", () => {
hp.haltAndClearStorage(() => {
initMain(undefined, true, "?cly_device_id=someID", true);
expect(Countly.get_device_id_type()).to.equal(Countly.DeviceIdType.DEVELOPER_SUPPLIED);
expect(Countly.get_device_id()).to.eq("someID");
validateInternalDeviceIdType(DeviceIdTypeInternalEnumsTest.URL_PROVIDED);
Countly.begin_session();
cy.fetch_local_request_queue().then((eq) => {
checkRequestsForT(eq, DeviceIdTypeInternalEnumsTest.URL_PROVIDED);
});
});
});
it("72-SDK is initialized with custom device id, with offline mode, with utm device id", () => {
hp.haltAndClearStorage(() => {
initMain("customID3", true, "?cly_device_id=someID2", true);
expect(Countly.get_device_id_type()).to.equal(Countly.DeviceIdType.DEVELOPER_SUPPLIED);
expect(Countly.get_device_id()).to.eq("someID2");
validateInternalDeviceIdType(DeviceIdTypeInternalEnumsTest.URL_PROVIDED);
Countly.begin_session();
cy.fetch_local_request_queue().then((eq) => {
checkRequestsForT(eq, DeviceIdTypeInternalEnumsTest.URL_PROVIDED);
});
});
});
});
34 changes: 24 additions & 10 deletions cypress/e2e/health_check.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,42 @@ function initMain() {
});
}

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

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

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

// check nothing in the request queue
cy.fetch_local_request_queue().then((rq) => {
expect(rq.length).to.equal(0);
});
});
});
});
it("Check no health check is sent in offline mode", () => {
hp.haltAndClearStorage(() => {
Countly.init({
app_key: "YOUR_APP_KEY",
url: "https://your.domain.count.ly",
test_mode: true,
offline_mode: true
});

cy.intercept("POST", "https://your.domain.count.ly/i").as("postXhr");
cy.get('@postXhr').should('not.exist');
cy.fetch_local_request_queue().then((rq) => {
expect(rq.length).to.equal(0);
});
});
});
});
8 changes: 4 additions & 4 deletions cypress/e2e/remaining_requests.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe("Remaining requests tests ", () => {
initMain(false);

// We will expect 4 requests: health check, begin_session, end_session, orientation
hp.interceptAndCheckRequests(undefined, undefined, undefined, "?hc=*", "hc", (requestParams) => {
hp.interceptAndCheckRequests("POST", undefined, undefined, "?hc=*", "hc", (requestParams) => {
const params = JSON.parse(requestParams.get("hc"));
assert.isTrue(typeof params.el === "number");
assert.isTrue(typeof params.wl === "number");
Expand All @@ -29,19 +29,19 @@ describe("Remaining requests tests ", () => {
cy.wait(1000).then(() => {
// Create a session
Countly.begin_session();
hp.interceptAndCheckRequests(undefined, undefined, undefined, "?begin_session=*", "begin_session", (requestParams) => {
hp.interceptAndCheckRequests("POST", undefined, undefined, "?begin_session=*", "begin_session", (requestParams) => {
expect(requestParams.get("begin_session")).to.equal("1");
expect(requestParams.get("rr")).to.equal("3");
expect(requestParams.get("av")).to.equal(av);
});
// End the session
Countly.end_session(undefined, true);
hp.interceptAndCheckRequests(undefined, undefined, undefined, "?end_session=*", "end", (requestParams) => {
hp.interceptAndCheckRequests("POST", undefined, undefined, "?end_session=*", "end", (requestParams) => {
expect(requestParams.get("end_session")).to.equal("1");
expect(requestParams.get("rr")).to.equal("2");
expect(requestParams.get("av")).to.equal(av);
});
hp.interceptAndCheckRequests(undefined, undefined, undefined, undefined, "orientation", (requestParams) => {
hp.interceptAndCheckRequests("POST", undefined, undefined, undefined, "orientation", (requestParams) => {
expect(JSON.parse(requestParams.get("events"))[0].key).to.equal("[CLY]_orientation");
expect(requestParams.get("rr")).to.equal("1");
expect(requestParams.get("av")).to.equal(av);
Expand Down
42 changes: 27 additions & 15 deletions cypress/support/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,31 +68,43 @@ var waitFunction = function(startTime, waitTime, waitIncrement, continueCallback
};

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

cy.intercept(requestUrl + endPoint + requestParams, (req) => {
const { url } = req;
req.reply(200, {result: "Success"}, {
// Intercept requests
cy.intercept(requestType, requestUrl + endPoint + "*", (req) => {
if (requestType === "POST" && req.body) {
// Parse URL-encoded body for POST requests
const params = new URLSearchParams(req.body);
callback(params);
} else {
// Parse URL parameters for GET requests
const url = new URL(req.url);
const params = url.searchParams;
callback(params);
}
req.reply(200, { result: "Success" }, {
"x-countly-rr": "2"
});
}).as(alias);

// Wait for the request alias to be triggered
cy.wait("@" + alias).then((xhr) => {
const url = new URL(xhr.request.url);
const searchParams = url.searchParams;
callback(searchParams);
const params = requestType === "POST" && xhr.request.body
? new URLSearchParams(xhr.request.body)
: new URL(xhr.request.url).searchParams;
callback(params);
});
}

Expand Down
16 changes: 5 additions & 11 deletions examples/Angular/main.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';

import Countly from 'countly-sdk-web';

Expand All @@ -22,10 +20,6 @@ Countly.init({
});
Countly.track_sessions();

if (environment.production) {
enableProdMode();

}
bootstrapApplication(AppComponent, appConfig)
.catch((err) => console.error(err));

platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));
Loading

0 comments on commit 5dfe7c7

Please sign in to comment.