Skip to content

Commit a8f0309

Browse files
authored
Merge pull request #595 from Countly/staging
Staging 25.4.2
2 parents 07152c3 + 56b4354 commit a8f0309

13 files changed

+649
-254
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## 25.4.2
2+
3+
- Mitigated an issue where manual feedback reporting could have failed
4+
- Mitigated a possible issue with request timeouts in IE11
5+
- Non window contexts also now uses POST requests by default
6+
- Added a new method `uploadUserProfilePicture` for uploading user profile images to server
7+
18
## 25.4.1
29

310
- Added automatic backoff mechanism which delays sending requests if server seems busy

cypress/e2e/bridged_utils.cy.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ function initMain(name, version) {
1515
}
1616

1717
const SDK_NAME = "javascript_native_web";
18-
const SDK_VERSION = "25.4.1";
18+
const SDK_VERSION = "25.4.2";
1919

2020
// tests
2121
describe("Bridged SDK Utilities Tests", () => {

cypress/e2e/namespace.cy.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/* eslint-disable cypress/no-unnecessary-waiting */
2+
/* eslint-disable require-jsdoc */
3+
var Countly = require("../../lib/countly");
4+
var hp = require("../support/helper");
5+
6+
const c1 = ['sessions', 'events', 'views', 'users', 'scrolls', 'clicks'];
7+
const c2 = ['events', 'users', 'location'];
8+
9+
describe("-", () => {
10+
it("-", () => {
11+
hp.haltAndClearStorage(() => {
12+
Countly.init({
13+
app_key: "YOUR_APP_KEY1",
14+
url: "https://your.domain.count.ly",
15+
namespace: "namespace1",
16+
clear_stored_id: true,
17+
device_id: "test-device-id1",
18+
require_consent: true,
19+
enable_orientation_tracking: false,
20+
});
21+
Countly.add_consent(c1);
22+
Countly.user_details({organization: "Test Organization1", custom: {"Test User1": "Test User1"}});
23+
Countly.track_sessions();
24+
Countly.track_pageview();
25+
Countly.track_clicks();
26+
Countly.track_scrolls();
27+
Countly.add_event({
28+
key: "test_event1",
29+
count: 1,
30+
segmentation: {test: "test1"},
31+
});
32+
33+
Countly.q.push(["init", {
34+
app_key: "YOUR_APP_KEY2", //must have different APP key
35+
url: "https://your.domain.count.ly",
36+
namespace: "namespace2",
37+
clear_stored_id: true,
38+
device_id: "test-device-id2",
39+
require_consent: true,
40+
enable_orientation_tracking: false,
41+
}])
42+
Countly.q.push(["YOUR_APP_KEY2", "add_consent", c2]);
43+
Countly.q.push(["YOUR_APP_KEY2", "user_details", {organization: "Test Organization2", custom: {"Test User2": "Test User2"}}]);
44+
Countly.q.push(["YOUR_APP_KEY2", "track_sessions"]);
45+
Countly.q.push(["YOUR_APP_KEY2", "track_pageview", undefined, undefined, {test: "test2"}]);
46+
Countly.q.push(["YOUR_APP_KEY2", "track_clicks"]);
47+
Countly.q.push(["YOUR_APP_KEY2", "track_scrolls"]);
48+
Countly.q.push(["YOUR_APP_KEY2", "add_event", {
49+
key: "test_event2",
50+
count: 1,
51+
segmentation: {test: "test2"},
52+
}]);
53+
54+
cy.wait(1000).then(() => {
55+
var queues = Countly._internals.getLocalQueues();
56+
var requests = Countly._internals.testingGetRequests();
57+
cy.log(queues);
58+
cy.log(requests);
59+
});
60+
});
61+
});
62+
});

cypress/e2e/salt.cy.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,15 @@ describe("Salt Tests", () => {
6060
hp.check_commons(paramsObject);
6161
expect(paramsObject.checksum256).to.be.ok;
6262
expect(paramsObject.checksum256.length).to.equal(64);
63+
expect(paramsObject.checksum256).to.match(/^[A-F0-9]{64}$/);
6364
// TODO: directly check the checksum with the node crypto api. Will need some extra decoding logic
6465
}
6566
});
6667
});
6768
});
6869
});
6970
it("Node and Web Crypto comparison", () => {
70-
const hash = sha256("text" + salt); // node crypto api
71+
const hash = sha256("text" + salt).toUpperCase(); // node crypto api
7172
Countly._internals.calculateChecksum("text", salt).then((hash2) => { // SDK uses web crypto api
7273
expect(hash2).to.equal(hash);
7374
});
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/* eslint-disable cypress/no-unnecessary-waiting */
2+
/* eslint-disable require-jsdoc */
3+
var Countly = require("../../lib/countly");
4+
var hp = require("../support/helper");
5+
6+
function initMain() {
7+
Countly.init({
8+
app_key: "YOUR_APP_KEY",
9+
url: "https://your.domain.count.ly",
10+
test_mode: true,
11+
test_mode_eq: true,
12+
debug: true
13+
});
14+
}
15+
16+
describe("Upload user profile picture", () => {
17+
it("queues an image upload request when given a File-like object (Blob)", () => {
18+
hp.haltAndClearStorage(() => {
19+
initMain();
20+
21+
// red dot PNG
22+
const base64Png = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4nGNgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII=";
23+
const binary = Cypress.Blob.base64StringToBlob(base64Png, 'image/png');
24+
binary.name = "avatar.png";
25+
Countly.uploadUserProfilePicture(binary);
26+
27+
cy.wait(500).then(() => {
28+
cy.getLocalStorage("YOUR_APP_KEY/cly_queue").then((val) => {
29+
expect(val).to.be.ok;
30+
const queue = JSON.parse(val);
31+
expect(queue.length).to.be.greaterThan(0);
32+
33+
const imgReq = queue.find(r => r.__imageUpload === true || r.imageData);
34+
expect(imgReq).to.be.ok;
35+
expect(imgReq.imageName).to.be.oneOf(["avatar.png", "avatar"]);
36+
expect(imgReq.imageType).to.equal('image/png');
37+
expect(imgReq.imageData).to.be.a('string').and.to.have.length.greaterThan(0);
38+
39+
expect(imgReq.user_details).to.be.a('string');
40+
const ud = JSON.parse(imgReq.user_details);
41+
expect(ud).to.be.an('object');
42+
});
43+
});
44+
});
45+
});
46+
47+
it("rejects non-image files and does not queue requests", () => {
48+
hp.haltAndClearStorage(() => {
49+
initMain();
50+
51+
const txtBlob = new Blob(["hello world"], { type: 'text/plain' });
52+
txtBlob.name = "test.txt";
53+
Countly.uploadUserProfilePicture(txtBlob);
54+
55+
cy.wait(300).then(() => {
56+
cy.getLocalStorage("YOUR_APP_KEY/cly_queue").then((val) => {
57+
if (!val) {
58+
expect(val).to.be.not.ok;
59+
return;
60+
}
61+
const queue = JSON.parse(val || '[]');
62+
const imgReq = queue.find(r => r.__imageUpload === true || r.imageData);
63+
expect(imgReq).to.not.exist;
64+
});
65+
});
66+
});
67+
});
68+
});

examples/example_async.html

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,15 @@
4444
}
4545
}]);
4646
}
47+
function uploadProfilePicture() {
48+
var fileInput = document.getElementById("profilePicInput");
49+
var file = fileInput.files[0];
50+
if (!file) {
51+
alert("Please select an image file.");
52+
return;
53+
}
54+
Countly.q.push(["uploadUserProfilePicture", file]);
55+
}
4756
</script>
4857
</head>
4958

@@ -58,6 +67,11 @@ <h1>Async Countly Implementation</h1>
5867
<img src="./images/team_countly.jpg" id="wallpaper" />
5968
<br />
6069
<input type="button" id="testButton" onclick="clickEvent()" value="Test Button">
70+
<br /><br />
71+
<input type="file" id="profilePicInput" accept="image/*">
72+
<br /><br />
73+
<br />
74+
<input type="button" onclick="uploadProfilePicture()" value="Upload Profile Picture">
6175
<p><a href='https://countly.com/'>Countly</a></p>
6276
</center>
6377
</body>

examples/example_sync.html

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,25 @@
2626
//track pageviews automatically
2727
Countly.track_pageview();
2828
Countly.track_errors();
29+
30+
document.getElementById("testButton").addEventListener("click", function () {
31+
Countly.add_event({
32+
key: "buttonClick",
33+
"segmentation": {
34+
"id": "id"
35+
}
36+
});
37+
});
38+
39+
document.getElementById("uploadBtn").addEventListener("click", function () {
40+
var fileInput = document.getElementById("profilePicInput");
41+
var file = fileInput.files[0];
42+
if (!file) {
43+
alert("Please select an image file.");
44+
return;
45+
}
46+
Countly.uploadUserProfilePicture(file);
47+
});
2948
</script>
3049
</head>
3150

