From 24e8cea06e6f7a3461c47ba2b35d4f435503ad0c Mon Sep 17 00:00:00 2001 From: ijunaid Date: Fri, 17 Feb 2023 10:03:46 +0500 Subject: [PATCH] Updated SDK version and Underlying SDK's (#104) * Updated SDK version and Underlying SDK's * Updated SDK version to 22.09.0 * Updated underlying android SDK to 22.06.2 * Updated underlying iOS SDK version to 22.09.0 * Updated changelog file * Update CHANGELOG.md --------- Co-authored-by: ArtursKadikis --- .versions | 2 +- CHANGELOG.md | 7 + Countly.js | 2 +- package.json | 2 +- plugin.xml | 4 +- src/android/CountlyNative.java | 2 +- src/ios/CountlyNative.m | 5 +- src/ios/CountlyiOS/CHANGELOG.md | 164 +++++----- src/ios/CountlyiOS/Countly-PL.podspec | 2 +- src/ios/CountlyiOS/Countly.h | 115 ++++--- src/ios/CountlyiOS/Countly.m | 281 +++++++++--------- src/ios/CountlyiOS/Countly.podspec | 2 +- src/ios/CountlyiOS/CountlyCommon.h | 2 + src/ios/CountlyiOS/CountlyCommon.m | 24 +- src/ios/CountlyiOS/CountlyConfig.h | 38 +-- src/ios/CountlyiOS/CountlyConfig.m | 5 - src/ios/CountlyiOS/CountlyConnectionManager.h | 3 + src/ios/CountlyiOS/CountlyConnectionManager.m | 81 ++++- src/ios/CountlyiOS/CountlyConsentManager.h | 2 + src/ios/CountlyiOS/CountlyConsentManager.m | 36 ++- src/ios/CountlyiOS/CountlyCrashReporter.h | 3 +- src/ios/CountlyiOS/CountlyCrashReporter.m | 19 +- src/ios/CountlyiOS/CountlyDeviceInfo.h | 10 + src/ios/CountlyiOS/CountlyDeviceInfo.m | 21 ++ src/ios/CountlyiOS/CountlyFeedbackWidget.m | 12 +- src/ios/CountlyiOS/CountlyPersistency.m | 9 +- src/ios/CountlyiOS/CountlyViewTracking.m | 114 +++++-- src/ios/CountlyiOS/Package.swift | 12 +- src/ios/CountlyiOS/README.md | 71 +++-- 29 files changed, 643 insertions(+), 407 deletions(-) diff --git a/.versions b/.versions index be6e359..8afa649 100644 --- a/.versions +++ b/.versions @@ -1,2 +1,2 @@ -countly:countly-sdk-js@21.11.0 +countly:countly-sdk-js@22.09.0 meteor@1.9.2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e69961..f24a832 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 22.09.0 +* Fixed notification trampoline restrictions in Android 12 using reverse activity trampolining implementation. +* Fixed a bug in Android SDK that would throw a null pointer exception when calling "CountlyPush.onTokenRefresh" and CountlyPush was not initialized +* Fixed a bug in Android SDK that would throw a null pointer exception when calling "CountlyPush.displayNotification " and CountlyPush was not initialized +* Updated underlying android SDK to 22.06.2 +* Updated underlying iOS SDK version to 22.09.0 + ## 21.11.0 * !! Major breaking change !! Deprecating "ADVERTISING_ID" as device ID generation strategy. SDK will fall back to 'OPEN_UDID'. All "ADVERTISING_ID" device ID's will have their type changed to "OPEN_UDID". If the device will have a "null" device ID, a random one will be generated. * !! Major breaking change !! Changing device ID without merging will now clear all consent. It has to be given again after this operation. diff --git a/Countly.js b/Countly.js index 50cdd8b..dec562a 100644 --- a/Countly.js +++ b/Countly.js @@ -2,7 +2,7 @@ Countly = {}; Countly.serverUrl = ""; Countly.appKey = ""; Countly.ready = false; -Countly.version = "21.11.0"; +Countly.version = "22.09.0"; Countly.isDebug = false; _isInitialized = false; if (window.cordova.platformId == "android") { diff --git a/package.json b/package.json index b31ead3..ecfde6f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "countly-sdk-js", - "version": "21.11.0", + "version": "22.09.0", "description": "Countly is an innovative, real-time, open source mobile analytics and push notifications platform. It collects data from mobile devices, and visualizes this information to analyze mobile application usage and end-user behavior. There are two parts of Countly: the server that collects and analyzes data, and mobile SDK that sends this data. Both parts are open source with different licensing terms.", "cordova": { "id": "countly-sdk-js", diff --git a/plugin.xml b/plugin.xml index 480b175..d8a8bbe 100644 --- a/plugin.xml +++ b/plugin.xml @@ -1,5 +1,5 @@ - + Countly Cordova SDK @@ -141,7 +141,7 @@ - + diff --git a/src/android/CountlyNative.java b/src/android/CountlyNative.java index b9d3c5d..29fb8b3 100644 --- a/src/android/CountlyNative.java +++ b/src/android/CountlyNative.java @@ -31,7 +31,7 @@ public class CountlyNative { public static final String TAG = "CountlyCordovaPlugin"; - private final String COUNTLY_CORDOVA_SDK_VERSION_STRING = "21.11.0"; + private final String COUNTLY_CORDOVA_SDK_VERSION_STRING = "22.09.0"; private final String COUNTLY_CORDOVA_SDK_NAME = "js-cordovab-android"; private Countly.CountlyMessagingMode pushTokenTypeVariable = Countly.CountlyMessagingMode.PRODUCTION; diff --git a/src/ios/CountlyNative.m b/src/ios/CountlyNative.m index 4937ecd..1974104 100644 --- a/src/ios/CountlyNative.m +++ b/src/ios/CountlyNative.m @@ -19,7 +19,7 @@ Boolean isInitialized = false; NSString *const pushPluginApplicationDidBecomeActiveNotification = @"pushPluginApplicationDidBecomeActiveNotification"; -NSString *const kCountlyCordovaSDKVersion = @"21.11.0"; +NSString *const kCountlyCordovaSDKVersion = @"22.09.0"; NSString *const kCountlyCordovaSDKName = @"js-cordovab-ios"; @interface CountlyFeedbackWidget () @@ -580,7 +580,8 @@ - (void)onCall:(NSString *)method commandString:(NSArray *)command callback:(Res NSException *myException = [NSException exceptionWithName:@"Exception" reason:execption userInfo:dict]; - [Countly.sharedInstance recordHandledException:myException withStackTrace:nsException]; + [Countly.sharedInstance recordException:myException isFatal:NO stackTrace:nsException segmentation:nil]; + result(@"logException!"); }); diff --git a/src/ios/CountlyiOS/CHANGELOG.md b/src/ios/CountlyiOS/CHANGELOG.md index 32f36a7..0a4242d 100644 --- a/src/ios/CountlyiOS/CHANGELOG.md +++ b/src/ios/CountlyiOS/CHANGELOG.md @@ -1,3 +1,39 @@ +## 22.09.0 +- Deleted previously deprecated `userLoggedIn:` and `userLoggedOut` methods +- Added new exception recording methods: `recordException:`, `recordException:isFatal:`, `recordException:isFatal:stackTrace:segmentation:` +- Deprecated existing exception recording methods: `recordHandledException:`, `recordHandledException:withStackTrace:`, `recordUnhandledException:withStackTrace:` +- Added `recordError:stackTrace:`, `recordError:isFatal:stackTrace:segmentation:` methods for Swift errors + +- Other various improvements + - Added device info to SDK initialization logs + + ## 22.06.2 +- Added direct requests support +- Fixed missing remote config consent in consents request + +- Other various improvements + - Updated some pragma marks + +## 22.06.1 +- Fixed user details consent issue on SDK start +- Updated feedback widget internal webview design and layout + +- Other various improvements + - Updated HeaderDocs, internal logs, inline notes and pragma marks + +## 22.06.0 +- Added `CountlyAutoViewTrackingName` protocol for supporting custom view titles with AutoViewTracking +- Added `setNewURLSessionConfiguration:` method to be able change URL session configuraion on the go (thanks @angelix) +- Added ability to save user details on SDK initialization +- Added device ID type to every request being sent +- Fixed missing remote config consent +- Fixed auto view tracking for iOS 13+ PageSheet modal presentations +- Deleted previously deprecated and inoperative methods and config flags + +- Other various improvements + - Updated HeaderDocs, internal logs, inline notes and pragma marks + - Updated Countly project settings for Xcode 13.4.1 (13F100) + ## 21.11.2 - Added direct and indirect attribution - Added platform info to default segmentation of push action events @@ -8,14 +44,10 @@ - Fixed possible SecTrustCopyExceptions leak - Deprecated `presentFeedbackWidgetWithID:completionHandler:` method - - ## 21.11.1 - Fixed a crash when some default user detail properties are set to `NSNull` (thanks @lhunath) - Updated README.md for minimum supported deployment targets - - ## 21.11.0 - Updated minimum supported OS versions as `iOS 10.0`, `tvOS 10.0`, `watchOS 4.0` and `macOS 10.14` - Updated some deprecated API usage to get rid of warnings @@ -42,16 +74,12 @@ - Deleted previously deprecated methods and properties - Refactored `connectionType` method - - ## 20.11.3 - Added optional appear and dismiss callbacks for feedback widget presenting - Added manually displayed and recorded feedback widgets support - Fixed HTTP method check for feedback widget requests - Implemented immediately sending of queued events when a widget event is recorded - - ## 20.11.2 - Added configurable internal log levels - Added internal logs for approximate received and sent data size for requests @@ -75,16 +103,12 @@ - Updated HeaderDocs, internal logs, inline notes and pragma marks - Updated Countly project settings for Xcode 12.4 - - ## 20.11.1 - Added `loggerDelegate` initial config property for receiving internal logs on production builds - Fixed manual view tracking state clean up when view tracking consent is cancelled - Updated `CountlyFeedbackWidget.h` as public header file in Xcode project file for Carthage - Added nullability specifiers for block parameters - - ## 20.11.0 - Added Surveys and NPS feedback widgets - Added Swift Package Manager support @@ -109,8 +133,6 @@ - Updated some constant key declarations for common use - Updated HeaderDocs, internal logs, inline notes and pragma marks - - ## 20.04.3 - Deprecated `recordLocation:`, `recordCity:andISOCountryCode:`, `recordIP:` methods - Added new combined `recordLocation:city:ISOCountryCode:IP:` method for recording location related info @@ -127,8 +149,6 @@ - Treated empty string `city`, `ISOCountryCode` and `IP` values as `nil` - Added warnings for the cases where `city` and `ISOCountryCode` code are not set as a pair - - ## 20.04.2 - Implemented overriding default metrics and adding custom ones - Fixed advertising tracking enabled check @@ -138,16 +158,14 @@ - Refactored extra slash check using `hasSuffix:` method - Renamed some app life cycle observing methods for clarity - - ## 20.04.1 - Added Application Performance Monitoring (Phase 1) - - Manual network traces - - Manual custom traces - - Semi-automatic app start time trace - - Automatic app foreground time trace - - Automatic app background time trace - - Consent handling for Application Performance Monitoring + - Manual network traces + - Manual custom traces + - Semi-automatic app start time trace + - Automatic app foreground time trace + - Automatic app background time trace + - Consent handling for Application Performance Monitoring - Added `COUNTLY_EXCLUDE_PUSHNOTIFICATIONS` flag to disable push notifications altogether in order to avoid App Store Connect warnings (thanks @grundleborg) - Fixed an incorrect internal logging on SDK start - Fixed location consent order to avoid some legacy Countly Server issue with location info being unavailable even after giving consent @@ -157,12 +175,10 @@ - Applied `alwaysUsePOST` flag to remote config requests - Other various improvements - - Deleted some unnecessary imports - - Updated HeaderDocs, internal logs, inline notes and pragma marks - - Added missing frameworks to CocoaPods podspec - - Added ability to override SDK name and version for bridge SDKs - - + - Deleted some unnecessary imports + - Updated HeaderDocs, internal logs, inline notes and pragma marks + - Added missing frameworks to CocoaPods podspec + - Added ability to override SDK name and version for bridge SDKs ## 20.04 - Added crash reporting feature for tvOS @@ -178,16 +194,14 @@ - Discarded OpenGL ES version info in crash reports - Other various improvements - - Deleted an unnecessary UIKit import - - Added precaution for possible nil lines in backtrace - - Added precaution for possible nil OS name value - - Replaced scheduledTimerWithTimeInterval call with timerWithTimeInterval (thanks @mt-rpranata) - - Updated architerture method for crash reports - - Updated CocoaPods podspec for core subspec approach - - Updated feature, consent and push test mode specifiers as NSString typedefs - - Updated HeaderDocs, internal logs, inline notes and pragma marks - - + - Deleted an unnecessary UIKit import + - Added precaution for possible nil lines in backtrace + - Added precaution for possible nil OS name value + - Replaced scheduledTimerWithTimeInterval call with timerWithTimeInterval (thanks @mt-rpranata) + - Updated architerture method for crash reports + - Updated CocoaPods podspec for core subspec approach + - Updated feature, consent and push test mode specifiers as NSString typedefs + - Updated HeaderDocs, internal logs, inline notes and pragma marks ## 19.08 - Added temporary device ID mode @@ -209,24 +223,22 @@ - Updated default device ID on tvOS as `identifierForVendor` - Other various improvements - - Renamed `forceDeviceIDInitialization` flag as `resetStoredDeviceID` - - Added lightweight generics for segmentation parameters - - Added dSYM upload script to preserved paths in Podspec - - Updated dSYM upload script to support paths with spaces - - Changed request cache policy to `NSURLRequestReloadIgnoringLocalCacheData` - - Added battery level for watchOS 4.0+ - - Added JSON validity check before converting objects - - Deleted unused `kCountlyCRKeyLoadAddress` constant - - Improved internal logging in binary images processing for crash reports - - Added persistency for generated `NSUUID` - - Added precaution to prevent invalid requests from being added to queue - - Discarded null check on request queue - - Discarded all APM related files - - Added length check for view tracking view name - - Added length check for view tracking exceptions - - Updated HeaderDocs, internal logs, inline notes and pragma marks - - + - Renamed `forceDeviceIDInitialization` flag as `resetStoredDeviceID` + - Added lightweight generics for segmentation parameters + - Added dSYM upload script to preserved paths in Podspec + - Updated dSYM upload script to support paths with spaces + - Changed request cache policy to `NSURLRequestReloadIgnoringLocalCacheData` + - Added battery level for watchOS 4.0+ + - Added JSON validity check before converting objects + - Deleted unused `kCountlyCRKeyLoadAddress` constant + - Improved internal logging in binary images processing for crash reports + - Added persistency for generated `NSUUID` + - Added precaution to prevent invalid requests from being added to queue + - Discarded null check on request queue + - Discarded all APM related files + - Added length check for view tracking view name + - Added length check for view tracking exceptions + - Updated HeaderDocs, internal logs, inline notes and pragma marks ## 19.02 - Added push notification support for macOS @@ -244,11 +256,9 @@ - Fixed feedback widget dismiss button position - Other various improvements - - Discarded separate UIWindow usage for presenting feedback widgets - - Added checksum to feedback widget requests - - Improved internal logging for request queue - - + - Discarded separate UIWindow usage for presenting feedback widgets + - Added checksum to feedback widget requests + - Improved internal logging for request queue ## 18.08 - Added feedback widgets support @@ -271,8 +281,6 @@ - Refactored custom crash log array and date formatter - Updated HeaderDocs, inline notes, pragma marks - - ## 18.04 - Added consent management for GDPR compliance - Exposed device ID to be used for data export and/or removal requests @@ -305,8 +313,6 @@ - Updated HeaderDocs, inline notes, pragma marks - Performed whitespace cleaning - - ## 18.01 - Added `attribution` config @@ -331,8 +337,6 @@ - Updated time related types as `NSTimeInterval` - Updated HeaderDocs - - ## 17.09 - Updated for Xcode 9 and iOS 11 @@ -359,8 +363,6 @@ - Updated HeaderDocs - Cleaned whitespace - - ## 17.05 - Added Rich Push Notifications support (attachments and custom action buttons) @@ -413,8 +415,6 @@ - Updated HeaderDocs - Cleaned whitespace - - ## 16.12 - Refactored push notifications @@ -462,8 +462,6 @@ - Updated HeaderDocs - Cleaned whitespace - - ## 16.06.4 - Fixed iOS10 zero-IDFA problem @@ -477,8 +475,6 @@ - Disabled server response dictionary check. - Other minor improvements like better internal logging, standardization, whitespacing, code cleaning, commenting, pragma marking and HeaderDocing - - ## 16.06.3 - Fixed a persistency related crash @@ -490,8 +486,6 @@ - Improved headerdocs grammar and formatting for easier integration and usage - Fixed some static analyzer warnings - - ## 16.06.2 - Added Star-Rating, the simplest form of feedback from users, both automatically and manually. @@ -501,8 +495,6 @@ - Improved headerdocs grammar and formatting for easier integration and usage - Fixed some static analyzer warnings - - ## 16.06.1 - Added support for certificate pinning. @@ -511,8 +503,6 @@ - Fixed a URL encoding issue which causes problems for Asian languages and some JSON payloads. - Fixed custom crash log formatter. - - ## 16.06 - Fixed a problem with changing device ID (for system generated device IDs) @@ -528,8 +518,6 @@ - Changed default alert title for push messages - Other minor improvements like better internal logging, standardization, whitespacing, code cleaning, commenting, pragma marking and HeaderDocing - - # 16.02.01 - Swithed to POST method for all requests by default @@ -537,8 +525,6 @@ - Fixed some issues with CocoaPods v1.0.0 - Other minor fixes and improvements - - ## 16.02 Completely re-written iOS SDK with watchOS, tvOS & OSX support @@ -553,14 +539,10 @@ Completely re-written iOS SDK with watchOS, tvOS & OSX support - Persistency without CoreData - Various performance improvements and minor bugfixes - - ## 15.06.01 Updated CocoaPods spec - - ## 15.06 - Added WatchKit support diff --git a/src/ios/CountlyiOS/Countly-PL.podspec b/src/ios/CountlyiOS/Countly-PL.podspec index 8f8c68b..3131b14 100644 --- a/src/ios/CountlyiOS/Countly-PL.podspec +++ b/src/ios/CountlyiOS/Countly-PL.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'Countly-PL' - s.version = '21.11.2' + s.version = '22.09.0' s.license = { :type => 'MIT', :file => 'LICENSE.md' } s.summary = 'Countly is an innovative, real-time, open source mobile analytics platform.' s.homepage = 'https://github.com/Countly/countly-sdk-ios' diff --git a/src/ios/CountlyiOS/Countly.h b/src/ios/CountlyiOS/Countly.h index ed8f5e5..2937c2a 100644 --- a/src/ios/CountlyiOS/Countly.h +++ b/src/ios/CountlyiOS/Countly.h @@ -31,6 +31,10 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)startWithConfig:(CountlyConfig *)config; + + +#pragma mark - Override Configuration + /** * Sets a new host to be used in requests. * @discussion Requests already queued previously will also be using the new host. @@ -51,10 +55,14 @@ NS_ASSUME_NONNULL_BEGIN - (void)setNewAppKey:(NSString *)newAppKey; /** - * @c setCustomHeaderFieldValue: method is deprecated. Please use @c URLSessionConfiguration property on @c CountlyConfig instead. - * @discussion Calling this method will have no effect. + * Sets a new URL session configuration to be used with all requests. + * @param newURLSessionConfiguration The new URL session configuration */ -- (void)setCustomHeaderFieldValue:(NSString *)customHeaderFieldValue DEPRECATED_MSG_ATTRIBUTE("Use 'URLSessionConfiguration' property on CountlyConfig instead!"); +- (void)setNewURLSessionConfiguration:(NSURLSessionConfiguration *)newURLSessionConfiguration; + + + +#pragma mark - Queue Operations /** * Flushes request and event queues. @@ -77,6 +85,19 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)removeDifferentAppKeysFromQueue; +/** + * Adds a direct request to the queue using given key-value pairs as query string pairs. + * @discussion requestParameters should be an @c NSDictionary, with keys and values are both @c NSString's only. + * @discussion Calls to this method will be ignored if: + * @discussion - There are not any consents given while @c requiresConsent flag is set on initial configuration. + * @param requestParameters Query string key-value pairs to be used in direct request + */ +- (void)addDirectRequest:(NSDictionary * _Nullable)requestParameters; + + + +#pragma mark - Sessions + /** * Starts session and sends @c begin_session request with default metrics for manual session handling. * @discussion This method needs to be called for starting a session only if @c manualSessionHandling flag is set on initial configuration. @@ -382,55 +403,71 @@ NS_ASSUME_NONNULL_BEGIN - (void)recordLocation:(CLLocationCoordinate2D)location city:(NSString * _Nullable)city ISOCountryCode:(NSString * _Nullable)ISOCountryCode IP:(NSString * _Nullable)IP; /** - * Records user's location info to be used for geo-location based push notifications and advanced user segmentation. - * @discussion By default, Countly Server uses a geo-ip database for acquiring user's location. - * @discussion If the app uses Core Location services and granted permission, a location with better accuracy can be provided using this method. - * @discussion This method overrides @c location property specified on initial configuration, and sends an immediate request. - * @param location User's location with latitude and longitude + * Disables geo-location based push notifications by clearing all existing location info. + * @discussion Once disabled, geo-location based push notifications can be enabled again by calling @c recordLocation: or @c recordCity:andISOCountryCode: or @c recordIP: method. */ -- (void)recordLocation:(CLLocationCoordinate2D)location DEPRECATED_MSG_ATTRIBUTE("Use 'recordLocation:city:ISOCountryCode:IP:' method instead!"); +- (void)disableLocationInfo; + + + +#pragma mark - Crash Reporting /** - * Records user's city and country info to be used for geo-location based push notifications and advanced user segmentation. - * @discussion By default, Countly Server uses a geo-ip database for acquiring user's location. - * @discussion If the app has information about user's city and/or country, these information can be provided using this method. - * @discussion This method overrides @c city and @c ISOCountryCode properties specified on initial configuration, and sends an immediate request. - * @param city User's city - * @param ISOCountryCode User's ISO country code in ISO 3166-1 alpha-2 format + * Records an non-fatal exception. + * @discussion A convenience method for @c recordException:isFatal:stackTrace:segmentation: + * with isFatal passed as @c NO, stack trace and segmentation are passed as @c nil. + * @param exception Exception to be recorded */ -- (void)recordCity:(NSString *)city andISOCountryCode:(NSString *)ISOCountryCode DEPRECATED_MSG_ATTRIBUTE("Use 'recordLocation:city:ISOCountryCode:IP:' method instead!"); +- (void)recordException:(NSException *)exception; /** - * Records user's IP address to be used for geo-location based push notifications and advanced user segmentation. - * @discussion By default, Countly Server uses a geo-ip database for acquiring user's location. - * @discussion If the app needs to explicitly specify the IP address due to network requirements, it can be provided using this method. - * @discussion This method overrides @c IP property specified on initial configuration, and sends an immediate request. - * @param IP User's explicit IP address + * Records an exception with fatality information. + * @discussion A convenience method for @c recordException:isFatal:stackTrace:segmentation: + * with stack trace and segmentation are passed as @c nil. + * @param isFatal Whether the exception is fatal or not */ -- (void)recordIP:(NSString *)IP DEPRECATED_MSG_ATTRIBUTE("Use 'recordLocation:city:ISOCountryCode:IP:' method instead!"); +- (void)recordException:(NSException *)exception isFatal:(BOOL)isFatal; /** - * Disables geo-location based push notifications by clearing all existing location info. - * @discussion Once disabled, geo-location based push notifications can be enabled again by calling @c recordLocation: or @c recordCity:andISOCountryCode: or @c recordIP: method. + * Records an exception with fatality information, given stack trace and segmentation. + * @discussion For manually recording all exceptions, fatal or not, with an ability to pass custom stack trace and segmentation data. + * @param exception Exception to be recorded + * @param isFatal Whether the exception is fatal or not + * @param stackTrace Stack trace to be recorded + * @param segmentation Crash segmentation to override @c crashSegmentation set on initial configuration */ -- (void)disableLocationInfo; - +- (void)recordException:(NSException *)exception isFatal:(BOOL)isFatal stackTrace:(NSArray * _Nullable)stackTrace segmentation:(NSDictionary * _Nullable)segmentation; +/** + * Records a Swift error with given stack trace. + * @discussion For manually recording Swift errors with an ability to pass custom stack trace. + * @param errorName A name describing the error to be recorded, a non-zero length valid string + * @param stackTrace Stack trace to be recorded + */ +- (void)recordError:(NSString *)errorName stackTrace:(NSArray * _Nullable)stackTrace; -#pragma mark - Crash Reporting +/** + * Records a Swift error with fatality information, given stack trace and segmentation. + * @discussion For manually recording Swift errors with an ability to pass custom stack trace and segmentation data. + * @param errorName A name describing the error to be recorded, a non-zero length valid string + * @param isFatal Whether the error is fatal or not + * @param stackTrace Stack trace to be recorded + * @param segmentation Crash segmentation to override @c crashSegmentation set on initial configuration + */ +- (void)recordError:(NSString *)errorName isFatal:(BOOL)isFatal stackTrace:(NSArray * _Nullable)stackTrace segmentation:(NSDictionary * _Nullable)segmentation; /** * Records a handled exception manually. * @param exception Exception to be recorded */ -- (void)recordHandledException:(NSException *)exception; +- (void)recordHandledException:(NSException *)exception DEPRECATED_MSG_ATTRIBUTE("Use 'recordException:' method instead!"); /** * Records a handled exception and given stack trace manually. * @param exception Exception to be recorded * @param stackTrace Stack trace to be recorded */ -- (void)recordHandledException:(NSException *)exception withStackTrace:(NSArray * _Nullable)stackTrace; +- (void)recordHandledException:(NSException *)exception withStackTrace:(NSArray * _Nullable)stackTrace DEPRECATED_MSG_ATTRIBUTE("Use 'recordException:isFatal:stackTrace:segmentation:' method instead! (passing isFatal:NO, segmentation:nil)"); /** * Records an unhandled exception and given stack trace manually. @@ -438,7 +475,7 @@ NS_ASSUME_NONNULL_BEGIN * @param exception Exception to be recorded * @param stackTrace Stack trace to be recorded */ -- (void)recordUnhandledException:(NSException *)exception withStackTrace:(NSArray * _Nullable)stackTrace; +- (void)recordUnhandledException:(NSException *)exception withStackTrace:(NSArray * _Nullable)stackTrace DEPRECATED_MSG_ATTRIBUTE("Use 'recordException:isFatal:stackTrace:segmentation:' method instead! (passing isFatal:YES, segmentation:nil)"); /** * Records custom logs to be delivered with crash report. @@ -512,20 +549,6 @@ NS_ASSUME_NONNULL_BEGIN */ + (CountlyUserDetails *)user; -/** - * Handles switching from device ID to custom user ID for logged in users - * @discussion When a user logs in, this user can be tracked with custom user ID instead of device ID. - * @discussion This is just a convenience method that handles setting user ID as new device ID and merging existing data on Countly Server. - * @param userID Custom user ID uniquely defining the logged in user - */ -- (void)userLoggedIn:(NSString *)userID DEPRECATED_MSG_ATTRIBUTE("Use 'setNewDeviceID:onServer:' method instead!"); - -/** - * Handles switching from custom user ID to device ID for logged out users - * @discussion When a user logs out, all the data can be tracked with default device ID henceforth. - * @discussion This is just a convenience method that handles resetting device ID to default one and starting a new session. - */ -- (void)userLoggedOut DEPRECATED_MSG_ATTRIBUTE("Use 'setNewDeviceID:onServer:' method instead!"); @@ -554,7 +577,7 @@ NS_ASSUME_NONNULL_BEGIN * @param widgetID ID of the feedback widget created on Countly Server. * @param completionHandler A completion handler block to be executed when feedback widget is dismissed by user or there is an error. */ -- (void)presentFeedbackWidgetWithID:(NSString *)widgetID completionHandler:(void (^)(NSError * __nullable error))completionHandler DEPRECATED_MSG_ATTRIBUTE("Use 'presentRatingWidgetWithID:' method instead!"); +- (void)presentFeedbackWidgetWithID:(NSString *)widgetID completionHandler:(void (^)(NSError * __nullable error))completionHandler DEPRECATED_MSG_ATTRIBUTE("Use 'presentRatingWidgetWithID:completionHandler' method instead!"); /** * Presents rating widget with given ID in a WKWebView placed in a UIViewController. @@ -627,7 +650,7 @@ NS_ASSUME_NONNULL_BEGIN /** * Records indirect attribution with given key-value pairs. - * @discussion Keys could be a predefined CLYAttributionKey or any non-zero length valid string. + * @discussion Keys could be a predefined @c CLYAttributionKey or any non-zero length valid string. * @discussion This method sends an immediate request. * @discussion Calls to this method will be ignored if: * @discussion - Consent for @c CLYConsentAttribution is not given, while @c requiresConsent flag is set on initial configuration. diff --git a/src/ios/CountlyiOS/Countly.m b/src/ios/CountlyiOS/Countly.m index a0c19c5..d002226 100644 --- a/src/ios/CountlyiOS/Countly.m +++ b/src/ios/CountlyiOS/Countly.m @@ -4,8 +4,6 @@ // // Please visit www.count.ly for more information. -#pragma mark - Core - #import "CountlyCommon.h" @interface Countly () @@ -19,6 +17,8 @@ @interface Countly () @implementation Countly +#pragma mark - Core + + (void)load { [super load]; @@ -62,8 +62,6 @@ - (instancetype)init return self; } -#pragma mark --- - - (void)startWithConfig:(CountlyConfig *)config { if (CountlyCommon.sharedInstance.hasStarted_) @@ -86,7 +84,12 @@ - (void)startWithConfig:(CountlyConfig *)config if (!config.host.length || [config.host isEqualToString:@"https://YOUR_COUNTLY_SERVER"]) [NSException raise:@"CountlyHostNotSetException" format:@"host property on CountlyConfig object is not set"]; - CLY_LOG_I(@"Initializing with %@ SDK v%@", CountlyCommon.sharedInstance.SDKName, CountlyCommon.sharedInstance.SDKVersion); + CLY_LOG_I(@"Initializing with %@ SDK v%@ on %@ with %@ %@", + CountlyCommon.sharedInstance.SDKName, + CountlyCommon.sharedInstance.SDKVersion, + CountlyDeviceInfo.device, + CountlyDeviceInfo.osName, + CountlyDeviceInfo.osVersion); if (!CountlyDeviceInfo.sharedInstance.deviceID || config.resetStoredDeviceID) { @@ -111,6 +114,8 @@ - (void)startWithConfig:(CountlyConfig *)config CountlyDeviceInfo.sharedInstance.customMetrics = [config.customMetrics cly_truncated:@"Custom metric"]; + [Countly.user save]; + #if (TARGET_OS_IOS) CountlyFeedbacks.sharedInstance.message = config.starRatingMessage; CountlyFeedbacks.sharedInstance.sessionCount = config.starRatingSessionCount; @@ -191,93 +196,7 @@ - (void)startWithConfig:(CountlyConfig *)config [self recordIndirectAttribution:config.indirectAttribution]; } - -- (void)setNewHost:(NSString *)newHost -{ - CLY_LOG_I(@"%s %@", __FUNCTION__, newHost); - - if (!newHost.length) - { - CLY_LOG_W(@"New host is invalid!"); - return; - } - - CountlyConnectionManager.sharedInstance.host = newHost; -} - -- (void)setNewAppKey:(NSString *)newAppKey -{ - CLY_LOG_I(@"%s %@", __FUNCTION__, newAppKey); - - if (!newAppKey.length) - { - CLY_LOG_W(@"New app key is invalid!"); - return; - } - - [self suspend]; - - [CountlyPerformanceMonitoring.sharedInstance clearAllCustomTraces]; - - CountlyConnectionManager.sharedInstance.appKey = newAppKey; - - [self resume]; -} - -- (void)setCustomHeaderFieldValue:(NSString *)customHeaderFieldValue -{ - CLY_LOG_W(@"setCustomHeaderFieldValue: method is deprecated. Please use `URLSessionConfiguration` property on `CountlyConfig` instead."); -} - -- (void)flushQueues -{ - CLY_LOG_I(@"%s", __FUNCTION__); - - [CountlyPersistency.sharedInstance flushEvents]; - [CountlyPersistency.sharedInstance flushQueue]; -} - -- (void)replaceAllAppKeysInQueueWithCurrentAppKey -{ - CLY_LOG_I(@"%s", __FUNCTION__); - - [CountlyPersistency.sharedInstance replaceAllAppKeysInQueueWithCurrentAppKey]; -} - -- (void)removeDifferentAppKeysFromQueue -{ - CLY_LOG_I(@"%s", __FUNCTION__); - - [CountlyPersistency.sharedInstance removeDifferentAppKeysFromQueue]; -} - -#pragma mark --- - -- (void)beginSession -{ - CLY_LOG_I(@"%s", __FUNCTION__); - - if (CountlyCommon.sharedInstance.manualSessionHandling) - [CountlyConnectionManager.sharedInstance beginSession]; -} - -- (void)updateSession -{ - CLY_LOG_I(@"%s", __FUNCTION__); - - if (CountlyCommon.sharedInstance.manualSessionHandling) - [CountlyConnectionManager.sharedInstance updateSession]; -} - -- (void)endSession -{ - CLY_LOG_I(@"%s", __FUNCTION__); - - if (CountlyCommon.sharedInstance.manualSessionHandling) - [CountlyConnectionManager.sharedInstance endSession]; -} - -#pragma mark --- +#pragma mark - - (void)onTimer:(NSTimer *)timer { @@ -344,8 +263,6 @@ - (void)resume isSuspended = NO; } -#pragma mark --- - - (void)applicationDidEnterBackground:(NSNotification *)notification { CLY_LOG_D(@"App did enter background."); @@ -385,6 +302,110 @@ - (void)dealloc } +#pragma mark - Override Configuration + +- (void)setNewHost:(NSString *)newHost +{ + CLY_LOG_I(@"%s %@", __FUNCTION__, newHost); + + if (!newHost.length) + { + CLY_LOG_W(@"New host is invalid!"); + return; + } + + CountlyConnectionManager.sharedInstance.host = newHost; +} + +- (void)setNewURLSessionConfiguration:(NSURLSessionConfiguration *)newURLSessionConfiguration +{ + CLY_LOG_I(@"%s %@", __FUNCTION__, newURLSessionConfiguration); + + CountlyConnectionManager.sharedInstance.URLSessionConfiguration = newURLSessionConfiguration; +} + +- (void)setNewAppKey:(NSString *)newAppKey +{ + CLY_LOG_I(@"%s %@", __FUNCTION__, newAppKey); + + if (!newAppKey.length) + { + CLY_LOG_W(@"New app key is invalid!"); + return; + } + + [self suspend]; + + [CountlyPerformanceMonitoring.sharedInstance clearAllCustomTraces]; + + CountlyConnectionManager.sharedInstance.appKey = newAppKey; + + [self resume]; +} + + + +#pragma mark - Queue Operations + +- (void)flushQueues +{ + CLY_LOG_I(@"%s", __FUNCTION__); + + [CountlyPersistency.sharedInstance flushEvents]; + [CountlyPersistency.sharedInstance flushQueue]; +} + +- (void)replaceAllAppKeysInQueueWithCurrentAppKey +{ + CLY_LOG_I(@"%s", __FUNCTION__); + + [CountlyPersistency.sharedInstance replaceAllAppKeysInQueueWithCurrentAppKey]; +} + +- (void)removeDifferentAppKeysFromQueue +{ + CLY_LOG_I(@"%s", __FUNCTION__); + + [CountlyPersistency.sharedInstance removeDifferentAppKeysFromQueue]; +} + +- (void)addDirectRequest:(NSDictionary * _Nullable)requestParameters +{ + CLY_LOG_I(@"%s %@", __FUNCTION__, requestParameters); + + [CountlyConnectionManager.sharedInstance addDirectRequest:requestParameters]; +} + + + +#pragma mark - Sessions + +- (void)beginSession +{ + CLY_LOG_I(@"%s", __FUNCTION__); + + if (CountlyCommon.sharedInstance.manualSessionHandling) + [CountlyConnectionManager.sharedInstance beginSession]; +} + +- (void)updateSession +{ + CLY_LOG_I(@"%s", __FUNCTION__); + + if (CountlyCommon.sharedInstance.manualSessionHandling) + [CountlyConnectionManager.sharedInstance updateSession]; +} + +- (void)endSession +{ + CLY_LOG_I(@"%s", __FUNCTION__); + + if (CountlyCommon.sharedInstance.manualSessionHandling) + [CountlyConnectionManager.sharedInstance endSession]; +} + + + #pragma mark - Device ID @@ -647,7 +668,7 @@ - (void)recordEvent:(NSString *)key segmentation:(NSDictionary *)segmentation co [CountlyPersistency.sharedInstance recordEvent:event]; } -#pragma mark --- +#pragma mark - - (void)startEvent:(NSString *)key { @@ -764,63 +785,71 @@ - (void)recordLocation:(CLLocationCoordinate2D)location city:(NSString * _Nullab [CountlyLocationManager.sharedInstance recordLocation:location city:city ISOCountryCode:ISOCountryCode IP:IP]; } -- (void)recordLocation:(CLLocationCoordinate2D)location +- (void)disableLocationInfo { - CLY_LOG_W(@"recordLocation: method is deprecated. Please use recordLocation:city:countryCode:IP: method instead."); + CLY_LOG_I(@"%s", __FUNCTION__); - [CountlyLocationManager.sharedInstance recordLocation:location city:nil ISOCountryCode:nil IP:nil]; + [CountlyLocationManager.sharedInstance disableLocationInfo]; } -- (void)recordCity:(NSString *)city andISOCountryCode:(NSString *)ISOCountryCode -{ - CLY_LOG_W(@"recordCity:andISOCountryCode: method is deprecated. Please use recordLocation:city:countryCode:IP: method instead."); - if (!city.length && !ISOCountryCode.length) - return; - [CountlyLocationManager.sharedInstance recordLocation:kCLLocationCoordinate2DInvalid city:city ISOCountryCode:ISOCountryCode IP:nil]; -} +#pragma mark - Crash Reporting -- (void)recordIP:(NSString *)IP +- (void)recordException:(NSException *)exception { - CLY_LOG_W(@"recordIP: method is deprecated. Please use recordLocation:city:countryCode:IP: method instead."); + CLY_LOG_I(@"%s %@", __FUNCTION__, exception); - if (!IP.length) - return; + [CountlyCrashReporter.sharedInstance recordException:exception isFatal:NO stackTrace:nil segmentation:nil]; +} + +- (void)recordException:(NSException *)exception isFatal:(BOOL)isFatal +{ + CLY_LOG_I(@"%s %@ %d", __FUNCTION__, exception, isFatal); - [CountlyLocationManager.sharedInstance recordLocation:kCLLocationCoordinate2DInvalid city:nil ISOCountryCode:nil IP:IP]; + [CountlyCrashReporter.sharedInstance recordException:exception isFatal:isFatal stackTrace:nil segmentation:nil]; } -- (void)disableLocationInfo +- (void)recordException:(NSException *)exception isFatal:(BOOL)isFatal stackTrace:(NSArray *)stackTrace segmentation:(NSDictionary *)segmentation { - CLY_LOG_I(@"%s", __FUNCTION__); + CLY_LOG_I(@"%s %@ %d %@ %@", __FUNCTION__, exception, isFatal, stackTrace, segmentation); - [CountlyLocationManager.sharedInstance disableLocationInfo]; + [CountlyCrashReporter.sharedInstance recordException:exception isFatal:isFatal stackTrace:stackTrace segmentation:segmentation]; } +- (void)recordError:(NSString *)errorName stackTrace:(NSArray * _Nullable)stackTrace +{ + CLY_LOG_I(@"%s %@ %@", __FUNCTION__, errorName, stackTrace); + [CountlyCrashReporter.sharedInstance recordError:errorName isFatal:NO stackTrace:stackTrace segmentation:nil]; +} -#pragma mark - Crash Reporting +- (void)recordError:(NSString *)errorName isFatal:(BOOL)isFatal stackTrace:(NSArray * _Nullable)stackTrace segmentation:(NSDictionary * _Nullable)segmentation +{ + CLY_LOG_I(@"%s %@ %d %@ %@", __FUNCTION__, errorName, isFatal, stackTrace, segmentation); + + [CountlyCrashReporter.sharedInstance recordError:errorName isFatal:isFatal stackTrace:stackTrace segmentation:segmentation]; +} - (void)recordHandledException:(NSException *)exception { CLY_LOG_I(@"%s %@", __FUNCTION__, exception); - [CountlyCrashReporter.sharedInstance recordException:exception withStackTrace:nil isFatal:NO]; + [CountlyCrashReporter.sharedInstance recordException:exception isFatal:NO stackTrace:nil segmentation:nil]; } - (void)recordHandledException:(NSException *)exception withStackTrace:(NSArray *)stackTrace { CLY_LOG_I(@"%s %@ %@", __FUNCTION__, exception, stackTrace); - [CountlyCrashReporter.sharedInstance recordException:exception withStackTrace:stackTrace isFatal:NO]; + [CountlyCrashReporter.sharedInstance recordException:exception isFatal:NO stackTrace:stackTrace segmentation:nil]; } - (void)recordUnhandledException:(NSException *)exception withStackTrace:(NSArray * _Nullable)stackTrace { CLY_LOG_I(@"%s %@ %@", __FUNCTION__, exception, stackTrace); - [CountlyCrashReporter.sharedInstance recordException:exception withStackTrace:stackTrace isFatal:YES]; + [CountlyCrashReporter.sharedInstance recordException:exception isFatal:YES stackTrace:stackTrace segmentation:nil]; } - (void)recordCrashLog:(NSString *)log @@ -897,24 +926,6 @@ + (CountlyUserDetails *)user return CountlyUserDetails.sharedInstance; } -- (void)userLoggedIn:(NSString *)userID -{ - CLY_LOG_I(@"%s %@", __FUNCTION__, userID); - - CLY_LOG_W(@"userLoggedIn: method is deprecated. Please directly use setNewDeviceID:onServer: method instead."); - - [self setNewDeviceID:userID onServer:YES]; -} - -- (void)userLoggedOut -{ - CLY_LOG_I(@"%s", __FUNCTION__); - - CLY_LOG_W(@"userLoggedOut method is deprecated. Please directly use setNewDeviceID:onServer: method instead."); - - [self setNewDeviceID:CLYDefaultDeviceID onServer:NO]; -} - #pragma mark - Star Rating diff --git a/src/ios/CountlyiOS/Countly.podspec b/src/ios/CountlyiOS/Countly.podspec index c620b95..85d3500 100644 --- a/src/ios/CountlyiOS/Countly.podspec +++ b/src/ios/CountlyiOS/Countly.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'Countly' - s.version = '21.11.2' + s.version = '22.09.0' s.license = { :type => 'MIT', :file => 'LICENSE.md' } s.summary = 'Countly is an innovative, real-time, open source mobile analytics platform.' s.homepage = 'https://github.com/Countly/countly-sdk-ios' diff --git a/src/ios/CountlyiOS/CountlyCommon.h b/src/ios/CountlyiOS/CountlyCommon.h index 94e1a61..928f9d9 100644 --- a/src/ios/CountlyiOS/CountlyCommon.h +++ b/src/ios/CountlyiOS/CountlyCommon.h @@ -31,6 +31,7 @@ #if (TARGET_OS_IOS) #import +#import #endif #if (TARGET_OS_WATCH) @@ -104,6 +105,7 @@ void CountlyPrint(NSString *stringToPrint); #if (TARGET_OS_IOS) @interface CLYInternalViewController : UIViewController +@property (nonatomic, weak) WKWebView* webView; @end @interface CLYButton : UIButton diff --git a/src/ios/CountlyiOS/CountlyCommon.m b/src/ios/CountlyiOS/CountlyCommon.m index 3985178..5a9f1cd 100644 --- a/src/ios/CountlyiOS/CountlyCommon.m +++ b/src/ios/CountlyiOS/CountlyCommon.m @@ -26,7 +26,7 @@ @interface CountlyCommon () #endif @end -NSString* const kCountlySDKVersion = @"21.11.2"; +NSString* const kCountlySDKVersion = @"22.09.0"; NSString* const kCountlySDKName = @"objc-native-ios"; NSString* const kCountlyErrorDomain = @"ly.count.ErrorDomain"; @@ -293,6 +293,28 @@ - (void)tryPresentingViewController:(UIViewController *)viewController withCompl #if (TARGET_OS_IOS) @implementation CLYInternalViewController : UIViewController +- (void)viewWillLayoutSubviews +{ + [super viewWillLayoutSubviews]; + + if (self.webView) + { + CGRect frame = CGRectInset(self.view.bounds, 20.0, 20.0); + + UIEdgeInsets insets = UIEdgeInsetsZero; + if (@available(iOS 11.0, *)) + { + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" + insets = UIApplication.sharedApplication.keyWindow.safeAreaInsets; + #pragma GCC diagnostic pop + } + + frame = UIEdgeInsetsInsetRect(frame, insets); + self.webView.frame = frame; + } +} + @end diff --git a/src/ios/CountlyiOS/CountlyConfig.h b/src/ios/CountlyiOS/CountlyConfig.h index bb40b7e..bd144c5 100644 --- a/src/ios/CountlyiOS/CountlyConfig.h +++ b/src/ios/CountlyiOS/CountlyConfig.h @@ -7,6 +7,11 @@ #import #import +#if (TARGET_OS_IOS || TARGET_OS_TV) +#import +#endif + + NS_ASSUME_NONNULL_BEGIN //NOTE: Countly features @@ -65,8 +70,6 @@ extern CLYConsent const CLYConsentPushNotifications; extern CLYConsent const CLYConsentLocation; extern CLYConsent const CLYConsentViewTracking; extern CLYConsent const CLYConsentAttribution; -extern CLYConsent const CLYConsentStarRating DEPRECATED_MSG_ATTRIBUTE("Please use CLYConsentFeedback instead!"); -extern CLYConsent const CLYConsentAppleWatch DEPRECATED_MSG_ATTRIBUTE("As automatic metrics for Apple Watch is not supported anymore, 'CLYConsentAppleWatch' is now inoperative!"); extern CLYConsent const CLYConsentPerformanceMonitoring; extern CLYConsent const CLYConsentFeedback; extern CLYConsent const CLYConsentRemoteConfig; @@ -109,8 +112,12 @@ typedef enum : NSUInteger @protocol CountlyLoggerDelegate - (void)internalLog:(NSString *)log withLevel:(CLYInternalLogLevel)level; +@end + + +@protocol CountlyAutoViewTrackingName @optional -- (void)internalLog:(NSString *)log DEPRECATED_MSG_ATTRIBUTE("Use 'internalLog:withLevel:' method instead!"); +- (NSString *)countlyAutoViewTrackingName; @end @@ -353,13 +360,6 @@ typedef enum : NSUInteger */ @property (nonatomic) BOOL manualSessionHandling; -/** - * @c enableAppleWatch property is deprecated. - * @discussion As automatic metrics for Apple Watch is not supported anymore, @c enableAppleWatch is now inoperative. - * @discussion Using this property will have no effect. - */ -@property (nonatomic) BOOL enableAppleWatch DEPRECATED_MSG_ATTRIBUTE("As automatic metrics for Apple Watch is not supported anymore, 'enableAppleWatch' is now inoperative!"); - #pragma mark - /** @@ -386,12 +386,6 @@ typedef enum : NSUInteger */ @property (nonatomic, copy) NSDictionary * indirectAttribution; -/** - * @c enableAttribution property is deprecated. Please use @c recordAttributionID method instead. - * @discussion Using this property will have no effect. - */ -@property (nonatomic) BOOL enableAttribution DEPRECATED_MSG_ATTRIBUTE("Use 'attributionID' property instead!"); - #pragma mark - /** @@ -459,18 +453,6 @@ typedef enum : NSUInteger */ @property (nonatomic, copy) NSArray* pinnedCertificates; -/** - * @c customHeaderFieldName property is deprecated. Please use @c URLSessionConfiguration property instead. - * @discussion Using this property will have no effect. - */ -@property (nonatomic, copy) NSString* customHeaderFieldName DEPRECATED_MSG_ATTRIBUTE("Use 'URLSessionConfiguration' property instead!"); - -/** - * @c customHeaderFieldValue property is deprecated. Please use @c URLSessionConfiguration property instead. - * @discussion Using this property will have no effect. - */ -@property (nonatomic, copy) NSString* customHeaderFieldValue DEPRECATED_MSG_ATTRIBUTE("Use 'URLSessionConfiguration' property instead!"); - /** * Salt value to be used for parameter tampering protection. * @discussion If set, every request sent to Countly Server will have @c checksum256 value generated by SHA256(request + secretSalt) diff --git a/src/ios/CountlyiOS/CountlyConfig.m b/src/ios/CountlyiOS/CountlyConfig.m index cfc0b0f..0d8f8d5 100644 --- a/src/ios/CountlyiOS/CountlyConfig.m +++ b/src/ios/CountlyiOS/CountlyConfig.m @@ -34,11 +34,6 @@ @implementation CountlyConfig CLYDeviceIDType const CLYDeviceIDTypeIDFV = @"CLYDeviceIDTypeIDFV"; CLYDeviceIDType const CLYDeviceIDTypeNSUUID = @"CLYDeviceIDTypeNSUUID"; -//NOTE: Legacy device ID options. They will fallback to default device ID. -NSString* const CLYIDFA = CLYDefaultDeviceID; -NSString* const CLYIDFV = CLYDefaultDeviceID; -NSString* const CLYOpenUDID = CLYDefaultDeviceID; - - (instancetype)init { if (self = [super init]) diff --git a/src/ios/CountlyiOS/CountlyConnectionManager.h b/src/ios/CountlyiOS/CountlyConnectionManager.h index b8f3b07..981cdcb 100644 --- a/src/ios/CountlyiOS/CountlyConnectionManager.h +++ b/src/ios/CountlyiOS/CountlyConnectionManager.h @@ -8,6 +8,7 @@ extern NSString* const kCountlyQSKeyAppKey; extern NSString* const kCountlyQSKeyDeviceID; +extern NSString* const kCountlyQSKeyDeviceIDType; extern NSString* const kCountlyQSKeySDKVersion; extern NSString* const kCountlyQSKeySDKName; extern NSString* const kCountlyQSKeyMethod; @@ -53,6 +54,8 @@ extern const NSInteger kCountlyGETRequestMaxLength; - (void)sendConsents:(NSString *)consents; - (void)sendPerformanceMonitoringTrace:(NSString *)trace; +- (void)addDirectRequest:(NSDictionary *)requestParameters; + - (void)proceedOnQueue; - (NSString *)queryEssentials; diff --git a/src/ios/CountlyiOS/CountlyConnectionManager.m b/src/ios/CountlyiOS/CountlyConnectionManager.m index 6182dab..5c56243 100644 --- a/src/ios/CountlyiOS/CountlyConnectionManager.m +++ b/src/ios/CountlyiOS/CountlyConnectionManager.m @@ -19,6 +19,7 @@ @interface CountlyConnectionManager () NSString* const kCountlyQSKeyDeviceID = @"device_id"; NSString* const kCountlyQSKeyDeviceIDOld = @"old_device_id"; +NSString* const kCountlyQSKeyDeviceIDType = @"t"; NSString* const kCountlyQSKeyTimestamp = @"timestamp"; NSString* const kCountlyQSKeyTimeZone = @"tz"; @@ -109,6 +110,16 @@ - (void)setHost:(NSString *)host } } +- (void)setURLSessionConfiguration:(NSURLSessionConfiguration *)URLSessionConfiguration +{ + if (URLSessionConfiguration != nil) + { + _URLSessionConfiguration = URLSessionConfiguration; + _URLSession = nil; + } +} + + - (void)proceedOnQueue { CLY_LOG_D(@"Proceeding on queue..."); @@ -497,19 +508,71 @@ - (void)sendPerformanceMonitoringTrace:(NSString *)trace #pragma mark --- +- (void)addDirectRequest:(NSDictionary *)requestParameters +{ + if (!CountlyConsentManager.sharedInstance.hasAnyConsent) + return; + + NSMutableDictionary* mutableRequestParameters = requestParameters.mutableCopy; + + for (NSString * reservedKey in self.reservedQueryStringKeys) + { + if (mutableRequestParameters[reservedKey]) + { + CLY_LOG_W(@"A reserved query string key detected in direct request parameters and it will be removed: %@", reservedKey); + [mutableRequestParameters removeObjectForKey:reservedKey]; + } + } + + NSMutableString* queryString = [self queryEssentials].mutableCopy; + + [mutableRequestParameters enumerateKeysAndObjectsUsingBlock:^(NSString * key, NSString * value, BOOL * stop) + { + [queryString appendFormat:@"&%@=%@", key, value]; + }]; + + [CountlyPersistency.sharedInstance addToQueue:queryString.copy]; + + [self proceedOnQueue]; +} + +#pragma mark --- + - (NSString *)queryEssentials { - return [NSString stringWithFormat:@"%@=%@&%@=%@&%@=%lld&%@=%d&%@=%d&%@=%d&%@=%@&%@=%@", - kCountlyQSKeyAppKey, self.appKey.cly_URLEscaped, - kCountlyQSKeyDeviceID, CountlyDeviceInfo.sharedInstance.deviceID.cly_URLEscaped, - kCountlyQSKeyTimestamp, (long long)(CountlyCommon.sharedInstance.uniqueTimestamp * 1000), - kCountlyQSKeyTimeHourOfDay, (int)CountlyCommon.sharedInstance.hourOfDay, - kCountlyQSKeyTimeDayOfWeek, (int)CountlyCommon.sharedInstance.dayOfWeek, - kCountlyQSKeyTimeZone, (int)CountlyCommon.sharedInstance.timeZone, - kCountlyQSKeySDKVersion, CountlyCommon.sharedInstance.SDKVersion, - kCountlyQSKeySDKName, CountlyCommon.sharedInstance.SDKName]; + return [NSString stringWithFormat:@"%@=%@&%@=%@&%@=%d&%@=%lld&%@=%d&%@=%d&%@=%d&%@=%@&%@=%@", + kCountlyQSKeyAppKey, self.appKey.cly_URLEscaped, + kCountlyQSKeyDeviceID, CountlyDeviceInfo.sharedInstance.deviceID.cly_URLEscaped, + kCountlyQSKeyDeviceIDType, (int)CountlyDeviceInfo.sharedInstance.deviceIDTypeValue, + kCountlyQSKeyTimestamp, (long long)(CountlyCommon.sharedInstance.uniqueTimestamp * 1000), + kCountlyQSKeyTimeHourOfDay, (int)CountlyCommon.sharedInstance.hourOfDay, + kCountlyQSKeyTimeDayOfWeek, (int)CountlyCommon.sharedInstance.dayOfWeek, + kCountlyQSKeyTimeZone, (int)CountlyCommon.sharedInstance.timeZone, + kCountlyQSKeySDKVersion, CountlyCommon.sharedInstance.SDKVersion, + kCountlyQSKeySDKName, CountlyCommon.sharedInstance.SDKName]; +} + + +- (NSArray *)reservedQueryStringKeys +{ + return + @[ + kCountlyQSKeyAppKey, + kCountlyQSKeyDeviceID, + kCountlyQSKeyDeviceIDType, + kCountlyQSKeyTimestamp, + kCountlyQSKeyTimeHourOfDay, + kCountlyQSKeyTimeDayOfWeek, + kCountlyQSKeyTimeZone, + kCountlyQSKeySDKVersion, + kCountlyQSKeySDKName, + kCountlyQSKeyDeviceID, + kCountlyQSKeyDeviceIDOld, + kCountlyQSKeyChecksum256, + ]; } + - (NSString *)locationRelatedInfoQueryString { if (!CountlyConsentManager.sharedInstance.consentForLocation || CountlyLocationManager.sharedInstance.isLocationInfoDisabled) diff --git a/src/ios/CountlyiOS/CountlyConsentManager.h b/src/ios/CountlyiOS/CountlyConsentManager.h index ec6314b..9e7dd0c 100644 --- a/src/ios/CountlyiOS/CountlyConsentManager.h +++ b/src/ios/CountlyiOS/CountlyConsentManager.h @@ -28,4 +28,6 @@ - (void)cancelConsentForFeatures:(NSArray *)features; - (void)cancelConsentForAllFeatures; - (void)cancelConsentForAllFeaturesWithoutSendingConsentsRequest; +- (BOOL)hasAnyConsent; + @end diff --git a/src/ios/CountlyiOS/CountlyConsentManager.m b/src/ios/CountlyiOS/CountlyConsentManager.m index cb9c73b..9163d51 100644 --- a/src/ios/CountlyiOS/CountlyConsentManager.m +++ b/src/ios/CountlyiOS/CountlyConsentManager.m @@ -14,8 +14,6 @@ CLYConsent const CLYConsentLocation = @"location"; CLYConsent const CLYConsentViewTracking = @"views"; CLYConsent const CLYConsentAttribution = @"attribution"; -CLYConsent const CLYConsentStarRating = @"star-rating"; -CLYConsent const CLYConsentAppleWatch = @"accessory-devices"; CLYConsent const CLYConsentPerformanceMonitoring = @"apm"; CLYConsent const CLYConsentFeedback = @"feedback"; CLYConsent const CLYConsentRemoteConfig = @"remote-config"; @@ -81,6 +79,9 @@ - (void)giveConsentForFeatures:(NSArray *)features //NOTE: Otherwise, if location consent is given after sessions consent, begin_session request will be sent with an empty string as location. if ([features containsObject:CLYConsentLocation] && !self.consentForLocation) self.consentForLocation = YES; + + if ([features containsObject:CLYConsentUserDetails] && !self.consentForUserDetails) + self.consentForUserDetails = YES; if ([features containsObject:CLYConsentSessions] && !self.consentForSessions) self.consentForSessions = YES; @@ -88,9 +89,6 @@ - (void)giveConsentForFeatures:(NSArray *)features if ([features containsObject:CLYConsentEvents] && !self.consentForEvents) self.consentForEvents = YES; - if ([features containsObject:CLYConsentUserDetails] && !self.consentForUserDetails) - self.consentForUserDetails = YES; - if ([features containsObject:CLYConsentCrashReporting] && !self.consentForCrashReporting) self.consentForCrashReporting = YES; @@ -106,7 +104,7 @@ - (void)giveConsentForFeatures:(NSArray *)features if ([features containsObject:CLYConsentPerformanceMonitoring] && !self.consentForPerformanceMonitoring) self.consentForPerformanceMonitoring = YES; - if ([self containsFeedbackOrStarRating:features] && !self.consentForFeedback) + if ([features containsObject:CLYConsentFeedback] && !self.consentForFeedback) self.consentForFeedback = YES; if ([features containsObject:CLYConsentRemoteConfig] && !self.consentForRemoteConfig) @@ -166,7 +164,7 @@ - (void)cancelConsentForFeatures:(NSArray *)features shouldSkipSendingConsentsRe if ([features containsObject:CLYConsentPerformanceMonitoring] && self.consentForPerformanceMonitoring) self.consentForPerformanceMonitoring = NO; - if ([self containsFeedbackOrStarRating:features] && self.consentForFeedback) + if ([features containsObject:CLYConsentFeedback] && self.consentForFeedback) self.consentForFeedback = NO; if ([features containsObject:CLYConsentRemoteConfig] && self.consentForRemoteConfig) @@ -191,6 +189,7 @@ - (void)sendConsents CLYConsentAttribution: @(self.consentForAttribution), CLYConsentPerformanceMonitoring: @(self.consentForPerformanceMonitoring), CLYConsentFeedback: @(self.consentForFeedback), + CLYConsentRemoteConfig: @(self.consentForRemoteConfig), }; [CountlyConnectionManager.sharedInstance sendConsents:[consents cly_JSONify]]; @@ -211,18 +210,28 @@ - (NSArray *)allFeatures CLYConsentAttribution, CLYConsentPerformanceMonitoring, CLYConsentFeedback, + CLYConsentRemoteConfig, ]; } -- (BOOL)containsFeedbackOrStarRating:(NSArray *)features + +- (BOOL)hasAnyConsent { - //NOTE: StarRating consent is merged into new Feedback consent. -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - return [features containsObject:CLYConsentFeedback] || [features containsObject:CLYConsentStarRating]; -#pragma GCC diagnostic pop + return + self.consentForSessions || + self.consentForEvents || + self.consentForUserDetails || + self.consentForCrashReporting || + self.consentForPushNotifications || + self.consentForLocation || + self.consentForViewTracking || + self.consentForAttribution || + self.consentForPerformanceMonitoring || + self.consentForFeedback || + self.consentForRemoteConfig; } + #pragma mark - @@ -269,6 +278,7 @@ - (void)setConsentForUserDetails:(BOOL)consentForUserDetails if (consentForUserDetails) { CLY_LOG_D(@"Consent for UserDetails is given."); + [Countly.user save]; } else { diff --git a/src/ios/CountlyiOS/CountlyCrashReporter.h b/src/ios/CountlyiOS/CountlyCrashReporter.h index e18b9d7..7e0d3fd 100644 --- a/src/ios/CountlyiOS/CountlyCrashReporter.h +++ b/src/ios/CountlyiOS/CountlyCrashReporter.h @@ -19,7 +19,8 @@ + (instancetype)sharedInstance; - (void)startCrashReporting; - (void)stopCrashReporting; -- (void)recordException:(NSException *)exception withStackTrace:(NSArray *)stackTrace isFatal:(BOOL)isFatal; +- (void)recordException:(NSException *)exception isFatal:(BOOL)isFatal stackTrace:(NSArray *)stackTrace segmentation:(NSDictionary *)segmentation; +- (void)recordError:(NSString *)errorName isFatal:(BOOL)isFatal stackTrace:(NSArray *)stackTrace segmentation:(NSDictionary *)segmentation; - (void)log:(NSString *)log; - (void)clearCrashLogs; @end diff --git a/src/ios/CountlyiOS/CountlyCrashReporter.m b/src/ios/CountlyiOS/CountlyCrashReporter.m index 91c06e7..8d63fa1 100644 --- a/src/ios/CountlyiOS/CountlyCrashReporter.m +++ b/src/ios/CountlyiOS/CountlyCrashReporter.m @@ -17,6 +17,7 @@ NSString* const kCountlyExceptionUserInfoBacktraceKey = @"kCountlyExceptionUserInfoBacktraceKey"; NSString* const kCountlyExceptionUserInfoSignalCodeKey = @"kCountlyExceptionUserInfoSignalCodeKey"; +NSString* const kCountlyExceptionUserInfoSegmentationOverrideKey = @"kCountlyExceptionUserInfoSegmentationOverrideKey"; NSString* const kCountlyCRKeyBinaryImages = @"_binary_images"; NSString* const kCountlyCRKeyOS = @"_os"; @@ -208,21 +209,28 @@ - (void)handlePendingCrashReport #endif -- (void)recordException:(NSException *)exception withStackTrace:(NSArray *)stackTrace isFatal:(BOOL)isFatal +- (void)recordException:(NSException *)exception isFatal:(BOOL)isFatal stackTrace:(NSArray *)stackTrace segmentation:(NSDictionary *)segmentation { if (!CountlyConsentManager.sharedInstance.consentForCrashReporting) return; - if (stackTrace) + if (stackTrace || segmentation) { NSMutableDictionary* userInfo = [NSMutableDictionary dictionaryWithDictionary:exception.userInfo]; userInfo[kCountlyExceptionUserInfoBacktraceKey] = stackTrace; + userInfo[kCountlyExceptionUserInfoSegmentationOverrideKey] = segmentation; exception = [NSException exceptionWithName:exception.name reason:exception.reason userInfo:userInfo]; } CountlyExceptionHandler(exception, isFatal, false); } +- (void)recordError:(NSString *)errorName isFatal:(BOOL)isFatal stackTrace:(NSArray *)stackTrace segmentation:(NSDictionary *)segmentation +{ + NSException* exception = [NSException exceptionWithName:@"Swift Error" reason:errorName userInfo:nil]; + [self recordException:exception isFatal:isFatal stackTrace:stackTrace segmentation:segmentation]; +} + void CountlyUncaughtExceptionHandler(NSException *exception) { CountlyExceptionHandler(exception, true, true); @@ -276,9 +284,14 @@ void CountlyExceptionHandler(NSException *exception, bool isFatal, bool isAutoDe if (CountlyCrashReporter.sharedInstance.crashSegmentation) [custom addEntriesFromDictionary:CountlyCrashReporter.sharedInstance.crashSegmentation]; + NSDictionary* segmentationOverride = exception.userInfo[kCountlyExceptionUserInfoSegmentationOverrideKey]; + if (segmentationOverride) + [custom addEntriesFromDictionary:segmentationOverride]; + NSMutableDictionary* userInfo = exception.userInfo.mutableCopy; [userInfo removeObjectForKey:kCountlyExceptionUserInfoBacktraceKey]; [userInfo removeObjectForKey:kCountlyExceptionUserInfoSignalCodeKey]; + [userInfo removeObjectForKey:kCountlyExceptionUserInfoSegmentationOverrideKey]; [custom addEntriesFromDictionary:userInfo]; if (custom.allKeys.count) @@ -474,7 +487,7 @@ - (void)reportException:(NSException *)exception //NOTE: Custom UncaughtExceptionHandler is called with an irrelevant stack trace, not the original crash call stack trace. //NOTE: And system's own UncaughtExceptionHandler handles the exception by just printing it to the Console. //NOTE: So, we intercept the exception here and record manually. - [CountlyCrashReporter.sharedInstance recordException:exception withStackTrace:nil isFatal:NO]; + [CountlyCrashReporter.sharedInstance recordException:exception isFatal:NO stackTrace:nil segmentation:nil]; } - (void)sendEvent:(NSEvent *)theEvent diff --git a/src/ios/CountlyiOS/CountlyDeviceInfo.h b/src/ios/CountlyiOS/CountlyDeviceInfo.h index a406463..cdaaca1 100644 --- a/src/ios/CountlyiOS/CountlyDeviceInfo.h +++ b/src/ios/CountlyiOS/CountlyDeviceInfo.h @@ -8,6 +8,15 @@ @interface CountlyDeviceInfo : NSObject +typedef enum : NSUInteger +{ + CLYDeviceIDTypeValueCustom = 0, + CLYDeviceIDTypeValueIDFV = 1, + CLYDeviceIDTypeValueNSUUID = 2, + CLYDeviceIDTypeValueTemporary = 9 +} CLYDeviceIDTypeValue; + + @property (nonatomic) NSString *deviceID; @property (nonatomic) NSDictionary* customMetrics; @@ -15,6 +24,7 @@ - (void)initializeDeviceID:(NSString *)deviceID; - (NSString *)ensafeDeviceID:(NSString *)deviceID; - (BOOL)isDeviceIDTemporary; +- (CLYDeviceIDTypeValue)deviceIDTypeValue; + (NSString *)device; + (NSString *)architecture; diff --git a/src/ios/CountlyiOS/CountlyDeviceInfo.m b/src/ios/CountlyiOS/CountlyDeviceInfo.m index f3ea34c..5120ddc 100644 --- a/src/ios/CountlyiOS/CountlyDeviceInfo.m +++ b/src/ios/CountlyiOS/CountlyDeviceInfo.m @@ -124,6 +124,27 @@ - (BOOL)isDeviceIDTemporary return [self.deviceID isEqualToString:CLYTemporaryDeviceID]; } +- (CLYDeviceIDTypeValue)deviceIDTypeValue +{ + CLYDeviceIDType deviceIDType = Countly.sharedInstance.deviceIDType; + + if ([deviceIDType isEqual:CLYDeviceIDTypeCustom]) + return CLYDeviceIDTypeValueCustom; + + if ([deviceIDType isEqual:CLYDeviceIDTypeIDFV]) + return CLYDeviceIDTypeValueIDFV; + + if ([deviceIDType isEqual:CLYDeviceIDTypeNSUUID]) + return CLYDeviceIDTypeValueNSUUID; + + if ([deviceIDType isEqual:CLYDeviceIDTypeTemporary]) + return CLYDeviceIDTypeValueTemporary; + + CLY_LOG_E(@"Device ID type is not one of the defined types."); + + return (CLYDeviceIDTypeValue)-1; +} + #pragma mark - + (NSString *)device diff --git a/src/ios/CountlyiOS/CountlyFeedbackWidget.m b/src/ios/CountlyiOS/CountlyFeedbackWidget.m index d694139..c3e7eb1 100644 --- a/src/ios/CountlyiOS/CountlyFeedbackWidget.m +++ b/src/ios/CountlyiOS/CountlyFeedbackWidget.m @@ -53,13 +53,17 @@ - (void)presentWithAppearBlock:(void(^ __nullable)(void))appearBlock andDismissB return; __block CLYInternalViewController* webVC = CLYInternalViewController.new; - webVC.view.backgroundColor = UIColor.whiteColor; - webVC.view.bounds = UIScreen.mainScreen.bounds; + webVC.view.backgroundColor = [UIColor.blackColor colorWithAlphaComponent:0.4]; webVC.modalPresentationStyle = UIModalPresentationCustom; WKWebView* webView = [WKWebView.alloc initWithFrame:webVC.view.bounds]; + webView.layer.shadowColor = UIColor.blackColor.CGColor; + webView.layer.shadowOpacity = 0.5; + webView.layer.shadowOffset = (CGSize){0.0f, 5.0f}; + webView.layer.masksToBounds = NO; webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; [webVC.view addSubview:webView]; + webVC.webView = webView; NSURLRequest* request = [self displayRequest]; [webView loadRequest:request]; @@ -76,8 +80,8 @@ - (void)presentWithAppearBlock:(void(^ __nullable)(void))appearBlock andDismissB [self recordReservedEventForDismissing]; }; - [webVC.view addSubview:dismissButton]; - [dismissButton positionToTopRightConsideringStatusBar]; + [webView addSubview:dismissButton]; + [dismissButton positionToTopRight]; [CountlyCommon.sharedInstance tryPresentingViewController:webVC withCompletion:appearBlock]; } diff --git a/src/ios/CountlyiOS/CountlyPersistency.m b/src/ios/CountlyiOS/CountlyPersistency.m index 3a25ba1..0c9f996 100644 --- a/src/ios/CountlyiOS/CountlyPersistency.m +++ b/src/ios/CountlyiOS/CountlyPersistency.m @@ -111,17 +111,25 @@ - (void)replaceAllTemporaryDeviceIDsInQueueWithDeviceID:(NSString *)deviceID NSString* temporaryDeviceIDQueryString = [NSString stringWithFormat:@"&%@=%@", kCountlyQSKeyDeviceID, CLYTemporaryDeviceID]; NSString* realDeviceIDQueryString = [NSString stringWithFormat:@"&%@=%@", kCountlyQSKeyDeviceID, deviceID.cly_URLEscaped]; + NSString* temporaryDeviceIDTypeQueryString = [NSString stringWithFormat:@"&%@=%d", kCountlyQSKeyDeviceIDType, (int)CLYDeviceIDTypeValueTemporary]; + NSString* realDeviceIDTypeQueryString = [NSString stringWithFormat:@"&%@=%d", kCountlyQSKeyDeviceIDType, (int)CountlyDeviceInfo.sharedInstance.deviceIDTypeValue]; + @synchronized (self) { + self.isQueueBeingModified = YES; + [self.queuedRequests.copy enumerateObjectsUsingBlock:^(NSString* queryString, NSUInteger idx, BOOL* stop) { if ([queryString containsString:temporaryDeviceIDQueryString]) { CLY_LOG_D(@"Detected a request with temporary device ID in queue and replaced it with real device ID."); NSString * replacedQueryString = [queryString stringByReplacingOccurrencesOfString:temporaryDeviceIDQueryString withString:realDeviceIDQueryString]; + replacedQueryString = [replacedQueryString stringByReplacingOccurrencesOfString:temporaryDeviceIDTypeQueryString withString:realDeviceIDTypeQueryString]; self.queuedRequests[idx] = replacedQueryString; } }]; + + self.isQueueBeingModified = NO; } } @@ -469,7 +477,6 @@ - (void)storeNotificationPermission:(BOOL)allowed - (BOOL)retrieveIsCustomDeviceID { return [NSUserDefaults.standardUserDefaults boolForKey:kCountlyIsCustomDeviceIDKey]; - } - (void)storeIsCustomDeviceID:(BOOL)isCustomDeviceID diff --git a/src/ios/CountlyiOS/CountlyViewTracking.m b/src/ios/CountlyiOS/CountlyViewTracking.m index 2e3120b..5599fe6 100644 --- a/src/ios/CountlyiOS/CountlyViewTracking.m +++ b/src/ios/CountlyiOS/CountlyViewTracking.m @@ -28,6 +28,7 @@ @interface CountlyViewTracking () #if (TARGET_OS_IOS || TARGET_OS_TV) @interface UIViewController (CountlyViewTracking) - (void)Countly_viewDidAppear:(BOOL)animated; +- (void)Countly_viewDidDisappear:(BOOL)animated; @end #endif @@ -200,6 +201,9 @@ - (void)swizzleViewTrackingMethods Method O_method = class_getInstanceMethod(UIViewController.class, @selector(viewDidAppear:)); Method C_method = class_getInstanceMethod(UIViewController.class, @selector(Countly_viewDidAppear:)); method_exchangeImplementations(O_method, C_method); + O_method = class_getInstanceMethod(UIViewController.class, @selector(viewDidDisappear:)); + C_method = class_getInstanceMethod(UIViewController.class, @selector(Countly_viewDidDisappear:)); + method_exchangeImplementations(O_method, C_method); } - (void)stopAutoViewTracking @@ -211,6 +215,39 @@ - (void)stopAutoViewTracking self.accumulatedTime = 0; } +- (void)performAutoViewTrackingForViewController:(UIViewController *)viewController +{ + if (!self.isAutoViewTrackingActive) + return; + + if (!CountlyConsentManager.sharedInstance.consentForViewTracking) + return; + + NSString* viewTitle = [self titleForViewController:viewController]; + + if ([self.lastView isEqualToString:viewTitle]) + return; + + BOOL isException = NO; + + for (NSString* exception in self.exceptionViewControllers) + { + isException = [viewTitle isEqualToString:exception] || + [viewController isKindOfClass:NSClassFromString(exception)] || + [NSStringFromClass(viewController.class) isEqualToString:exception]; + + if (isException) + { + CLY_LOG_V(@"%@ is an exceptional view, so it will be ignored for view tracking.", viewTitle); + break; + } + } + + if (!isException) + [self startView:viewTitle]; +} + + #pragma mark - - (void)setIsAutoViewTrackingActive:(BOOL)isAutoViewTrackingActive @@ -247,7 +284,16 @@ - (NSString*)titleForViewController:(UIViewController *)viewController if (!viewController) return nil; - NSString* title = viewController.title; + NSString* title = nil; + + if ([viewController respondsToSelector:@selector(countlyAutoViewTrackingName)]) + { + CLY_LOG_I(@"Viewcontroller conforms to CountlyAutoViewTrackingName protocol for custom auto view tracking name."); + title = [(id)viewController countlyAutoViewTrackingName]; + } + + if (!title) + title = viewController.title; if (!title) title = [viewController.navigationItem.titleView isKindOfClass:UILabel.class] ? ((UILabel *)viewController.navigationItem.titleView).text : nil; @@ -291,34 +337,64 @@ - (void)Countly_viewDidAppear:(BOOL)animated { [self Countly_viewDidAppear:animated]; - if (!CountlyViewTracking.sharedInstance.isAutoViewTrackingActive) - return; - - if (!CountlyConsentManager.sharedInstance.consentForViewTracking) - return; + [CountlyViewTracking.sharedInstance performAutoViewTrackingForViewController:self]; - NSString* viewTitle = [CountlyViewTracking.sharedInstance titleForViewController:self]; + if (self.isPageSheetModal) + { + //NOTE: Since iOS 13, modals with PageSheet presentation style + // does not trigger `viewDidAppear` on presenting view controller when they are dismissed. + // Also, `self.presentingViewController` property is nil in both `viewWillDisappear` and `viewDidDisappear`. + // So, we store it here in modal's `viewDidAppear` to be used for view tracking later. + CLY_LOG_I(@"A modal view controller with PageSheet presentation style is presented on iOS 13+. Storing presenting view controller to be used later."); + + UIViewController* presenting = self.presentingViewController; + if ([presenting isKindOfClass:UINavigationController.class]) + { + presenting = ((UINavigationController *)presenting).topViewController; + } - if ([CountlyViewTracking.sharedInstance.lastView isEqualToString:viewTitle]) - return; + self.presentingVC = presenting; + } +} - BOOL isException = NO; +- (void)Countly_viewDidDisappear:(BOOL)animated +{ + [self Countly_viewDidDisappear:animated]; - for (NSString* exception in CountlyViewTracking.sharedInstance.exceptionViewControllers) + if (self.presentingVC) { - isException = [viewTitle isEqualToString:exception] || - [self isKindOfClass:NSClassFromString(exception)] || - [NSStringFromClass(self.class) isEqualToString:exception]; + CLY_LOG_I(@"A modal view controller with PageSheet presentation style is dismissed on iOS 13+. Forcing auto view tracking with stored presenting view controller."); + [CountlyViewTracking.sharedInstance performAutoViewTrackingForViewController:self.presentingVC]; + self.presentingVC = nil; + } +} - if (isException) +- (BOOL)isPageSheetModal +{ + //NOTE: iOS 13 check is not related to availability of UIModalPresentationPageSheet, + // but needed due to behavioral difference in presenting logic compared to previous iOS versions. +#if (TARGET_OS_IOS) + if (@available(iOS 13.0, *)) + { + if (self.modalPresentationStyle == UIModalPresentationPageSheet && self.isBeingPresented) { - CLY_LOG_V(@"%@ is an exceptional view, so it will be ignored for view tracking.", viewTitle); - break; + return YES; } } +#endif - if (!isException) - [CountlyViewTracking.sharedInstance startView:viewTitle]; + return NO; } + +- (void)setPresentingVC:(UIViewController *)presentingVC +{ + objc_setAssociatedObject(self, @selector(presentingVC), presentingVC, OBJC_ASSOCIATION_ASSIGN); +} + +- (UIViewController *)presentingVC +{ + return objc_getAssociatedObject(self, @selector(presentingVC)); +} + @end #endif diff --git a/src/ios/CountlyiOS/Package.swift b/src/ios/CountlyiOS/Package.swift index 216f05f..a37185d 100644 --- a/src/ios/CountlyiOS/Package.swift +++ b/src/ios/CountlyiOS/Package.swift @@ -8,10 +8,10 @@ let package = Package( platforms: [ - .iOS(.v8), - .macOS(.v10_10), - .tvOS(.v9), - .watchOS(.v2) + .iOS(.v10), + .macOS(.v10_14), + .tvOS(.v10), + .watchOS(.v4) ], products: @@ -32,10 +32,12 @@ let package = Package( [ "Info.plist", "Countly.podspec", + "Countly-PL.podspec", "LICENSE.md", "README.md", "countly_dsym_uploader.sh", - "CHANGELOG.md" + "CHANGELOG.md", + "SECURITY.md" ], linkerSettings: diff --git a/src/ios/CountlyiOS/README.md b/src/ios/CountlyiOS/README.md index 02a7fff..4212a58 100644 --- a/src/ios/CountlyiOS/README.md +++ b/src/ios/CountlyiOS/README.md @@ -1,59 +1,58 @@ +[![Codacy Badge](https://app.codacy.com/project/badge/Grade/68dc667ef1e6465e99fa9d4b2ee56e58)](https://www.codacy.com/gh/Countly/countly-sdk-ios/dashboard?utm_source=github.com&utm_medium=referral&utm_content=Countly/countly-sdk-ios&utm_campaign=Badge_Grade) + # Countly iOS SDK -[![Platform](https://img.shields.io/cocoapods/p/Countly.svg?style=flat)](https://support.count.ly/hc/en-us/articles/360037753511-iOS-watchOS-tvOS-macOS#supported-system-versions) -[![GitHub license](https://img.shields.io/badge/license-MIT-lightgrey.svg)](https://github.com/Countly/countly-sdk-ios/blob/master/LICENSE.md) -[![GitHub release](https://img.shields.io/github/release/Countly/countly-sdk-ios.svg)](https://github.com/Countly/countly-sdk-ios/releases) -[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/Countly.svg)](https://support.count.ly/hc/en-us/articles/360037753511-iOS-watchOS-tvOS-macOS#cocoapods) -[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://support.count.ly/hc/en-us/articles/360037753511-iOS-watchOS-tvOS-macOS#carthage) +This repository contains the Countly iOS SDK, which can be integrated into iOS, watchOS, tvOS & macOS applications. The Countly iOS SDK is intended to be used with [Countly Community Edition](https://github.com/Countly/countly-server) or [Countly Enterprise Edition](https://count.ly/product). ## What is Countly? -[Countly](http://count.ly) is a product analytics solution and innovation enabler that helps teams track product performance and customer journey and behavior across [mobile](https://count.ly/mobile-analytics), [web](http://count.ly/web-analytics), and [desktop](https://count.ly/desktop-analytics) applications. [Ensuring privacy by design](https://count.ly/your-data-your-rules), Countly allows you to innovate and enhance your products to provide personalized and customized customer experiences, and meet key business and revenue goals. +[Countly](https://count.ly) is a product analytics solution and innovation enabler that helps teams track product performance and customer journey and behavior across [mobile](https://count.ly/mobile-analytics), [web](http://count.ly/web-analytics), +and [desktop](https://count.ly/desktop-analytics) applications. [Ensuring privacy by design](https://count.ly/privacy-by-design), Countly allows you to innovate and enhance your products to provide personalized and customized customer experiences, and meet key business and revenue goals. Track, measure, and take action - all without leaving Countly. -## About this SDK -This repository includes Countly iOS SDK with watchOS, tvOS & macOS support. -The minimum deployment targets are `iOS 10.0`, `watchOS 4.0`, `tvOS 10.0` , `macOS 10.14`, and it requires `Xcode 13.0+`. - -See [Countly iOS SDK documentation](https://support.count.ly/hc/en-us/articles/360037753511-iOS-watchOS-tvOS-macOS) for integration and details. - -## Sample iOS Application -We also have a useful [sample iOS application](https://github.com/Countly/countly-sample-ios) which demonstrates how to use this SDK in depth. -It includes iOS (both Objective-C and Swift), watchOS, tvOS and macOS sample projects. -Feel free to use them as a reference while you develop your application and also for easily testing your Countly Server. +* **Slack user?** [Join our Slack Community](https://slack.count.ly) +* **Questions or feature requests?** [Post in our Community Forum](https://support.count.ly/hc/en-us/community/topics) +* **Looking for the Countly Server?** [Countly Community Edition repository](https://github.com/Countly/countly-server) +* **Looking for other Countly SDKs?** [An overview of all Countly SDKs for mobile, web and desktop](https://support.count.ly/hc/en-us/articles/360037236571-Downloading-and-Installing-SDKs#officially-supported-sdks) -![iOS-sample-app](https://count.ly/github/countly-ios-sample-app.png) +## Integrating Countly SDK in your projects -## Security - -Security is very important to us. If you discover any issue regarding security, please disclose the information responsibly by sending an email to security@count.ly and **not by creating a GitHub issue**. +For a detailed description on how to use this SDK [check out our documentation](https://support.count.ly/hc/en-us/articles/360037753511-iOS-watchOS-tvOS-macOS). -## Other Countly Resources -This SDK needs one of the following counterpart Countly Server editions to work: +For information about how to add the SDK to your project, please check [this section of the documentation](https://support.count.ly/hc/en-us/articles/360037753511-iOS-watchOS-tvOS-macOS#adding-the-sdk-to-the-project). -* [Countly Community Edition](https://github.com/Countly/countly-server) (downloadable from GitHub) -* [Countly Enterprise Edition](https://count.ly/product) +You can find minimal SDK integration information for your project in [this section of the documentation](https://support.count.ly/hc/en-us/articles/360037753511-iOS-watchOS-tvOS-macOS#minimal-setup). -For more information about Countly Enterprise Edition, please see [comparison of Countly editions](https://count.ly/pricing#compare-editions). +For an example integration of this SDK, you can have a look [here](https://github.com/Countly/countly-sample-ios). -There are also other [Countly SDK repositories](https://support.count.ly/hc/en-us/articles/360037236571-Downloading-Installing-SDKs) both official and community supported. +This SDK supports the following features: +* [Analytics](https://support.count.ly/hc/en-us/articles/4431589003545-Analytics) +* [Push Notifications](https://support.count.ly/hc/en-us/articles/4405405459225-Push-Notifications) +* [User Profiles](https://support.count.ly/hc/en-us/articles/4403281285913-User-Profiles) +* [Crash Reports](https://support.count.ly/hc/en-us/articles/4404213566105-Crashes-Errors) +* [A/B Testing](https://support.count.ly/hc/en-us/articles/4416496362393-A-B-Testing-) +* [Performance Monitoring](https://support.count.ly/hc/en-us/articles/4734457847705-Performance) +* [Feedback Widgets](https://support.count.ly/hc/en-us/articles/4652903481753-Feedback-Surveys-NPS-and-Ratings-) -## How can I help you with your efforts? -Glad you asked. We need ideas, feedbacks and constructive comments. -All your suggestions will be taken care with upmost importance. -We are on [Twitter](https://twitter.com/gocountly), [Facebook](https://www.facebook.com/Countly) and [YouTube](https://www.youtube.com/user/GoCountly) if you would like to keep up with our fast progress! +## Security +Security is very important to us. If you discover any issue regarding security, please disclose the information responsibly by sending an email to security@count.ly and **not by creating a GitHub issue**. ## Badges -If you like Countly, [why not use one of our badges](https://count.ly/brand-assets) and give a link back to us, so others could know about this wonderful platform? +If you like Countly, [why not use one of our badges](https://count.ly/brand-assets) and give a link back to us so others know about this wonderful platform? Countly - Product Analytics - Countly - Product Analytics +```JS +Countly - Product Analytics +``` Countly - Product Analytics - Countly - Product Analytics +```JS +Countly - Product Analytics +``` + +## How can I help you with your efforts? +Glad you asked! We need ideas, feedback and constructive comments. All your suggestions will be taken care of with utmost importance. For feature requests and engaging with the community, join [our Slack Community](https://slack.count.ly) or [Community Forum](https://support.count.ly/hc/en-us/community/topics). -## Support -Have any questions? -Visit [Countly Community Area](https://support.count.ly/hc/en-us/community/topics "Countly Community Area") or join our [Slack community](https://slack.count.ly). +We are on [Twitter](http://twitter.com/gocountly), [Facebook](https://www.facebook.com/Countly) and [LinkedIn](https://www.linkedin.com/company/countly) if you would like to keep up with Countly related updates.