Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plenty of fixes (Rating, offline mode and 24.11 update) #17

Merged
merged 2 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading