Skip to content

Commit 423d73c

Browse files
Merge pull request #468 from Countly/fix_widdgets
fix: feedback widget auto-close issue
2 parents 63d3222 + 1b87859 commit 423d73c

4 files changed

Lines changed: 45 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## XX.XX.XX
2+
* Mitigated an issue where feedback widgets could be closed automatically after about a minute even while the user was still interacting with them.
3+
14
## 26.1.3
25
* Added a content configuration option to reload the content web view when its load stalls, enabled via "enableContentReloadOnStall" on "CountlyContentConfig", with a configurable stall timeout "setContentReloadOnStallTimeout:" in milliseconds (default 1000).
36
* Added a content configuration option to disable pinch zoom, disabled via "disableZoom".

CountlyTests/CountlyWebViewManager+Tests.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
- (void)cancelPendingReload;
2424
- (void)contentShownDeadlineReached;
2525
- (void)recordEventsWithJSONString:(NSString *)jsonString;
26+
- (BOOL)isFeedbackWidgetURL:(NSURL *)url;
2627

2728
@end
2829
#endif

CountlyTests/CountlyWebViewManagerTests.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,24 @@ class CountlyWebViewManagerTests: XCTestCase {
765765
XCTAssertFalse(timer.isValid)
766766
}
767767

768+
func testIsFeedbackWidgetURL_recognizesFeedbackWidgetURLs() {
769+
// Feedback widget URLs (host/feedback/<type>?...&widget_id=...) must be recognized so the
770+
// content-shown deadline is NOT armed for them (widgets never emit [CLY]_content_shown).
771+
XCTAssertTrue(manager.isFeedbackWidgetURL(URL(string: "https://example.count.ly/feedback/nps?app_key=k&widget_id=abc123")!))
772+
XCTAssertTrue(manager.isFeedbackWidgetURL(URL(string: "https://example.count.ly/feedback/survey?widget_id=x")!))
773+
XCTAssertTrue(manager.isFeedbackWidgetURL(URL(string: "https://example.count.ly/feedback/rating?widget_id=x")!))
774+
// Base-path host still matches (uses a path-segment contains check, not a prefix).
775+
XCTAssertTrue(manager.isFeedbackWidgetURL(URL(string: "https://example.com/base/feedback/nps?widget_id=x")!))
776+
}
777+
778+
func testIsFeedbackWidgetURL_rejectsContentURLs() {
779+
// Content URLs (host/_external/content?...) must NOT be treated as feedback widgets, so
780+
// the content-shown deadline stays armed for real content.
781+
XCTAssertFalse(manager.isFeedbackWidgetURL(URL(string: "https://countly.teb.com.tr/_external/content?app_id=1&id=2&journeyId=3")!))
782+
XCTAssertFalse(manager.isFeedbackWidgetURL(URL(string: "https://example.count.ly/o/sdk/content?method=queue")!))
783+
XCTAssertFalse(manager.isFeedbackWidgetURL(URL(string: "about:blank")!))
784+
}
785+
768786
func testNonContentShownEvent_leavesDeadlineTimerRunning() {
769787
// A different event must NOT cancel the deadline.
770788
let timer = Timer.scheduledTimer(withTimeInterval: 60, repeats: false) { _ in }

CountlyWebViewManager.m

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -181,10 +181,15 @@ - (void)createWebViewWithURL:(NSURL *)url
181181
// Arm the absolute content-shown deadline ONCE (not per navigation, so reloads cannot
182182
// extend it). If [CLY]_content_shown is not reported within kCLYContentShownDeadline, the
183183
// web view is closed regardless of retry/stall/verify state.
184-
__weak typeof(self) weakSelf = self;
185-
self.contentShownDeadlineTimer = [NSTimer scheduledTimerWithTimeInterval:kCLYContentShownDeadline repeats:NO block:^(NSTimer * _Nonnull timer) {
186-
[weakSelf contentShownDeadlineReached];
187-
}];
184+
// Only for CONTENT: feedback widgets (survey/NPS/rating) load a different SDK-built URL and
185+
// never report [CLY]_content_shown, so arming the deadline for them would force-close a
186+
// widget the user is still filling out. Skip it for feedback widget URLs.
187+
if (![self isFeedbackWidgetURL:url]) {
188+
__weak typeof(self) weakSelf = self;
189+
self.contentShownDeadlineTimer = [NSTimer scheduledTimerWithTimeInterval:kCLYContentShownDeadline repeats:NO block:^(NSTimer * _Nonnull timer) {
190+
[weakSelf contentShownDeadlineReached];
191+
}];
192+
}
188193

189194
CLYButton *dismissButton = [CLYButton dismissAlertButton:@"X"];
190195
[self configureDismissButton:dismissButton forWebView:webView];
@@ -526,6 +531,20 @@ - (void)contentShownDeadlineReached {
526531
[self closeWebView];
527532
}
528533

534+
// A feedback widget is presented through the same web view manager but loads a URL the SDK
535+
// builds in the feedback module as "<host>/feedback/<type>?...&widget_id=<id>&..." (see
536+
// CountlyFeedbackWidget generateWidgetURL), whereas content loads "<host>/_external/content?...".
537+
// Feedback widgets never report [CLY]_content_shown, so we must not arm the content-shown
538+
// deadline for them. Recognized by the SDK's own "/feedback/" endpoint path segment
539+
// (kCountlyEndpointFeedback), which content URLs never contain.
540+
- (BOOL)isFeedbackWidgetURL:(NSURL *)url {
541+
if (!url) return NO;
542+
NSString *path = url.path;
543+
if (!path) return NO;
544+
NSString *feedbackSegment = [kCountlyEndpointFeedback stringByAppendingString:@"/"];
545+
return [path rangeOfString:feedbackSegment].location != NSNotFound;
546+
}
547+
529548
- (void)userContentController:(WKUserContentController *)userContentController
530549
didReceiveScriptMessage:(WKScriptMessage *)message
531550
{

0 commit comments

Comments
 (0)