@@ -40,19 +59,12 @@ <h1>Sync Countly Implementation</h1>
4059
<img src="./images/team_countly.jpg" id="wallpaper" />
4160
<br />
4261
<input type="button" id="testButton" onclick="clickEvent()" value="Test Button">
62+
<br /><br />
63+
<input type="file" id="profilePicInput" accept="image/*">
64+
<br /><br />
65+
<input type="button" id="uploadBtn" value="Upload Profile Picture">
4366
<p><a href='https://countly.com/'>Countly</a></p>
4467
</center>
45-
<script type='text/javascript'>
46-
//send event on button click
47-
function clickEvent() {
48-
Countly.add_event({
49-
key: "buttonClick",
50-
"segmentation": {
51-
"id": "id"
52-
}
53-
});
54-
}
55-
</script>
5668
</body>
5769

5870
</html>

examples/examples_feedback_widgets.html

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@
2525
// Displaying feedback widgets
2626
//=================================================
2727
Countly.feedback.showNPS();
28-
28+
2929
// OR with a specific ID, name or tag
3030
// const id = 'ID_from_server';
3131
// Countly.feedback.showNPS(id);
32-
32+
3333
// Other feedback widgets
3434
// Countly.feedback.showSurvey();
3535
// Countly.feedback.showRating();
@@ -39,7 +39,6 @@
3939
// ADVANCED: Fetching and displaying feedback widgets
4040
//=================================================
4141
function fetchAndDisplayWidget() {
42-
// Fetch user's feedbacks widgets from the server (must have been created at server first)
4342
Countly.get_available_feedback_widgets(feedbackWidgetsCallback);
4443
}
4544

@@ -51,7 +50,7 @@
5150
}
5251

5352
// Decide which which widget to show. Here the first rating widget is selected.
54-
const widgetType = "rating";
53+
const widgetType = "survey";
5554
const countlyFeedbackWidget = countlyPresentableFeedback.find(widget => widget.type === widgetType);
5655
if (!countlyFeedbackWidget) {
5756
console.error(`[Countly] No ${widgetType} widget found`);
@@ -89,6 +88,7 @@
8988
console.error(`[Countly] No ${widgetType} widget found`);
9089
return;
9190
}
91+
console.log(`[Countly] Found ${widgetType} widget`);
9292

9393
// Get data with the widget object
9494
Countly.getFeedbackWidgetData(countlyFeedbackWidget,
@@ -100,22 +100,40 @@
100100
}
101101

