diff --git a/CHANGELOG.md b/CHANGELOG.md index f6c5a2b7f0..5dd81362d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Mobile Center SDK for iOS Change Log +## Version 0.12.2 + +This version contains a buf fix and improvements. **Verified all functionalities against iOS 11 GM.** + +### MobileCenterCrashes + +* **[Improvement]** Added a millisecond precision to crash logs for more accurate log time. + +### MobileCenterDistribute + +* **[Improvement]** Improved swizzling behavior for deprecated `openURL` method if it is used by applications. +* **[Fix]** Fixed being stuck on activating in-app update. It is back to open Safari in-app page for activation. + +___ + ## Version 0.12.1 This version contains bug fixes. diff --git a/Config/Global.xcconfig b/Config/Global.xcconfig index 5a242cceb8..da6c8777c4 100644 --- a/Config/Global.xcconfig +++ b/Config/Global.xcconfig @@ -1,5 +1,5 @@ BUILD_NUMBER = 1 -VERSION_STRING = 0.12.1 +VERSION_STRING = 0.12.2 // :Mark: Architectures MS_WATCH_ARCHS = armv7k diff --git a/Documentation/iOS/MobileCenter/.jazzy.yaml b/Documentation/iOS/MobileCenter/.jazzy.yaml index 495b247ece..3efb7cbdfe 100644 --- a/Documentation/iOS/MobileCenter/.jazzy.yaml +++ b/Documentation/iOS/MobileCenter/.jazzy.yaml @@ -5,7 +5,7 @@ sdk: iphonesimulator theme: ../../Themes/apple module: MobileCenter -module_version: 0.12.1 +module_version: 0.12.2 author: Microsoft Corp author_url: http://www.microsoft.com diff --git a/Documentation/iOS/MobileCenterAnalytics/.jazzy.yaml b/Documentation/iOS/MobileCenterAnalytics/.jazzy.yaml index 1cc6c85a5a..41280ef0f8 100644 --- a/Documentation/iOS/MobileCenterAnalytics/.jazzy.yaml +++ b/Documentation/iOS/MobileCenterAnalytics/.jazzy.yaml @@ -5,7 +5,7 @@ sdk: iphonesimulator theme: ../../Themes/apple module: MobileCenterAnalytics -module_version: 0.12.1 +module_version: 0.12.2 author: Microsoft Corp author_url: http://www.microsoft.com diff --git a/Documentation/iOS/MobileCenterCrashes/.jazzy.yaml b/Documentation/iOS/MobileCenterCrashes/.jazzy.yaml index 60fe27634f..f50946fe69 100644 --- a/Documentation/iOS/MobileCenterCrashes/.jazzy.yaml +++ b/Documentation/iOS/MobileCenterCrashes/.jazzy.yaml @@ -5,7 +5,7 @@ sdk: iphonesimulator theme: ../../Themes/apple module: Crashes -module_version: 0.12.1 +module_version: 0.12.2 author: Microsoft Corp author_url: http://www.microsoft.com diff --git a/Documentation/iOS/MobileCenterDistribute/.jazzy.yaml b/Documentation/iOS/MobileCenterDistribute/.jazzy.yaml index d36d1cd5fc..dfde796dad 100644 --- a/Documentation/iOS/MobileCenterDistribute/.jazzy.yaml +++ b/Documentation/iOS/MobileCenterDistribute/.jazzy.yaml @@ -5,7 +5,7 @@ sdk: iphonesimulator theme: ../../Themes/apple module: Distribute -module_version: 0.12.1 +module_version: 0.12.2 author: Microsoft Corp author_url: http://www.microsoft.com diff --git a/Documentation/iOS/MobileCenterPush/.jazzy.yaml b/Documentation/iOS/MobileCenterPush/.jazzy.yaml index 783ed41a53..eb6e5a106c 100644 --- a/Documentation/iOS/MobileCenterPush/.jazzy.yaml +++ b/Documentation/iOS/MobileCenterPush/.jazzy.yaml @@ -5,7 +5,7 @@ sdk: iphonesimulator theme: ../../Themes/apple module: MobileCenterPush -module_version: 0.12.1 +module_version: 0.12.2 author: Microsoft Corp author_url: http://www.microsoft.com diff --git a/Documentation/macOS/MobileCenter/.jazzy.yaml b/Documentation/macOS/MobileCenter/.jazzy.yaml index ef9e7d71c7..95dc4057dd 100644 --- a/Documentation/macOS/MobileCenter/.jazzy.yaml +++ b/Documentation/macOS/MobileCenter/.jazzy.yaml @@ -5,7 +5,7 @@ sdk: macosx theme: ../../Themes/apple module: MobileCenter -module_version: 0.12.1 +module_version: 0.12.2 author: Microsoft Corp author_url: http://www.microsoft.com diff --git a/Documentation/macOS/MobileCenterAnalytics/.jazzy.yaml b/Documentation/macOS/MobileCenterAnalytics/.jazzy.yaml index 64e2d4f301..018cebd119 100644 --- a/Documentation/macOS/MobileCenterAnalytics/.jazzy.yaml +++ b/Documentation/macOS/MobileCenterAnalytics/.jazzy.yaml @@ -5,7 +5,7 @@ sdk: macosx theme: ../../Themes/apple module: MobileCenterAnalytics -module_version: 0.12.1 +module_version: 0.12.2 author: Microsoft Corp author_url: http://www.microsoft.com diff --git a/Documentation/macOS/MobileCenterCrashes/.jazzy.yaml b/Documentation/macOS/MobileCenterCrashes/.jazzy.yaml index ddf03982c1..fae1af185c 100644 --- a/Documentation/macOS/MobileCenterCrashes/.jazzy.yaml +++ b/Documentation/macOS/MobileCenterCrashes/.jazzy.yaml @@ -5,7 +5,7 @@ sdk: macosx theme: ../../Themes/apple module: Crashes -module_version: 0.12.1 +module_version: 0.12.2 author: Microsoft Corp author_url: http://www.microsoft.com diff --git a/Documentation/macOS/MobileCenterPush/.jazzy.yaml b/Documentation/macOS/MobileCenterPush/.jazzy.yaml index 199f4d707a..62e590304d 100644 --- a/Documentation/macOS/MobileCenterPush/.jazzy.yaml +++ b/Documentation/macOS/MobileCenterPush/.jazzy.yaml @@ -5,7 +5,7 @@ sdk: macosx theme: ../../Themes/apple module: MobileCenterPush -module_version: 0.12.1 +module_version: 0.12.2 author: Microsoft Corp author_url: http://www.microsoft.com diff --git a/Documentation/tvOS/MobileCenter/.jazzy.yaml b/Documentation/tvOS/MobileCenter/.jazzy.yaml index d572cb0478..e393301bd7 100644 --- a/Documentation/tvOS/MobileCenter/.jazzy.yaml +++ b/Documentation/tvOS/MobileCenter/.jazzy.yaml @@ -5,7 +5,7 @@ sdk: appletvsimulator theme: ../../Themes/apple module: MobileCenter -module_version: 0.12.1 +module_version: 0.12.2 author: Microsoft Corp author_url: http://www.microsoft.com diff --git a/Documentation/tvOS/MobileCenterAnalytics/.jazzy.yaml b/Documentation/tvOS/MobileCenterAnalytics/.jazzy.yaml index 8ad1057d01..c651e21d37 100644 --- a/Documentation/tvOS/MobileCenterAnalytics/.jazzy.yaml +++ b/Documentation/tvOS/MobileCenterAnalytics/.jazzy.yaml @@ -5,7 +5,7 @@ sdk: appletvsimulator theme: ../../Themes/apple module: MobileCenterAnalytics -module_version: 0.12.1 +module_version: 0.12.2 author: Microsoft Corp author_url: http://www.microsoft.com diff --git a/Documentation/tvOS/MobileCenterCrashes/.jazzy.yaml b/Documentation/tvOS/MobileCenterCrashes/.jazzy.yaml index b9b94628d8..f057b8f493 100644 --- a/Documentation/tvOS/MobileCenterCrashes/.jazzy.yaml +++ b/Documentation/tvOS/MobileCenterCrashes/.jazzy.yaml @@ -5,7 +5,7 @@ sdk: appletvsimulator theme: ../../Themes/apple module: Crashes -module_version: 0.12.1 +module_version: 0.12.2 author: Microsoft Corp author_url: http://www.microsoft.com diff --git a/MobileCenter.podspec b/MobileCenter.podspec index 7a988d0b08..81cb199b21 100644 --- a/MobileCenter.podspec +++ b/MobileCenter.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'MobileCenter' - s.version = '0.12.1' + s.version = '0.12.2' s.summary = 'Mobile Center is mission control for mobile apps. Get faster release cycles, higher-quality apps, and the insights to build what users want.' s.description = <<-DESC diff --git a/MobileCenter/MobileCenter.xcodeproj/project.pbxproj b/MobileCenter/MobileCenter.xcodeproj/project.pbxproj index 0f7f8a2a94..14d9c589b5 100644 --- a/MobileCenter/MobileCenter.xcodeproj/project.pbxproj +++ b/MobileCenter/MobileCenter.xcodeproj/project.pbxproj @@ -93,13 +93,11 @@ 043120781EE0BCF9007054C5 /* MSLogContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E171B591D234717000DC480 /* MSLogContainer.m */; }; 0431207B1EE0BCF9007054C5 /* MSMobileCenterErrors.m in Sources */ = {isa = PBXBuildFile; fileRef = 388993391E29B26D00C27B36 /* MSMobileCenterErrors.m */; }; 0431207C1EE0BCF9007054C5 /* MSDeviceHistoryInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = B2FD53611E56501B0050F909 /* MSDeviceHistoryInfo.m */; }; - 0431207D1EE0BCF9007054C5 /* MSCustomProperties.m in Sources */ = {isa = PBXBuildFile; fileRef = F803BBF21E8E3677004B1E7A /* MSCustomProperties.m */; }; 043120801EE0BCF9007054C5 /* MSHttpSender.m in Sources */ = {isa = PBXBuildFile; fileRef = E84B8E341D235226006FD231 /* MSHttpSender.m */; }; 043120811EE0BCF9007054C5 /* MSServiceAbstract.m in Sources */ = {isa = PBXBuildFile; fileRef = D38024051E7126F500466558 /* MSServiceAbstract.m */; }; 043120821EE0BCF9007054C5 /* MSLogManagerDefault.m in Sources */ = {isa = PBXBuildFile; fileRef = E84B8E2D1D2351DB006FD231 /* MSLogManagerDefault.m */; }; 043120831EE0BCF9007054C5 /* MSStartServiceLog.m in Sources */ = {isa = PBXBuildFile; fileRef = D38023E61E6EFC7C00466558 /* MSStartServiceLog.m */; }; 043120841EE0BCF9007054C5 /* MSSenderUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = E8753D6D1D4BE53F00241513 /* MSSenderUtil.m */; }; - 043120851EE0BCF9007054C5 /* MSCustomPropertiesLog.m in Sources */ = {isa = PBXBuildFile; fileRef = F803BBF81E8E3989004B1E7A /* MSCustomPropertiesLog.m */; }; 043120861EE0BCF9007054C5 /* MSAbstractLog.m in Sources */ = {isa = PBXBuildFile; fileRef = E88EBBEB1D2C612E007E7785 /* MSAbstractLog.m */; }; 043120871EE0BCF9007054C5 /* MSKeychainUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 045BC3161E3FD88600B6C960 /* MSKeychainUtil.m */; }; 043120881EE0BCF9007054C5 /* MSWrapperLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 35D0B7521DDFABFD003EACCD /* MSWrapperLogger.m */; }; @@ -115,7 +113,6 @@ 0431209B1EE0BCF9007054C5 /* MSSenderCall.h in Headers */ = {isa = PBXBuildFile; fileRef = E80EB1051D50273700C9003F /* MSSenderCall.h */; }; 0431209C1EE0BCF9007054C5 /* MSSender.h in Headers */ = {isa = PBXBuildFile; fileRef = E84B8E311D235209006FD231 /* MSSender.h */; }; 0431209D1EE0BCF9007054C5 /* MSServiceCommon.h in Headers */ = {isa = PBXBuildFile; fileRef = 387C758D1D64DF2500D68CC1 /* MSServiceCommon.h */; }; - 043120A01EE0BCF9007054C5 /* MSCustomProperties.h in Headers */ = {isa = PBXBuildFile; fileRef = F803BBF11E8E3677004B1E7A /* MSCustomProperties.h */; settings = {ATTRIBUTES = (Public, ); }; }; 043120A21EE0BCF9007054C5 /* MSMobileCenter.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E0401551D1C9AAA0051BCFA /* MSMobileCenter.h */; settings = {ATTRIBUTES = (Public, ); }; }; 043120A31EE0BCF9007054C5 /* MSLogManagerDefaultPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 04FD126E1E415697007ABFE7 /* MSLogManagerDefaultPrivate.h */; }; 043120A41EE0BCF9007054C5 /* MSMobileCenterPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 38E1B6791DDE3FDF000EFED1 /* MSMobileCenterPrivate.h */; }; @@ -157,12 +154,9 @@ 043120D31EE0BCF9007054C5 /* MSKeychainUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = 045BC3181E3FD8AC00B6C960 /* MSKeychainUtil.h */; }; 043120D51EE0BCF9007054C5 /* MSChannelDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 5949380E604D45340244FEF1 /* MSChannelDelegate.h */; }; 043120D61EE0BCF9007054C5 /* MSIngestionSender.h in Headers */ = {isa = PBXBuildFile; fileRef = BA6824A001520825F18DFC42 /* MSIngestionSender.h */; }; - 0446DF021F3B864600C8E338 /* MSMockOriginalAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 3805DDA61EB7A6A6001DB846 /* MSMockOriginalAppDelegate.m */; }; - 0446DF031F3B864600C8E338 /* MSCustomPropertiesLogTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F803BC391E8E6963004B1E7A /* MSCustomPropertiesLogTests.m */; }; 0446DF041F3B864600C8E338 /* MSMockUserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = D377A30C1E83A05900B2C97A /* MSMockUserDefaults.m */; }; 0446DF051F3B864600C8E338 /* MSLogWithPropertiesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 045660FA1D99EEEB002F7055 /* MSLogWithPropertiesTests.m */; }; 0446DF061F3B864600C8E338 /* MSDeviceLogTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E3E2CC01D3596AE00B1EE50 /* MSDeviceLogTests.m */; }; - 0446DF071F3B864600C8E338 /* MSMockCustomAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 3872CA971ECA0B04006B2E3B /* MSMockCustomAppDelegate.m */; }; 0446DF081F3B864600C8E338 /* MSDeviceTrackerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 385FC0541D37EBD700A1799F /* MSDeviceTrackerTests.m */; }; 0446DF091F3B864600C8E338 /* MSAppDelegateForwarderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 38641B041EB0F40800B2CE73 /* MSAppDelegateForwarderTests.m */; }; 0446DF0A1F3B864600C8E338 /* MSChannelDefaultTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6EB1F40D1D2443B7005F9F99 /* MSChannelDefaultTests.m */; }; @@ -181,7 +175,6 @@ 0446DF171F3B864600C8E338 /* MSServiceAbstractTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 387C75951D64EE1900D68CC1 /* MSServiceAbstractTests.m */; }; 0446DF181F3B864600C8E338 /* MSChannelConfigurationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E48A5A31D3831FE006E8B5F /* MSChannelConfigurationTests.m */; }; 0446DF191F3B864600C8E338 /* MSLoggerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B24F3F161D93A3FF00827213 /* MSLoggerTests.m */; }; - 0446DF1A1F3B864600C8E338 /* MSCustomPropertiesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F803BC371E8E6927004B1E7A /* MSCustomPropertiesTests.m */; }; 0446DF1B1F3B864600C8E338 /* MSSenderUtilTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 04B7BBEE1E5FAD4D001A0CE1 /* MSSenderUtilTests.m */; }; 0446DF1C1F3B864600C8E338 /* MSMockLog.m in Sources */ = {isa = PBXBuildFile; fileRef = E88D17051D35B6B500A5EA57 /* MSMockLog.m */; }; 0446DF1D1F3B864600C8E338 /* MSLogManagerDefaultTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E48A5A61D383893006E8B5F /* MSLogManagerDefaultTests.m */; }; @@ -293,8 +286,6 @@ 04873E831F2FCE3E00A13AFF /* MSAppDelegateForwarder.m in Sources */ = {isa = PBXBuildFile; fileRef = 38C4471F1ECE5352002E1B11 /* MSAppDelegateForwarder.m */; }; 04873E841F2FCF9000A13AFF /* MSAppDelegateForwarderPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 38C447201ECE5352002E1B11 /* MSAppDelegateForwarderPrivate.h */; }; 04873E871F2FDDEC00A13AFF /* MSAppDelegateForwarderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 38641B041EB0F40800B2CE73 /* MSAppDelegateForwarderTests.m */; }; - 04873E881F2FDECE00A13AFF /* MSMockOriginalAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 3805DDA61EB7A6A6001DB846 /* MSMockOriginalAppDelegate.m */; }; - 04873E891F2FDFD500A13AFF /* MSMockCustomAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 3872CA971ECA0B04006B2E3B /* MSMockCustomAppDelegate.m */; }; 0499F8551EDF979700C3EDDA /* MSLogWithProperties.h in Headers */ = {isa = PBXBuildFile; fileRef = 3844FF191E8C2716003E9194 /* MSLogWithProperties.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0499F8561EDF979A00C3EDDA /* MSDevice.m in Sources */ = {isa = PBXBuildFile; fileRef = 3844FF1A1E8C2716003E9194 /* MSDevice.m */; }; 0499F8571EDF979D00C3EDDA /* MSDevice.h in Headers */ = {isa = PBXBuildFile; fileRef = 3844FF1B1E8C2716003E9194 /* MSDevice.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -317,7 +308,6 @@ 3592ABA81DC90E3600EF4592 /* MSLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 3592ABA61DC90E3600EF4592 /* MSLogger.m */; }; 35D0B7531DDFABFD003EACCD /* MSWrapperLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 35D0B7511DDFABFD003EACCD /* MSWrapperLogger.h */; }; 35D0B7541DDFABFD003EACCD /* MSWrapperLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 35D0B7521DDFABFD003EACCD /* MSWrapperLogger.m */; }; - 3805DDA71EB7A6A6001DB846 /* MSMockOriginalAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 3805DDA61EB7A6A6001DB846 /* MSMockOriginalAppDelegate.m */; }; 380A4DCB1DD6908A00E99219 /* MSUtilityTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 380A4DCA1DD6908A00E99219 /* MSUtilityTests.m */; }; 381C91E91D3DB65D004512F1 /* MSDeviceTracker.h in Headers */ = {isa = PBXBuildFile; fileRef = 381C91E51D3DB65D004512F1 /* MSDeviceTracker.h */; }; 382CFCF11EC3817B003FE40B /* MSChannelDefaultPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 382CFCF01EC3817B003FE40B /* MSChannelDefaultPrivate.h */; }; @@ -338,7 +328,6 @@ 385FC0551D37EBD700A1799F /* MSDeviceTrackerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 385FC0541D37EBD700A1799F /* MSDeviceTrackerTests.m */; }; 38641B051EB0F40800B2CE73 /* MSAppDelegateForwarderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 38641B041EB0F40800B2CE73 /* MSAppDelegateForwarderTests.m */; }; 386E8D931E25932100EECF0F /* MSHttpTestUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 386E8D911E25932100EECF0F /* MSHttpTestUtil.m */; }; - 3872CA981ECA0B04006B2E3B /* MSMockCustomAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 3872CA971ECA0B04006B2E3B /* MSMockCustomAppDelegate.m */; }; 387C75811D6270A300D68CC1 /* MSServiceAbstractInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 387C757F1D6270A300D68CC1 /* MSServiceAbstractInternal.h */; }; 387C758F1D64E50800D68CC1 /* MSServiceCommon.h in Headers */ = {isa = PBXBuildFile; fileRef = 387C758D1D64DF2500D68CC1 /* MSServiceCommon.h */; }; 387C75961D64EE1900D68CC1 /* MSServiceAbstractTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 387C75951D64EE1900D68CC1 /* MSServiceAbstractTests.m */; }; @@ -600,8 +589,6 @@ 3592ABA61DC90E3600EF4592 /* MSLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSLogger.m; sourceTree = ""; }; 35D0B7511DDFABFD003EACCD /* MSWrapperLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSWrapperLogger.h; sourceTree = ""; }; 35D0B7521DDFABFD003EACCD /* MSWrapperLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSWrapperLogger.m; sourceTree = ""; }; - 3805DDA51EB7A5F6001DB846 /* MSMockOriginalAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSMockOriginalAppDelegate.h; sourceTree = ""; }; - 3805DDA61EB7A6A6001DB846 /* MSMockOriginalAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSMockOriginalAppDelegate.m; sourceTree = ""; }; 380A4DCA1DD6908A00E99219 /* MSUtilityTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSUtilityTests.m; sourceTree = ""; }; 381C91E51D3DB65D004512F1 /* MSDeviceTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSDeviceTracker.h; sourceTree = ""; }; 382CFCF01EC3817B003FE40B /* MSChannelDefaultPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSChannelDefaultPrivate.h; sourceTree = ""; }; @@ -623,8 +610,6 @@ 38641B041EB0F40800B2CE73 /* MSAppDelegateForwarderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSAppDelegateForwarderTests.m; sourceTree = ""; }; 386E8D911E25932100EECF0F /* MSHttpTestUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSHttpTestUtil.m; sourceTree = ""; }; 386E8D921E25932100EECF0F /* MSHttpTestUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSHttpTestUtil.h; sourceTree = ""; }; - 3872CA961ECA0B04006B2E3B /* MSMockCustomAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSMockCustomAppDelegate.h; sourceTree = ""; }; - 3872CA971ECA0B04006B2E3B /* MSMockCustomAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSMockCustomAppDelegate.m; sourceTree = ""; }; 387C757F1D6270A300D68CC1 /* MSServiceAbstractInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = MSServiceAbstractInternal.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 387C758D1D64DF2500D68CC1 /* MSServiceCommon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = MSServiceCommon.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 387C75951D64EE1900D68CC1 /* MSServiceAbstractTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MSServiceAbstractTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; @@ -1128,10 +1113,6 @@ E88D17051D35B6B500A5EA57 /* MSMockLog.m */, D377A30B1E83A04600B2C97A /* MSMockUserDefaults.h */, D377A30C1E83A05900B2C97A /* MSMockUserDefaults.m */, - 3805DDA51EB7A5F6001DB846 /* MSMockOriginalAppDelegate.h */, - 3805DDA61EB7A6A6001DB846 /* MSMockOriginalAppDelegate.m */, - 3872CA961ECA0B04006B2E3B /* MSMockCustomAppDelegate.h */, - 3872CA971ECA0B04006B2E3B /* MSMockCustomAppDelegate.m */, 805F3F691F209C8A00B489E4 /* MSMockService.h */, 805F3F6A1F209C9D00B489E4 /* MSMockService.m */, 04311FEE1EE083C2007054C5 /* MSTestFrameworks.h */, @@ -1235,7 +1216,6 @@ B2CD74C91F22BEC70070E7DF /* MSDBStoragePrivate.h in Headers */, 0431209D1EE0BCF9007054C5 /* MSServiceCommon.h in Headers */, B2181B7B1F26852600D2EEAE /* MSWrapperSdk.h in Headers */, - 043120A01EE0BCF9007054C5 /* MSCustomProperties.h in Headers */, 043120A21EE0BCF9007054C5 /* MSMobileCenter.h in Headers */, 043120A31EE0BCF9007054C5 /* MSLogManagerDefaultPrivate.h in Headers */, 043120A41EE0BCF9007054C5 /* MSMobileCenterPrivate.h in Headers */, @@ -1719,7 +1699,6 @@ 045C0BEF1EEB12300038FD6B /* MSAppDelegateForwarder.m in Sources */, B2CD74A51F22BE270070E7DF /* MSUtility.m in Sources */, 0431207C1EE0BCF9007054C5 /* MSDeviceHistoryInfo.m in Sources */, - 0431207D1EE0BCF9007054C5 /* MSCustomProperties.m in Sources */, B2181B7C1F26852B00D2EEAE /* MSWrapperSdk.m in Sources */, 041EFD281EE1CAA6006DCD56 /* MSDevice.m in Sources */, 043120801EE0BCF9007054C5 /* MSHttpSender.m in Sources */, @@ -1729,7 +1708,6 @@ 043120831EE0BCF9007054C5 /* MSStartServiceLog.m in Sources */, 043120841EE0BCF9007054C5 /* MSSenderUtil.m in Sources */, B2CD74C01F22BE270070E7DF /* MSUtility+File.m in Sources */, - 043120851EE0BCF9007054C5 /* MSCustomPropertiesLog.m in Sources */, 043120861EE0BCF9007054C5 /* MSAbstractLog.m in Sources */, 043120871EE0BCF9007054C5 /* MSKeychainUtil.m in Sources */, B2CD748A1F22BD910070E7DF /* MSLogDBStorage.m in Sources */, @@ -1749,12 +1727,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0446DF021F3B864600C8E338 /* MSMockOriginalAppDelegate.m in Sources */, - 0446DF031F3B864600C8E338 /* MSCustomPropertiesLogTests.m in Sources */, 0446DF041F3B864600C8E338 /* MSMockUserDefaults.m in Sources */, 0446DF051F3B864600C8E338 /* MSLogWithPropertiesTests.m in Sources */, 0446DF061F3B864600C8E338 /* MSDeviceLogTests.m in Sources */, - 0446DF071F3B864600C8E338 /* MSMockCustomAppDelegate.m in Sources */, 0446DF081F3B864600C8E338 /* MSDeviceTrackerTests.m in Sources */, 0446DF091F3B864600C8E338 /* MSAppDelegateForwarderTests.m in Sources */, 0446DF0A1F3B864600C8E338 /* MSChannelDefaultTests.m in Sources */, @@ -1773,7 +1748,6 @@ 0446DF171F3B864600C8E338 /* MSServiceAbstractTests.m in Sources */, 0446DF181F3B864600C8E338 /* MSChannelConfigurationTests.m in Sources */, 0446DF191F3B864600C8E338 /* MSLoggerTests.m in Sources */, - 0446DF1A1F3B864600C8E338 /* MSCustomPropertiesTests.m in Sources */, 0446DF1B1F3B864600C8E338 /* MSSenderUtilTests.m in Sources */, 0446DF1C1F3B864600C8E338 /* MSMockLog.m in Sources */, 0446DF1D1F3B864600C8E338 /* MSLogManagerDefaultTests.m in Sources */, @@ -1785,7 +1759,6 @@ buildActionMask = 2147483647; files = ( 046AEAD31ECA562A00CBE511 /* MSCustomPropertiesLogTests.m in Sources */, - 04873E881F2FDECE00A13AFF /* MSMockOriginalAppDelegate.m in Sources */, 0446DF321F3B870700C8E338 /* MSDBStorageTests.m in Sources */, 04A140871ECE63BB001CEE94 /* MSServiceAbstractTests.m in Sources */, 0446DF311F3B86FE00C8E338 /* MSStoragePerfomanceTests.m in Sources */, @@ -1802,7 +1775,6 @@ 046AEAE41ECA562A00CBE511 /* MSStartServiceLogTests.m in Sources */, 0446DF331F3B870A00C8E338 /* MSLogDBStorageTests.m in Sources */, 046AEAE61ECA562A00CBE511 /* MSChannelConfigurationTests.m in Sources */, - 04873E891F2FDFD500A13AFF /* MSMockCustomAppDelegate.m in Sources */, 046AEAE81ECA562A00CBE511 /* MSCustomPropertiesTests.m in Sources */, 04A140861ECE60C6001CEE94 /* MSUtilityTests.m in Sources */, 046AEAE91ECA562A00CBE511 /* MSSenderUtilTests.m in Sources */, @@ -1901,12 +1873,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3805DDA71EB7A6A6001DB846 /* MSMockOriginalAppDelegate.m in Sources */, F803BC3A1E8E6963004B1E7A /* MSCustomPropertiesLogTests.m in Sources */, D377A30D1E83A05900B2C97A /* MSMockUserDefaults.m in Sources */, 045660FB1D99EEEB002F7055 /* MSLogWithPropertiesTests.m in Sources */, 6E3E2CC11D3596AE00B1EE50 /* MSDeviceLogTests.m in Sources */, - 3872CA981ECA0B04006B2E3B /* MSMockCustomAppDelegate.m in Sources */, 385FC0551D37EBD700A1799F /* MSDeviceTrackerTests.m in Sources */, 38641B051EB0F40800B2CE73 /* MSAppDelegateForwarderTests.m in Sources */, 6EB1F40E1D2443B7005F9F99 /* MSChannelDefaultTests.m in Sources */, diff --git a/MobileCenter/MobileCenter/Internals/AppDelegate/MSAppDelegateForwarder.h b/MobileCenter/MobileCenter/Internals/AppDelegate/MSAppDelegateForwarder.h index 78dde3fa5a..3b13a45396 100644 --- a/MobileCenter/MobileCenter/Internals/AppDelegate/MSAppDelegateForwarder.h +++ b/MobileCenter/MobileCenter/Internals/AppDelegate/MSAppDelegateForwarder.h @@ -41,9 +41,6 @@ NS_ASSUME_NONNULL_BEGIN /** * Flush debugging traces accumulated until now. - * TODO: We should find a way for customers to set the log level in their configuration somehow so that it'll be set at - * the time of the swizzling. This will allow having swizzling traces in real time, in that case we can remove the whole - * trace buffer mechanism. */ + (void)flushTraceBuffer; diff --git a/MobileCenter/MobileCenter/Internals/AppDelegate/MSAppDelegateForwarder.m b/MobileCenter/MobileCenter/Internals/AppDelegate/MSAppDelegateForwarder.m index 2f316c2d8a..d197bfd275 100644 --- a/MobileCenter/MobileCenter/Internals/AppDelegate/MSAppDelegateForwarder.m +++ b/MobileCenter/MobileCenter/Internals/AppDelegate/MSAppDelegateForwarder.m @@ -17,16 +17,27 @@ static NSString *const kMSReturnedValueSelectorPart = @"returnedValue:"; static NSString *const kMSIsAppDelegateForwarderEnabledKey = @"MobileCenterAppDelegateForwarderEnabled"; +// Original selectors with special handling. +static NSString *const kMSDidReceiveRemoteNotificationFetchHandler = + @"application:didReceiveRemoteNotification:fetchCompletionHandler:"; +static NSString *const kMSOpenURLSourceApplicationAnnotation = @"application:openURL:sourceApplication:annotation:"; +static NSString *const kMSOpenURLOptions = @"application:openURL:options:"; + static NSHashTable> *_delegates = nil; static NSMutableSet *_selectorsToSwizzle = nil; static NSArray *_selectorsNotToOverride = nil; +static NSDictionary *_deprecatedSelectors = nil; static NSMutableDictionary *_originalImplementations = nil; -static NSMutableArray *_traceBuffer = nil; +static NSMutableArray *traceBuffer = nil; static IMP _originalSetDelegateImp = NULL; static BOOL _enabled = YES; @implementation MSAppDelegateForwarder ++ (void)initialize { + traceBuffer = [NSMutableArray new]; +} + + (void)load { /* @@ -43,11 +54,11 @@ + (void)load { // Swizzle `setDelegate:` of Application class. if (MSAppDelegateForwarder.enabled) { - [MSAppDelegateForwarder.traceBuffer addObject:^{ + [self addTraceBlock:^{ MSLogDebug([MSMobileCenter logTag], @"Application delegate forwarder is enabled. It may use swizzling."); }]; } else { - [MSAppDelegateForwarder.traceBuffer addObject:^{ + [self addTraceBlock:^{ MSLogDebug([MSMobileCenter logTag], @"Application delegate forwarder is disabled. It won't use swizzling."); }]; } @@ -79,19 +90,41 @@ + (void)setDelegates:(NSHashTable> *)delegates { + (NSArray *)selectorsNotToOverride { if (!_selectorsNotToOverride) { #if !TARGET_OS_OSX - _selectorsNotToOverride = - @[ NSStringFromSelector(@selector(application:didReceiveRemoteNotification:fetchCompletionHandler:)) ]; + _selectorsNotToOverride = @[ kMSDidReceiveRemoteNotificationFetchHandler ]; #endif } return _selectorsNotToOverride; } ++ (NSDictionary *)deprecatedSelectors { + if (!_deprecatedSelectors) { +#if TARGET_OS_OSX + _deprecatedSelectors = @{}; +#else + _deprecatedSelectors = @{kMSOpenURLOptions : kMSOpenURLSourceApplicationAnnotation}; +#endif + } + return _deprecatedSelectors; +} + + (NSMutableDictionary *)originalImplementations { return _originalImplementations ?: (_originalImplementations = [NSMutableDictionary new]); } -+ (NSMutableArray *)traceBuffer { - return _traceBuffer ?: (_traceBuffer = [NSMutableArray new]); ++ (void)addTraceBlock:(void (^)())block { + @synchronized(traceBuffer) { + if (traceBuffer) { + static dispatch_once_t onceToken = 0; + dispatch_once(&onceToken, ^{ + [traceBuffer addObject:^{ + MSLogVerbose([MSMobileCenter logTag], @"Start buffering traces."); + }]; + }); + [traceBuffer addObject:block]; + } else { + block(); + } + } } + (IMP)originalSetDelegateImp { @@ -144,8 +177,6 @@ + (void)swizzleOriginalDelegate:(id)originalDelegate { // Swizzle all registered selectors. for (NSString *selectorString in self.selectorsToSwizzle) { - - // The same selector is used on both forwarder and delegate. originalSelector = NSSelectorFromString(selectorString); customSelector = NSSelectorFromString([kMSCustomSelectorPrefix stringByAppendingString:selectorString]); originalImp = @@ -165,11 +196,15 @@ + (IMP)swizzleOriginalSelector:(SEL)originalSelector originalClass:(Class)originalClass { // Replace original implementation - NSString *originalSelectorString = NSStringFromSelector(originalSelector); + NSString *originalSelectorStr = NSStringFromSelector(originalSelector); Method originalMethod = class_getInstanceMethod(originalClass, originalSelector); IMP customImp = class_getMethodImplementation(self, customSelector); IMP originalImp = NULL; BOOL methodAdded = NO; + BOOL skipped = NO; + NSString *warningMsg; + NSString *remediationMsg = @"You need to explicitly call the Mobile Center API" + @" from your app delegate implementation."; // Replace original implementation by the custom one. if (originalMethod) { @@ -179,17 +214,42 @@ + (IMP)swizzleOriginalSelector:(SEL)originalSelector * depend on the SDK return value for its own logic so customers already have to call the SDK API * in their implementation which makes swizzling useless. */ - if (![self.selectorsNotToOverride containsObject:originalSelectorString]) { + if (![self.selectorsNotToOverride containsObject:originalSelectorStr]) { originalImp = method_setImplementation(originalMethod, customImp); + } else { + warningMsg = + [NSString stringWithFormat:@"This selector is not supported when already implemented. %@", remediationMsg]; } } else if (![originalClass instancesRespondToSelector:originalSelector]) { - /* - * The original class may not implement the selector (e.g.: optional method from protocol), - * add the method to the original class and associate it with the custom implementation. - */ - Method customMethod = class_getInstanceMethod(self, customSelector); - methodAdded = class_addMethod(originalClass, originalSelector, customImp, method_getTypeEncoding(customMethod)); + // Check for deprecation. + NSString *deprecatedSelectorStr = self.deprecatedSelectors[originalSelectorStr]; + if (deprecatedSelectorStr && + [originalClass instancesRespondToSelector:NSSelectorFromString(deprecatedSelectorStr)]) { + + /* + * An implementation for the deprecated selector exists. Don't add the new method, it might eclipse the original + * implementation. + */ + warningMsg = [NSString + stringWithFormat: + @"No implementation found for this selector, though an implementation of its deprecated API '%@' exists.", + deprecatedSelectorStr]; + } else { + + // Skip this selector if it's deprecated and doesn't have an implementation. + if ([self.deprecatedSelectors.allValues containsObject:originalSelectorStr]) { + skipped = YES; + } else { + + /* + * The original class may not implement the selector (e.g.: optional method from protocol), + * add the method to the original class and associate it with the custom implementation. + */ + Method customMethod = class_getInstanceMethod(self, customSelector); + methodAdded = class_addMethod(originalClass, originalSelector, customImp, method_getTypeEncoding(customMethod)); + } + } } /* @@ -199,18 +259,23 @@ + (IMP)swizzleOriginalSelector:(SEL)originalSelector */ // Validate swizzling. - if (!originalImp && !methodAdded) { - [self.traceBuffer addObject:^{ - MSLogError([MSMobileCenter logTag], - @"Cannot swizzle selector '%@' of class '%@'. You will have to explicitly call APIs from " - @"Mobile Center in your app delegate implementation.", - originalSelectorString, originalClass); - }]; - } else { - [self.traceBuffer addObject:^{ - MSLogDebug([MSMobileCenter logTag], @"Selector '%@' of class '%@' is swizzled.", originalSelectorString, - originalClass); - }]; + if (!skipped) { + if (!originalImp && !methodAdded) { + [self addTraceBlock:^{ + NSString *message = [NSString + stringWithFormat:@"Cannot swizzle selector '%@' of class '%@'.", originalSelectorStr, originalClass]; + if (warningMsg) { + MSLogWarning([MSMobileCenter logTag], @"%@ %@", message, warningMsg); + } else { + MSLogError([MSMobileCenter logTag], @"%@ %@", message, remediationMsg); + } + }]; + } else { + [self addTraceBlock:^{ + MSLogDebug([MSMobileCenter logTag], @"Selector '%@' of class '%@' is swizzled.", originalSelectorStr, + originalClass); + }]; + } } return originalImp; } @@ -356,21 +421,6 @@ - (void)custom_application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:error]; } -#if TARGET_OS_OSX -- (void)custom_applicationDidFinishLaunching:(NSNotification *)notification { - IMP originalImp = NULL; - - // Forward to the original delegate. - [MSAppDelegateForwarder.originalImplementations[NSStringFromSelector(_cmd)] getValue:&originalImp]; - if (originalImp) { - ((void (*)(id, SEL, NSNotification *))originalImp)(self, _cmd, notification); - } - - // Forward to custom delegates. - [[MSAppDelegateForwarder sharedInstance] applicationDidFinishLaunching:(NSNotification *)notification]; -} -#endif - #if TARGET_OS_OSX - (void)custom_application:(NSApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { #else @@ -465,15 +515,24 @@ - (void)forwardInvocation:(NSInvocation *)invocation { #pragma mark - Logging + (void)flushTraceBuffer { - - // Only trace once. - static dispatch_once_t traceOnceToken; - dispatch_once(&traceOnceToken, ^{ - for (dispatch_block_t traceBlock in self.traceBuffer) { - traceBlock(); + if (traceBuffer) { + @synchronized(traceBuffer) { + for (dispatch_block_t traceBlock in traceBuffer) { + traceBlock(); + } + [traceBuffer removeAllObjects]; + traceBuffer = nil; + MSLogVerbose([MSMobileCenter logTag], @"Stop buffering traces, flushed."); } - [self.traceBuffer removeAllObjects]; - }); + } +} + +#pragma mark - Testing + ++ (void)reset { + [self.delegates removeAllObjects]; + [self.originalImplementations removeAllObjects]; + [self.selectorsToSwizzle removeAllObjects]; } @end diff --git a/MobileCenter/MobileCenter/Internals/AppDelegate/MSAppDelegateForwarderPrivate.h b/MobileCenter/MobileCenter/Internals/AppDelegate/MSAppDelegateForwarderPrivate.h index ac1c4b2834..2aea189892 100644 --- a/MobileCenter/MobileCenter/Internals/AppDelegate/MSAppDelegateForwarderPrivate.h +++ b/MobileCenter/MobileCenter/Internals/AppDelegate/MSAppDelegateForwarderPrivate.h @@ -22,14 +22,14 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, class, readonly) NSArray *selectorsNotToOverride; /** - * Keep track of the original delegate's method implementations. + * Dictionary of deprecated original selectors indexed by their new equivalent. */ -@property(nonatomic, class, readonly) NSMutableDictionary *originalImplementations; +@property(nonatomic, class, readonly) NSDictionary *deprecatedSelectors; /** - * Trace buffer storing debbuging traces. + * Keep track of the original delegate's method implementations. */ -@property(nonatomic, class, readonly) NSMutableArray *traceBuffer; +@property(nonatomic, class, readonly) NSMutableDictionary *originalImplementations; #if TARGET_OS_OSX /** @@ -49,6 +49,11 @@ NS_ASSUME_NONNULL_BEGIN */ + (void)swizzleOriginalDelegate:(id)originalDelegate; +/** + * Reset the app delegate forwarder, used for testing only. + */ ++ (void)reset; + @end NS_ASSUME_NONNULL_END diff --git a/MobileCenter/MobileCenter/Internals/AppDelegate/MSNSAppDelegate.h b/MobileCenter/MobileCenter/Internals/AppDelegate/MSNSAppDelegate.h index 77a5518edc..0eeb85cc3b 100644 --- a/MobileCenter/MobileCenter/Internals/AppDelegate/MSNSAppDelegate.h +++ b/MobileCenter/MobileCenter/Internals/AppDelegate/MSNSAppDelegate.h @@ -52,14 +52,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)application:(NSApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo; -/** - * Sent by the default notification center after the application has been launched and initialized but before it has - * received its first event. - * - * @param notification A notification that caused the application launch. - */ -- (void)applicationDidFinishLaunching:(NSNotification *)notification; - @end NS_ASSUME_NONNULL_END diff --git a/MobileCenter/MobileCenter/Internals/Channel/MSChannelDefault.m b/MobileCenter/MobileCenter/Internals/Channel/MSChannelDefault.m index d74d255695..74f4d9c107 100644 --- a/MobileCenter/MobileCenter/Internals/Channel/MSChannelDefault.m +++ b/MobileCenter/MobileCenter/Internals/Channel/MSChannelDefault.m @@ -1,4 +1,4 @@ -#import "MSChannelDefault.h" +#import "MSAbstractLogInternal.h" #import "MSChannelDefaultPrivate.h" #import "MSMobileCenterErrors.h" #import "MSMobileCenterInternal.h" @@ -140,9 +140,17 @@ - (void)flushQueue { self.pendingBatchQueueFull = YES; } MSLogContainer *container = [[MSLogContainer alloc] initWithBatchId:batchId andLogs:logArray]; - MSLogDebug([MSMobileCenter logTag], @"Sending %lu log(s), group Id: %@, batch Id:%@, payload:\n%@", - (unsigned long)[container.logs count], self.configuration.groupId, batchId, - [container serializeLogWithPrettyPrinting:YES]); + + // Optimization. If the current log level is greater than MSLogLevelDebug, we can skip it. + if ([MSMobileCenter logLevel] <= MSLogLevelDebug) { + unsigned long count = [container.logs count]; + for (unsigned long i = 0; i < count; i++) { + MSLogDebug([MSMobileCenter logTag], + @"Sending %lu/%lu log(s), group Id: %@, batch Id:%@, payload:\n%@", (i + 1), count, + self.configuration.groupId, batchId, + [(MSAbstractLog *)container.logs[i] serializeLogWithPrettyPrinting:YES]); + } + } // Notify delegates. [self enumerateDelegatesForSelector:@selector(channel:willSendLog:) diff --git a/MobileCenter/MobileCenter/Internals/Model/MSAbstractLog.m b/MobileCenter/MobileCenter/Internals/Model/MSAbstractLog.m index 161596eda5..88c55331e9 100644 --- a/MobileCenter/MobileCenter/Internals/Model/MSAbstractLog.m +++ b/MobileCenter/MobileCenter/Internals/Model/MSAbstractLog.m @@ -69,4 +69,18 @@ - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeObject:self.device forKey:kMSDevice]; } +#pragma mark - Utility + +- (NSString *)serializeLogWithPrettyPrinting:(BOOL)prettyPrint { + NSString *jsonString; + NSJSONWritingOptions printOptions = prettyPrint ? NSJSONWritingPrettyPrinted : (NSJSONWritingOptions)0; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:[self serializeToDictionary] options:printOptions error:nil]; + + if (jsonData) { + jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + jsonString = [jsonString stringByReplacingOccurrencesOfString:@"\\/" withString:@"/"]; + } + return jsonString; +} + @end diff --git a/MobileCenter/MobileCenter/Internals/Model/MSAbstractLogInternal.h b/MobileCenter/MobileCenter/Internals/Model/MSAbstractLogInternal.h index 8d34b374cd..549cf5dc6f 100644 --- a/MobileCenter/MobileCenter/Internals/Model/MSAbstractLogInternal.h +++ b/MobileCenter/MobileCenter/Internals/Model/MSAbstractLogInternal.h @@ -4,4 +4,13 @@ @interface MSAbstractLog () +/** + * Serialize logs into a JSON string. + * + * @param prettyPrint boolean indicates pretty printing. + * + * @return A serialized string. + */ +- (NSString *)serializeLogWithPrettyPrinting:(BOOL)prettyPrint; + @end diff --git a/MobileCenter/MobileCenter/Internals/Util/MSUtility+Date.m b/MobileCenter/MobileCenter/Internals/Util/MSUtility+Date.m index 9de9ac8985..639d935dde 100644 --- a/MobileCenter/MobileCenter/Internals/Util/MSUtility+Date.m +++ b/MobileCenter/MobileCenter/Internals/Util/MSUtility+Date.m @@ -17,7 +17,7 @@ + (NSString *)dateToISO8601:(NSDate *)date { dateFormatter = [[NSDateFormatter alloc] init]; [dateFormatter setLocale:[NSLocale systemLocale]]; [dateFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]]; - [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"]; + [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"]; } return [dateFormatter stringFromDate:date]; } diff --git a/MobileCenter/MobileCenter/Internals/Util/MSUtility+Environment.m b/MobileCenter/MobileCenter/Internals/Util/MSUtility+Environment.m index fba2786be4..8dd8d5518b 100644 --- a/MobileCenter/MobileCenter/Internals/Util/MSUtility+Environment.m +++ b/MobileCenter/MobileCenter/Internals/Util/MSUtility+Environment.m @@ -8,11 +8,7 @@ @implementation MSUtility (Environment) + (MSEnvironment)currentAppEnvironment { -#if TARGET_OS_SIMULATOR - return MSEnvironmentOther; -#elif TARGET_OS_OSX - - // TODO: This is not implemented for macOS. +#if TARGET_OS_SIMULATOR || TARGET_OS_OSX return MSEnvironmentOther; #else diff --git a/MobileCenter/MobileCenter/MSMobileCenter.h b/MobileCenter/MobileCenter/MSMobileCenter.h index 9fa20227fd..57cc185251 100644 --- a/MobileCenter/MobileCenter/MSMobileCenter.h +++ b/MobileCenter/MobileCenter/MSMobileCenter.h @@ -3,7 +3,9 @@ #import "MSConstants.h" @class MSWrapperSdk; +#if !TARGET_OS_TV @class MSCustomProperties; +#endif @interface MSMobileCenter : NSObject @@ -96,12 +98,14 @@ */ + (void)setWrapperSdk:(MSWrapperSdk *)wrapperSdk; +#if !TARGET_OS_TV /** * Set the custom properties. * * @param customProperties Custom properties object. */ + (void)setCustomProperties:(MSCustomProperties *)customProperties; +#endif /** * Check whether the application delegate forwarder is enabled or not. diff --git a/MobileCenter/MobileCenter/MSMobileCenter.m b/MobileCenter/MobileCenter/MSMobileCenter.m index 67080036d7..39525fa102 100644 --- a/MobileCenter/MobileCenter/MSMobileCenter.m +++ b/MobileCenter/MobileCenter/MSMobileCenter.m @@ -9,9 +9,11 @@ #import "MSLogger.h" #import "MSMobileCenterInternal.h" #import "MSStartServiceLog.h" +#if !TARGET_OS_TV #import "MSCustomProperties.h" #import "MSCustomPropertiesLog.h" #import "MSCustomPropertiesPrivate.h" +#endif // Singleton static MSMobileCenter *sharedInstance = nil; @@ -109,9 +111,11 @@ + (void)setWrapperSdk:(MSWrapperSdk *)wrapperSdk { [[MSDeviceTracker sharedInstance] setWrapperSdk:wrapperSdk]; } +#if !TARGET_OS_TV + (void)setCustomProperties:(MSCustomProperties *)customProperties { [[self sharedInstance] setCustomProperties:customProperties]; } +#endif /** * Check if the debugger is attached @@ -282,6 +286,7 @@ - (void)setLogUrl:(NSString *)logUrl { } } +#if !TARGET_OS_TV - (void)setCustomProperties:(MSCustomProperties *)customProperties { if (!customProperties || customProperties.properties == 0) { MSLogError([MSMobileCenter logTag], @"Custom properties may not be null or empty"); @@ -289,6 +294,7 @@ - (void)setCustomProperties:(MSCustomProperties *)customProperties { } [self sendCustomPropertiesLog:customProperties.properties]; } +#endif - (void)setEnabled:(BOOL)isEnabled { self.enabledStateUpdating = YES; @@ -407,6 +413,7 @@ - (void)sendStartServiceLog:(NSArray *)servicesNames { [self.logManager processLog:serviceLog forGroupId:kMSGroupId]; } +#if !TARGET_OS_TV - (void)sendCustomPropertiesLog:(NSDictionary *)properties { MSCustomPropertiesLog *customPropertiesLog = [MSCustomPropertiesLog new]; customPropertiesLog.properties = properties; @@ -414,6 +421,7 @@ - (void)sendCustomPropertiesLog:(NSDictionary *)properti // FIXME: withPriority parameter need to be removed on merge. [self.logManager processLog:customPropertiesLog forGroupId:kMSGroupId]; } +#endif + (void)resetSharedInstance { onceToken = 0; // resets the once_token so dispatch_once will run again diff --git a/MobileCenter/MobileCenter/MobileCenter.h b/MobileCenter/MobileCenter/MobileCenter.h index f74cc0d3ae..ff3a5c5102 100644 --- a/MobileCenter/MobileCenter/MobileCenter.h +++ b/MobileCenter/MobileCenter/MobileCenter.h @@ -10,4 +10,6 @@ #import "MSServiceAbstract.h" #import "MSWrapperSdk.h" #import "MSMobileCenterErrors.h" +#if !TARGET_OS_TV #import "MSCustomProperties.h" +#endif diff --git a/MobileCenter/MobileCenterTests/MSAppDelegateForwarderTests.m b/MobileCenter/MobileCenterTests/MSAppDelegateForwarderTests.m index dfd93cdbd2..335e0d8df2 100644 --- a/MobileCenter/MobileCenterTests/MSAppDelegateForwarderTests.m +++ b/MobileCenter/MobileCenterTests/MSAppDelegateForwarderTests.m @@ -10,15 +10,11 @@ #endif #import "MSAppDelegateForwarderPrivate.h" -#import "MSMockCustomAppDelegate.h" -#import "MSMockOriginalAppDelegate.h" #import "MSTestFrameworks.h" #import "MSUtility+Application.h" @interface MSAppDelegateForwarderTest : XCTestCase -@property(nonatomic) MSMockOriginalAppDelegate *originalAppDelegateMock; -@property(nonatomic) MSMockCustomAppDelegate *customAppDelegateMock; @property(nonatomic) MSApplication *appMock; @end @@ -38,18 +34,15 @@ @implementation MSAppDelegateForwarderTest - (void)setUp { [super setUp]; + // The app delegate forwarder is already set via the load method, reset it for testing. + [MSAppDelegateForwarder reset]; + // Mock app delegate. self.appMock = OCMClassMock([MSApplication class]); - self.originalAppDelegateMock = [MSMockOriginalAppDelegate new]; - self.customAppDelegateMock = [MSMockCustomAppDelegate new]; - id utilMock = OCMClassMock([MSUtility class]); - OCMStub([utilMock sharedAppDelegate]).andReturn(self.originalAppDelegateMock); } - (void)tearDown { - - // Clear delegates. - MSAppDelegateForwarder.delegates = [NSHashTable new]; + [MSAppDelegateForwarder reset]; [super tearDown]; } @@ -91,8 +84,7 @@ - (void)testSwizzleOriginalPushDelegate { NSError *expectedError = [[NSError alloc] initWithDomain:NSItemProviderErrorDomain code:123 userInfo:@{}]; // App delegate not implementing any selector. - Class originalAppDelegateClass = [self createClassConformingToProtocol:@protocol(MSApplicationDelegate)]; - id originalAppDelegate = [originalAppDelegateClass new]; + id originalAppDelegate = [self createOriginalAppDelegateInstance]; SEL selectorToSwizzle = @selector(application:didFailToRegisterForRemoteNotificationsWithError:); [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:selectorToSwizzle]; @@ -113,15 +105,12 @@ - (void)testSwizzleOriginalPushDelegate { */ // App delegate implementing the selector directly. - originalAppDelegateClass = [self createClassConformingToProtocol:@protocol(MSApplicationDelegate)]; + originalAppDelegate = [self createOriginalAppDelegateInstance]; __block BOOL wasCalled = NO; id selectorImp = ^{ wasCalled = YES; }; - Method method = class_getInstanceMethod(originalAppDelegateClass, selectorToSwizzle); - const char *types = method_getTypeEncoding(method); - [self addSelector:selectorToSwizzle implementation:selectorImp types:types toClass:originalAppDelegateClass]; - originalAppDelegate = [originalAppDelegateClass new]; + [self addSelector:selectorToSwizzle implementation:selectorImp toInstance:originalAppDelegate]; [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:selectorToSwizzle]; /* @@ -142,11 +131,10 @@ - (void)testSwizzleOriginalPushDelegate { */ // App delegate implementing the selector indirectly. - Class baseClass = [self createClassConformingToProtocol:@protocol(MSApplicationDelegate)]; - [self addSelector:selectorToSwizzle implementation:selectorImp types:types toClass:baseClass]; - originalAppDelegateClass = [self createClassWithBaseClass:baseClass andConformItToProtocol:nil]; + id originalBaseAppDelegate = [self createOriginalAppDelegateInstance]; + [self addSelector:selectorToSwizzle implementation:selectorImp toInstance:originalBaseAppDelegate]; + originalAppDelegate = [self createInstanceWithBaseClass:[originalBaseAppDelegate class] andConformItToProtocol:nil]; wasCalled = NO; - originalAppDelegate = [originalAppDelegateClass new]; [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:selectorToSwizzle]; /* @@ -172,11 +160,10 @@ - (void)testSwizzleOriginalPushDelegate { id baseSelectorImp = ^{ baseWasCalled = YES; }; - baseClass = [self createClassConformingToProtocol:@protocol(MSApplicationDelegate)]; - [self addSelector:selectorToSwizzle implementation:baseSelectorImp types:types toClass:baseClass]; - originalAppDelegateClass = [self createClassWithBaseClass:baseClass andConformItToProtocol:nil]; - [self addSelector:selectorToSwizzle implementation:selectorImp types:types toClass:originalAppDelegateClass]; - originalAppDelegate = [originalAppDelegateClass new]; + originalBaseAppDelegate = [self createOriginalAppDelegateInstance]; + [self addSelector:selectorToSwizzle implementation:baseSelectorImp toInstance:originalBaseAppDelegate]; + originalAppDelegate = [self createInstanceWithBaseClass:[originalBaseAppDelegate class] andConformItToProtocol:nil]; + [self addSelector:selectorToSwizzle implementation:selectorImp toInstance:originalAppDelegate]; [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:selectorToSwizzle]; /* @@ -198,21 +185,16 @@ - (void)testSwizzleOriginalPushDelegate { */ // App delegate not implementing any selector still responds to selector. - originalAppDelegateClass = [self createClassConformingToProtocol:@protocol(MSApplicationDelegate)]; + originalAppDelegate = [self createOriginalAppDelegateInstance]; SEL instancesRespondToSelector = @selector(instancesRespondToSelector:); id instancesRespondToSelectorImp = ^{ return YES; }; - method = class_getClassMethod(originalAppDelegateClass, instancesRespondToSelector); - const char *instancesRespondToSelectorTypes = method_getTypeEncoding(method); - // Adding a class method to a class requires its meta class. - Class originalAppDelegateMetaClass = object_getClass(originalAppDelegateClass); + // Adding a class method to a class requires its meta class. A meta class is the superclass of a class. [self addSelector:instancesRespondToSelector implementation:instancesRespondToSelectorImp - types:instancesRespondToSelectorTypes - toClass:originalAppDelegateMetaClass]; - originalAppDelegate = [originalAppDelegateClass new]; + toClass:object_getClass([originalAppDelegate class])]; [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:selectorToSwizzle]; /* @@ -225,10 +207,10 @@ - (void)testSwizzleOriginalPushDelegate { */ // Original delegate still responding to selector. - assertThatBool([originalAppDelegateClass instancesRespondToSelector:selectorToSwizzle], isTrue()); + assertThatBool([[originalAppDelegate class] instancesRespondToSelector:selectorToSwizzle], isTrue()); // Swizzling did not happened so no method added/replaced for this selector. - assertThatBool(class_getInstanceMethod(originalAppDelegateClass, selectorToSwizzle) == NULL, isTrue()); + assertThatBool(class_getInstanceMethod([originalAppDelegate class], selectorToSwizzle) == NULL, isTrue()); } #if !TARGET_OS_OSX @@ -245,8 +227,7 @@ - (void)testSwizzleOriginalOpenURLDelegate { NSDictionary *expectedOptions = @{}; // App delegate not implementing any selector. - Class originalAppDelegateClass = [self createClassConformingToProtocol:@protocol(MSApplicationDelegate)]; - id originalAppDelegate = [originalAppDelegateClass new]; + id originalAppDelegate = [self createOriginalAppDelegateInstance]; SEL selectorToSwizzle = @selector(application:openURL:options:); [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:selectorToSwizzle]; @@ -267,16 +248,13 @@ - (void)testSwizzleOriginalOpenURLDelegate { */ // App delegate implementing the selector directly. - originalAppDelegateClass = [self createClassConformingToProtocol:@protocol(MSApplicationDelegate)]; + originalAppDelegate = [self createOriginalAppDelegateInstance]; __block BOOL wasCalled = NO; id selectorImp = ^{ wasCalled = YES; return YES; }; - Method method = class_getInstanceMethod(originalAppDelegateClass, selectorToSwizzle); - const char *types = method_getTypeEncoding(method); - [self addSelector:selectorToSwizzle implementation:selectorImp types:types toClass:originalAppDelegateClass]; - originalAppDelegate = [originalAppDelegateClass new]; + [self addSelector:selectorToSwizzle implementation:selectorImp toInstance:originalAppDelegate]; [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:selectorToSwizzle]; /* @@ -297,11 +275,10 @@ - (void)testSwizzleOriginalOpenURLDelegate { */ // App delegate implementing the selector indirectly. - Class baseClass = [self createClassConformingToProtocol:@protocol(MSApplicationDelegate)]; - [self addSelector:selectorToSwizzle implementation:selectorImp types:types toClass:baseClass]; - originalAppDelegateClass = [self createClassWithBaseClass:baseClass andConformItToProtocol:nil]; + id originalBaseAppDelegate = [self createOriginalAppDelegateInstance]; + [self addSelector:selectorToSwizzle implementation:selectorImp toInstance:originalBaseAppDelegate]; + originalAppDelegate = [self createInstanceWithBaseClass:[originalBaseAppDelegate class] andConformItToProtocol:nil]; wasCalled = NO; - originalAppDelegate = [originalAppDelegateClass new]; [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:selectorToSwizzle]; /* @@ -327,11 +304,10 @@ - (void)testSwizzleOriginalOpenURLDelegate { id baseSelectorImp = ^{ baseWasCalled = YES; }; - baseClass = [self createClassConformingToProtocol:@protocol(MSApplicationDelegate)]; - [self addSelector:selectorToSwizzle implementation:baseSelectorImp types:types toClass:baseClass]; - originalAppDelegateClass = [self createClassWithBaseClass:baseClass andConformItToProtocol:nil]; - [self addSelector:selectorToSwizzle implementation:selectorImp types:types toClass:originalAppDelegateClass]; - originalAppDelegate = [originalAppDelegateClass new]; + originalBaseAppDelegate = [self createOriginalAppDelegateInstance]; + [self addSelector:selectorToSwizzle implementation:baseSelectorImp toInstance:originalBaseAppDelegate]; + originalAppDelegate = [self createInstanceWithBaseClass:[originalBaseAppDelegate class] andConformItToProtocol:nil]; + [self addSelector:selectorToSwizzle implementation:selectorImp toInstance:originalAppDelegate]; [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:selectorToSwizzle]; /* @@ -353,21 +329,16 @@ - (void)testSwizzleOriginalOpenURLDelegate { */ // App delegate not implementing any selector still responds to selector. - originalAppDelegateClass = [self createClassConformingToProtocol:@protocol(MSApplicationDelegate)]; + originalAppDelegate = [self createOriginalAppDelegateInstance]; SEL instancesRespondToSelector = @selector(instancesRespondToSelector:); id instancesRespondToSelectorImp = ^{ return YES; }; - method = class_getClassMethod(originalAppDelegateClass, instancesRespondToSelector); - const char *instancesRespondToSelectorTypes = method_getTypeEncoding(method); // Adding a class method to a class requires its meta class. - Class originalAppDelegateMetaClass = object_getClass(originalAppDelegateClass); [self addSelector:instancesRespondToSelector implementation:instancesRespondToSelectorImp - types:instancesRespondToSelectorTypes - toClass:originalAppDelegateMetaClass]; - originalAppDelegate = [originalAppDelegateClass new]; + toClass:object_getClass([originalAppDelegate class])]; [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:selectorToSwizzle]; /* @@ -380,10 +351,10 @@ - (void)testSwizzleOriginalOpenURLDelegate { */ // Original delegate still responding to selector. - assertThatBool([originalAppDelegateClass instancesRespondToSelector:selectorToSwizzle], isTrue()); + assertThatBool([[originalAppDelegate class] instancesRespondToSelector:selectorToSwizzle], isTrue()); // Swizzling did not happened so no method added/replaced for this selector. - assertThatBool(class_getInstanceMethod(originalAppDelegateClass, selectorToSwizzle) == NULL, isTrue()); + assertThatBool(class_getInstanceMethod([originalAppDelegate class], selectorToSwizzle) == NULL, isTrue()); } #endif @@ -419,29 +390,26 @@ - (void)testWithoutCustomDelegate { // If NSURL *expectedURL = [NSURL URLWithString:@"https://www.contoso.com/sending-positive-waves"]; - NSDictionary *expectedAnnotation = @{}; + NSDictionary *expectedOptions = @{}; BOOL expectedReturnedValue = YES; MSApplication *appMock = self.appMock; XCTestExpectation *originalCalledExpectation = [self expectationWithDescription:@"Original delegate called."]; - SEL originalOpenURLiOS42Sel = @selector(application:openURL:sourceApplication:annotation:); - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:originalOpenURLiOS42Sel]; - self.originalAppDelegateMock.delegateValidators[NSStringFromSelector(originalOpenURLiOS42Sel)] = - ^(MSApplication *application, NSURL *url, NSString *sApplication, id annotation) { - - // Then - assertThat(application, is(appMock)); - assertThat(url, is(expectedURL)); - assertThat(sApplication, nilValue()); - assertThat(annotation, is(expectedAnnotation)); - [originalCalledExpectation fulfill]; - return expectedReturnedValue; - }; + SEL originalOpenURLSel = @selector(application:openURL:options:); + [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:originalOpenURLSel]; + id originalAppDelegate = [self createOriginalAppDelegateInstance]; + id originalOpenURLImp = ^(__attribute__((unused)) id itSelf, MSApplication *application, NSURL *url, id options) { + + // Then + assertThat(application, is(appMock)); + assertThat(url, is(expectedURL)); + assertThat(options, is(expectedOptions)); + [originalCalledExpectation fulfill]; + return expectedReturnedValue; + }; + [self addSelector:originalOpenURLSel implementation:originalOpenURLImp toInstance:originalAppDelegate]; // When - BOOL returnedValue = [self.originalAppDelegateMock application:self.appMock - openURL:expectedURL - sourceApplication:nil - annotation:expectedAnnotation]; + BOOL returnedValue = [originalAppDelegate application:self.appMock openURL:expectedURL options:expectedOptions]; // Then assertThatUnsignedLong(MSAppDelegateForwarder.delegates.count, equalToUnsignedLong(0)); @@ -459,19 +427,21 @@ - (void)testWithoutCustomDelegateNotReturningValue { SEL originalDidRegisterForRemoteNotificationsWithDeviceTokenSel = @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:); [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:originalDidRegisterForRemoteNotificationsWithDeviceTokenSel]; - self.originalAppDelegateMock - .delegateValidators[NSStringFromSelector(originalDidRegisterForRemoteNotificationsWithDeviceTokenSel)] = - ^(MSApplication *application, NSData *deviceToken) { + id originalAppDelegate = [self createOriginalAppDelegateInstance]; + id originalDidRegisterForRemoteNotificationsWithDeviceTokenImp = + ^(__attribute__((unused)) id itSelf, MSApplication *application, NSData *deviceToken) { // Then assertThat(application, is(appMock)); assertThat(deviceToken, is(expectedToken)); [originalCalledExpectation fulfill]; }; + [self addSelector:originalDidRegisterForRemoteNotificationsWithDeviceTokenSel + implementation:originalDidRegisterForRemoteNotificationsWithDeviceTokenImp + toInstance:originalAppDelegate]; // When - [self.originalAppDelegateMock application:self.appMock - didRegisterForRemoteNotificationsWithDeviceToken:expectedToken]; + [originalAppDelegate application:self.appMock didRegisterForRemoteNotificationsWithDeviceToken:expectedToken]; // Then assertThatUnsignedLong(MSAppDelegateForwarder.delegates.count, equalToUnsignedLong(0)); @@ -483,45 +453,44 @@ - (void)testWithOneCustomDelegate { // If NSURL *expectedURL = [NSURL URLWithString:@"https://www.contoso.com/sending-positive-waves"]; - NSDictionary *expectedAnnotation = @{}; + NSDictionary *expectedOptions = @{}; BOOL expectedReturnedValue = YES; MSApplication *appMock = self.appMock; XCTestExpectation *originalCalledExpectation = [self expectationWithDescription:@"Original delegate called."]; XCTestExpectation *customCalledExpectation = [self expectationWithDescription:@"Custom delegate called."]; - SEL originalOpenURLiOS42Sel = @selector(application:openURL:sourceApplication:annotation:); - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:originalOpenURLiOS42Sel]; - self.originalAppDelegateMock.delegateValidators[NSStringFromSelector(originalOpenURLiOS42Sel)] = - ^(MSApplication *application, NSURL *url, NSString *sApplication, id annotation) { + SEL originalOpenURLiOS90Sel = @selector(application:openURL:options:); + [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:originalOpenURLiOS90Sel]; + id originalAppDelegate = [self createOriginalAppDelegateInstance]; + id originalOpenURLiOS90Imp = + ^(__attribute__((unused)) id itSelf, MSApplication *application, NSURL *url, id options) { // Then assertThat(application, is(appMock)); assertThat(url, is(expectedURL)); - assertThat(sApplication, nilValue()); - assertThat(annotation, is(expectedAnnotation)); + assertThat(options, is(expectedOptions)); [originalCalledExpectation fulfill]; return expectedReturnedValue; }; - NSString *customOpenURLiOS42Str = - NSStringFromSelector(@selector(application:openURL:sourceApplication:annotation:returnedValue:)); - self.customAppDelegateMock.delegateValidators[customOpenURLiOS42Str] = - ^(MSApplication *application, NSURL *url, NSString *sApplication, id annotation, BOOL returnedValue) { + [self addSelector:originalOpenURLiOS90Sel implementation:originalOpenURLiOS90Imp toInstance:originalAppDelegate]; + SEL customOpenURLiOS90Sel = @selector(application:openURL:options:returnedValue:); + id customAppDelegate = [self createCustomAppDelegateInstance]; + id customOpenURLiOS90Imp = + ^(__attribute__((unused)) id itSelf, MSApplication *application, NSURL *url, id options, BOOL returnedValue) { // Then assertThat(application, is(appMock)); assertThat(url, is(expectedURL)); - assertThat(sApplication, nilValue()); - assertThat(annotation, is(expectedAnnotation)); + assertThat(options, is(expectedOptions)); assertThatBool(returnedValue, is(@(expectedReturnedValue))); [customCalledExpectation fulfill]; return expectedReturnedValue; }; - [MSAppDelegateForwarder addDelegate:self.customAppDelegateMock]; + [self addSelector:customOpenURLiOS90Sel implementation:customOpenURLiOS90Imp toInstance:customAppDelegate]; + [MSAppDelegateForwarder addDelegate:customAppDelegate]; + [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; // When - BOOL returnedValue = [self.originalAppDelegateMock application:self.appMock - openURL:expectedURL - sourceApplication:nil - annotation:expectedAnnotation]; + BOOL returnedValue = [originalAppDelegate application:self.appMock openURL:expectedURL options:expectedOptions]; // Then assertThatBool(returnedValue, is(@(expectedReturnedValue))); @@ -537,28 +506,36 @@ - (void)testWithOneCustomDelegateNotReturningValue { XCTestExpectation *originalCalledExpectation = [self expectationWithDescription:@"Original delegate called."]; XCTestExpectation *customCalledExpectation = [self expectationWithDescription:@"Custom delegate called."]; SEL didRegisterNotificationSel = @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:); - NSString *didRegisterNotificationStr = NSStringFromSelector(didRegisterNotificationSel); [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:didRegisterNotificationSel]; - self.originalAppDelegateMock.delegateValidators[didRegisterNotificationStr] = - ^(MSApplication *application, NSData *deviceToken) { + id originalAppDelegate = [self createOriginalAppDelegateInstance]; + id originalDidRegisterNotificationImp = + ^(__attribute__((unused)) id itSelf, MSApplication *application, NSData *deviceToken) { // Then assertThat(application, is(appMock)); assertThat(deviceToken, is(expectedToken)); [originalCalledExpectation fulfill]; }; - self.customAppDelegateMock.delegateValidators[didRegisterNotificationStr] = - ^(MSApplication *application, NSData *deviceToken) { + [self addSelector:didRegisterNotificationSel + implementation:originalDidRegisterNotificationImp + toInstance:originalAppDelegate]; + id customAppDelegate = [self createCustomAppDelegateInstance]; + id customDidRegisterNotificationImp = + ^(__attribute__((unused)) id itSelf, MSApplication *application, NSData *deviceToken) { // Then assertThat(application, is(appMock)); assertThat(deviceToken, is(expectedToken)); [customCalledExpectation fulfill]; }; - [MSAppDelegateForwarder addDelegate:self.customAppDelegateMock]; + [self addSelector:didRegisterNotificationSel + implementation:customDidRegisterNotificationImp + toInstance:customAppDelegate]; + [MSAppDelegateForwarder addDelegate:customAppDelegate]; + [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; // When - [self.originalAppDelegateMock application:appMock didRegisterForRemoteNotificationsWithDeviceToken:expectedToken]; + [originalAppDelegate application:appMock didRegisterForRemoteNotificationsWithDeviceToken:expectedToken]; // Then [self waitForExpectations:@[ originalCalledExpectation, customCalledExpectation ] timeout:1]; @@ -575,10 +552,11 @@ - (void)testDontForwardSelectorsNotToOverrideIfAlreadyImplementedByOriginalDeleg MSApplication *appMock = self.appMock; XCTestExpectation *originalCalledExpectation = [self expectationWithDescription:@"Original delegate called."]; SEL didReceiveRemoteNotificationSel = @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:); - NSString *didReceiveRemoteNotificationStr = NSStringFromSelector(didReceiveRemoteNotificationSel); [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:didReceiveRemoteNotificationSel]; - self.originalAppDelegateMock.delegateValidators[didReceiveRemoteNotificationStr] = - ^(MSApplication *application, NSDictionary *userInfo, void (^completionHandler)(UIBackgroundFetchResult result)) { + id originalAppDelegate = [self createOriginalAppDelegateInstance]; + id originalDidReceiveRemoteNotificationImp = + ^(__attribute__((unused)) id itSelf, MSApplication *application, NSDictionary *userInfo, + void (^completionHandler)(UIBackgroundFetchResult result)) { // Then assertThat(application, is(appMock)); @@ -586,21 +564,30 @@ - (void)testDontForwardSelectorsNotToOverrideIfAlreadyImplementedByOriginalDeleg assertThat(completionHandler, is(expectedCompletionHandler)); [originalCalledExpectation fulfill]; }; - self.customAppDelegateMock.delegateValidators[didReceiveRemoteNotificationStr] = - ^(__attribute__((unused)) MSApplication *application, __attribute__((unused)) NSData *deviceToken) { + [self addSelector:didReceiveRemoteNotificationSel + implementation:originalDidReceiveRemoteNotificationImp + toInstance:originalAppDelegate]; + id customAppDelegate = [self createCustomAppDelegateInstance]; + id customDidReceiveRemoteNotificationImp = + ^(__attribute__((unused)) id itSelf, __attribute__((unused)) MSApplication *application, + __attribute__((unused)) NSData *deviceToken) { // Then XCTFail(@"This method is already implemented in the original delegate and is marked not to be swizzled."); }; - [MSAppDelegateForwarder addDelegate:self.customAppDelegateMock]; + [self addSelector:didReceiveRemoteNotificationSel + implementation:customDidReceiveRemoteNotificationImp + toInstance:customAppDelegate]; + [MSAppDelegateForwarder addDelegate:customAppDelegate]; // When - [self.originalAppDelegateMock application:appMock - didReceiveRemoteNotification:expectedUserInfo - fetchCompletionHandler:expectedCompletionHandler]; + [originalAppDelegate application:appMock + didReceiveRemoteNotification:expectedUserInfo + fetchCompletionHandler:expectedCompletionHandler]; // Then - assertThatBool([MSAppDelegateForwarder.selectorsNotToOverride containsObject:didReceiveRemoteNotificationStr], + assertThatBool([MSAppDelegateForwarder.selectorsNotToOverride + containsObject:NSStringFromSelector(didReceiveRemoteNotificationSel)], isTrue()); [self waitForExpectations:@[ originalCalledExpectation ] timeout:1]; } @@ -611,61 +598,59 @@ - (void)testWithMultipleCustomOpenURLDelegates { // If NSURL *expectedURL = [NSURL URLWithString:@"https://www.contoso.com/sending-positive-waves"]; - NSDictionary *expectedAnnotation = @{}; + NSDictionary *expectedOptions = @{}; BOOL expectedReturnedValue = YES; XCTestExpectation *originalCalledExpectation = [self expectationWithDescription:@"Original delegate called."]; XCTestExpectation *customCalledExpectation1 = [self expectationWithDescription:@"Custom delegate 1 called."]; XCTestExpectation *customCalledExpectation2 = [self expectationWithDescription:@"Custom delegate 2 called."]; MSApplication *appMock = self.appMock; - SEL originalOpenURLiOS42Sel = @selector(application:openURL:sourceApplication:annotation:); - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:originalOpenURLiOS42Sel]; - self.originalAppDelegateMock.delegateValidators[NSStringFromSelector(originalOpenURLiOS42Sel)] = - ^(MSApplication *application, NSURL *url, NSString *sApplication, id annotation) { + SEL originalOpenURLiOS90Sel = @selector(application:openURL:options:); + [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:originalOpenURLiOS90Sel]; + id originalAppDelegate = [self createOriginalAppDelegateInstance]; + id originalOpenURLiOS90Imp = + ^(__attribute__((unused)) id itSelf, MSApplication *application, NSURL *url, id options) { // Then assertThat(application, is(appMock)); assertThat(url, is(expectedURL)); - assertThat(sApplication, nilValue()); - assertThat(annotation, is(expectedAnnotation)); + assertThat(options, is(expectedOptions)); [originalCalledExpectation fulfill]; return expectedReturnedValue; }; - MSMockCustomAppDelegate *customAppDelegateMock1 = [MSMockCustomAppDelegate new]; - NSString *customOpenURLiOS42Str = - NSStringFromSelector(@selector(application:openURL:sourceApplication:annotation:returnedValue:)); - customAppDelegateMock1.delegateValidators[customOpenURLiOS42Str] = - ^(MSApplication *application, NSURL *url, NSString *sApplication, id annotation, BOOL returnedValue) { + [self addSelector:originalOpenURLiOS90Sel implementation:originalOpenURLiOS90Imp toInstance:originalAppDelegate]; + SEL customOpenURLiOS90Sel = @selector(application:openURL:options:returnedValue:); + id customAppDelegate1 = [self createCustomAppDelegateInstance]; + id customOpenURLiOS90Imp1 = + ^(__attribute__((unused)) id itSelf, MSApplication *application, NSURL *url, id options, BOOL returnedValue) { // Then assertThat(application, is(appMock)); assertThat(url, is(expectedURL)); - assertThat(sApplication, nilValue()); - assertThat(annotation, is(expectedAnnotation)); + assertThat(options, is(expectedOptions)); assertThatBool(returnedValue, is(@(expectedReturnedValue))); [customCalledExpectation1 fulfill]; return expectedReturnedValue; }; - MSMockCustomAppDelegate *customAppDelegateMock2 = [MSMockCustomAppDelegate new]; - customAppDelegateMock2.delegateValidators[customOpenURLiOS42Str] = - ^(MSApplication *application, NSURL *url, NSString *sApplication, id annotation, BOOL returnedValue) { + [self addSelector:customOpenURLiOS90Sel implementation:customOpenURLiOS90Imp1 toInstance:customAppDelegate1]; + id customAppDelegate2 = [self createCustomAppDelegateInstance]; + id customOpenURLiOS90Imp2 = + ^(__attribute__((unused)) id itSelf, MSApplication *application, NSURL *url, id options, BOOL returnedValue) { // Then assertThat(application, is(appMock)); assertThat(url, is(expectedURL)); - assertThat(sApplication, nilValue()); - assertThat(annotation, is(expectedAnnotation)); + assertThat(options, is(expectedOptions)); assertThatBool(returnedValue, is(@(expectedReturnedValue))); [customCalledExpectation2 fulfill]; return expectedReturnedValue; }; - [MSAppDelegateForwarder addDelegate:customAppDelegateMock1]; - [MSAppDelegateForwarder addDelegate:customAppDelegateMock2]; + [self addSelector:customOpenURLiOS90Sel implementation:customOpenURLiOS90Imp2 toInstance:customAppDelegate2]; + [MSAppDelegateForwarder addDelegate:customAppDelegate1]; + [MSAppDelegateForwarder addDelegate:customAppDelegate2]; + [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; // When - BOOL returnedValue = [self.originalAppDelegateMock application:self.appMock - openURL:expectedURL - sourceApplication:nil - annotation:expectedAnnotation]; + BOOL returnedValue = [originalAppDelegate application:self.appMock openURL:expectedURL options:expectedOptions]; // Then assertThatBool(returnedValue, is(@(expectedReturnedValue))); @@ -685,41 +670,50 @@ - (void)testWithMultipleCustomPushDelegates { SEL originalDidRegisterForRemoteNotificationWithDeviceTokenSel = @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:); [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:originalDidRegisterForRemoteNotificationWithDeviceTokenSel]; - self.originalAppDelegateMock - .delegateValidators[NSStringFromSelector(originalDidRegisterForRemoteNotificationWithDeviceTokenSel)] = - ^(MSApplication *application, NSData *deviceToken) { + id originalAppDelegate = [self createOriginalAppDelegateInstance]; + id originalDidRegisterForRemoteNotificationWithDeviceTokenImp = + ^(__attribute__((unused)) id itSelf, MSApplication *application, NSData *deviceToken) { // Then assertThat(application, is(appMock)); assertThat(deviceToken, is(expectedToken)); [originalCalledExpectation fulfill]; }; - MSMockCustomAppDelegate *customAppDelegateMock1 = [MSMockCustomAppDelegate new]; - NSString *customDidRegisterForRemoteNotificationWithDeviceTokenStr = - NSStringFromSelector(@selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)); - customAppDelegateMock1.delegateValidators[customDidRegisterForRemoteNotificationWithDeviceTokenStr] = - ^(MSApplication *application, NSData *deviceToken) { + [self addSelector:originalDidRegisterForRemoteNotificationWithDeviceTokenSel + implementation:originalDidRegisterForRemoteNotificationWithDeviceTokenImp + toInstance:originalAppDelegate]; + SEL customDidRegisterForRemoteNotificationWithDeviceTokenSel = + @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:); + id customAppDelegate1 = [self createCustomAppDelegateInstance]; + id customDidRegisterForRemoteNotificationWithDeviceTokenImp1 = + ^(__attribute__((unused)) id itSelf, MSApplication *application, NSData *deviceToken) { // Then assertThat(application, is(appMock)); assertThat(deviceToken, is(expectedToken)); [customCalledExpectation1 fulfill]; }; - MSMockCustomAppDelegate *customAppDelegateMock2 = [MSMockCustomAppDelegate new]; - customAppDelegateMock2.delegateValidators[customDidRegisterForRemoteNotificationWithDeviceTokenStr] = - ^(MSApplication *application, NSData *deviceToken) { + [self addSelector:customDidRegisterForRemoteNotificationWithDeviceTokenSel + implementation:customDidRegisterForRemoteNotificationWithDeviceTokenImp1 + toInstance:customAppDelegate1]; + id customAppDelegate2 = [self createCustomAppDelegateInstance]; + id customDidRegisterForRemoteNotificationWithDeviceTokenImp2 = + ^(__attribute__((unused)) id itSelf, MSApplication *application, NSData *deviceToken) { // Then assertThat(application, is(appMock)); assertThat(deviceToken, is(expectedToken)); [customCalledExpectation2 fulfill]; }; - [MSAppDelegateForwarder addDelegate:customAppDelegateMock1]; - [MSAppDelegateForwarder addDelegate:customAppDelegateMock2]; + [self addSelector:customDidRegisterForRemoteNotificationWithDeviceTokenSel + implementation:customDidRegisterForRemoteNotificationWithDeviceTokenImp2 + toInstance:customAppDelegate2]; + [MSAppDelegateForwarder addDelegate:customAppDelegate1]; + [MSAppDelegateForwarder addDelegate:customAppDelegate2]; + [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; // When - [self.originalAppDelegateMock application:self.appMock - didRegisterForRemoteNotificationsWithDeviceToken:expectedToken]; + [originalAppDelegate application:self.appMock didRegisterForRemoteNotificationsWithDeviceToken:expectedToken]; // Then [self waitForExpectations:@[ originalCalledExpectation, customCalledExpectation1, customCalledExpectation2 ] @@ -737,36 +731,38 @@ - (void)testWithRemovedCustomOpenURLDelegate { XCTestExpectation *originalCalledExpectation = [self expectationWithDescription:@"Original delegate called."]; SEL originalOpenURLiOS42Sel = @selector(application:openURL:sourceApplication:annotation:); [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:originalOpenURLiOS42Sel]; - self.originalAppDelegateMock.delegateValidators[NSStringFromSelector(originalOpenURLiOS42Sel)] = - ^(MSApplication *application, NSURL *url, NSString *sApplication, id annotation) { - - // Then - assertThat(application, is(appMock)); - assertThat(url, is(expectedURL)); - assertThat(sApplication, nilValue()); - assertThat(annotation, is(expectedAnnotation)); - [originalCalledExpectation fulfill]; - return expectedReturnedValue; - }; - NSString *customOpenURLiOS42Str = - NSStringFromSelector(@selector(application:openURL:sourceApplication:annotation:returnedValue:)); - self.customAppDelegateMock.delegateValidators[customOpenURLiOS42Str] = - ^(__attribute__((unused)) MSApplication *application, __attribute__((unused)) NSURL *url, - __attribute__((unused)) NSString *sApplication, __attribute__((unused)) id annotation, - __attribute__((unused)) BOOL returnedValue) { - - // Then - XCTFail(@"Custom delegate got called but is removed."); - return expectedReturnedValue; - }; - [MSAppDelegateForwarder addDelegate:self.customAppDelegateMock]; - [MSAppDelegateForwarder removeDelegate:self.customAppDelegateMock]; + id originalAppDelegate = [self createOriginalAppDelegateInstance]; + id originalOpenURLiOS42Imp = ^(__attribute__((unused)) id itSelf, MSApplication *application, NSURL *url, + NSString *sApplication, id annotation) { + + // Then + assertThat(application, is(appMock)); + assertThat(url, is(expectedURL)); + assertThat(sApplication, nilValue()); + assertThat(annotation, is(expectedAnnotation)); + [originalCalledExpectation fulfill]; + return expectedReturnedValue; + }; + [self addSelector:originalOpenURLiOS42Sel implementation:originalOpenURLiOS42Imp toInstance:originalAppDelegate]; + SEL customOpenURLiOS42Sel = @selector(application:openURL:sourceApplication:annotation:returnedValue:); + id customAppDelegate = [self createCustomAppDelegateInstance]; + id customOpenURLiOS42Imp = ^(__attribute__((unused)) id itSelf, __attribute__((unused)) MSApplication *application, + __attribute__((unused)) NSURL *url, __attribute__((unused)) NSString *sApplication, + __attribute__((unused)) id annotation, __attribute__((unused)) BOOL returnedValue) { + + // Then + XCTFail(@"Custom delegate got called but is removed."); + return expectedReturnedValue; + }; + [self addSelector:customOpenURLiOS42Sel implementation:customOpenURLiOS42Imp toInstance:customAppDelegate]; + [MSAppDelegateForwarder addDelegate:customAppDelegate]; + [MSAppDelegateForwarder removeDelegate:customAppDelegate]; // When - BOOL returnedValue = [self.originalAppDelegateMock application:self.appMock - openURL:expectedURL - sourceApplication:nil - annotation:expectedAnnotation]; + BOOL returnedValue = [originalAppDelegate application:self.appMock + openURL:expectedURL + sourceApplication:nil + annotation:expectedAnnotation]; // Then assertThatBool(returnedValue, is(@(expectedReturnedValue))); @@ -783,29 +779,36 @@ - (void)testWithRemovedCustomDidRegisterForRemoteNotificationWithDeviceTokenDele SEL originalDidRegisterForRemoteNotificationWithDeviceTokenSel = @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:); [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:originalDidRegisterForRemoteNotificationWithDeviceTokenSel]; - self.originalAppDelegateMock - .delegateValidators[NSStringFromSelector(originalDidRegisterForRemoteNotificationWithDeviceTokenSel)] = - ^(MSApplication *application, NSData *deviceToken) { + id originalAppDelegate = [self createOriginalAppDelegateInstance]; + id originalDidRegisterForRemoteNotificationWithDeviceTokenImp = + ^(__attribute__((unused)) id itSelf, MSApplication *application, NSData *deviceToken) { // Then assertThat(application, is(appMock)); assertThat(deviceToken, is(expectedToken)); [originalCalledExpectation fulfill]; }; - NSString *customDidRegisterForRemoteNotificationWithDeviceTokenStr = - NSStringFromSelector(@selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)); - self.customAppDelegateMock.delegateValidators[customDidRegisterForRemoteNotificationWithDeviceTokenStr] = - ^(__attribute__((unused)) MSApplication *application, __attribute__((unused)) NSData *deviceToken) { + [self addSelector:originalDidRegisterForRemoteNotificationWithDeviceTokenSel + implementation:originalDidRegisterForRemoteNotificationWithDeviceTokenImp + toInstance:originalAppDelegate]; + SEL customDidRegisterForRemoteNotificationWithDeviceTokenSel = + @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:); + id customAppDelegate = [self createCustomAppDelegateInstance]; + id customDidRegisterForRemoteNotificationWithDeviceTokenImp = + ^(__attribute__((unused)) id itSelf, __attribute__((unused)) MSApplication *application, + __attribute__((unused)) NSData *deviceToken) { // Then XCTFail(@"Custom delegate got called but is removed."); }; - [MSAppDelegateForwarder addDelegate:self.customAppDelegateMock]; - [MSAppDelegateForwarder removeDelegate:self.customAppDelegateMock]; + [self addSelector:customDidRegisterForRemoteNotificationWithDeviceTokenSel + implementation:customDidRegisterForRemoteNotificationWithDeviceTokenImp + toInstance:customAppDelegate]; + [MSAppDelegateForwarder addDelegate:customAppDelegate]; + [MSAppDelegateForwarder removeDelegate:customAppDelegate]; // When - [self.originalAppDelegateMock application:self.appMock - didRegisterForRemoteNotificationsWithDeviceToken:expectedToken]; + [originalAppDelegate application:self.appMock didRegisterForRemoteNotificationsWithDeviceToken:expectedToken]; // Then [self waitForExpectations:@[ originalCalledExpectation ] timeout:1]; @@ -822,36 +825,38 @@ - (void)testDontForwardOpenURLOnDisable { XCTestExpectation *originalCalledExpectation = [self expectationWithDescription:@"Original delegate called."]; SEL originalOpenURLiOS42Sel = @selector(application:openURL:sourceApplication:annotation:); [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:originalOpenURLiOS42Sel]; - self.originalAppDelegateMock.delegateValidators[NSStringFromSelector(originalOpenURLiOS42Sel)] = - ^(MSApplication *application, NSURL *url, NSString *sApplication, id annotation) { - - // Then - assertThat(application, is(appMock)); - assertThat(url, is(expectedURL)); - assertThat(sApplication, nilValue()); - assertThat(annotation, is(expectedAnnotation)); - [originalCalledExpectation fulfill]; - return expectedReturnedValue; - }; - NSString *customOpenURLiOS42Str = - NSStringFromSelector(@selector(application:openURL:sourceApplication:annotation:returnedValue:)); - self.customAppDelegateMock.delegateValidators[customOpenURLiOS42Str] = - ^(__attribute__((unused)) MSApplication *application, __attribute__((unused)) NSURL *url, - __attribute__((unused)) NSString *sApplication, __attribute__((unused)) id annotation, - __attribute__((unused)) BOOL returnedValue) { - - // Then - XCTFail(@"Custom delegate got called but is removed."); - return expectedReturnedValue; - }; - [MSAppDelegateForwarder addDelegate:self.customAppDelegateMock]; + id originalAppDelegate = [self createOriginalAppDelegateInstance]; + id originalOpenURLiOS42Imp = ^(__attribute__((unused)) id itSelf, MSApplication *application, NSURL *url, + NSString *sApplication, id annotation) { + + // Then + assertThat(application, is(appMock)); + assertThat(url, is(expectedURL)); + assertThat(sApplication, nilValue()); + assertThat(annotation, is(expectedAnnotation)); + [originalCalledExpectation fulfill]; + return expectedReturnedValue; + }; + [self addSelector:originalOpenURLiOS42Sel implementation:originalOpenURLiOS42Imp toInstance:originalAppDelegate]; + SEL customOpenURLiOS42Sel = @selector(application:openURL:sourceApplication:annotation:returnedValue:); + id customAppDelegate = [self createCustomAppDelegateInstance]; + id customOpenURLiOS42Imp = ^(__attribute__((unused)) id itSelf, __attribute__((unused)) MSApplication *application, + __attribute__((unused)) NSURL *url, __attribute__((unused)) NSString *sApplication, + __attribute__((unused)) id annotation, __attribute__((unused)) BOOL returnedValue) { + + // Then + XCTFail(@"Custom delegate got called but is removed."); + return expectedReturnedValue; + }; + [self addSelector:customOpenURLiOS42Sel implementation:customOpenURLiOS42Imp toInstance:customAppDelegate]; + [MSAppDelegateForwarder addDelegate:customAppDelegate]; MSAppDelegateForwarder.enabled = NO; // When - BOOL returnedValue = [self.originalAppDelegateMock application:self.appMock - openURL:expectedURL - sourceApplication:nil - annotation:expectedAnnotation]; + BOOL returnedValue = [originalAppDelegate application:self.appMock + openURL:expectedURL + sourceApplication:nil + annotation:expectedAnnotation]; // Then assertThatBool(returnedValue, is(@(expectedReturnedValue))); @@ -869,29 +874,36 @@ - (void)testDontForwardDidRegisterForRemoteNotificationWithDeviceTokenOnDisable SEL originalDidRegisterForRemoteNotificationWithDeviceTokenSel = @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:); [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:originalDidRegisterForRemoteNotificationWithDeviceTokenSel]; - self.originalAppDelegateMock - .delegateValidators[NSStringFromSelector(originalDidRegisterForRemoteNotificationWithDeviceTokenSel)] = - ^(MSApplication *application, NSData *deviceToken) { + id originalAppDelegate = [self createOriginalAppDelegateInstance]; + id originalDidRegisterForRemoteNotificationWithDeviceTokenImp = + ^(__attribute__((unused)) id itSelf, MSApplication *application, NSData *deviceToken) { // Then assertThat(application, is(appMock)); assertThat(deviceToken, is(expectedToken)); [originalCalledExpectation fulfill]; }; - NSString *customDidRegisterForRemoteNotificationWithDeviceTokenStr = - NSStringFromSelector(@selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)); - self.customAppDelegateMock.delegateValidators[customDidRegisterForRemoteNotificationWithDeviceTokenStr] = - ^(__attribute__((unused)) MSApplication *application, __attribute__((unused)) NSData *deviceToken) { + [self addSelector:originalDidRegisterForRemoteNotificationWithDeviceTokenSel + implementation:originalDidRegisterForRemoteNotificationWithDeviceTokenImp + toInstance:originalAppDelegate]; + SEL customDidRegisterForRemoteNotificationWithDeviceTokenSel = + @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:); + id customAppDelegate = [self createCustomAppDelegateInstance]; + id customDidRegisterForRemoteNotificationWithDeviceTokenImp = + ^(__attribute__((unused)) id itSelf, __attribute__((unused)) MSApplication *application, + __attribute__((unused)) NSData *deviceToken) { // Then XCTFail(@"Custom delegate got called but is removed."); }; - [MSAppDelegateForwarder addDelegate:self.customAppDelegateMock]; + [self addSelector:customDidRegisterForRemoteNotificationWithDeviceTokenSel + implementation:customDidRegisterForRemoteNotificationWithDeviceTokenImp + toInstance:customAppDelegate]; + [MSAppDelegateForwarder addDelegate:customAppDelegate]; MSAppDelegateForwarder.enabled = NO; // When - [self.originalAppDelegateMock application:self.appMock - didRegisterForRemoteNotificationsWithDeviceToken:expectedToken]; + [originalAppDelegate application:self.appMock didRegisterForRemoteNotificationsWithDeviceToken:expectedToken]; // Then [self waitForExpectations:@[ originalCalledExpectation ] timeout:1]; @@ -912,56 +924,60 @@ - (void)testReturnValueChaining { MSApplication *appMock = self.appMock; SEL originalOpenURLiOS42Sel = @selector(application:openURL:sourceApplication:annotation:); [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:originalOpenURLiOS42Sel]; - self.originalAppDelegateMock.delegateValidators[NSStringFromSelector(originalOpenURLiOS42Sel)] = - ^(MSApplication *application, NSURL *url, NSString *sApplication, id annotation) { - - // Then - assertThat(application, is(appMock)); - assertThat(url, is(expectedURL)); - assertThat(sApplication, nilValue()); - assertThat(annotation, is(expectedAnnotation)); - [originalCalledExpectation fulfill]; - expectedReturnedValue = initialReturnValue; - return expectedReturnedValue; - }; - MSMockCustomAppDelegate *customAppDelegateMock1 = [MSMockCustomAppDelegate new]; - MSMockCustomAppDelegate *customAppDelegateMock2 = [MSMockCustomAppDelegate new]; - NSString *customOpenURLiOS42Str = - NSStringFromSelector(@selector(application:openURL:sourceApplication:annotation:returnedValue:)); - customAppDelegateMock1.delegateValidators[customOpenURLiOS42Str] = - ^(MSApplication *application, NSURL *url, NSString *sApplication, id annotation, BOOL returnedValue) { - - // Then - assertThat(application, is(appMock)); - assertThat(url, is(expectedURL)); - assertThat(sApplication, nilValue()); - assertThat(annotation, is(expectedAnnotation)); - assertThatBool(returnedValue, is(@(expectedReturnedValue))); - expectedReturnedValue = !returnedValue; - [customCalledExpectation1 fulfill]; - return expectedReturnedValue; - }; - customAppDelegateMock2.delegateValidators[customOpenURLiOS42Str] = - ^(MSApplication *application, NSURL *url, NSString *sApplication, id annotation, BOOL returnedValue) { - - // Then - assertThat(application, is(appMock)); - assertThat(url, is(expectedURL)); - assertThat(sApplication, nilValue()); - assertThat(annotation, is(expectedAnnotation)); - assertThatBool(returnedValue, is(@(expectedReturnedValue))); - expectedReturnedValue = !returnedValue; - [customCalledExpectation2 fulfill]; - return expectedReturnedValue; - }; - [MSAppDelegateForwarder addDelegate:customAppDelegateMock1]; - [MSAppDelegateForwarder addDelegate:customAppDelegateMock2]; + id originalAppDelegate = [self createOriginalAppDelegateInstance]; + id originalOpenURLiOS42Imp = ^(__attribute__((unused)) id itSelf, MSApplication *application, NSURL *url, + NSString *sApplication, id annotation) { + + // Then + assertThat(application, is(appMock)); + assertThat(url, is(expectedURL)); + assertThat(sApplication, nilValue()); + assertThat(annotation, is(expectedAnnotation)); + [originalCalledExpectation fulfill]; + expectedReturnedValue = initialReturnValue; + return expectedReturnedValue; + }; + [self addSelector:originalOpenURLiOS42Sel implementation:originalOpenURLiOS42Imp toInstance:originalAppDelegate]; + SEL customOpenURLiOS42Sel = @selector(application:openURL:sourceApplication:annotation:returnedValue:); + id customAppDelegate1 = [self createCustomAppDelegateInstance]; + id customOpenURLiOS42Imp1 = ^(__attribute__((unused)) id itSelf, MSApplication *application, NSURL *url, + NSString *sApplication, id annotation, BOOL returnedValue) { + + // Then + assertThat(application, is(appMock)); + assertThat(url, is(expectedURL)); + assertThat(sApplication, nilValue()); + assertThat(annotation, is(expectedAnnotation)); + assertThatBool(returnedValue, is(@(expectedReturnedValue))); + expectedReturnedValue = !returnedValue; + [customCalledExpectation1 fulfill]; + return expectedReturnedValue; + }; + [self addSelector:customOpenURLiOS42Sel implementation:customOpenURLiOS42Imp1 toInstance:customAppDelegate1]; + id customAppDelegate2 = [self createCustomAppDelegateInstance]; + id customOpenURLiOS42Imp2 = ^(__attribute__((unused)) id itSelf, MSApplication *application, NSURL *url, + NSString *sApplication, id annotation, BOOL returnedValue) { + + // Then + assertThat(application, is(appMock)); + assertThat(url, is(expectedURL)); + assertThat(sApplication, nilValue()); + assertThat(annotation, is(expectedAnnotation)); + assertThatBool(returnedValue, is(@(expectedReturnedValue))); + expectedReturnedValue = !returnedValue; + [customCalledExpectation2 fulfill]; + return expectedReturnedValue; + }; + [self addSelector:customOpenURLiOS42Sel implementation:customOpenURLiOS42Imp2 toInstance:customAppDelegate2]; + [MSAppDelegateForwarder addDelegate:customAppDelegate1]; + [MSAppDelegateForwarder addDelegate:customAppDelegate2]; + [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; // When - BOOL returnedValue = [self.originalAppDelegateMock application:self.appMock - openURL:expectedURL - sourceApplication:nil - annotation:expectedAnnotation]; + BOOL returnedValue = [originalAppDelegate application:self.appMock + openURL:expectedURL + sourceApplication:nil + annotation:expectedAnnotation]; // Then assertThatBool(returnedValue, is(@(expectedReturnedValue))); @@ -974,78 +990,36 @@ - (void)testReturnValueChaining { - (void)testOpenURLForwardMethodNotImplementedByOriginalDelegate { // If - NSURL *expectedURL = [NSURL URLWithString:@"https://www.contoso.com/sending-positive-waves"]; - NSDictionary *expectedOptions = @{}; - BOOL expectedReturnedValue = NO; + NSError *expectedError = [NSError errorWithDomain:@"Don't worry, not a real error." code:404 userInfo:nil]; MSApplication *appMock = self.appMock; XCTestExpectation *customCalledExpectation = [self expectationWithDescription:@"Custom delegate called."]; - SEL originalOpenURLiOS9Sel = @selector(application:openURL:options:); - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:originalOpenURLiOS9Sel]; - NSString *customOpenURLiOS9Str = NSStringFromSelector(@selector(application:openURL:options:returnedValue:)); - self.customAppDelegateMock.delegateValidators[customOpenURLiOS9Str] = - ^(MSApplication *application, NSURL *url, NSDictionary *options, - BOOL returnedValue) { + SEL didFailToRegisterForNotifSel = @selector(application:didFailToRegisterForRemoteNotificationsWithError:); + [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:didFailToRegisterForNotifSel]; + id originalAppDelegate = [self createOriginalAppDelegateInstance]; + id customAppDelegate = [self createCustomAppDelegateInstance]; + id didFailToRegisterForNotifImp = ^(__attribute__((unused)) id itSelf, MSApplication *application, NSError *error) { + + // Then + assertThat(application, is(appMock)); + assertThat(error, is(expectedError)); + [customCalledExpectation fulfill]; + }; - // Then - assertThat(application, is(appMock)); - assertThat(url, is(expectedURL)); - assertThat(options, is(expectedOptions)); - assertThatBool(returnedValue, is(@(expectedReturnedValue))); - [customCalledExpectation fulfill]; - return expectedReturnedValue; - }; - [MSAppDelegateForwarder addDelegate:self.customAppDelegateMock]; + [self addSelector:didFailToRegisterForNotifSel + implementation:didFailToRegisterForNotifImp + toInstance:customAppDelegate]; + [MSAppDelegateForwarder addDelegate:customAppDelegate]; + [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; // When - BOOL returnedValue = - [self.originalAppDelegateMock application:self.appMock openURL:expectedURL options:expectedOptions]; + [originalAppDelegate application:self.appMock didFailToRegisterForRemoteNotificationsWithError:expectedError]; // Then - assertThatBool(returnedValue, is(@(expectedReturnedValue))); [self waitForExpectations:@[ customCalledExpectation ] timeout:1]; } #endif -#if TARGET_OS_OSX -- (void)testDidReceiveNotification { - - // If - id userNotificationUserInfoMock = OCMClassMock([NSUserNotification class]); - id notificationMock = OCMClassMock([NSNotification class]); - NSDictionary *notificationUserInfo = @{NSApplicationLaunchUserNotificationKey : userNotificationUserInfoMock}; - NSDictionary *expectedUserInfo = @{ @"aKey" : @"aThingBehindADoor" }; - OCMStub([notificationMock userInfo]).andReturn(notificationUserInfo); - OCMStub([userNotificationUserInfoMock userInfo]).andReturn(expectedUserInfo); - XCTestExpectation *customCalledExpectation = [self expectationWithDescription:@"Custom delegate called."]; - - // Setup an empty original delegate. - Class originalAppDelegateClass = [self createClassConformingToProtocol:@protocol(MSApplicationDelegate)]; - id originalAppDelegate = [originalAppDelegateClass new]; - SEL applicationDidFinishLaunchingSel = @selector(applicationDidFinishLaunching:); - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:applicationDidFinishLaunchingSel]; - - // Setup a custom delegate. - self.customAppDelegateMock.delegateValidators[NSStringFromSelector(applicationDidFinishLaunchingSel)] = - ^(NSNotification *notification) { - - // Then - XCTAssertNotNil(notification); - NSUserNotification *userNotification = - [notification.userInfo objectForKey:NSApplicationLaunchUserNotificationKey]; - XCTAssertNotNil(userNotification); - assertThat(userNotification.userInfo, is(expectedUserInfo)); - [customCalledExpectation fulfill]; - }; - [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; - [MSAppDelegateForwarder addDelegate:self.customAppDelegateMock]; - - // When - [originalAppDelegate applicationDidFinishLaunching:notificationMock]; - - // Then - [self waitForExpectations:@[ customCalledExpectation ] timeout:1]; -} -#elif TARGET_OS_IOS +#if TARGET_OS_IOS // TODO: Push doesn't support tvOS. Temporaily disable the test. - (void)testDidReceiveRemoteNotification { @@ -1064,37 +1038,43 @@ - (void)testDidReceiveRemoteNotification { XCTestExpectation *customCalledExpectation = [self expectationWithDescription:@"Custom delegate called."]; // Setup an empty original delegate. - Class originalAppDelegateClass = [self createClassConformingToProtocol:@protocol(MSApplicationDelegate)]; - id originalAppDelegate = [originalAppDelegateClass new]; - SEL didReceiveRemoteNotification1Sel = @selector(application:didReceiveRemoteNotification:); - SEL didReceiveRemoteNotification2Sel = @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:); - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:didReceiveRemoteNotification1Sel]; - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:didReceiveRemoteNotification2Sel]; + id originalAppDelegate = [self createOriginalAppDelegateInstance]; + SEL didReceiveRemoteNotificationSel1 = @selector(application:didReceiveRemoteNotification:); + SEL didReceiveRemoteNotificationSel2 = @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:); + [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:didReceiveRemoteNotificationSel1]; + [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:didReceiveRemoteNotificationSel2]; // Setup a custom delegate. - self.customAppDelegateMock.delegateValidators[NSStringFromSelector(didReceiveRemoteNotification1Sel)] = - ^(MSApplication *application, NSDictionary *userInfo) { + id customAppDelegate = [self createCustomAppDelegateInstance]; + id didReceiveRemoteNotificationImp1 = + ^(__attribute__((unused)) id itSelf, MSApplication *application, NSDictionary *userInfo) { // Then assertThat(application, is(appMock)); assertThat(userInfo, is(expectedUserInfo)); }; - self.customAppDelegateMock.delegateValidators[NSStringFromSelector(didReceiveRemoteNotification2Sel)] = - ^(MSApplication *application, NSDictionary *userInfo, void (^fetchHandler)(UIBackgroundFetchResult)) { - - // Then - assertThat(application, is(appMock)); - assertThat(userInfo, is(expectedUserInfo)); - assertThat(fetchHandler, is(fetchHandler)); - - // The original handler must only be called after all delegate did run. - assertThatBool(isOriginalHandlerCalled, isFalse()); - fetchHandler(expectedFetchResult); - assertThatBool(isOriginalHandlerCalled, isFalse()); - [customCalledExpectation fulfill]; - }; + [self addSelector:didReceiveRemoteNotificationSel1 + implementation:didReceiveRemoteNotificationImp1 + toInstance:customAppDelegate]; + id didReceiveRemoteNotificationImp2 = ^(__attribute__((unused)) id itSelf, MSApplication *application, + NSDictionary *userInfo, void (^fetchHandler)(UIBackgroundFetchResult)) { + + // Then + assertThat(application, is(appMock)); + assertThat(userInfo, is(expectedUserInfo)); + assertThat(fetchHandler, is(fetchHandler)); + + // The original handler must only be called after all delegate did run. + assertThatBool(isOriginalHandlerCalled, isFalse()); + fetchHandler(expectedFetchResult); + assertThatBool(isOriginalHandlerCalled, isFalse()); + [customCalledExpectation fulfill]; + }; + [self addSelector:didReceiveRemoteNotificationSel2 + implementation:didReceiveRemoteNotificationImp2 + toInstance:customAppDelegate]; [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; - [MSAppDelegateForwarder addDelegate:self.customAppDelegateMock]; + [MSAppDelegateForwarder addDelegate:customAppDelegate]; // When [originalAppDelegate application:appMock didReceiveRemoteNotification:expectedUserInfo]; @@ -1109,6 +1089,191 @@ - (void)testDidReceiveRemoteNotification { assertThatBool(isOriginalHandlerCalled, isTrue()); assertThatInteger(forwardedFetchResult, equalToInteger(expectedFetchResult)); } + +- (void)testDontSwizzleDeprecatedAPIIfNoAPIImplemented { + + /* + * If + */ + + // Mock a custom app delegate. + id customDelegate = OCMProtocolMock(@protocol(MSAppDelegate)); + [MSAppDelegateForwarder addDelegate:customDelegate]; + NSURL *expectedURL = [NSURL URLWithString:@"https://www.contoso.com/sending-positive-waves"]; + id expectedOptions = @{}; + OCMExpect([customDelegate application:self.appMock openURL:expectedURL options:expectedOptions returnedValue:NO]); + + // App delegate not implementing any API. + SEL deprecatedSelector = @selector(application:openURL:sourceApplication:annotation:); + SEL newSelector = @selector(application:openURL:options:); + id originalAppDelegate = [self createOriginalAppDelegateInstance]; + [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:deprecatedSelector]; + [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:newSelector]; + + /* + * When + */ + [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; + [originalAppDelegate application:self.appMock openURL:expectedURL options:expectedOptions]; + + /* + * Then + */ + assertThatBool([originalAppDelegate respondsToSelector:newSelector], isTrue()); + assertThatBool([originalAppDelegate respondsToSelector:deprecatedSelector], isFalse()); + OCMVerify([customDelegate application:self.appMock openURL:expectedURL options:expectedOptions returnedValue:NO]); +} + +- (void)testSwizzleDeprecatedAPIIfNoNewAPIImplemented { + + /* + * If + */ + + // Mock a custom app delegate. + id customDelegate = OCMProtocolMock(@protocol(MSAppDelegate)); + [MSAppDelegateForwarder addDelegate:customDelegate]; + NSURL *expectedURL = [NSURL URLWithString:@"https://www.contoso.com/sending-positive-waves"]; + id expectedAnotation = @{}; + OCMExpect([customDelegate application:self.appMock + openURL:expectedURL + sourceApplication:nil + annotation:expectedAnotation + returnedValue:YES]); + + // App delegate implementing just the deprecated API. + SEL deprecatedSelector = @selector(application:openURL:sourceApplication:annotation:); + SEL newSelector = @selector(application:openURL:options:); + id originalAppDelegate = [self createOriginalAppDelegateInstance]; + __block short nbCalls = 0; + id selectorImp = ^{ + nbCalls++; + return YES; + }; + [self addSelector:deprecatedSelector implementation:selectorImp toInstance:originalAppDelegate]; + [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:deprecatedSelector]; + [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:newSelector]; + + /* + * When + */ + [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; + [originalAppDelegate application:self.appMock openURL:expectedURL sourceApplication:nil annotation:expectedAnotation]; + + /* + * Then + */ + assertThatBool([originalAppDelegate respondsToSelector:newSelector], isFalse()); + assertThatBool([originalAppDelegate respondsToSelector:deprecatedSelector], isTrue()); + assertThatShort(nbCalls, equalToShort(1)); + OCMVerify([customDelegate application:self.appMock + openURL:expectedURL + sourceApplication:nil + annotation:expectedAnotation + returnedValue:YES]); +} + +- (void)testSwizzleDeprecatedAPIIfJustNewAPIImplemented { + + /* + * If + */ + + // Mock a custom app delegate. + id customDelegate = OCMProtocolMock(@protocol(MSAppDelegate)); + [MSAppDelegateForwarder addDelegate:customDelegate]; + NSURL *expectedURL = [NSURL URLWithString:@"https://www.contoso.com/sending-positive-waves"]; + id expectedOptions = @{}; + OCMExpect([customDelegate application:self.appMock openURL:expectedURL options:expectedOptions returnedValue:YES]); + + // App delegate implementing just the new API. + SEL deprecatedSelector = @selector(application:openURL:sourceApplication:annotation:); + SEL newSelector = @selector(application:openURL:options:); + id originalAppDelegate = [self createOriginalAppDelegateInstance]; + __block short nbCalls = 0; + id selectorImp = ^{ + nbCalls++; + return YES; + }; + [self addSelector:newSelector implementation:selectorImp toInstance:originalAppDelegate]; + [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:deprecatedSelector]; + [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:newSelector]; + + /* + * When + */ + [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; + [originalAppDelegate application:self.appMock openURL:expectedURL options:expectedOptions]; + + /* + * Then + */ + assertThatBool([originalAppDelegate respondsToSelector:deprecatedSelector], isFalse()); + assertThatBool([originalAppDelegate respondsToSelector:newSelector], isTrue()); + assertThatShort(nbCalls, equalToShort(1)); + OCMVerify([customDelegate application:self.appMock openURL:expectedURL options:expectedOptions returnedValue:YES]); +} + +- (void)testSwizzleDeprecatedAPIIfAllAPIsImplemented { + + /* + * If + */ + + // Mock a custom app delegate. + id customDelegate = OCMProtocolMock(@protocol(MSAppDelegate)); + [MSAppDelegateForwarder addDelegate:customDelegate]; + NSURL *expectedURL = [NSURL URLWithString:@"https://www.contoso.com/sending-positive-waves"]; + id expectedAnotation = @{}; + id expectedOptions = @{}; + OCMExpect([customDelegate application:self.appMock openURL:expectedURL options:expectedOptions returnedValue:YES]); + OCMExpect([customDelegate application:self.appMock + openURL:expectedURL + sourceApplication:nil + annotation:expectedAnotation + returnedValue:YES]); + + // App delegate implementing all the APIs. + SEL deprecatedSelector = @selector(application:openURL:sourceApplication:annotation:); + SEL newSelector = @selector(application:openURL:options:); + id originalAppDelegate = [self createOriginalAppDelegateInstance]; + __block short deprecatedSelectorNbCalls = 0; + __block short newSelectorNbCalls = 0; + id deprecatedSelectorImp = ^{ + deprecatedSelectorNbCalls++; + return YES; + }; + id newSelectorImp = ^{ + newSelectorNbCalls++; + return YES; + }; + [self addSelector:deprecatedSelector implementation:deprecatedSelectorImp toInstance:originalAppDelegate]; + [self addSelector:newSelector implementation:newSelectorImp toInstance:originalAppDelegate]; + [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:deprecatedSelector]; + [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:newSelector]; + + /* + * When + */ + [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; + [originalAppDelegate application:self.appMock openURL:expectedURL sourceApplication:nil annotation:expectedAnotation]; + [originalAppDelegate application:self.appMock openURL:expectedURL options:expectedOptions]; + + /* + * Then + */ + assertThatBool([originalAppDelegate respondsToSelector:newSelector], isTrue()); + assertThatBool([originalAppDelegate respondsToSelector:deprecatedSelector], isTrue()); + assertThatShort(newSelectorNbCalls, equalToShort(1)); + assertThatShort(deprecatedSelectorNbCalls, equalToShort(1)); + OCMVerify([customDelegate application:self.appMock openURL:expectedURL options:expectedOptions returnedValue:YES]); + OCMVerify([customDelegate application:self.appMock + openURL:expectedURL + sourceApplication:nil + annotation:expectedAnotation + returnedValue:YES]); +} + #endif #pragma mark - Private @@ -1117,11 +1282,11 @@ - (NSString *)generateClassName { return [@"C" stringByAppendingString:MS_UUID_STRING]; } -- (Class)createClassConformingToProtocol:(Protocol *)protocol { - return [self createClassWithBaseClass:[NSObject class] andConformItToProtocol:protocol]; +- (id)createInstanceConformingToProtocol:(Protocol *)protocol { + return [self createInstanceWithBaseClass:[NSObject class] andConformItToProtocol:protocol]; } -- (Class)createClassWithBaseClass:(Class) class andConformItToProtocol:(Protocol *)protocol { +- (id)createInstanceWithBaseClass:(Class) class andConformItToProtocol:(Protocol *)protocol { // Generate class name to prevent conflicts in runtime added classes. const char *name = [[self generateClassName] UTF8String]; @@ -1130,10 +1295,24 @@ - (Class)createClassWithBaseClass:(Class) class andConformItToProtocol:(Protocol class_addProtocol(newClass, protocol); } objc_registerClassPair(newClass); - return newClass; + return [newClass new]; } -- (void)addSelector : (SEL)selector implementation : (id)block types : (const char *)types toClass : (Class) class { + - (id)createOriginalAppDelegateInstance { + return [self createInstanceConformingToProtocol:@protocol(MSAppDelegate)]; +} + +- (id)createCustomAppDelegateInstance { + return [self createInstanceConformingToProtocol:@protocol(MSAppDelegate)]; +} + +- (void)addSelector:(SEL)selector implementation:(id)block toInstance:(id)instance { + [self addSelector:selector implementation:block toClass:[instance class]]; +} + +- (void)addSelector:(SEL)selector implementation:(id)block toClass:(id) class { + Method method = class_getInstanceMethod(class, selector); + const char *types = method_getTypeEncoding(method); IMP imp = imp_implementationWithBlock(block); class_addMethod(class, selector, imp, types); } diff --git a/MobileCenter/MobileCenterTests/MSCustomPropertiesLogTests.m b/MobileCenter/MobileCenterTests/MSCustomPropertiesLogTests.m index 451be770c8..5026fda93c 100644 --- a/MobileCenter/MobileCenterTests/MSCustomPropertiesLogTests.m +++ b/MobileCenter/MobileCenterTests/MSCustomPropertiesLogTests.m @@ -46,7 +46,7 @@ - (void)testSerializingToDictionaryWorks { NSArray *actualProperties = actual[@"properties"]; assertThat(actualProperties, hasCountOf(5)); NSArray *needProperties = @[@{@"name": @"t1", @"type": @"string", @"value": string}, - @{@"name": @"t2", @"type": @"date_time", @"value": @"1970-01-01T00:00:00Z"}, + @{@"name": @"t2", @"type": @"date_time", @"value": @"1970-01-01T00:00:00.000Z"}, @{@"name": @"t3", @"type": @"number", @"value": number}, @{@"name": @"t4", @"type": @"boolean", @"value": boolean}, @{@"name": @"t5", @"type": @"clear"}]; diff --git a/MobileCenter/MobileCenterTests/MSMobileCenterTests.m b/MobileCenter/MobileCenterTests/MSMobileCenterTests.m index c09a8feacc..eebfe91fe3 100644 --- a/MobileCenter/MobileCenterTests/MSMobileCenterTests.m +++ b/MobileCenter/MobileCenterTests/MSMobileCenterTests.m @@ -1,12 +1,13 @@ +#include +#if !TARGET_OS_TV #import "MSCustomProperties.h" #import "MSCustomPropertiesLog.h" +#endif #import "MSLogManagerDefault.h" #import "MSMobileCenter.h" #import "MSMobileCenterInternal.h" #import "MSMobileCenterPrivate.h" #import "MSMockService.h" -#import "MSMockCustomAppDelegate.h" -#import "MSMockOriginalAppDelegate.h" #import "MSMockUserDefaults.h" #import "MSStartServiceLog.h" #import "MSTestFrameworks.h" @@ -146,6 +147,7 @@ - (void)testDefaultLogUrl { XCTAssertTrue([[[MSMobileCenter sharedInstance] logUrl] isEqualToString:@"https://in.mobile.azure.com"]); } +#if !TARGET_OS_TV - (void)testSetCustomProperties { // If @@ -172,6 +174,7 @@ - (void)testSetCustomProperties { // Then OCMVerifyAll(logManager); } +#endif - (void)testConfigureWithAppSecret { [MSMobileCenter configureWithAppSecret:@"App-Secret"]; diff --git a/MobileCenter/MobileCenterTests/Util/MSMockCustomAppDelegate.h b/MobileCenter/MobileCenterTests/Util/MSMockCustomAppDelegate.h deleted file mode 100644 index 3a722a4256..0000000000 --- a/MobileCenter/MobileCenterTests/Util/MSMockCustomAppDelegate.h +++ /dev/null @@ -1,32 +0,0 @@ -#import -#if TARGET_OS_OSX -#import -#import "MSNSAppDelegate.h" -#else -#import -#import "MSUIAppDelegate.h" -#endif - -#if TARGET_OS_OSX -typedef BOOL (^CustomDidRegisterNotificationValidator)(NSApplication *, NSData *); -typedef BOOL (^CustomDidFinishLaunchingValidator)(NSNotification *); -#else -typedef BOOL (^CustomOpenURLiOS42Validator)(UIApplication *, NSURL *, NSString *, id, BOOL); -typedef BOOL (^CustomOpenURLiOS9Validator)(UIApplication *, NSURL *, NSDictionary *, - BOOL); -typedef BOOL (^CustomDidRegisterNotificationValidator)(UIApplication *, NSData *); -typedef BOOL (^CustomDidReceiveNotificationWorkaroundValidator)(UIApplication *application, NSDictionary *userInfo); -typedef BOOL (^CustomDidReceiveNotificationValidator)(UIApplication *application, NSDictionary *userInfo, - void (^fetchHandler)(UIBackgroundFetchResult)); -#endif - -/* - * We Can't use OCMock to mock original app delegate since the class needs to own the method implementation. - * We also can't use OCMock's protocol mocks since they artificially responds to any selector from the protocol. - * Adding this class that can be used for both custom and original delegate to solve the issue for some tests. - */ -@interface MSMockCustomAppDelegate : NSObject - -@property(nonatomic, readonly) NSMutableDictionary *delegateValidators; - -@end diff --git a/MobileCenter/MobileCenterTests/Util/MSMockCustomAppDelegate.m b/MobileCenter/MobileCenterTests/Util/MSMockCustomAppDelegate.m deleted file mode 100644 index 4e7008cdfe..0000000000 --- a/MobileCenter/MobileCenterTests/Util/MSMockCustomAppDelegate.m +++ /dev/null @@ -1,86 +0,0 @@ -#import - -#import "MSAppDelegateForwarder.h" -#import "MSMockCustomAppDelegate.h" - -@implementation MSMockCustomAppDelegate - -- (instancetype)init { - if ((self = [super init])) { - _delegateValidators = [NSMutableDictionary new]; - } - return self; -} - -#pragma mark - MSAppDelegate - -#if TARGET_OS_OSX - -- (void)application:(NSApplication *)application - didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { - CustomDidRegisterNotificationValidator validator = self.delegateValidators[NSStringFromSelector(_cmd)]; - validator(application, deviceToken); -} - -- (void)applicationDidFinishLaunching:(NSNotification *)notification { - CustomDidFinishLaunchingValidator validator = self.delegateValidators[NSStringFromSelector(_cmd)]; - validator(notification); -} - -#else -- (BOOL)application:(UIApplication *)application - openURL:(NSURL *)url - sourceApplication:(NSString *)sourceApplication - annotation:(id)annotation - returnedValue:(BOOL)returnedValue { - CustomOpenURLiOS42Validator validator = self.delegateValidators[NSStringFromSelector(_cmd)]; - return validator(application, url, sourceApplication, annotation, returnedValue); -} - -- (BOOL)application:(UIApplication *)application - openURL:(NSURL *)url - options:(NSDictionary *)options - returnedValue:(BOOL)returnedValue { - CustomOpenURLiOS9Validator validator = self.delegateValidators[NSStringFromSelector(_cmd)]; - return validator(application, url, options, returnedValue); -} - -- (void)application:(UIApplication *)application - didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { - CustomDidRegisterNotificationValidator validator = self.delegateValidators[NSStringFromSelector(_cmd)]; - validator(application, deviceToken); -} - -- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { - CustomDidReceiveNotificationWorkaroundValidator validator = self.delegateValidators[NSStringFromSelector(_cmd)]; - validator(application, userInfo); -} - -- (void)application:(UIApplication *)application - didReceiveRemoteNotification:(NSDictionary *)userInfo - fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { - CustomDidReceiveNotificationValidator validator = self.delegateValidators[NSStringFromSelector(_cmd)]; - validator(application, userInfo, completionHandler); -} -#endif - -@end - -#pragma mark - Swizzling - -@implementation MSAppDelegateForwarder (MSDistribute) - -+ (void)load { - - // Register selectors to swizzle for this mock. - [self addAppDelegateSelectorToSwizzle:@selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)]; - -#if !TARGET_OS_OSX - [self addAppDelegateSelectorToSwizzle:@selector(application:openURL:options:)]; - [self addAppDelegateSelectorToSwizzle:@selector(application:openURL:sourceApplication:annotation:)]; - [self addAppDelegateSelectorToSwizzle:@selector(application:didReceiveRemoteNotification:)]; - [self addAppDelegateSelectorToSwizzle:@selector(application:didReceiveRemoteNotification:fetchCompletionHandler:)]; -#endif -} - -@end diff --git a/MobileCenter/MobileCenterTests/Util/MSMockOriginalAppDelegate.h b/MobileCenter/MobileCenterTests/Util/MSMockOriginalAppDelegate.h deleted file mode 100644 index 63c8d2309c..0000000000 --- a/MobileCenter/MobileCenterTests/Util/MSMockOriginalAppDelegate.h +++ /dev/null @@ -1,26 +0,0 @@ -#import -#if TARGET_OS_OSX -#import -#else -#import -#endif - -#if TARGET_OS_OSX -typedef BOOL (^OriginalDidRegisterNotificationValidator)(NSApplication *, NSData *); -typedef BOOL (^OriginalDidFinishLaunchingValidator)(NSNotification *); -#else -typedef BOOL (^OriginalOpenURLiOS42Validator)(UIApplication *, NSURL *, NSString *, id); -typedef BOOL (^OriginalDidRegisterNotificationValidator)(UIApplication *, NSData *); -typedef BOOL (^OriginalDidReceiveNotification)(UIApplication *, NSDictionary *, void (^)(UIBackgroundFetchResult)); -#endif - -/* - * We Can't use OCMock to mock original app delegate since the class needs to own the method implementation. - * We also can't use OCMock's protocol mocks since they artificially responds to any selector from the protocol. - * Adding this class that can be used for both custom and original delegate to solve the issue for some tests. - */ -@interface MSMockOriginalAppDelegate : NSObject - -@property(nonatomic, readonly) NSMutableDictionary *delegateValidators; - -@end diff --git a/MobileCenter/MobileCenterTests/Util/MSMockOriginalAppDelegate.m b/MobileCenter/MobileCenterTests/Util/MSMockOriginalAppDelegate.m deleted file mode 100644 index 7f4b43e40a..0000000000 --- a/MobileCenter/MobileCenterTests/Util/MSMockOriginalAppDelegate.m +++ /dev/null @@ -1,63 +0,0 @@ -#import - -#import "MSAppDelegateForwarder.h" -#import "MSMockOriginalAppDelegate.h" -#import "MSAppDelegateForwarderPrivate.h" - -@implementation MSMockOriginalAppDelegate - -- (instancetype)init { - if ((self = [super init])) { - _delegateValidators = [NSMutableDictionary new]; - - // Force swizzling for tests. - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - [MSAppDelegateForwarder swizzleOriginalDelegate:self]; - }); - } - return self; -} - -#if TARGET_OS_OSX - -#pragma mark - NSApplication - -- (void)application:(NSApplication *)application - didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { - OriginalDidRegisterNotificationValidator validator = self.delegateValidators[NSStringFromSelector(_cmd)]; - validator(application, deviceToken); -} - -- (void)applicationDidFinishLaunching:(NSNotification *)notification { - OriginalDidFinishLaunchingValidator validator = self.delegateValidators[NSStringFromSelector(_cmd)]; - validator(notification); -} - -#else - -#pragma mark - UIApplication - -- (BOOL)application:(UIApplication *)application - openURL:(NSURL *)url - sourceApplication:(NSString *)sourceApplication - annotation:(id)annotation { - OriginalOpenURLiOS42Validator validator = self.delegateValidators[NSStringFromSelector(_cmd)]; - return validator(application, url, sourceApplication, annotation); -} - -- (void)application:(UIApplication *)application - didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { - OriginalDidRegisterNotificationValidator validator = self.delegateValidators[NSStringFromSelector(_cmd)]; - validator(application, deviceToken); -} - -- (void)application:(UIApplication *)application - didReceiveRemoteNotification:(NSDictionary *)userInfo - fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { - OriginalDidReceiveNotification validator = self.delegateValidators[NSStringFromSelector(_cmd)]; - validator(application, userInfo, completionHandler); -} -#endif - -@end diff --git a/MobileCenterAnalytics/MobileCenterAnalyticsTests/MSEventLogTests.m b/MobileCenterAnalytics/MobileCenterAnalyticsTests/MSEventLogTests.m index 9982969736..930c36ba25 100644 --- a/MobileCenterAnalytics/MobileCenterAnalyticsTests/MSEventLogTests.m +++ b/MobileCenterAnalytics/MobileCenterAnalyticsTests/MSEventLogTests.m @@ -52,7 +52,7 @@ - (void)testSerializingEventToDictionaryWorks { assertThat(actual[@"type"], equalTo(typeName)); assertThat(actual[@"properties"], equalTo(properties)); assertThat(actual[@"device"], notNilValue()); - assertThat(actual[@"timestamp"], equalTo(@"1970-01-01T00:00:42Z")); + assertThat(actual[@"timestamp"], equalTo(@"1970-01-01T00:00:42.000Z")); } - (void)testNSCodingSerializationAndDeserializationWorks { diff --git a/MobileCenterAnalytics/MobileCenterAnalyticsTests/MSPageLogTests.m b/MobileCenterAnalytics/MobileCenterAnalyticsTests/MSPageLogTests.m index 13871c8278..7736bd33e8 100644 --- a/MobileCenterAnalytics/MobileCenterAnalyticsTests/MSPageLogTests.m +++ b/MobileCenterAnalytics/MobileCenterAnalyticsTests/MSPageLogTests.m @@ -49,7 +49,7 @@ - (void)testSerializingPageToDictionaryWorks { assertThat(actual[@"type"], equalTo(typeName)); assertThat(actual[@"properties"], equalTo(properties)); assertThat(actual[@"device"], notNilValue()); - assertThat(actual[@"timestamp"], equalTo(@"1970-01-01T00:00:42Z")); + assertThat(actual[@"timestamp"], equalTo(@"1970-01-01T00:00:42.000Z")); } - (void)testNSCodingSerializationAndDeserializationWorks { diff --git a/MobileCenterAnalytics/MobileCenterAnalyticsTests/MSStartSessionLogTests.m b/MobileCenterAnalytics/MobileCenterAnalyticsTests/MSStartSessionLogTests.m index 9af454e9f1..b3833dc265 100644 --- a/MobileCenterAnalytics/MobileCenterAnalyticsTests/MSStartSessionLogTests.m +++ b/MobileCenterAnalytics/MobileCenterAnalyticsTests/MSStartSessionLogTests.m @@ -41,7 +41,7 @@ - (void)testSerializingSessionToDictionaryWorks { assertThat(actual, notNilValue()); assertThat(actual[@"type"], equalTo(typeName)); assertThat(actual[@"device"], notNilValue()); - assertThat(actual[@"timestamp"], equalTo(@"1970-01-01T00:00:42Z")); + assertThat(actual[@"timestamp"], equalTo(@"1970-01-01T00:00:42.000Z")); } - (void)testNSCodingSerializationAndDeserializationWorks { diff --git a/MobileCenterCrashes/MobileCenterCrashes.xcodeproj/project.pbxproj b/MobileCenterCrashes/MobileCenterCrashes.xcodeproj/project.pbxproj index 30d274cafc..52831b7df4 100644 --- a/MobileCenterCrashes/MobileCenterCrashes.xcodeproj/project.pbxproj +++ b/MobileCenterCrashes/MobileCenterCrashes.xcodeproj/project.pbxproj @@ -339,6 +339,12 @@ 6EC99A3C1D416CCF0016C325 /* live_report_xamarin.plcrash in Resources */ = {isa = PBXBuildFile; fileRef = 6EC99A351D416CCF0016C325 /* live_report_xamarin.plcrash */; }; 6EC99A3D1D416CCF0016C325 /* log_report_xamarin in Resources */ = {isa = PBXBuildFile; fileRef = 6EC99A361D416CCF0016C325 /* log_report_xamarin */; }; 8024743B1EAE077800AEC284 /* MSErrorAttachmentLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 8024743A1EAE077800AEC284 /* MSErrorAttachmentLog.m */; }; + 922446841F621F3A00E4034A /* MSHandledErrorLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 922446831F621F3A00E4034A /* MSHandledErrorLog.m */; }; + 9224468C1F62276C00E4034A /* MSHandledErrorLogTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 922446851F6222EC00E4034A /* MSHandledErrorLogTests.m */; }; + 9224468D1F62277400E4034A /* MSHandledErrorLogTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 922446851F6222EC00E4034A /* MSHandledErrorLogTests.m */; }; + 9224468E1F62277500E4034A /* MSHandledErrorLogTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 922446851F6222EC00E4034A /* MSHandledErrorLogTests.m */; }; + 9224468F1F634AD200E4034A /* MSHandledErrorLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 922446831F621F3A00E4034A /* MSHandledErrorLog.m */; }; + 922446901F634AD600E4034A /* MSHandledErrorLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 922446831F621F3A00E4034A /* MSHandledErrorLog.m */; }; B24F3F0F1D93368F00827213 /* MSErrorLogFormatterTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = B24F3F0E1D93368F00827213 /* MSErrorLogFormatterTests.mm */; }; B24F3F121D9341BC00827213 /* MSErrorLogFormatterPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = B24F3F101D93417B00827213 /* MSErrorLogFormatterPrivate.h */; }; B2A0A71C1D9C2AE700729A58 /* MSCrashesTestUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B2A0A71B1D9C2AE700729A58 /* MSCrashesTestUtil.m */; }; @@ -566,6 +572,9 @@ 6EC99A351D416CCF0016C325 /* live_report_xamarin.plcrash */ = {isa = PBXFileReference; lastKnownFileType = file; path = live_report_xamarin.plcrash; sourceTree = ""; }; 6EC99A361D416CCF0016C325 /* log_report_xamarin */ = {isa = PBXFileReference; fileEncoding = 1; lastKnownFileType = text; path = log_report_xamarin; sourceTree = ""; }; 8024743A1EAE077800AEC284 /* MSErrorAttachmentLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSErrorAttachmentLog.m; sourceTree = ""; }; + 922446821F621D4500E4034A /* MSHandledErrorLog.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSHandledErrorLog.h; sourceTree = ""; }; + 922446831F621F3A00E4034A /* MSHandledErrorLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSHandledErrorLog.m; sourceTree = ""; }; + 922446851F6222EC00E4034A /* MSHandledErrorLogTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSHandledErrorLogTests.m; sourceTree = ""; }; B24F3F0E1D93368F00827213 /* MSErrorLogFormatterTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MSErrorLogFormatterTests.mm; sourceTree = ""; }; B24F3F101D93417B00827213 /* MSErrorLogFormatterPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSErrorLogFormatterPrivate.h; sourceTree = ""; }; B2A0A71A1D9C2AE700729A58 /* MSCrashesTestUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSCrashesTestUtil.h; sourceTree = ""; }; @@ -908,6 +917,7 @@ 6E7D5C811D3EC06C009EC9AC /* MSConstants.h */, 350B29F31F1E6F1D009B91CF /* MSWrapperExceptionTests.m */, B2FF130B1DD12F61003DC677 /* MSAppleErrorLogTests.m */, + 922446851F6222EC00E4034A /* MSHandledErrorLogTests.m */, B2F120D41D6546740060DED7 /* MSErrorAttachmentLogTests.m */, 6E7D5C7F1D3EAEB5009EC9AC /* MSBinaryTests.m */, 59493B275715F01438B2E6FD /* MSCrashesUtilTests.m */, @@ -984,6 +994,8 @@ 6E7D5C661D3E9321009EC9AC /* MSBinary.m */, 6E7D5C691D3E9332009EC9AC /* MSAppleErrorLog.h */, 6E7D5C6A1D3E9332009EC9AC /* MSAppleErrorLog.m */, + 922446821F621D4500E4034A /* MSHandledErrorLog.h */, + 922446831F621F3A00E4034A /* MSHandledErrorLog.m */, B2F120DF1D657CF10060DED7 /* MSErrorReport.m */, B2F120E61D657F4F0060DED7 /* MSErrorReportPrivate.h */, 6E7D5C711D3E9381009EC9AC /* MSThread.h */, @@ -1528,6 +1540,7 @@ 0446DF721F3B977100C8E338 /* MSErrorLogFormatterTests.mm in Sources */, 0446DF731F3B977100C8E338 /* MSCrashesTestUtil.m in Sources */, 0446DF741F3B977100C8E338 /* MSBinaryTests.m in Sources */, + 9224468E1F62277500E4034A /* MSHandledErrorLogTests.m in Sources */, 0446DF751F3B977100C8E338 /* MSWrapperExceptionManagerTests.m in Sources */, 0446DF761F3B977100C8E338 /* MSErrorAttachmentLogTests.m in Sources */, 0446DF771F3B977100C8E338 /* MSMockCrashesDelegate.m in Sources */, @@ -1541,6 +1554,7 @@ files = ( 0485AFA11EAA887500C10CAF /* MSErrorReport.m in Sources */, 0485AFA21EAA887500C10CAF /* MSCrashesCXXExceptionHandler.mm in Sources */, + 9224468F1F634AD200E4034A /* MSHandledErrorLog.m in Sources */, 0485AFA31EAA887500C10CAF /* MSAbstractErrorLog.m in Sources */, 0485AFA41EAA887500C10CAF /* MSException.m in Sources */, 0499F8751EDFAE1D00C3EDDA /* MSErrorAttachmentLog+Utility.m in Sources */, @@ -1575,6 +1589,7 @@ 0493276C1ECA170D00D0187A /* MSErrorLogFormatterTests.mm in Sources */, 0493276D1ECA170D00D0187A /* MSCrashesTestUtil.m in Sources */, 0493276E1ECA170D00D0187A /* MSBinaryTests.m in Sources */, + 9224468D1F62277400E4034A /* MSHandledErrorLogTests.m in Sources */, 049327701ECA170D00D0187A /* MSWrapperExceptionManagerTests.m in Sources */, 049327711ECA170D00D0187A /* MSErrorAttachmentLogTests.m in Sources */, 049327721ECA170D00D0187A /* MSMockCrashesDelegate.m in Sources */, @@ -1588,6 +1603,7 @@ files = ( 04EBBEB21F01C0C90006B8AE /* MSErrorReport.m in Sources */, 04EBBEB31F01C0C90006B8AE /* MSCrashesCXXExceptionHandler.mm in Sources */, + 922446901F634AD600E4034A /* MSHandledErrorLog.m in Sources */, 04EBBEB41F01C0C90006B8AE /* MSAbstractErrorLog.m in Sources */, 04EBBEB51F01C0C90006B8AE /* MSException.m in Sources */, 04EBBEB61F01C0C90006B8AE /* MSErrorAttachmentLog+Utility.m in Sources */, @@ -1612,6 +1628,7 @@ files = ( B2F120E51D657CF10060DED7 /* MSErrorReport.m in Sources */, 6E73FE6B1D402F79008CDC15 /* MSCrashesCXXExceptionHandler.mm in Sources */, + 922446841F621F3A00E4034A /* MSHandledErrorLog.m in Sources */, B2CD3BFA1D80EE49000A8A91 /* MSAbstractErrorLog.m in Sources */, 3858A21A1E93F3B400535A69 /* MSErrorAttachmentLog+Utility.m in Sources */, 6E7D5C701D3E9346009EC9AC /* MSException.m in Sources */, @@ -1646,6 +1663,7 @@ B24F3F0F1D93368F00827213 /* MSErrorLogFormatterTests.mm in Sources */, B2A0A71C1D9C2AE700729A58 /* MSCrashesTestUtil.m in Sources */, 6E7D5C801D3EAEB5009EC9AC /* MSBinaryTests.m in Sources */, + 9224468C1F62276C00E4034A /* MSHandledErrorLogTests.m in Sources */, 35EF18E01DDBCF6C00731CA8 /* MSWrapperExceptionManagerTests.m in Sources */, B2F120D51D6546740060DED7 /* MSErrorAttachmentLogTests.m in Sources */, BA682CFA6F4C5A8841507CF7 /* MSMockCrashesDelegate.m in Sources */, diff --git a/MobileCenterCrashes/MobileCenterCrashes/Internals/MSCrashesInternal.h b/MobileCenterCrashes/MobileCenterCrashes/Internals/MSCrashesInternal.h index 807fa7cd8a..2cb05211c6 100644 --- a/MobileCenterCrashes/MobileCenterCrashes/Internals/MSCrashesInternal.h +++ b/MobileCenterCrashes/MobileCenterCrashes/Internals/MSCrashesInternal.h @@ -23,4 +23,10 @@ */ - (void)configureCrashReporterWithUncaughtExceptionHandlerEnabled:(BOOL)enableUncaughtExceptionHandler; +/* + * Track handled exception directly as model form. + * This API is not public and is used by wrapper SDKs. + */ +- (void)trackModelException:(MSException *)exception; + @end diff --git a/MobileCenterCrashes/MobileCenterCrashes/Internals/MSCrashesPrivate.h b/MobileCenterCrashes/MobileCenterCrashes/Internals/MSCrashesPrivate.h index 143f371e79..f19291c29a 100644 --- a/MobileCenterCrashes/MobileCenterCrashes/Internals/MSCrashesPrivate.h +++ b/MobileCenterCrashes/MobileCenterCrashes/Internals/MSCrashesPrivate.h @@ -70,10 +70,7 @@ typedef struct MSCrashesCallbacks { MSCrashesPostCrashSignalCallback handleSignal; } MSCrashesCallbacks; -// TODO: Mach exception handler is not supported on tvOS. -#if !TARGET_OS_TV @property(nonatomic, assign, getter=isMachExceptionHandlerEnabled) BOOL enableMachExceptionHandler; -#endif /** * A list containing all crash files that currently stored on disk for this app. diff --git a/MobileCenterCrashes/MobileCenterCrashes/Internals/Model/MSHandledErrorLog.h b/MobileCenterCrashes/MobileCenterCrashes/Internals/Model/MSHandledErrorLog.h new file mode 100644 index 0000000000..99cf96b2fb --- /dev/null +++ b/MobileCenterCrashes/MobileCenterCrashes/Internals/Model/MSHandledErrorLog.h @@ -0,0 +1,21 @@ +#import +#import "MSAbstractErrorLog.h" + +@class MSException; + +/** + * Handled Error log for managed platforms (such as Xamarin, Unity, Android Dalvik/ART). + */ +@interface MSHandledErrorLog : MSAbstractLog + +/** + * Unique identifier for this error. + */ +@property(nonatomic, copy) NSString *errorId; + +/** + * The exception. + */ +@property(nonatomic) MSException *exception; + +@end diff --git a/MobileCenterCrashes/MobileCenterCrashes/Internals/Model/MSHandledErrorLog.m b/MobileCenterCrashes/MobileCenterCrashes/Internals/Model/MSHandledErrorLog.m new file mode 100644 index 0000000000..524d325e3e --- /dev/null +++ b/MobileCenterCrashes/MobileCenterCrashes/Internals/Model/MSHandledErrorLog.m @@ -0,0 +1,60 @@ +#import "MSException.h" +#import "MSHandledErrorLog.h" + +static NSString *const kMSTypeError = @"handled_error"; +static NSString *const kMSId = @"id"; +static NSString *const kMSException = @"exception"; + +@implementation MSHandledErrorLog + +- (instancetype)init { + if ((self = [super init])) { + self.type = kMSTypeError; + } + return self; +} + +- (NSMutableDictionary *)serializeToDictionary { + NSMutableDictionary *dict = [super serializeToDictionary]; + + if (self.errorId) { + dict[kMSId] = self.errorId; + } + if (self.exception) { + dict[kMSException] = [self.exception serializeToDictionary]; + } + return dict; +} + +- (BOOL)isValid { + return [super isValid] && self.errorId && self.exception; +} + +- (BOOL)isEqual:(id)object { + if (![object isKindOfClass:[MSHandledErrorLog class]] || ![super isEqual:object]) { + return NO; + } + MSHandledErrorLog *errorLog = (MSHandledErrorLog *)object; + return ((!self.errorId && !errorLog.errorId) || + [self.errorId isEqual:errorLog.errorId]) && + ((!self.exception && !errorLog.exception) || [self.exception isEqual:errorLog.exception]); +} + +#pragma mark - NSCoding + +- (instancetype)initWithCoder:(NSCoder *)coder { + self = [super initWithCoder:coder]; + if (self) { + _errorId = [coder decodeObjectForKey:kMSId]; + _exception = [coder decodeObjectForKey:kMSException]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + [super encodeWithCoder:coder]; + [coder encodeObject:self.errorId forKey:kMSId]; + [coder encodeObject:self.exception forKey:kMSException]; +} + +@end diff --git a/MobileCenterCrashes/MobileCenterCrashes/Internals/Util/MSErrorLogFormatter.m b/MobileCenterCrashes/MobileCenterCrashes/Internals/Util/MSErrorLogFormatter.m index 7b29881a15..efd36f8645 100644 --- a/MobileCenterCrashes/MobileCenterCrashes/Internals/Util/MSErrorLogFormatter.m +++ b/MobileCenterCrashes/MobileCenterCrashes/Internals/Util/MSErrorLogFormatter.m @@ -223,6 +223,12 @@ + (MSAppleErrorLog *)errorLogFromCrashReport:(MSPLCrashReport *)report { errorLog.appLaunchTimestamp = [self getAppLaunchTimeFromReport:report]; errorLog.timestamp = [self getCrashTimeFromReport:report]; + // FIXME: PLCrashReporter doesn't support millisecond precision, here is a workaround to fill 999 for its millisecond. + double timestampInSeconds = [errorLog.timestamp timeIntervalSince1970]; + if (timestampInSeconds - (int)timestampInSeconds == 0) { + errorLog.timestamp = [NSDate dateWithTimeIntervalSince1970:(timestampInSeconds + 0.999)]; + } + // CPU Type and Subtype for the crash. We need to query the binary images for that. NSArray *images = report.images; for (MSPLCrashReportBinaryImageInfo *image in images) { diff --git a/MobileCenterCrashes/MobileCenterCrashes/MSCrashes.h b/MobileCenterCrashes/MobileCenterCrashes/MSCrashes.h index 394b3b87ab..19bbdb9679 100644 --- a/MobileCenterCrashes/MobileCenterCrashes/MSCrashes.h +++ b/MobileCenterCrashes/MobileCenterCrashes/MSCrashes.h @@ -91,7 +91,6 @@ typedef NS_ENUM(NSUInteger, MSUserConfirmation) { /// @name Configuration ///----------------------------------------------------------------------------- -// TODO: Mach exception handler is not supported on tvOS. #if !TARGET_OS_TV /** * Disable the Mach exception server. @@ -108,7 +107,10 @@ typedef NS_ENUM(NSUInteger, MSUserConfirmation) { * `MSCrashes.disableMachExceptionHandler()` * `MSMobileCenter.start("YOUR_APP_ID", withServices: [MSAnalytics.self, MSCrashes.self])` * - * @discussion This can be useful to disable the Mach exception handler when you are debugging the Crashes service while + * tvOS does not support the Mach exception handler, thus crashes that are caused by stack overflows cannot + * be detected. As a result, disabling the Mach exception server is not available in the tvOS SDK. + * + * @discussion It can be useful to disable the Mach exception handler when you are debugging the Crashes service while * developing, especially when you attach the debugger to your application after launch. */ + (void)disableMachExceptionHandler; diff --git a/MobileCenterCrashes/MobileCenterCrashes/MSCrashes.mm b/MobileCenterCrashes/MobileCenterCrashes/MSCrashes.mm index da125bdbab..989b35fc7b 100644 --- a/MobileCenterCrashes/MobileCenterCrashes/MSCrashes.mm +++ b/MobileCenterCrashes/MobileCenterCrashes/MSCrashes.mm @@ -8,6 +8,7 @@ #import "MSErrorAttachmentLog.h" #import "MSErrorAttachmentLogInternal.h" #import "MSErrorLogFormatter.h" +#import "MSHandledErrorLog.h" #import "MSMobileCenterInternal.h" #import "MSServiceAbstractProtected.h" #import "MSWrapperExceptionManagerInternal.h" @@ -229,18 +230,32 @@ + (MSErrorReport *_Nullable)lastSessionCrashReport { return [[self sharedInstance] getLastSessionCrashReport]; } -/* This can never be binded to Xamarin */ -// TODO: Mach exception handler is not supported on tvOS. -#if !TARGET_OS_TV +/** + * This can never be bound to Xamarin. + * + * This method is not part of the publicly available APIs on tvOS as Mach exception handling is not possible on tvOS. + * The property is NO by default there. + */ + (void)disableMachExceptionHandler { [[self sharedInstance] setEnableMachExceptionHandler:NO]; } -#endif + (void)setDelegate:(_Nullable id)delegate { [[self sharedInstance] setDelegate:delegate]; } +/** + * Track handled exception directly as model form. + * This API is not public and is used by wrapper SDKs. + */ ++ (void)trackModelException:(MSException *)exception { + @synchronized(self) { + if ([[self sharedInstance] canBeUsed]) { + [[self sharedInstance] trackModelException:exception]; + } + } +} + #pragma mark - Service initialization - (instancetype)init { @@ -252,7 +267,6 @@ - (instancetype)init { _analyzerInProgressFile = [_crashesDir URLByAppendingPathComponent:kMSAnalyzerFilename]; _didCrashInLastSession = NO; - // TODO: Mach exception handler is not supported on tvOS. #if !TARGET_OS_TV _enableMachExceptionHandler = YES; #endif @@ -391,6 +405,10 @@ - (MSInitializationPriority)initializationPriority { return MSInitializationPriorityMax; } +- (void)setEnableMachExceptionHandler:(BOOL)enableMachExceptionHandler { + _enableMachExceptionHandler = enableMachExceptionHandler; +} + #pragma mark - MSLogManagerDelegate /** @@ -419,6 +437,7 @@ - (void)onEnqueuingLog:(id)log withInternalId:(NSString *)internalId { NSNumberFormatter *timestampFormatter = [[NSNumberFormatter alloc] init]; timestampFormatter.numberStyle = NSNumberFormatterDecimalStyle; long indexToDelete = 0; + MSLogVerbose([MSCrashes logTag], @"Storing a log to Crashes Buffer: (sid: %@, type: %@)", log.sid, log.type); for (auto it = msCrashesLogBuffer.begin(), end = msCrashesLogBuffer.end(); it != end; ++it) { // We've found an empty element, buffer our log. @@ -477,15 +496,15 @@ - (void)onEnqueuingLog:(id)log withInternalId:(NSString *)internalId { - (void)onFinishedPersistingLog:(id)log withInternalId:(NSString *)internalId { (void)log; - [self deleteBufferedLogWithInternalId:internalId]; + [self deleteBufferedLog:log withInternalId:internalId]; } - (void)onFailedPersistingLog:(id)log withInternalId:(NSString *)internalId { (void)log; - [self deleteBufferedLogWithInternalId:internalId]; + [self deleteBufferedLog:log withInternalId:internalId]; } -- (void)deleteBufferedLogWithInternalId:(NSString *)internalId { +- (void)deleteBufferedLog:(id)log withInternalId:(NSString *)internalId { @synchronized(self) { for (auto it = msCrashesLogBuffer.begin(), end = msCrashesLogBuffer.end(); it != end; ++it) { NSString *bufferId = [NSString stringWithCString:it->internalId.c_str() encoding:NSUTF8StringEncoding]; @@ -501,6 +520,7 @@ - (void)deleteBufferedLogWithInternalId:(NSString *)internalId { * delete the buffer file. */ unlink(it->bufferPath.c_str()); + MSLogVerbose([MSCrashes logTag], @"Deleted a log from Crashes Buffer (sid: %@, type: %@)", log.sid, log.type); MSLogVerbose([MSCrashes logTag], @"Deleted crash buffer file: %@.", [NSString stringWithCString:it->bufferPath.c_str() encoding:[NSString defaultCStringEncoding]]); } @@ -561,7 +581,6 @@ - (void)configureCrashReporterWithUncaughtExceptionHandlerEnabled:(BOOL)enableUn PLCrashReporterSignalHandlerType signalHandlerType = PLCrashReporterSignalHandlerTypeBSD; - // TODO: Mach exception handler is not supported on tvOS. #if !TARGET_OS_TV if (self.isMachExceptionHandlerEnabled) { signalHandlerType = PLCrashReporterSignalHandlerTypeMach; @@ -1012,4 +1031,21 @@ + (BOOL)validatePropertiesForAttachment:(MSErrorAttachmentLog *)attachment { return errorIdValid && attachmentIdValid && attachmentDataValid && contentTypeValid; } +#pragma mark - Handled exceptions + +- (void)trackModelException:(MSException *)exception { + if (![self isEnabled]) + return; + + // Create an error log. + MSHandledErrorLog *log = [MSHandledErrorLog new]; + + // Set properties of the error log. + log.errorId = MS_UUID_STRING; + log.exception = exception; + + // Send log to log manager. + [self.logManager processLog:log forGroupId:self.groupId]; +} + @end diff --git a/MobileCenterCrashes/MobileCenterCrashesTests/MSAppleErrorLogTests.m b/MobileCenterCrashes/MobileCenterCrashesTests/MSAppleErrorLogTests.m index f7c3e03fe7..5db844b6ff 100644 --- a/MobileCenterCrashes/MobileCenterCrashesTests/MSAppleErrorLogTests.m +++ b/MobileCenterCrashes/MobileCenterCrashesTests/MSAppleErrorLogTests.m @@ -84,7 +84,7 @@ - (void)testSerializationToDictionaryWorks { assertThat(actual[@"error_thread_id"], equalTo(self.sut.errorThreadId)); assertThat(actual[@"error_thread_name"], equalTo(self.sut.errorThreadName)); XCTAssertEqual([actual[@"fatal"] boolValue], self.sut.fatal); - assertThat(actual[@"app_launch_timestamp"], equalTo(@"1970-01-01T00:00:42Z")); + assertThat(actual[@"app_launch_timestamp"], equalTo(@"1970-01-01T00:00:42.000Z")); assertThat(actual[@"architecture"], equalTo(self.sut.architecture)); // Exception fields. diff --git a/MobileCenterCrashes/MobileCenterCrashesTests/MSCrashesTests.mm b/MobileCenterCrashes/MobileCenterCrashesTests/MSCrashesTests.mm index a46fed5bd7..33e91b803a 100644 --- a/MobileCenterCrashes/MobileCenterCrashesTests/MSCrashesTests.mm +++ b/MobileCenterCrashes/MobileCenterCrashesTests/MSCrashesTests.mm @@ -9,6 +9,7 @@ #import "MSErrorAttachmentLogInternal.h" #import "MSErrorLogFormatter.h" #import "MSException.h" +#import "MSHandledErrorLog.h" #import "MSLogManagerDefault.h" #import "MSMobileCenter.h" #import "MSMobileCenterInternal.h" @@ -24,6 +25,7 @@ static NSString *const kMSTestAppSecret = @"TestAppSecret"; static NSString *const kMSCrashesServiceName = @"Crashes"; static NSString *const kMSFatal = @"fatal"; +static NSString *const kMSTypeHandledError = @"handled_error"; static unsigned int kMaxAttachmentsPerCrashReport = 2; @interface MSCrashes () @@ -494,28 +496,35 @@ - (void)testInitializationPriorityCorrect { XCTAssertTrue([[MSCrashes sharedInstance] initializationPriority] == MSInitializationPriorityMax); } -// TODO: Mach exception handler is not supported on tvOS. -#if !TARGET_OS_TV +// The Mach exception handler is not supported on tvOS. +#if TARGET_OS_TV +- (void) testMachExceptionHandlerDisabledOnTvOS { + + // Then + XCTAssertFalse([[MSCrashes sharedInstance] isMachExceptionHandlerEnabled]); +} +#else - (void)testDisableMachExceptionWorks { - + // Then XCTAssertTrue([[MSCrashes sharedInstance] isMachExceptionHandlerEnabled]); - + // When [MSCrashes disableMachExceptionHandler]; - + // Then XCTAssertFalse([[MSCrashes sharedInstance] isMachExceptionHandlerEnabled]); - + // Then XCTAssertTrue([self.sut isMachExceptionHandlerEnabled]); - + // When [self.sut setEnableMachExceptionHandler:NO]; - + // Then XCTAssertFalse([self.sut isMachExceptionHandlerEnabled]); } + #endif - (void)testAbstractErrorLogSerialization { @@ -572,6 +581,37 @@ - (void)testWarningMessageAboutTooManyErrorAttachments { XCTAssertTrue(warningMessageHasBeenPrinted); } +- (void)testTrackModelException { + + // If + __block NSString *type; + __block NSString *errorId; + __block MSException *exception; + id logManagerMock = OCMProtocolMock(@protocol(MSLogManager)); + OCMStub([logManagerMock processLog:[OCMArg isKindOfClass:[MSAbstractLog class]] forGroupId:OCMOCK_ANY]) + .andDo(^(NSInvocation *invocation) { + MSHandledErrorLog *log; + [invocation getArgument:&log atIndex:2]; + type = log.type; + errorId = log.errorId; + exception = log.exception; + }); + [MSMobileCenter configureWithAppSecret:kMSTestAppSecret]; + [self.sut startWithLogManager:logManagerMock appSecret:kMSTestAppSecret]; + + // When + MSException *expectedException = [MSException new]; + expectedException.message = @"Oh this is wrong..."; + expectedException.stackTrace = @"mock strace"; + expectedException.type = @"Some.Exception"; + [self.sut trackModelException:expectedException]; + + // Then + assertThat(type, is(kMSTypeHandledError)); + assertThat(errorId, notNilValue()); + assertThat(exception, is(expectedException)); +} + #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-parameter" - (NSArray *)attachmentsWithCrashes:(MSCrashes *)crashes forErrorReport:(MSErrorReport *)errorReport { diff --git a/MobileCenterCrashes/MobileCenterCrashesTests/MSErrorLogFormatterTests.mm b/MobileCenterCrashes/MobileCenterCrashesTests/MSErrorLogFormatterTests.mm index ee006ad305..20cb15bf2a 100644 --- a/MobileCenterCrashes/MobileCenterCrashesTests/MSErrorLogFormatterTests.mm +++ b/MobileCenterCrashes/MobileCenterCrashesTests/MSErrorLogFormatterTests.mm @@ -43,7 +43,10 @@ - (void)testCreateErrorReport { XCTAssertEqual(errorReport.signal, crashReport.signalInfo.name); XCTAssertEqual(errorReport.exceptionName, nil); XCTAssertEqual(errorReport.exceptionReason, nil); - assertThat(errorReport.appErrorTime, equalTo(crashReport.systemInfo.timestamp)); + + // FIXME: PLCrashReporter doesn't support millisecond precision, here is a workaround to fill 999 for its millisecond. + XCTAssertEqual([errorReport.appErrorTime timeIntervalSince1970], + [crashReport.systemInfo.timestamp timeIntervalSince1970] + 0.999); assertThat(errorReport.appStartTime, equalTo(crashReport.processInfo.processStartTime)); /* @@ -68,7 +71,10 @@ - (void)testCreateErrorReport { XCTAssertEqual(errorReport.signal, crashReport.signalInfo.name); assertThat(errorReport.exceptionName, equalTo(crashReport.exceptionInfo.exceptionName)); assertThat(errorReport.exceptionReason, equalTo(crashReport.exceptionInfo.exceptionReason)); - assertThat(errorReport.appErrorTime, equalTo(crashReport.systemInfo.timestamp)); + + // FIXME: PLCrashReporter doesn't support millisecond precision, here is a workaround to fill 999 for its millisecond. + XCTAssertEqual([errorReport.appErrorTime timeIntervalSince1970], + [crashReport.systemInfo.timestamp timeIntervalSince1970] + 0.999); assertThat(errorReport.appStartTime, equalTo(crashReport.processInfo.processStartTime)); /* @@ -424,7 +430,10 @@ - (void)assertIsCrashProbeReportValidConverted:(NSString *)filename { assertThat(errorLog.parentProcessId, equalTo(@(crashReport.processInfo.parentProcessID))); assertThat(errorLog.parentProcessName, equalTo(crashReport.processInfo.parentProcessName)); assertThat(errorLog.errorThreadId, equalTo(@(crashedThread.threadNumber))); - assertThat(errorLog.timestamp, equalTo(crashReport.systemInfo.timestamp)); + + // FIXME: PLCrashReporter doesn't support millisecond precision, here is a workaround to fill 999 for its millisecond. + XCTAssertEqual([errorLog.timestamp timeIntervalSince1970], + [crashReport.systemInfo.timestamp timeIntervalSince1970] + 0.999); assertThat(errorLog.appLaunchTimestamp, equalTo(crashReport.processInfo.processStartTime)); NSArray *images = crashReport.images; diff --git a/MobileCenterCrashes/MobileCenterCrashesTests/MSHandledErrorLogTests.m b/MobileCenterCrashes/MobileCenterCrashesTests/MSHandledErrorLogTests.m new file mode 100644 index 0000000000..eb69450039 --- /dev/null +++ b/MobileCenterCrashes/MobileCenterCrashesTests/MSHandledErrorLogTests.m @@ -0,0 +1,124 @@ +#import "MSCrashesTestUtil.h" +#import "MSException.h" +#import "MSHandledErrorLog.h" +#import "MSTestFrameworks.h" + +@interface MSHandledErrorLogTests : XCTestCase + +@property(nonatomic) MSHandledErrorLog *sut; + +@end + +@implementation MSHandledErrorLogTests + +#pragma mark - Housekeeping + +- (void)setUp { + [super setUp]; + self.sut = [self handledErrorLog]; +} + +- (void)tearDown { + [super tearDown]; +} + +#pragma mark - Helper + +- (MSHandledErrorLog *)handledErrorLog { + MSHandledErrorLog *handledErrorLog = [MSHandledErrorLog new]; + handledErrorLog.type = @"handled_error"; + handledErrorLog.exception = [MSCrashesTestUtil exception]; + handledErrorLog.errorId = @"123"; + return handledErrorLog; +} + +#pragma mark - Tests + +- (void)testInitializationWorks { + XCTAssertNotNil(self.sut); +} + +- (void)testSerializationToDictionaryWorks { + + // When + NSDictionary *actual = [self.sut serializeToDictionary]; + + // Then + XCTAssertNotNil(actual); + assertThat(actual[@"type"], equalTo(self.sut.type)); + assertThat(actual[@"id"], equalTo(self.sut.errorId)); + NSDictionary *exceptionDictionary = actual[@"exception"]; + XCTAssertNotNil(exceptionDictionary); + assertThat(exceptionDictionary[@"type"], equalTo(self.sut.exception.type)); + assertThat(exceptionDictionary[@"message"], equalTo(self.sut.exception.message)); + assertThat(exceptionDictionary[@"wrapper_sdk_name"], equalTo(self.sut.exception.wrapperSdkName)); +} + +- (void)testNSCodingSerializationAndDeserializationWorks { + + // When + NSData *serializedEvent = [NSKeyedArchiver archivedDataWithRootObject:self.sut]; + id actual = [NSKeyedUnarchiver unarchiveObjectWithData:serializedEvent]; + + // Then + assertThat(actual, notNilValue()); + assertThat(actual, instanceOf([MSHandledErrorLog class])); + + // The MSHandledErrorLog. + MSHandledErrorLog *actualLog = actual; + assertThat(actualLog, equalTo(self.sut)); + XCTAssertTrue([actualLog isEqual:self.sut]); + assertThat(actualLog.type, equalTo(self.sut.type)); + assertThat(actualLog.errorId, equalTo(self.sut.errorId)); + + // The exception field. + MSException *actualException = actualLog.exception; + assertThat(actualException.type, equalTo(self.sut.exception.type)); + assertThat(actualException.message, equalTo(self.sut.exception.message)); + assertThat(actualException.wrapperSdkName, equalTo(self.sut.exception.wrapperSdkName)); +} + +- (void)testIsEqual { + + // When + MSHandledErrorLog *first = [self handledErrorLog]; + MSHandledErrorLog *second = [self handledErrorLog]; + + // Then + XCTAssertTrue([first isEqual:second]); + + // When + second.errorId = MS_UUID_STRING; + + // Then + XCTAssertFalse([first isEqual:second]); +} + +- (void)testIsValid { + + // When + MSHandledErrorLog *log = [MSHandledErrorLog new]; + log.device = OCMClassMock([MSDevice class]); + OCMStub([log.device isValid]).andReturn(YES); + log.sid = @"sid"; + log.timestamp = [NSDate dateWithTimeIntervalSince1970:42]; + log.errorId = @"errorId"; + log.sid = MS_UUID_STRING; + + // Then + XCTAssertFalse([log isValid]); + + // When + log.errorId = MS_UUID_STRING; + + // Then + XCTAssertFalse([log isValid]); + + // When + log.exception = [MSCrashesTestUtil exception]; + + // Then + XCTAssertTrue([log isValid]); +} + +@end diff --git a/MobileCenterDistribute/MobileCenterDistribute/MSDistribute.m b/MobileCenterDistribute/MobileCenterDistribute/MSDistribute.m index af384ce9c3..e5c466173a 100644 --- a/MobileCenterDistribute/MobileCenterDistribute/MSDistribute.m +++ b/MobileCenterDistribute/MobileCenterDistribute/MSDistribute.m @@ -1,6 +1,5 @@ #import #import - #import "MSAppDelegateForwarder.h" #import "MSDistribute.h" #import "MSDistributeAppDelegate.h" @@ -119,7 +118,6 @@ - (void)notifyUpdateAction:(MSUpdateAction)action { MSLogDebug([MSDistribute logTag], @"The release has already been processed."); return; } - switch (action) { case MSUpdateActionUpdate: #if TARGET_OS_SIMULATOR @@ -214,28 +212,41 @@ - (void)requestInstallInformationWith:(NSString *)releaseHash { if (url) { /* - * Only iOS 9.x and 10.x will download the update after users click the "Install" button. + * Only iOS 9.x and 10.x will download the update after users click the "Install" button. * We need to force-exit the application for other versions or for any versions when the update is mandatory. */ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpartial-availability" Class clazz = [SFSafariViewController class]; -#pragma clang diagnostic pop - /* - * TODO Checking operating system version is a workaround for iOS 11 where SFSafariViewController can't read Safari's cookies. - * Revert this change when SFAuthenticationSession will be ready. - */ - if (clazz && ![NSProcessInfo.processInfo isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){11, 0, 0}]) { + if (clazz) { +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0 + if (@available(iOS 11.0, *)) { + + // iOS 11 + Class authClazz = [SFAuthenticationSession class]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self openURLInAuthenticationSessionWith:url fromClass:authClazz]; + }); + } else { + + // Compiling against iOS 11 but running on iOS 9 and 10. + dispatch_async(dispatch_get_main_queue(), ^{ + [self openURLInSafariViewControllerWith:url fromClass:clazz]; + }); + } +#else - // Manipulate App UI on the main queue. + // The app is not compiled against the iOS 11 SDK, use the logic for iOS 9 and 10. dispatch_async(dispatch_get_main_queue(), ^{ - [self openURLInEmbeddedSafari:url fromClass:clazz]; + [self openURLInSafariViewControllerWith:url fromClass:clazz]; }); +#endif } else { // iOS 8.x. [self openURLInSafariApp:url]; } +#pragma clang diagnostic pop } } else { @@ -446,7 +457,38 @@ - (nullable NSURL *)buildTokenRequestURLWithAppSecret:(NSString *)appSecret rele return components.URL; } -- (void)openURLInEmbeddedSafari:(NSURL *)url fromClass:(Class)clazz { +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0 +- (void)openURLInAuthenticationSessionWith:(NSURL *)url fromClass:(Class)clazz { + MSLogDebug([MSDistribute logTag], @"Using SFAuthenticationSession to open URL: %@", url); + NSString *callbackUrlScheme = [NSString stringWithFormat:kMSDefaultCustomSchemeFormat, self.appSecret]; + if (@available(iOS 11.0, *)) { + SFAuthenticationSession *session = [[clazz alloc] + initWithURL:url + callbackURLScheme:callbackUrlScheme + completionHandler:^(NSURL *callbackUrl, NSError *error) { + + self.authenticationSession = nil; + if (error != nil) { + MSLogDebug([MSDistribute logTag], @"Called %@ with errror: %@", callbackUrl, error.localizedDescription); + } + if (error.code == SFAuthenticationErrorCanceledLogin) { + MSLogError([MSDistribute logTag], @"Authentication session was cancelled by user or failed."); + } + if (callbackUrl) { + [self openURL:callbackUrl]; + } + }]; + self.authenticationSession = session; + + BOOL success = [session start]; + if (success) { + MSLogDebug([MSDistribute logTag], @"Authentication Session Started, showing confirmation dialog"); + } + } +} +#endif + +- (void)openURLInSafariViewControllerWith:(NSURL *)url fromClass:(Class)clazz { MSLogDebug([MSDistribute logTag], @"Using SFSafariViewController to open URL: %@", url); // Init safari controller with the install URL. diff --git a/MobileCenterDistribute/MobileCenterDistribute/MSDistributePrivate.h b/MobileCenterDistribute/MobileCenterDistribute/MSDistributePrivate.h index 60a2cefce5..1634414eb8 100644 --- a/MobileCenterDistribute/MobileCenterDistribute/MSDistributePrivate.h +++ b/MobileCenterDistribute/MobileCenterDistribute/MSDistributePrivate.h @@ -1,8 +1,16 @@ #import +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreserved-id-macro" +#ifndef __IPHONE_11_0 +#define __IPHONE_11_0 110000 +#endif +#pragma clang diagnostic pop + #import "MSAlertController.h" #import "MSUIAppDelegate.h" #import "MSDistribute.h" +#import NS_ASSUME_NONNULL_BEGIN @@ -101,6 +109,13 @@ static NSString *const kMSDistributionGroupIdKey = @"MSDistributionGroupId"; */ @property(nonatomic) id appDelegate; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0 +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpartial-availability" +@property(nullable, nonatomic) SFAuthenticationSession *authenticationSession; +#pragma clang diagnostic pop +#endif + /** * Returns the singleton instance. Meant for testing/demo apps only. * @@ -118,13 +133,23 @@ static NSString *const kMSDistributionGroupIdKey = @"MSDistributionGroupId"; */ - (nullable NSURL *)buildTokenRequestURLWithAppSecret:(NSString *)appSecret releaseHash:(NSString *)releaseHash; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0 +/** + * Open the given URL using an `SFAuthenticationSession`. Must run on the UI thread! iOS 11 only. + * + * @param url URL to open. + * @param clazz `SFAuthenticationSession` class. + */ +- (void)openURLInAuthenticationSessionWith:(NSURL *)url fromClass:(Class)clazz; +#endif + /** - * Open the given URL using an `SFSafariViewController`. Must run on the UI thread! iOS 9+ only. + * Open the given URL using an `SFSafariViewController`. Must run on the UI thread! iOS 9 and 10 only. * * @param url URL to open. * @param clazz `SFSafariViewController` class. */ -- (void)openURLInEmbeddedSafari:(NSURL *)url fromClass:(Class)clazz; +- (void)openURLInSafariViewControllerWith:(NSURL *)url fromClass:(Class)clazz; /** * Open the given URL using the Safari application. iOS 8.x only. @@ -149,7 +174,9 @@ static NSString *const kMSDistributionGroupIdKey = @"MSDistributionGroupId"; * @param distributionGroupId The distribution group Id in keychain. * @param releaseHash The release hash of the current version. */ -- (void)checkLatestRelease:(nullable NSString *)updateToken distributionGroupId:(NSString *)distributionGroupId releaseHash:(NSString *)releaseHash; +- (void)checkLatestRelease:(nullable NSString *)updateToken + distributionGroupId:(NSString *)distributionGroupId + releaseHash:(NSString *)releaseHash; /** * Send a request to get information for installation. diff --git a/MobileCenterDistribute/MobileCenterDistributeTests/MSDistributeTests.m b/MobileCenterDistribute/MobileCenterDistributeTests/MSDistributeTests.m index 95501fcf70..1274e01a15 100644 --- a/MobileCenterDistribute/MobileCenterDistributeTests/MSDistributeTests.m +++ b/MobileCenterDistribute/MobileCenterDistributeTests/MSDistributeTests.m @@ -25,7 +25,7 @@ static NSString *const kMSDistributeServiceName = @"Distribute"; // Mocked SFSafariViewController for url validation. -@interface SFSafariViewController : UIViewController +@interface SFSafariViewControllerMock : UIViewController @property(class, nonatomic) NSURL *url; @@ -35,10 +35,10 @@ - (instancetype)initWithURL:(NSURL *)url; static NSURL *sfURL; -@implementation SFSafariViewController +@implementation SFSafariViewControllerMock - (instancetype)initWithURL:(NSURL *)url { - if ((self = [super init])) { + if ((self = [self init])) { [[self class] setUrl:url]; } return self; @@ -130,7 +130,7 @@ - (void)testInstallURL { OCMStub([self.bundleMock objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(bundleArray); OCMStub([self.bundleMock objectForInfoDictionaryKey:@"MSAppName"]).andReturn(@"Something"); id distributeMock = OCMPartialMock(self.sut); - OCMStub([distributeMock openURLInEmbeddedSafari:OCMOCK_ANY fromClass:OCMOCK_ANY]).andDo(nil); + OCMStub([distributeMock openURLInSafariViewControllerWith:OCMOCK_ANY fromClass:OCMOCK_ANY]).andDo(nil); // Disable for now to bypass initializing sender. [distributeMock setEnabled:NO]; @@ -222,14 +222,14 @@ - (void)testOpenURLInSafariApp { [appMock stopMocking]; } -- (void)testOpenURLInEmbeddedSafari { +- (void)testOpenURLInSafariViewControllerWithUrl { // If NSURL *url = [NSURL URLWithString:@"https://contoso.com"]; // When @try { - [self.sut openURLInEmbeddedSafari:url fromClass:[SFSafariViewController class]]; + [self.sut openURLInSafariViewControllerWith:url fromClass:[SFSafariViewControllerMock class]]; } @catch (__attribute__((unused)) NSException *ex) { /** @@ -239,7 +239,7 @@ - (void)testOpenURLInEmbeddedSafari { } // Then - assertThat(SFSafariViewController.url, is(url)); + assertThat(SFSafariViewControllerMock.url, is(url)); } - (void)testSetApiUrlWorks { @@ -1055,7 +1055,8 @@ - (void)testApplyEnabledStateTrue { OCMVerify([distributeMock requestInstallInformationWith:kMSTestReleaseHash]); // If, private distribution - [MSKeychainUtil storeString:@"UpdateToken" forKey:kMSUpdateTokenKey]; + id keychainUtilMock = OCMClassMock([MSKeychainUtil class]); + OCMStub([keychainUtilMock stringForKey:kMSUpdateTokenKey]).andReturn(@"UpdateToken"); [self.settingsMock setObject:@"DistributionGroupId" forKey:kMSDistributionGroupIdKey]; // When @@ -1196,7 +1197,7 @@ - (void)testWithoutNetwork { OCMReject([distributeMock buildTokenRequestURLWithAppSecret:OCMOCK_ANY releaseHash:kMSTestReleaseHash]); // We should not touch UI in a unit testing environment. - OCMStub([distributeMock openURLInEmbeddedSafari:OCMOCK_ANY fromClass:OCMOCK_ANY]).andDo(nil); + OCMStub([distributeMock openURLInSafariViewControllerWith:OCMOCK_ANY fromClass:OCMOCK_ANY]).andDo(nil); // When [distributeMock requestInstallInformationWith:kMSTestReleaseHash]; diff --git a/MobileCenterPush/MobileCenterPush/Internal/MSPushAppDelegate.h b/MobileCenterPush/MobileCenterPush/Internal/MSPushAppDelegate.h index c75b174f98..8420f73b25 100644 --- a/MobileCenterPush/MobileCenterPush/Internal/MSPushAppDelegate.h +++ b/MobileCenterPush/MobileCenterPush/Internal/MSPushAppDelegate.h @@ -5,10 +5,6 @@ #import "MSUIAppDelegate.h" #endif -#if TARGET_OS_OSX -@interface MSPushAppDelegate : NSObject -#else @interface MSPushAppDelegate : NSObject -#endif @end diff --git a/MobileCenterPush/MobileCenterPush/Internal/MSPushAppDelegate.m b/MobileCenterPush/MobileCenterPush/Internal/MSPushAppDelegate.m index cf413f30d2..4828941379 100644 --- a/MobileCenterPush/MobileCenterPush/Internal/MSPushAppDelegate.m +++ b/MobileCenterPush/MobileCenterPush/Internal/MSPushAppDelegate.m @@ -48,20 +48,6 @@ - (void)application:(__attribute__((unused))UIApplication *)application } #endif -#if TARGET_OS_OSX -- (void)applicationDidFinishLaunching:(NSNotification *)notification { - NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter]; - center.delegate = self; - [MSPush didReceiveNotification:notification]; -} - -- (void)userNotificationCenter:(NSUserNotificationCenter *)__unused center - didActivateNotification:(NSUserNotification *)notification { - [MSPush didReceiveUserNotification:notification]; -} - -#endif - @end #pragma mark - Swizzling @@ -74,9 +60,7 @@ + (void)load { [self addAppDelegateSelectorToSwizzle:@selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)]; [self addAppDelegateSelectorToSwizzle:@selector(application:didFailToRegisterForRemoteNotificationsWithError:)]; [self addAppDelegateSelectorToSwizzle:@selector(application:didReceiveRemoteNotification:)]; -#if TARGET_OS_OSX - [self addAppDelegateSelectorToSwizzle:@selector(applicationDidFinishLaunching:)]; -#else +#if !TARGET_OS_OSX [self addAppDelegateSelectorToSwizzle:@selector(application:didReceiveRemoteNotification:fetchCompletionHandler:)]; #endif } diff --git a/MobileCenterPush/MobileCenterPush/Internal/MSPushPrivate.h b/MobileCenterPush/MobileCenterPush/Internal/MSPushPrivate.h index d36c09f637..14d04df6e9 100644 --- a/MobileCenterPush/MobileCenterPush/Internal/MSPushPrivate.h +++ b/MobileCenterPush/MobileCenterPush/Internal/MSPushPrivate.h @@ -4,7 +4,11 @@ @protocol MSAppDelegate; +#if TARGET_OS_OSX +@interface MSPush () +#else @interface MSPush () +#endif @property(nonatomic) id delegate; @@ -39,4 +43,11 @@ */ - (void)registerForRemoteNotifications; +#if TARGET_OS_OSX +/** + * Observer to register user notification center delegate when application launches. + */ +- (void)applicationDidFinishLaunching:(NSNotification *)notification; +#endif + @end diff --git a/MobileCenterPush/MobileCenterPush/MSPush.h b/MobileCenterPush/MobileCenterPush/MSPush.h index 956e13946e..bf7dec4392 100644 --- a/MobileCenterPush/MobileCenterPush/MSPush.h +++ b/MobileCenterPush/MobileCenterPush/MSPush.h @@ -22,26 +22,6 @@ NS_ASSUME_NONNULL_BEGIN */ + (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error; -#if TARGET_OS_OSX -/** - * Callback for notification with notification on macOS. - * - * @param notification The notification that triggered the application launch. - * - * @return YES if the notification was sent via Mobile Center. - */ -+ (BOOL)didReceiveNotification:(NSNotification *)notification; - -/** - * Callback for notification with user notification on macOS. - * - * @param notification The received user notification. - * - * @return YES if the notification was sent via Mobile Center. - */ -+ (BOOL)didReceiveUserNotification:(NSUserNotification *)notification; -#endif - /** * Callback for notification with user info. * diff --git a/MobileCenterPush/MobileCenterPush/MSPush.m b/MobileCenterPush/MobileCenterPush/MSPush.m index 13394f469b..fbeefdb604 100644 --- a/MobileCenterPush/MobileCenterPush/MSPush.m +++ b/MobileCenterPush/MobileCenterPush/MSPush.m @@ -97,16 +97,6 @@ + (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { [[self sharedInstance] didFailToRegisterForRemoteNotificationsWithError:error]; } -#if TARGET_OS_OSX -+ (BOOL)didReceiveNotification:(NSNotification *)notification { - return [[self sharedInstance] didReceiveNotification:notification]; -} - -+ (BOOL)didReceiveUserNotification:(NSUserNotification *)notification { - return [[self sharedInstance] didReceiveUserNotification:notification]; -} -#endif - + (BOOL)didReceiveRemoteNotification:(NSDictionary *)userInfo { return [[self sharedInstance] didReceiveRemoteNotification:userInfo fromUserNotification:NO]; } @@ -120,12 +110,23 @@ + (void)setDelegate:(nullable id)delegate { - (void)applyEnabledState:(BOOL)isEnabled { [super applyEnabledState:isEnabled]; if (isEnabled) { +#if TARGET_OS_OSX + [NSUserNotificationCenter defaultUserNotificationCenter].delegate = self; + [MS_NOTIFICATION_CENTER addObserver:self + selector:@selector(applicationDidFinishLaunching:) + name:NSApplicationDidFinishLaunchingNotification + object:nil]; +#endif [MSAppDelegateForwarder addDelegate:self.appDelegate]; if (!self.pushTokenHasBeenSent) { [self registerForRemoteNotifications]; } MSLogInfo([MSPush logTag], @"Push service has been enabled."); } else { +#if TARGET_OS_OSX + [NSUserNotificationCenter defaultUserNotificationCenter].delegate = nil; + [MS_NOTIFICATION_CENTER removeObserver:self name:NSApplicationDidFinishLaunchingNotification object:nil]; +#endif [MSAppDelegateForwarder removeDelegate:self.appDelegate]; MSLogInfo([MSPush logTag], @"Push service has been disabled."); } @@ -170,18 +171,6 @@ - (void)registerForRemoteNotifications { #endif } -#if TARGET_OS_OSX - -// TODO: Implement macOS. Seems it is dead code. -#else -- (void)application:(UIApplication *)application - didRegisterUserNotificationSettings:(UIUserNotificationSettings *)__unused notificationSettings { - - // register to receive notifications - [application registerForRemoteNotifications]; -} -#endif - - (NSString *)convertTokenToString:(NSData *)token { if (!token) return nil; @@ -215,10 +204,6 @@ - (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { } #if TARGET_OS_OSX -- (BOOL)didReceiveNotification:(NSNotification *)notification { - return [self didReceiveUserNotification:[notification.userInfo objectForKey:NSApplicationLaunchUserNotificationKey]]; -} - - (BOOL)didReceiveUserNotification:(NSUserNotification *)notification { if (notification && [self didReceiveRemoteNotification:notification.userInfo fromUserNotification:YES]) { NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter]; @@ -229,6 +214,15 @@ - (BOOL)didReceiveUserNotification:(NSUserNotification *)notification { } return NO; } + +- (void)applicationDidFinishLaunching:(NSNotification *)notification { + [self didReceiveUserNotification:[notification.userInfo objectForKey:NSApplicationLaunchUserNotificationKey]]; +} + +- (void)userNotificationCenter:(NSUserNotificationCenter *)__unused center + didActivateNotification:(NSUserNotification *)notification { + [self didReceiveUserNotification:notification]; +} #endif - (BOOL)didReceiveRemoteNotification:(NSDictionary *)userInfo fromUserNotification:(BOOL)userNotification { diff --git a/MobileCenterPush/MobileCenterPushTests/MSPushTests.m b/MobileCenterPush/MobileCenterPushTests/MSPushTests.m index 41b7015333..1d8647c64b 100644 --- a/MobileCenterPush/MobileCenterPushTests/MSPushTests.m +++ b/MobileCenterPush/MobileCenterPushTests/MSPushTests.m @@ -36,8 +36,6 @@ - (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken; - (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error; #if TARGET_OS_OSX -- (BOOL)didReceiveNotification:(NSNotification *)notification; - - (BOOL)didReceiveUserNotification:(NSUserNotification *)notification; #endif @@ -190,9 +188,9 @@ - (void)testNotificationReceivedWithAlertObject { OCMStub([userNotificationUserInfoMock userInfo]).andReturn(userInfo); #endif - // When +// When #if TARGET_OS_OSX - BOOL result = [MSPush didReceiveNotification:notificationMock]; + [self.sut applicationDidFinishLaunching:notificationMock]; #else BOOL result = [MSPush didReceiveRemoteNotification:userInfo]; #endif @@ -204,7 +202,7 @@ - (void)testNotificationReceivedWithAlertObject { [self waitForExpectationsWithTimeout:1 handler:^(NSError *error) { #if TARGET_OS_OSX - OCMVerify([pushMock didReceiveNotification:notificationMock]); + OCMVerify([pushMock didReceiveUserNotification:userNotificationUserInfoMock]); #else OCMVerify([pushMock didReceiveRemoteNotification:userInfo]); #endif @@ -217,7 +215,9 @@ - (void)testNotificationReceivedWithAlertObject { XCTFail(@"Expectation Failed with error: %@", error); } }]; +#if !TARGET_OS_OSX XCTAssertTrue(result); +#endif [pushMock stopMocking]; } @@ -245,9 +245,9 @@ - (void)testNotificationReceivedWithAlertString { OCMStub([userNotificationUserInfoMock userInfo]).andReturn(userInfo); #endif - // When +// When #if TARGET_OS_OSX - BOOL result = [MSPush didReceiveNotification:notificationMock]; + [self.sut applicationDidFinishLaunching:notificationMock]; #else BOOL result = [MSPush didReceiveRemoteNotification:userInfo]; #endif @@ -259,7 +259,7 @@ - (void)testNotificationReceivedWithAlertString { [self waitForExpectationsWithTimeout:1 handler:^(NSError *error) { #if TARGET_OS_OSX - OCMVerify([pushMock didReceiveNotification:notificationMock]); + OCMVerify([pushMock didReceiveUserNotification:userNotificationUserInfoMock]); #else OCMVerify([pushMock didReceiveRemoteNotification:userInfo]); #endif @@ -272,7 +272,9 @@ - (void)testNotificationReceivedWithAlertString { XCTFail(@"Expectation Failed with error: %@", error); } }]; +#if !TARGET_OS_OSX XCTAssertTrue(result); +#endif [pushMock stopMocking]; } @@ -302,11 +304,11 @@ - (void)testNotificationReceivedForNonMobileCenterNotification { // When #if TARGET_OS_OSX - BOOL result = [MSPush didReceiveNotification:notificationMock]; + [self.sut applicationDidFinishLaunching:notificationMock]; #else BOOL result = [MSPush didReceiveRemoteNotification:invalidUserInfo]; -#endif XCTAssertFalse(result); +#endif dispatch_async(dispatch_get_main_queue(), ^{ [notificationReceived fulfill]; }); @@ -314,7 +316,7 @@ - (void)testNotificationReceivedForNonMobileCenterNotification { // Then [self waitForExpectationsWithTimeout:1 handler:^(NSError *error) { - + // Then OCMVerifyAll(pushDelegateMock); XCTAssertNil(pushNotification); @@ -326,7 +328,7 @@ - (void)testNotificationReceivedForNonMobileCenterNotification { } - (void)testPushAppDelegateCallbacks { - + // If #if TARGET_OS_OSX id applicationMock = OCMClassMock([NSApplication class]); @@ -337,64 +339,43 @@ - (void)testPushAppDelegateCallbacks { OCMStub([pushMock sharedInstance]).andReturn(pushMock); [MSPush resetSharedInstance]; MSPushAppDelegate *delegate = [MSPushAppDelegate new]; - + // When id deviceTokenMock = OCMClassMock([NSData class]); [delegate application:applicationMock didRegisterForRemoteNotificationsWithDeviceToken:deviceTokenMock]; - + // Then OCMVerify([pushMock didRegisterForRemoteNotificationsWithDeviceToken:deviceTokenMock]); - + // When id errorMock = OCMClassMock([NSError class]); [delegate application:applicationMock didFailToRegisterForRemoteNotificationsWithError:errorMock]; - + // Then OCMVerify([pushMock didFailToRegisterForRemoteNotificationsWithError:errorMock]); - + // When id userInfoMock = OCMClassMock([NSDictionary class]); [delegate application:applicationMock didReceiveRemoteNotification:userInfoMock]; - + // Then OCMVerify([pushMock didReceiveRemoteNotification:userInfoMock]); - + #if !TARGET_OS_OSX - + // When XCTestExpectation *notificationReceived = [self expectationWithDescription:@"Valid notification received."]; - [delegate application:applicationMock didReceiveRemoteNotification:userInfoMock fetchCompletionHandler:^(__unused UIBackgroundFetchResult result) { - [notificationReceived fulfill]; - }]; - + [delegate application:applicationMock + didReceiveRemoteNotification:userInfoMock + fetchCompletionHandler:^(__unused UIBackgroundFetchResult result) { + [notificationReceived fulfill]; + }]; + // Then OCMVerify([pushMock didReceiveRemoteNotification:userInfoMock]); [self waitForExpectations:@[ notificationReceived ] timeout:0]; - #endif - -#if TARGET_OS_OSX - - // If - id userNotificationCenterMock = OCMClassMock([NSUserNotificationCenter class]); - OCMStub([userNotificationCenterMock defaultUserNotificationCenter]).andReturn(userNotificationCenterMock); - id notificationMock = OCMClassMock([NSNotification class]); - - // When - [delegate applicationDidFinishLaunching:notificationMock]; - - // Then - OCMVerify([userNotificationCenterMock setDelegate:delegate]); - OCMVerify([pushMock didReceiveNotification:notificationMock]); - // When - [delegate userNotificationCenter:userNotificationCenterMock didActivateNotification: notificationMock]; - - // Then - OCMVerify([pushMock didReceiveUserNotification:notificationMock]); - -#endif - [pushMock stopMocking]; } diff --git a/Puppet/Puppet.xcodeproj/project.pbxproj b/Puppet/Puppet.xcodeproj/project.pbxproj index 924b4e382f..a2bff0d190 100644 --- a/Puppet/Puppet.xcodeproj/project.pbxproj +++ b/Puppet/Puppet.xcodeproj/project.pbxproj @@ -32,6 +32,7 @@ 5CE60DF11EDF0B670060303A /* MSCrashResultViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CE60DF01EDF0B670060303A /* MSCrashResultViewController.m */; }; 6EC99A211D4151A00016C325 /* CrashReporter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EC99A201D4151A00016C325 /* CrashReporter.framework */; }; 6EC99A281D4152FA0016C325 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EC99A271D4152FA0016C325 /* libc++.tbd */; }; + 80493AB71F6A701900E6E895 /* MSPushViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 80493AB61F6A701900E6E895 /* MSPushViewController.m */; }; 808A10FC1EF312A000DFD41B /* MSPropertiesTableDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 808A10F91EF312A000DFD41B /* MSPropertiesTableDataSource.m */; }; 808A10FD1EF312A000DFD41B /* MSPropertyViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 808A10FB1EF312A000DFD41B /* MSPropertyViewCell.m */; }; B21B26C71DDE490400FF0378 /* MSCrashesDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B21B26C61DDE490400FF0378 /* MSCrashesDetailViewController.m */; }; @@ -233,6 +234,27 @@ remoteGlobalIDString = 6E0401321D1C98690051BCFA; remoteInfo = MobileCenterCrashes; }; + 388CCA081F699BFF00780C59 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = E82E1B821D1CA63000D281C1 /* MobileCenter.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 0446DF2B1F3B864600C8E338; + remoteInfo = MobileCenterTVOSTests; + }; + 388CCA151F699BFF00780C59 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = E85547FB1D2D6C8D002DF6E2 /* MobileCenterAnalytics.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 0446DF541F3B8E6300C8E338; + remoteInfo = MobileCenterAnalyticsTVOSTests; + }; + 388CCA221F699BFF00780C59 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = E82E1B7C1D1CA62900D281C1 /* MobileCenterCrashes.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 0446DF9D1F3B977100C8E338; + remoteInfo = MobileCenterCrashesTVOSTests; + }; 5CE2583C1EA5097100DA8FB9 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = E82E1B571D1CA58D00D281C1 /* Project object */; @@ -367,6 +389,8 @@ 6EC99A201D4151A00016C325 /* CrashReporter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CrashReporter.framework; path = ../../Vendor/PLCrashReporter/CrashReporter.framework; sourceTree = ""; }; 6EC99A251D4152EB0016C325 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; 6EC99A271D4152FA0016C325 /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; + 80493AB51F6A701900E6E895 /* MSPushViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSPushViewController.h; sourceTree = ""; }; + 80493AB61F6A701900E6E895 /* MSPushViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSPushViewController.m; sourceTree = ""; }; 808A10F81EF312A000DFD41B /* MSPropertiesTableDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MSPropertiesTableDataSource.h; path = ../../Vendor/Common/Utils/MSPropertiesTableDataSource.h; sourceTree = ""; }; 808A10F91EF312A000DFD41B /* MSPropertiesTableDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MSPropertiesTableDataSource.m; path = ../../Vendor/Common/Utils/MSPropertiesTableDataSource.m; sourceTree = ""; }; 808A10FA1EF312A000DFD41B /* MSPropertyViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MSPropertyViewCell.h; path = ../../Vendor/Common/Utils/MSPropertyViewCell.h; sourceTree = ""; }; @@ -613,6 +637,7 @@ 046AEAA91ECA509000CBE511 /* libMobileCenterCrashes.a */, 046AEAAB1ECA509000CBE511 /* MobileCenterCrashes.xctest */, 04EBBF1C1F01CCBE0006B8AE /* libMobileCenterCrashes.a */, + 388CCA231F699BFF00780C59 /* MobileCenterCrashes.xctest */, ); name = Products; sourceTree = ""; @@ -625,6 +650,7 @@ 046AEA9A1ECA509000CBE511 /* libMobileCenter.a */, 048494D11ECCD9E40020B0FC /* MobileCenter.xctest */, 04685E661EE776AC00A1592F /* libMobileCenter.a */, + 388CCA091F699BFF00780C59 /* MobileCenter.xctest */, ); name = Products; sourceTree = ""; @@ -637,6 +663,7 @@ 046AEAA11ECA509000CBE511 /* libMobileCenterAnalytics.a */, 048494D91ECCD9E40020B0FC /* MobileCenterAnalytics.xctest */, 04685E701EE776AC00A1592F /* libMobileCenterAnalytics.a */, + 388CCA161F699BFF00780C59 /* MobileCenterAnalytics.xctest */, ); name = Products; sourceTree = ""; @@ -668,6 +695,8 @@ B2E611F41DDE72BA00A9DF86 /* MSFakeCXXClass.mm */, E89E6A2A1D396D7900CAA2CD /* MSMainViewController.h */, E89E6A2B1D396D7900CAA2CD /* MSMainViewController.m */, + 80493AB51F6A701900E6E895 /* MSPushViewController.h */, + 80493AB61F6A701900E6E895 /* MSPushViewController.m */, ); name = Classes; sourceTree = ""; @@ -788,6 +817,9 @@ DevelopmentTeam = L4RARDNZ2Y; ProvisioningStyle = Manual; SystemCapabilities = { + com.apple.BackgroundModes = { + enabled = 1; + }; com.apple.Push = { enabled = 1; }; @@ -958,6 +990,27 @@ remoteRef = 04EBBF1B1F01CCBE0006B8AE /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + 388CCA091F699BFF00780C59 /* MobileCenter.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = MobileCenter.xctest; + remoteRef = 388CCA081F699BFF00780C59 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 388CCA161F699BFF00780C59 /* MobileCenterAnalytics.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = MobileCenterAnalytics.xctest; + remoteRef = 388CCA151F699BFF00780C59 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 388CCA231F699BFF00780C59 /* MobileCenterCrashes.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = MobileCenterCrashes.xctest; + remoteRef = 388CCA221F699BFF00780C59 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; B2C070CB1E5F59A90076D6A9 /* MobileCenterDistributeResources.bundle */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; @@ -1077,6 +1130,7 @@ 808A10FC1EF312A000DFD41B /* MSPropertiesTableDataSource.m in Sources */, E89E6A301D39704900CAA2CD /* MSAnalyticsViewController.m in Sources */, E82E1B641D1CA58D00D281C1 /* main.m in Sources */, + 80493AB71F6A701900E6E895 /* MSPushViewController.m in Sources */, 808A10FD1EF312A000DFD41B /* MSPropertyViewCell.m in Sources */, BA6827C9F0410233C245D989 /* MSDistributeViewController.m in Sources */, 5CE60DF11EDF0B670060303A /* MSCrashResultViewController.m in Sources */, diff --git a/Puppet/Puppet/AppDelegate.m b/Puppet/Puppet/AppDelegate.m index c9cdbb102e..0c07cc2d9e 100644 --- a/Puppet/Puppet/AppDelegate.m +++ b/Puppet/Puppet/AppDelegate.m @@ -29,7 +29,8 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // View controller should register in NSNotificationCenter before SDK start. - crashResultViewController = [[[[self window] rootViewController] storyboard] instantiateViewControllerWithIdentifier:@"crashResult"]; + crashResultViewController = + [[[[self window] rootViewController] storyboard] instantiateViewControllerWithIdentifier:@"crashResult"]; // Customize Mobile Center SDK. [MSDistribute setDelegate:self]; @@ -123,8 +124,7 @@ - (void)crashes { [MSCrashes setDelegate:self]; [MSCrashes setUserConfirmationHandler:(^(NSArray *errorReports) { - [NSNotificationCenter.defaultCenter postNotificationName:kDidShouldAwaitUserConfirmationEvent - object:nil]; + [NSNotificationCenter.defaultCenter postNotificationName:kDidShouldAwaitUserConfirmationEvent object:nil]; // Show a dialog to the user where they can choose if they want to provide a crash report. MSAlertController *alertController = [MSAlertController @@ -158,27 +158,23 @@ - (void)crashes { #pragma mark - MSCrashesDelegate - (BOOL)crashes:(MSCrashes *)crashes shouldProcessErrorReport:(MSErrorReport *)errorReport { - [NSNotificationCenter.defaultCenter postNotificationName:kShouldProcessErrorReportEvent - object:nil]; + [NSNotificationCenter.defaultCenter postNotificationName:kShouldProcessErrorReportEvent object:nil]; NSLog(@"Should process error report with: %@", errorReport.exceptionReason); return YES; } - (void)crashes:(MSCrashes *)crashes willSendErrorReport:(MSErrorReport *)errorReport { - [NSNotificationCenter.defaultCenter postNotificationName:kWillSendErrorReportEvent - object:nil]; + [NSNotificationCenter.defaultCenter postNotificationName:kWillSendErrorReportEvent object:nil]; NSLog(@"Will send error report with: %@", errorReport.exceptionReason); } - (void)crashes:(MSCrashes *)crashes didSucceedSendingErrorReport:(MSErrorReport *)errorReport { - [NSNotificationCenter.defaultCenter postNotificationName:kDidSucceedSendingErrorReportEvent - object:nil]; + [NSNotificationCenter.defaultCenter postNotificationName:kDidSucceedSendingErrorReportEvent object:nil]; NSLog(@"Did succeed error report sending with: %@", errorReport.exceptionReason); } - (void)crashes:(MSCrashes *)crashes didFailSendingErrorReport:(MSErrorReport *)errorReport withError:(NSError *)error { - [NSNotificationCenter.defaultCenter postNotificationName:kDidFailSendingErrorReportEvent - object:nil]; + [NSNotificationCenter.defaultCenter postNotificationName:kDidFailSendingErrorReportEvent object:nil]; NSLog(@"Did fail sending report with: %@, and error: %@", errorReport.exceptionReason, error.localizedDescription); } @@ -226,23 +222,32 @@ - (BOOL)distribute:(MSDistribute *)distribute releaseAvailableWithDetails:(MSRel #pragma mark - MSPushDelegate - (void)push:(MSPush *)push didReceivePushNotification:(MSPushNotification *)pushNotification { + NSString *title = pushNotification.title; NSString *message = pushNotification.message; + NSMutableString *customData = nil; for (NSString *key in pushNotification.customData) { - message = [NSString stringWithFormat:@"%@\n%@: %@", message, key, [pushNotification.customData objectForKey:key]]; + ([customData length] == 0) ? customData = [NSMutableString new] : [customData appendString:@", "]; + [customData appendFormat:@"%@: %@", key, [pushNotification.customData objectForKey:key]]; + } + if (UIApplication.sharedApplication.applicationState == UIApplicationStateBackground) { + NSLog(@"%@ Notification received in background, title: \"%@\", message: \"%@\", custom data: \"%@\"", kPUPLogTag, + title, message, customData); + } else { + message = [NSString stringWithFormat:@"%@%@%@", (message ? message : @""), (message && customData ? @"\n" : @""), + (customData ? customData : @"")]; + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title + message:message + delegate:self + cancelButtonTitle:@"OK" + otherButtonTitles:nil]; + [alert show]; } - UIAlertView *alert = [[UIAlertView alloc] initWithTitle:pushNotification.title - message:message - delegate:self - cancelButtonTitle:@"OK" - otherButtonTitles:nil]; - [alert show]; } #pragma mark - MSAnalyticsDelegate - (void)analytics:(MSAnalytics *)analytics willSendEventLog:(MSEventLog *)eventLog { - [NSNotificationCenter.defaultCenter postNotificationName:kWillSendEventLog - object:[self msLogEventToLocal:eventLog]]; + [NSNotificationCenter.defaultCenter postNotificationName:kWillSendEventLog object:[self msLogEventToLocal:eventLog]]; } - (void)analytics:(MSAnalytics *)analytics didSucceedSendingEventLog:(MSEventLog *)eventLog { @@ -255,14 +260,14 @@ - (void)analytics:(MSAnalytics *)analytics didFailSendingEventLog:(MSEventLog *) object:[self msLogEventToLocal:eventLog]]; } -- (EventLog*) msLogEventToLocal:(MSEventLog*) msLog { +- (EventLog *)msLogEventToLocal:(MSEventLog *)msLog { EventLog *log = [EventLog new]; log.eventName = msLog.name; if (!msLog.properties) { return log; } - //Collect props + // Collect props for (NSString *key in msLog.properties) { NSString *value = [msLog.properties objectForKey:key]; [log.properties setObject:value forKey:key]; @@ -272,7 +277,7 @@ - (EventLog*) msLogEventToLocal:(MSEventLog*) msLog { #pragma mark - Public -+(UIViewController*)crashResultViewController { ++ (UIViewController *)crashResultViewController { return crashResultViewController; } diff --git a/Puppet/Puppet/Base.lproj/Main.storyboard b/Puppet/Puppet/Base.lproj/Main.storyboard index d2266a0018..5619abf80a 100644 --- a/Puppet/Puppet/Base.lproj/Main.storyboard +++ b/Puppet/Puppet/Base.lproj/Main.storyboard @@ -1,15 +1,67 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -29,7 +81,7 @@ + + @@ -820,7 +872,7 @@ - + @@ -855,7 +907,7 @@ - + @@ -878,7 +930,7 @@ - + @@ -911,7 +963,7 @@ - + @@ -964,7 +1016,7 @@ - + @@ -1021,7 +1073,7 @@ - + @@ -1065,7 +1117,7 @@ - + @@ -1112,7 +1164,7 @@ - + diff --git a/SasquatchMac/SasquatchMac/MobileCenterDelegate.swift b/SasquatchMac/SasquatchMac/MobileCenterDelegate.swift index 171ee26099..e0a8debf88 100644 --- a/SasquatchMac/SasquatchMac/MobileCenterDelegate.swift +++ b/SasquatchMac/SasquatchMac/MobileCenterDelegate.swift @@ -18,8 +18,10 @@ import Foundation // Modules section. func isAnalyticsEnabled() -> Bool func isCrashesEnabled() -> Bool + func isPushEnabled() -> Bool func setAnalyticsEnabled(_ isEnabled: Bool) func setCrashesEnabled(_ isEnabled: Bool) + func setPushEnabled(_ isEnabled: Bool) // MSAnalytics section. func trackEvent(_ eventName: String) diff --git a/SasquatchMac/SasquatchMac/PushViewController.swift b/SasquatchMac/SasquatchMac/PushViewController.swift new file mode 100644 index 0000000000..784ec334cb --- /dev/null +++ b/SasquatchMac/SasquatchMac/PushViewController.swift @@ -0,0 +1,22 @@ +import Cocoa + +class PushViewController: NSViewController { + + var mobileCenter: MobileCenterDelegate? + + @IBOutlet weak var setEnabledButton: NSButton! + + override func viewDidLoad() { + super.viewDidLoad(); + mobileCenter = MobileCenterProvider.shared().mobileCenter; + } + + override func viewWillAppear() { + setEnabledButton?.state = mobileCenter!.isPushEnabled() ? 1 : 0; + } + + @IBAction func setEnabled(_ sender: NSButton) { + mobileCenter?.setPushEnabled(sender.state == 1) + sender.state = mobileCenter!.isPushEnabled() ? 1 : 0 + } +} diff --git a/SasquatchMac/SasquatchMac/ViewControllers/AnalyticsViewController.swift b/SasquatchMac/SasquatchMac/ViewControllers/AnalyticsViewController.swift index ecc9eff616..8f3b236cdc 100644 --- a/SasquatchMac/SasquatchMac/ViewControllers/AnalyticsViewController.swift +++ b/SasquatchMac/SasquatchMac/ViewControllers/AnalyticsViewController.swift @@ -20,13 +20,16 @@ class AnalyticsViewController : NSViewController, NSTableViewDataSource, NSTable override func viewDidLoad() { super.viewDidLoad(); mobileCenter = MobileCenterProvider.shared().mobileCenter; - setEnabledButton?.state = mobileCenter!.isAnalyticsEnabled() ? 1 : 0; table?.delegate = self; table?.dataSource = self; NotificationCenter.default.addObserver(self, selector: #selector(self.editingDidBegin), name: .NSControlTextDidBeginEditing, object: nil); NotificationCenter.default.addObserver(self, selector: #selector(self.editingDidEnd), name: .NSControlTextDidEndEditing, object: nil); } + override func viewWillAppear() { + setEnabledButton?.state = mobileCenter!.isAnalyticsEnabled() ? 1 : 0; + } + override func viewDidDisappear() { super.viewDidDisappear(); NotificationCenter.default.removeObserver(self); diff --git a/SasquatchMac/SasquatchMac/ViewControllers/CrashesViewController.swift b/SasquatchMac/SasquatchMac/ViewControllers/CrashesViewController.swift index f647b818b4..8dfae4b3d0 100644 --- a/SasquatchMac/SasquatchMac/ViewControllers/CrashesViewController.swift +++ b/SasquatchMac/SasquatchMac/ViewControllers/CrashesViewController.swift @@ -13,7 +13,10 @@ class CrashesViewController : NSViewController, NSTableViewDataSource, NSTableVi crashesTableView.dataSource = self crashesTableView.delegate = self mobileCenter = MobileCenterProvider.shared().mobileCenter - setEnabledButton?.state = mobileCenter!.isCrashesEnabled() ? 1 : 0 + } + + override func viewWillAppear() { + setEnabledButton?.state = mobileCenter!.isCrashesEnabled() ? 1 : 0; } @IBAction func setEnabled(sender : NSButton) { diff --git a/SasquatchMac/SasquatchMacObjC/AppDelegate.m b/SasquatchMac/SasquatchMacObjC/AppDelegate.m index 5521c7d273..e2302dbae0 100644 --- a/SasquatchMac/SasquatchMacObjC/AppDelegate.m +++ b/SasquatchMac/SasquatchMacObjC/AppDelegate.m @@ -19,7 +19,7 @@ - (instancetype)init { [self setupPush]; // Start MobileCenter. - [MSMobileCenter start:@"8649b73e-6df0-4985-a039-8ab1453d44f3" + [MSMobileCenter start:@"4b3f7d94-c64b-4aac-94f5-894c55c64bfe" withServices:@[ [MSAnalytics class], [MSCrashes class], [MSPush class] ]]; [MobileCenterProvider shared].mobileCenter = [[MobileCenterDelegateObjC alloc] init]; return self; @@ -115,9 +115,10 @@ - (void)push:(MSPush *)push didReceivePushNotification:(MSPushNotification *)pus for (NSString *key in pushNotification.customData) { message = [NSString stringWithFormat:@"%@\n%@: %@", message, key, [pushNotification.customData objectForKey:key]]; } - MSAlertController *alertController = [MSAlertController alertControllerWithTitle:pushNotification.title - message:message - style:NSAlertStyleInformational]; + MSAlertController *alertController = [MSAlertController + alertControllerWithTitle:(pushNotification.title ? pushNotification.title : @"Push notification received") + message:message + style:NSAlertStyleInformational]; [alertController addActionWithTitle:@"OK" handler:^(){ }]; diff --git a/SasquatchMac/SasquatchMacObjC/MobileCenterDelegateObjC.m b/SasquatchMac/SasquatchMacObjC/MobileCenterDelegateObjC.m index 1101abf3cb..32d48919bf 100644 --- a/SasquatchMac/SasquatchMacObjC/MobileCenterDelegateObjC.m +++ b/SasquatchMac/SasquatchMacObjC/MobileCenterDelegateObjC.m @@ -3,6 +3,7 @@ @import MobileCenter; @import MobileCenterAnalytics; @import MobileCenterCrashes; +@import MobileCenterPush; /** * MobileCenterDelegate implementation in Objective C. @@ -40,12 +41,18 @@ - (BOOL)isAnalyticsEnabled { - (BOOL)isCrashesEnabled { return [MSCrashes isEnabled]; } +- (BOOL)isPushEnabled { + return [MSPush isEnabled]; +} - (void)setAnalyticsEnabled:(BOOL)isEnabled { return [MSAnalytics setEnabled:isEnabled]; } - (void)setCrashesEnabled:(BOOL)isEnabled { return [MSCrashes setEnabled:isEnabled]; } +- (void)setPushEnabled:(BOOL)isEnabled { + return [MSPush setEnabled:isEnabled]; +} #pragma mark - MSAnalytics section. - (void)trackEvent:(NSString *)eventName { diff --git a/SasquatchMac/SasquatchMacSwift/AppDelegate.swift b/SasquatchMac/SasquatchMacSwift/AppDelegate.swift index 0a6783ddc8..e03fb67067 100644 --- a/SasquatchMac/SasquatchMacSwift/AppDelegate.swift +++ b/SasquatchMac/SasquatchMacSwift/AppDelegate.swift @@ -37,7 +37,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, MSCrashesDelegate, MSPushDel // Start MobileCenter. MSMobileCenter.setLogLevel(MSLogLevel.verbose) MSMobileCenter.setLogUrl("https://in-integration.dev.avalanch.es") - MSMobileCenter.start("7ee5f412-02f7-45ea-a49c-b4ebf2911325", withServices : [ MSAnalytics.self, MSCrashes.self, MSPush.self ]) + MSMobileCenter.start("c62b8db6-191e-496a-b1a1-267b9bf326c4", withServices : [ MSAnalytics.self, MSCrashes.self, MSPush.self ]) MobileCenterProvider.shared().mobileCenter = MobileCenterDelegateSwift() } diff --git a/SasquatchMac/SasquatchMacSwift/MobileCenterDelegateSwift.swift b/SasquatchMac/SasquatchMacSwift/MobileCenterDelegateSwift.swift index 84b576567c..89b26f9b8c 100644 --- a/SasquatchMac/SasquatchMacSwift/MobileCenterDelegateSwift.swift +++ b/SasquatchMac/SasquatchMacSwift/MobileCenterDelegateSwift.swift @@ -1,6 +1,7 @@ import MobileCenter import MobileCenterAnalytics import MobileCenterCrashes +import MobileCenterPush /** * MobileCenterDelegate implementation in Swift. @@ -38,12 +39,18 @@ class MobileCenterDelegateSwift : MobileCenterDelegate { func isCrashesEnabled() -> Bool { return MSCrashes.isEnabled() } + func isPushEnabled() -> Bool { + return MSPush.isEnabled() + } func setAnalyticsEnabled(_ isEnabled: Bool) { MSAnalytics.setEnabled(isEnabled) } func setCrashesEnabled(_ isEnabled: Bool) { MSCrashes.setEnabled(isEnabled) } + func setPushEnabled(_ isEnabled: Bool) { + MSPush.setEnabled(isEnabled) + } //MARK: MSAnalytics section. func trackEvent(_ eventName: String) { diff --git a/SasquatchTV/SasquatchTVObjC/AppDelegate.m b/SasquatchTV/SasquatchTVObjC/AppDelegate.m index 9e11d65007..6d5fd56aec 100644 --- a/SasquatchTV/SasquatchTVObjC/AppDelegate.m +++ b/SasquatchTV/SasquatchTVObjC/AppDelegate.m @@ -16,7 +16,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( [MSMobileCenter setLogLevel:MSLogLevelVerbose]; [MSMobileCenter setLogUrl:@"https://in-integration.dev.avalanch.es"]; - [MSMobileCenter start:@"7ee5f412-02f7-45ea-a49c-b4ebf2911325" withServices:@[ [MSAnalytics class], [MSCrashes class] ]]; + [MSMobileCenter start:@"68065a02-edbb-4fc3-a323-3b8ca2beae80" withServices:@[ [MSAnalytics class], [MSCrashes class] ]]; [self crashes]; [self setMobileCenterDelegate]; return YES; diff --git a/SasquatchTV/SasquatchTVSwift/AppDelegate.swift b/SasquatchTV/SasquatchTVSwift/AppDelegate.swift index 5e2858bf19..5d820384bf 100644 --- a/SasquatchTV/SasquatchTVSwift/AppDelegate.swift +++ b/SasquatchTV/SasquatchTVSwift/AppDelegate.swift @@ -13,7 +13,7 @@ class AppDelegate : UIResponder, UIApplicationDelegate, MSCrashesDelegate { // Override point for customization after application launch. MSMobileCenter.setLogLevel(MSLogLevel.verbose); - MSMobileCenter.start("0dbca56b-b9ae-4d53-856a-7c2856137d85", withServices : [MSAnalytics.self, MSCrashes.self]); + MSMobileCenter.start("fbdeeff6-a549-4653-9f6d-f97dfe7f07b4", withServices : [MSAnalytics.self, MSCrashes.self]); // Crashes Delegate. MSCrashes.setDelegate(self)