Skip to content
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
* Added the "sendPushTokenAlways" config option, which sends the push token even for users who have not allowed notifications, in iOS.
* Added support for the notification small icon given by the "com.google.firebase.messaging.default_notification_icon" meta-data in Android.
* Added the "setAllowedIntentSchemes" option to "CountlyConfig.content" and "CountlyConfig.push" for limiting the URI schemes that content, feedback widget and push notification links open, in Android. "data", "zip", "intent", "file", "content", "javascript" and "jar" stay blocked.
* Added config options of the underlying Android and iOS SDKs:
* "enableManualSessionControlHybridMode", "setMetricOverride", "setTrackOrientationChanges", "enableClearStoredDeviceId" and "sdkInternalLimits.setMaxValueSizePicture"
* in Android, "disableSDKLoggingInProduction", "disableWebView", "disableHealthCheck", "setAppCrawlerNames" and "enableRecordAllThreadsWithCrash"
* in iOS, "doNotShowAlertForNotifications"
* in iOS, the "disableZoom", "disableRotation", "enableContentReloadOnStall" and "setContentReloadOnStallTimeout" options of "CountlyConfig.content" (Experimental!)
* Added Web support for:
* push notifications: give the "push" consent, set the VAPID public key with "CountlyConfig.push.setVapidPublicKey", copy "countly_sw.js" into your application's "web" folder and call "askForNotificationPermission" from a user gesture
* "disablePushNotifications", and "onNotification" for received, clicked and closed notifications
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1886,12 +1886,19 @@ private void populateConfig(JSONObject _config) throws JSONException {
if (_config.has("maxStackTraceLinesPerThread")) {
this.config.sdkInternalLimits.setMaxStackTraceLinesPerThread(_config.getInt("maxStackTraceLinesPerThread"));
}
if (_config.has("maxValueSizePicture")) {
this.config.sdkInternalLimits.setMaxValueSizePicture(_config.getInt("maxValueSizePicture"));
}
// Internal Limits END --------------------------------

if (_config.has("enableUnhandledCrashReporting") && _config.getBoolean("enableUnhandledCrashReporting")) {
this.config.crashes.enableCrashReporting();
}

if (_config.optBoolean("recordAllThreadsWithCrash", false)) {
this.config.crashes.enableRecordAllThreadsWithCrash();
}

if (_config.has("maxRequestQueueSize")) {
this.config.setMaxRequestQueueSize(_config.getInt("maxRequestQueueSize"));
}
Expand All @@ -1908,6 +1915,10 @@ private void populateConfig(JSONObject _config) throws JSONException {
enableManualSessionControl();
}

if (_config.optBoolean("manualSessionControlHybridMode", false)) {
this.config.enableManualSessionControlHybridMode();
}

String countryCode = null;
String city = null;
String gpsCoordinates = null;
Expand Down Expand Up @@ -1963,6 +1974,28 @@ private void populateConfig(JSONObject _config) throws JSONException {
this.config.disableViewRestartForManualRecording();
}

if (_config.has("metricOverride")) {
this.config.setMetricOverride(toMapString(_config.getJSONObject("metricOverride")));
}
if (_config.has("trackOrientationChanges")) {
this.config.setTrackOrientationChanges(_config.getBoolean("trackOrientationChanges"));
}
if (_config.optBoolean("clearStoredDeviceId", false)) {
this.config.enableClearStoredDeviceId();
}
if (_config.optBoolean("sdkLoggingInProductionDisabled", false)) {
this.config.disableSDKLoggingInProduction();
}
if (_config.optBoolean("webViewDisabled", false)) {
this.config.disableWebView();
}
if (_config.optBoolean("healthCheckDisabled", false)) {
this.config.disableHealthCheck();
}
if (_config.has("appCrawlerNames")) {
this.config.setAppCrawlerNames(toStringArray(_config.getJSONArray("appCrawlerNames")));
}

if (_config.has("enableAllConsents") && _config.getBoolean("enableAllConsents")) {
this.config.giveAllConsents();
}
Expand Down
15 changes: 15 additions & 0 deletions example/lib/config_object.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class CountlyConfiguration {
// ..sdkInternalLimits.setMaxBreadcrumbCount(100) // Max breadcrumbs before oldest is deleted (default 100)
// ..sdkInternalLimits.setMaxStackTraceLinesPerThread(30) // Max stack trace lines per thread (default 30)
// ..sdkInternalLimits.setMaxStackTraceLineLength(200) // Max characters per stack trace line (default 200)
// ..sdkInternalLimits.setMaxValueSizePicture(4096) // Max characters of the profile picture URL
//------------------------------------------------------------

// Content features
Expand All @@ -92,11 +93,16 @@ class CountlyConfiguration {
// }) // Set a global callback for content actions
// ..content.setZoneTimerInterval(30) // Set interval for automatic content zone refresh in seconds
// ..content.setWebviewDisplayOption(WebViewDisplayOption.safeArea) // Set webview display mode (immersive or safeArea)
// ..content.disableZoom() // Turn off zooming in content (iOS only)
// ..content.disableRotation() // Keep content in its portrait layout (iOS only)
// ..content.enableContentReloadOnStall() // Reload content that does not appear while loading (iOS only)
// ..content.setContentReloadOnStallTimeout(1000) // Milliseconds before stalled content is reloaded (iOS only)
//------------------------------------------------------------

// Device ID configuration
// ..setDeviceId('CUSTOM_DEVICE_ID') // Set a custom device ID
// ..enableTemporaryDeviceIDMode() // Enable temporary device ID mode
// ..enableClearStoredDeviceId() // Clear the stored device ID at every start, for development only

// Queue and timing configuration
// ..setEventQueueSizeToSend(10) // Set event queue threshold before sending (default 10)
Expand All @@ -110,10 +116,19 @@ class CountlyConfiguration {
// ..disableStoringDefaultPushConsent() // Disable storing default push consent
// ..disableViewRestartForManualRecording() // Disable view restart for manual recording
// ..setSDKBehaviorSettings('{}') // Provide server config if not available at init time
// ..setMetricOverride({'_app_version': '1.2.3'}) // Override the metrics sent when a session begins
// ..setTrackOrientationChanges(false) // Stop recording the device orientation
// ..disableSDKLoggingInProduction() // Keep SDK logs off in release builds (Android only)
// ..disableWebView() // Turn off content, feedback widgets and the rating popup (Android only)
// ..disableHealthCheck() // Stop the health check request (Android only)
// ..setAppCrawlerNames(['Crawler Device']) // Add device names of app crawlers to ignore (Android only)
// ..enableRecordAllThreadsWithCrash() // Add the stack traces of all threads to crashes (Android only)
// ..doNotShowAlertForNotifications() // Do not show Countly push notifications in the foreground (iOS only)

// ..setStarRatingTextMessage('Message for start rating dialog')
// ..setParameterTamperingProtectionSalt('salt') // Set the optional salt to be used for calculating the checksum of requested data which will be sent with each request
// ..enableManualSessionHandling() // Enable manual session handling
// ..enableManualSessionControlHybridMode() // With manual session handling, update the running session automatically
// ..setHttpPostForced(true) // Set to 'false' if you want HTTP POST not to be used for all requests
// ..setRequestTimeoutDuration(15) // Set the request timeout duration to 15 seconds
// ..setCustomNetworkRequestHeaders({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#import "CountlyFLPushNotifications.h"
#import "CountlyCommon.h"
#import "CountlyPushNotifications.h"
#import "UserNotifications/UserNotifications.h"

#ifndef COUNTLY_EXCLUDE_PUSHNOTIFICATIONS
Expand Down Expand Up @@ -139,7 +140,7 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNot
NSDictionary *countlyPayload = notification.request.content.userInfo[kCountlyPNKeyCountlyPayload];
NSString *notificationID = countlyPayload[kCountlyPNKeyNotificationID];

if (notificationID) {
if (notificationID && !CountlyPushNotifications.sharedInstance.doNotShowAlertForNotifications) {
UNNotificationPresentationOptions presentationOption = UNNotificationPresentationOptionNone;
if (@available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *)) {
presentationOption = UNNotificationPresentationOptionList | UNNotificationPresentationOptionBanner;
Expand Down
42 changes: 42 additions & 0 deletions ios/countly_flutter/Sources/countly_flutter/CountlyFlutterPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -1662,6 +1662,10 @@ - (void)populateConfig:(NSDictionary *)_config {
if (maxStackTraceLinesPerThread) {
[config.sdkInternalLimits setMaxStackTraceLinesPerThread:[maxStackTraceLinesPerThread intValue]];
}
NSNumber *maxValueSizePicture = [self configValueFrom:_config key:@"maxValueSizePicture" ofClass:NSNumber.class];
if (maxValueSizePicture) {
[config.sdkInternalLimits setMaxValueSizePicture:[maxValueSizePicture intValue]];
}
// Internal Limits End ---------------------

NSNumber *enableUnhandledCrashReporting = [self configValueFrom:_config key:@"enableUnhandledCrashReporting" ofClass:NSNumber.class];
Expand All @@ -1688,6 +1692,10 @@ - (void)populateConfig:(NSDictionary *)_config {
if (manualSessionEnabled && [manualSessionEnabled boolValue]) {
config.manualSessionHandling = YES;
}
NSNumber *manualSessionControlHybridMode = [self configValueFrom:_config key:@"manualSessionControlHybridMode" ofClass:NSNumber.class];
if (manualSessionControlHybridMode && [manualSessionControlHybridMode boolValue]) {
config.enableManualSessionControlHybridMode = YES;
}
[config remoteConfigRegisterGlobalCallback:^(CLYRequestResult _Nonnull response, NSError *_Nonnull error, BOOL fullValueUpdate, NSDictionary<NSString *, CountlyRCData *> *_Nonnull downloadedValues) {
[self remoteConfigDownloadCallback:[NSNumber numberWithInt:-2] response:response fullValueUpdate:fullValueUpdate error:error downloadedValues:downloadedValues];
}];
Expand Down Expand Up @@ -1717,6 +1725,19 @@ - (void)populateConfig:(NSDictionary *)_config {
config.disableViewRestartForManualRecording = YES;
}

NSDictionary *metricOverride = [self configValueFrom:_config key:@"metricOverride" ofClass:NSDictionary.class];
if (metricOverride) {
config.customMetrics = metricOverride;
}
NSNumber *trackOrientationChanges = [self configValueFrom:_config key:@"trackOrientationChanges" ofClass:NSNumber.class];
if (trackOrientationChanges) {
config.enableOrientationTracking = [trackOrientationChanges boolValue];
}
NSNumber *clearStoredDeviceId = [self configValueFrom:_config key:@"clearStoredDeviceId" ofClass:NSNumber.class];
if (clearStoredDeviceId && [clearStoredDeviceId boolValue]) {
config.resetStoredDeviceID = YES;
}

NSString *gpsCoordinate = [self configValueFrom:_config key:@"locationGpsCoordinates" ofClass:NSString.class];
CLLocationCoordinate2D coordinate = [self getCoordinate:gpsCoordinate];
if (CLLocationCoordinate2DIsValid(coordinate)) {
Expand Down Expand Up @@ -1761,6 +1782,10 @@ - (void)populateConfig:(NSDictionary *)_config {
if (sendPushTokenAlways && [sendPushTokenAlways boolValue]) {
config.sendPushTokenAlways = YES;
}
NSNumber *doNotShowAlertForNotifications = [self configValueFrom:_config key:@"doNotShowAlertForNotifications" ofClass:NSNumber.class];
if (doNotShowAlertForNotifications && [doNotShowAlertForNotifications boolValue]) {
config.doNotShowAlertForNotifications = YES;
}
#endif

[config.content setGlobalContentCallback:^(ContentStatus contentStatus, NSDictionary *contentData) {
Expand Down Expand Up @@ -1791,6 +1816,23 @@ - (void)populateConfig:(NSDictionary *)_config {
}
}

NSNumber *contentZoomDisabled = [self configValueFrom:_config key:@"contentZoomDisabled" ofClass:NSNumber.class];
if (contentZoomDisabled && [contentZoomDisabled boolValue]) {
[config.content disableZoom];
}
NSNumber *contentRotationDisabled = [self configValueFrom:_config key:@"contentRotationDisabled" ofClass:NSNumber.class];
if (contentRotationDisabled && [contentRotationDisabled boolValue]) {
[config.content disableRotation];
}
NSNumber *contentReloadOnStallEnabled = [self configValueFrom:_config key:@"contentReloadOnStallEnabled" ofClass:NSNumber.class];
if (contentReloadOnStallEnabled && [contentReloadOnStallEnabled boolValue]) {
[config.content enableContentReloadOnStall];
}
NSNumber *contentReloadOnStallTimeout = [self configValueFrom:_config key:@"contentReloadOnStallTimeout" ofClass:NSNumber.class];
if (contentReloadOnStallTimeout) {
[config.content setContentReloadOnStallTimeout:[contentReloadOnStallTimeout unsignedIntegerValue]];
}

} @catch (NSException *exception) {
COUNTLY_FLUTTER_LOG(@"[populateConfig], Unable to parse Config object: %@", exception);
}
Expand Down
42 changes: 42 additions & 0 deletions lib/src/configuration_interfaces/countly_config_content.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/// This class holds content features specific configurations to be used with CountlyConfig class and serves as an interface.
/// You can chain multiple configurations.
import '../content_builder.dart';
import '../countly_flutter.dart';
import 'intent_schemes.dart';

class CountlyConfigContent {
Expand All @@ -9,12 +10,20 @@ class CountlyConfigContent {
int? _zoneTimerInterval;
WebViewDisplayOption? _webviewDisplayOption;
List<String>? _allowedIntentSchemes;
bool _zoomDisabled = false;
bool _rotationDisabled = false;
bool _reloadOnStallEnabled = false;
int? _reloadOnStallTimeout;

/// getters
ContentCallback? get contentCallback => _contentCallback;
int? get zoneTimerInterval => _zoneTimerInterval;
WebViewDisplayOption? get webviewDisplayOption => _webviewDisplayOption;
List<String>? get allowedIntentSchemes => _allowedIntentSchemes;
bool get zoomDisabled => _zoomDisabled;
bool get rotationDisabled => _rotationDisabled;
bool get reloadOnStallEnabled => _reloadOnStallEnabled;
int? get reloadOnStallTimeout => _reloadOnStallTimeout;

/// setters / methods

Expand Down Expand Up @@ -50,4 +59,37 @@ class CountlyConfigContent {
_allowedIntentSchemes = allowedIntentSchemesFrom(schemes, 'content.setAllowedIntentSchemes');
return this;
}

/// This is an experimental feature and it can have breaking changes
/// Turn off pinch and double tap zoom in the content web view (Only for iOS).
CountlyConfigContent disableZoom() {
_zoomDisabled = true;
return this;
}

/// This is an experimental feature and it can have breaking changes
/// Keep displayed content in its portrait layout whatever the device orientation is (Only for iOS).
CountlyConfigContent disableRotation() {
_rotationDisabled = true;
return this;
}

/// This is an experimental feature and it can have breaking changes
/// Reload content whose page does not appear while loading, instead of closing it (Only for iOS).
CountlyConfigContent enableContentReloadOnStall() {
_reloadOnStallEnabled = true;
return this;
}

/// This is an experimental feature and it can have breaking changes
/// Set how long content may take to appear before it is reloaded, when "enableContentReloadOnStall" is set (Only for iOS).
/// [int milliseconds]: the waiting time, at least 1 (default 1000)
CountlyConfigContent setContentReloadOnStallTimeout(int milliseconds) {
if (milliseconds < 1) {
Countly.log('setContentReloadOnStallTimeout, timeout must be at least 1 millisecond, ignoring [$milliseconds]', logLevel: LogLevel.ERROR);
return this;
}
_reloadOnStallTimeout = milliseconds;
return this;
}
}
10 changes: 10 additions & 0 deletions lib/src/configuration_interfaces/countly_config_limits.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class CountlyConfigSDKInternalLimits {
int _maxBreadcrumbCount = 0;
int _maxStackTraceLinesPerThread = 0;
int _maxStackTraceLineLength = 0;
int _maxValueSizePicture = 0;

/// getters
int get maxKeyLength => _maxKeyLength;
Expand All @@ -16,6 +17,7 @@ class CountlyConfigSDKInternalLimits {
int get maxBreadcrumbCount => _maxBreadcrumbCount;
int get maxStackTraceLinesPerThread => _maxStackTraceLinesPerThread;
int get maxStackTraceLineLength => _maxStackTraceLineLength;
int get maxValueSizePicture => _maxValueSizePicture;

/// setters / methods

Expand Down Expand Up @@ -66,4 +68,12 @@ class CountlyConfigSDKInternalLimits {
_maxStackTraceLineLength = stackTraceLineLengthLimit;
return this;
}

/// Limits the length of the profile picture URL (Only for Android and iOS)
///
/// [pictureValueSizeLimit] is the max char size of the "picture" user property
CountlyConfigSDKInternalLimits setMaxValueSizePicture(int pictureValueSizeLimit) {
_maxValueSizePicture = pictureValueSizeLimit;
return this;
}
}
Loading
Loading