102102
const CountlyWidgetData = feedbackData;
103+
console.log(`[Countly] Found ${widgetType} widget data`, CountlyWidgetData);
103104
// record data according to the widget type
104105
if (CountlyWidgetData.type === 'nps') {
105-
Countly.reportFeedbackWidgetManually(countlyFeedbackWidget, CountlyWidgetData, { rating: 3, comment: "comment" });
106+
console.log(`[Countly] Recording NPS data manually`);
107+
Countly.reportFeedbackWidgetManually(countlyFeedbackWidget, CountlyWidgetData, { rating: 5, comment: "comment" });
106108
} else if (CountlyWidgetData.type === 'survey') {
109+
console.log(`[Countly] Recording Survey data manually`);
107110
var widgetResponse = {};
108111
// form the key/value pairs according to data
109-
widgetResponse["answ-" + CountlyWidgetData.questions[0].id] = CountlyWidgetData.questions[0].type === "rating" ? 3 : "answer";
112+
widgetResponse["answ-" + CountlyWidgetData.questions[0].id] = CountlyWidgetData.questions[0].type === "rating" ? 5 : "answer";
110113
Countly.reportFeedbackWidgetManually(countlyFeedbackWidget, CountlyWidgetData, widgetResponse);
111114
} else if (CountlyWidgetData.type === 'rating') {
112-
Countly.reportFeedbackWidgetManually(countlyFeedbackWidget, CountlyWidgetData, { rating: 3, comment: "comment", email: "email", contactMe: true });
115+
console.log(`[Countly] Recording Rating data manually`);
116+
Countly.reportFeedbackWidgetManually(countlyFeedbackWidget, CountlyWidgetData, { rating: 5, comment: "comment", email: "email", contactMe: true });
113117
}
114118
}
115119

116120
);
117121
})
118122
}
123+
124+
document.querySelector("#one").addEventListener("click", function () {
125+
fetchAndDisplayWidget();
126+
});
127+
document.querySelector("#two").addEventListener("click", function () {
128+
getFeedbackWidgetListAndDoThings('nps');
129+
});
130+
document.querySelector("#three").addEventListener("click", function () {
131+
getFeedbackWidgetListAndDoThings('survey');
132+
});
133+
document.querySelector("#four").addEventListener("click", function () {
134+
getFeedbackWidgetListAndDoThings('rating');
135+
});
136+
119137
</script>
120138
</head>
121139

@@ -129,10 +147,10 @@ <h1>Feedback Widgets</h1>
129147
<center>
130148
<img src="./images/team_countly.jpg" id="wallpaper" />
131149
<p><a href='https://countly.com/'>Countly</a></p>
132-
<p><button onclick="fetchAndDisplayWidget()">Enable Feedback Widget</button></p>
133-
<p><button onclick="getFeedbackWidgetListAndDoThings('nps')">Manually Record NPS Data</button></p>
134-
<p><button onclick="getFeedbackWidgetListAndDoThings('survey')">Manually Record Survey Data</button></p>
135-
<p><button onclick="getFeedbackWidgetListAndDoThings('rating')">Manually Record Rating Data</button></p>
150+
<p><button id="one">Enable Feedback Widget</button></p>
151+
<p><button id="two">Manually Record NPS Data</button></p>
152+
<p><button id="three">Manually Record Survey Data</button></p>
153+
<p><button id="four">Manually Record Rating Data</button></p>
136154
</center>
137155
</body>
138156

lib/countly.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,12 @@ declare module "countly-sdk-web" {
214214
custom?: { [key: string]: any };
215215
}): void;
216216

217+
/**
218+
* Upload user profile picture (image) to the server.
219+
* @param {File|Blob} imageFile - The image file or blob to upload
220+
*/
221+
function uploadUserProfilePicture(imageFile: File | Blob): void;
222+
217223
namespace userData {
218224
/**
219225
* Sets user's custom property value

0 commit comments

Comments
 (0)