diff --git a/Scripts/release.sh b/Scripts/release.sh index db423d2dd..ab40c3e2c 100755 --- a/Scripts/release.sh +++ b/Scripts/release.sh @@ -7,6 +7,7 @@ NOTES="$2" # Update constant in codebase sed -i '' 's/NSString \*const kMParticleSDKVersion = @".*/NSString *const kMParticleSDKVersion = @"'"$VERSION"'";/' mParticle-Apple-SDK/MPIConstants.m +sed -i '' 's/let kMParticleSDKVersion = ".*/let kMParticleSDKVersion = "'"$VERSION"'"/' mParticle-Apple-SDK/MPConstants.swift # Update framework plist file /usr/libexec/PlistBuddy -c "Set CFBundleShortVersionString $VERSION" Framework/Info.plist diff --git a/UnitTests/MPURLRequestBuilderTests.m b/UnitTests/MPURLRequestBuilderTests.m index fe5db4a10..4275a1c92 100644 --- a/UnitTests/MPURLRequestBuilderTests.m +++ b/UnitTests/MPURLRequestBuilderTests.m @@ -1,5 +1,6 @@ #import #import +#import "mParticle.h" #import "MPURLRequestBuilder.h" #import "MPStateMachine.h" #import "MPIConstants.h" @@ -14,18 +15,18 @@ #import "MPMessage.h" #import "MPBaseTestCase.h" #import "MPKitConfiguration.h" -#import "MParticleWebView.h" #import "MPExtensionProtocol.h" #import "MPURL.h" #import "MPUpload.h" -#import "mParticle.h" +#import "MParticleSwift.h" + @interface MParticle () + (dispatch_queue_t)messageQueue; @property (nonatomic, strong) MPStateMachine_PRIVATE *stateMachine; @property (nonatomic, strong) MPKitContainer_PRIVATE *kitContainer_PRIVATE; -@property (nonatomic, strong) MParticleWebView *webView; +@property (nonatomic, strong) MParticleWebView_PRIVATE *webView; @end @@ -370,7 +371,7 @@ - (void)testInvalidURLs { - (void)testEventRequest { MParticle *sharedInstance = [MParticle sharedInstance]; - MParticleWebView *webview = sharedInstance.webView; + MParticleWebView_PRIVATE *webview = sharedInstance.webView; NSString *agent = @"Example resolved agent"; id mockWebView = OCMPartialMock(webview); diff --git a/UnitTests/MParticleTests.m b/UnitTests/MParticleTests.m index 7b85ddf6f..6a15fb74c 100644 --- a/UnitTests/MParticleTests.m +++ b/UnitTests/MParticleTests.m @@ -6,7 +6,6 @@ #import "MPSession.h" #import "MPBackendController.h" #import "MPURLRequestBuilder.h" -#import "MParticleWebView.h" #import "MPPersistenceController.h" #import "MPIUserDefaults.h" #import "MPURL.h" @@ -26,7 +25,7 @@ + (dispatch_queue_t)messageQueue; @property (nonatomic, strong) MParticleOptions *options; - (BOOL)isValidBridgeName:(NSString *)bridgeName; - (void)handleWebviewCommand:(NSString *)command dictionary:(NSDictionary *)dictionary; -@property (nonatomic, strong) MParticleWebView *webView; +@property (nonatomic, strong) MParticleWebView_PRIVATE *webView; @end @interface MParticleUser () @@ -980,7 +979,7 @@ - (void)testSetATTStatusRemoveIDFA { } - (void)testUserAgentDefault { - id mockWebView = OCMClassMock([MParticleWebView class]); + id mockWebView = OCMClassMock([MParticleWebView_PRIVATE class]); #if TARGET_OS_IOS == 1 [[[mockWebView stub] andReturn:@"Example resolved agent"] userAgent]; #else @@ -1003,7 +1002,7 @@ - (void)testUserAgentDefault { - (void)testUserAgentCustom { NSString *customAgent = @"Foo 1.2.3 Like Bar"; - id mockWebView = OCMClassMock([MParticleWebView class]); + id mockWebView = OCMClassMock([MParticleWebView_PRIVATE class]); [[[mockWebView stub] andReturn:customAgent] userAgent]; id mockMParticle = OCMPartialMock([MParticle sharedInstance]); [[[mockMParticle stub] andReturn:mockWebView] webView]; diff --git a/UnitTests/MParticleWebViewTests.m b/UnitTests/MParticleWebViewTests.m index 177eb387c..31a6a6e8b 100644 --- a/UnitTests/MParticleWebViewTests.m +++ b/UnitTests/MParticleWebViewTests.m @@ -1,41 +1,29 @@ #import #import -#import "MParticleWebView.h" #import "mParticle.h" #import "MPApplication.h" +#import "MParticleSwift.h" #import -@interface MPApplication_PRIVATE () -+ (void)setMockApplication:(id)mockApplication; -@end - -@interface MParticleWebView () - -- (void)evaluateAgent; -- (BOOL)canAndShouldCollect; - +@interface MParticleWebView_PRIVATE () @property (nonatomic) NSDate *initializedDate; -@property (nonatomic) NSString *resolvedAgent; +@property (nonatomic) NSString *resolvedUserAgent; @property (nonatomic, assign) BOOL isCollecting; -@property (nonatomic, assign) int retryCount; - #if TARGET_OS_IOS == 1 @property (nonatomic) WKWebView *webView; #endif - @end @interface MParticleWebViewTests : XCTestCase - -@property (nonatomic, strong) MParticleWebView *webView; - +@property (nonatomic, strong) MParticleWebView_PRIVATE *webView; @end @implementation MParticleWebViewTests - (void)setUp { // Put setup code here. This method is called before the invocation of each test method in the class. - _webView = [[MParticleWebView alloc] init]; + dispatch_queue_t messageQueue = dispatch_queue_create("com.mparticle.messageQueue", DISPATCH_QUEUE_SERIAL); + _webView = [[MParticleWebView_PRIVATE alloc] initWithMessageQueue:messageQueue]; } - (void)tearDown { @@ -48,62 +36,51 @@ - (void)testInit { } - (void)testUserAgentCustom { - [_webView startWithCustomUserAgent:@"Test User Agent" shouldCollect:NO defaultAgentOverride:nil]; + [_webView startWithCustomUserAgent:@"Test User Agent" shouldCollect:NO defaultUserAgentOverride:nil]; XCTAssertEqualObjects(_webView.userAgent, @"Test User Agent"); } - (void)testUserAgentDisabled { - [_webView startWithCustomUserAgent:nil shouldCollect:NO defaultAgentOverride:nil]; + [_webView startWithCustomUserAgent:nil shouldCollect:NO defaultUserAgentOverride:nil]; NSString *defaultAgent = [NSString stringWithFormat:@"mParticle Apple SDK/%@", MParticle.sharedInstance.version]; XCTAssertEqualObjects(_webView.userAgent, defaultAgent); } - (void)testUserAgentDefaultOverride { - [_webView startWithCustomUserAgent:nil shouldCollect:NO defaultAgentOverride:@"Test User Agent"]; + [_webView startWithCustomUserAgent:nil shouldCollect:NO defaultUserAgentOverride:@"Test User Agent"]; NSString *defaultAgent = [NSString stringWithFormat:@"mParticle Apple SDK/%@", MParticle.sharedInstance.version]; XCTAssertNotEqualObjects(_webView.userAgent, defaultAgent); XCTAssertEqualObjects(_webView.userAgent, @"Test User Agent"); } -- (void)testUserAgentCapture { - MParticleWebView *mockWebView = OCMPartialMock(_webView); -#if TARGET_OS_IOS == 1 - [[(id)mockWebView expect] evaluateAgent]; -#else - [[(id)mockWebView reject] evaluateAgent]; -#endif - [mockWebView startWithCustomUserAgent:nil shouldCollect:YES defaultAgentOverride:nil]; - [(id)mockWebView verify]; -} - - (void)testShouldCollectResolved { - _webView.resolvedAgent = @"Test User Agent"; + _webView.resolvedUserAgent = @"Test User Agent"; XCTAssertFalse([_webView shouldDelayUpload:5]); } - (void)testShouldCollectPending { - _webView.resolvedAgent = nil; + _webView.resolvedUserAgent = nil; _webView.isCollecting = YES; _webView.initializedDate = [NSDate date]; XCTAssertTrue([_webView shouldDelayUpload:5]); } - (void)testShouldCollectNoDate { - _webView.resolvedAgent = nil; + _webView.resolvedUserAgent = nil; _webView.isCollecting = YES; _webView.initializedDate = nil; XCTAssertFalse([_webView shouldDelayUpload:5]); } - (void)testShouldCollectTooLong { - _webView.resolvedAgent = nil; + _webView.resolvedUserAgent = nil; _webView.isCollecting = YES; _webView.initializedDate = [NSDate dateWithTimeIntervalSinceNow:-6]; XCTAssertFalse([_webView shouldDelayUpload:5]); } - (void)testShouldCollectTimeLeft { - _webView.resolvedAgent = nil; + _webView.resolvedUserAgent = nil; _webView.isCollecting = YES; _webView.initializedDate = [NSDate dateWithTimeIntervalSinceNow:-4]; XCTAssertTrue([_webView shouldDelayUpload:5]); @@ -111,21 +88,7 @@ - (void)testShouldCollectTimeLeft { - (void)testOriginalDefaultAgent { NSString *defaultAgent = [NSString stringWithFormat:@"mParticle Apple SDK/%@", MParticle.sharedInstance.version]; - XCTAssertEqualObjects(_webView.originalDefaultAgent, defaultAgent); -} - -- (void)testBackgroundCollection { - id mockApplication = OCMClassMock([UIApplication class]); - OCMStub([mockApplication applicationState]).andReturn(UIApplicationStateBackground); - [MPApplication_PRIVATE setMockApplication:mockApplication]; - MParticleWebView *mockWebView = OCMPartialMock(_webView); -#if TARGET_OS_IOS == 1 - [[(id)mockWebView expect] evaluateAgent]; -#else - [[(id)mockWebView reject] evaluateAgent]; -#endif - [mockWebView startWithCustomUserAgent:nil shouldCollect:YES defaultAgentOverride:nil]; - [(id)mockWebView verify]; + XCTAssertEqualObjects(_webView.originalDefaultUserAgent, defaultAgent); } @end diff --git a/mParticle-Apple-SDK.xcodeproj/project.pbxproj b/mParticle-Apple-SDK.xcodeproj/project.pbxproj index 489eb8ab8..75043e4ec 100644 --- a/mParticle-Apple-SDK.xcodeproj/project.pbxproj +++ b/mParticle-Apple-SDK.xcodeproj/project.pbxproj @@ -19,6 +19,8 @@ 531BCF372B28A23400F5C573 /* MPIdentityCaching.m in Sources */ = {isa = PBXBuildFile; fileRef = 531BCF332B28A23400F5C573 /* MPIdentityCaching.m */; }; 531BCF3A2B28A83E00F5C573 /* MPIdentityCachingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 531BCF392B28A83E00F5C573 /* MPIdentityCachingTests.m */; }; 531BCF3B2B28A83E00F5C573 /* MPIdentityCachingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 531BCF392B28A83E00F5C573 /* MPIdentityCachingTests.m */; }; + 534C11B32D08A73700466F71 /* MParticleWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 534C11B22D08A73100466F71 /* MParticleWebView.swift */; }; + 534C11B42D08A73700466F71 /* MParticleWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 534C11B22D08A73100466F71 /* MParticleWebView.swift */; }; 534C11B62D08F53D00466F71 /* MPUserAttributeChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 534C11B52D08F53D00466F71 /* MPUserAttributeChange.swift */; }; 534C11B72D08F53D00466F71 /* MPUserAttributeChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 534C11B52D08F53D00466F71 /* MPUserAttributeChange.swift */; }; 534CD25C29CE2877008452B3 /* NSNumber+MPFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A79B2629CDFB1F00E7489F /* NSNumber+MPFormatter.swift */; }; @@ -160,7 +162,6 @@ 53A79BD229CDFB2000E7489F /* MPApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 53A79B0D29CDFB1F00E7489F /* MPApplication.m */; }; 53A79BD729CDFB2000E7489F /* MPUploadBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 53A79B1229CDFB1F00E7489F /* MPUploadBuilder.m */; }; 53A79BDA29CDFB2000E7489F /* MPDevice.m in Sources */ = {isa = PBXBuildFile; fileRef = 53A79B1529CDFB1F00E7489F /* MPDevice.m */; }; - 53A79BDB29CDFB2000E7489F /* MParticleWebView.h in Headers */ = {isa = PBXBuildFile; fileRef = 53A79B1629CDFB1F00E7489F /* MParticleWebView.h */; }; 53A79BDC29CDFB2000E7489F /* MPLaunchInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 53A79B1729CDFB1F00E7489F /* MPLaunchInfo.h */; }; 53A79BDF29CDFB2000E7489F /* MPMessageBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = 53A79B1A29CDFB1F00E7489F /* MPMessageBuilder.h */; }; 53A79BE229CDFB2000E7489F /* MPIUserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = 53A79B1D29CDFB1F00E7489F /* MPIUserDefaults.m */; }; @@ -169,7 +170,6 @@ 53A79BE929CDFB2000E7489F /* MPApplication.h in Headers */ = {isa = PBXBuildFile; fileRef = 53A79B2429CDFB1F00E7489F /* MPApplication.h */; settings = {ATTRIBUTES = (Public, ); }; }; 53A79BEB29CDFB2000E7489F /* NSNumber+MPFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A79B2629CDFB1F00E7489F /* NSNumber+MPFormatter.swift */; }; 53A79BEE29CDFB2000E7489F /* MPLaunchInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 53A79B2929CDFB1F00E7489F /* MPLaunchInfo.m */; }; - 53A79BEF29CDFB2000E7489F /* MParticleWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = 53A79B2A29CDFB1F00E7489F /* MParticleWebView.m */; }; 53A79BF029CDFB2000E7489F /* MPDevice.h in Headers */ = {isa = PBXBuildFile; fileRef = 53A79B2B29CDFB1F00E7489F /* MPDevice.h */; }; 53A79BF129CDFB2000E7489F /* MPBracket.h in Headers */ = {isa = PBXBuildFile; fileRef = 53A79B2C29CDFB1F00E7489F /* MPBracket.h */; }; 53A79BF329CDFB2000E7489F /* MPCustomModulePreference.h in Headers */ = {isa = PBXBuildFile; fileRef = 53A79B2F29CDFB1F00E7489F /* MPCustomModulePreference.h */; }; @@ -346,7 +346,6 @@ 53A79D2829CE23F700E7489F /* MPCustomModulePreference.h in Headers */ = {isa = PBXBuildFile; fileRef = 53A79B2F29CDFB1F00E7489F /* MPCustomModulePreference.h */; }; 53A79D2929CE23F700E7489F /* MPDataModelProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 53A79AC229CDFB1E00E7489F /* MPDataModelProtocol.h */; }; 53A79D2A29CE23F700E7489F /* MPAppDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 53A79B3929CDFB1F00E7489F /* MPAppDelegateProxy.h */; }; - 53A79D2B29CE23F700E7489F /* MParticleWebView.h in Headers */ = {isa = PBXBuildFile; fileRef = 53A79B1629CDFB1F00E7489F /* MParticleWebView.h */; }; 53A79D2C29CE23F700E7489F /* MPIConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 53A79ACF29CDFB1F00E7489F /* MPIConstants.h */; }; 53A79D2E29CE23F700E7489F /* MPBackendController.h in Headers */ = {isa = PBXBuildFile; fileRef = 53A79AA329CDFB1E00E7489F /* MPBackendController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 53A79D2F29CE23F700E7489F /* MPKitConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 53A79B5F29CDFB1F00E7489F /* MPKitConfiguration.h */; }; @@ -398,7 +397,6 @@ 53A79D6729CE23F700E7489F /* MPForwardQueueParameters.m in Sources */ = {isa = PBXBuildFile; fileRef = 53A79B5129CDFB1F00E7489F /* MPForwardQueueParameters.m */; }; 53A79D6829CE23F700E7489F /* MPDevice.m in Sources */ = {isa = PBXBuildFile; fileRef = 53A79B1529CDFB1F00E7489F /* MPDevice.m */; }; 53A79D6929CE23F700E7489F /* NSDictionary+MPCaseInsensitive.m in Sources */ = {isa = PBXBuildFile; fileRef = 53A79B0929CDFB1F00E7489F /* NSDictionary+MPCaseInsensitive.m */; }; - 53A79D6B29CE23F700E7489F /* MParticleWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = 53A79B2A29CDFB1F00E7489F /* MParticleWebView.m */; }; 53A79D6C29CE23F700E7489F /* MPURLRequestBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 53A79AB229CDFB1E00E7489F /* MPURLRequestBuilder.m */; }; 53A79D6D29CE23F700E7489F /* MPCustomModulePreference.m in Sources */ = {isa = PBXBuildFile; fileRef = 53A79B3129CDFB1F00E7489F /* MPCustomModulePreference.m */; }; 53A79D6E29CE23F700E7489F /* FilteredMPIdentityApiRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 53A79A9B29CDFB1E00E7489F /* FilteredMPIdentityApiRequest.m */; }; @@ -527,6 +525,7 @@ 531BCF322B28A23400F5C573 /* MPIdentityCaching.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPIdentityCaching.h; sourceTree = ""; }; 531BCF332B28A23400F5C573 /* MPIdentityCaching.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPIdentityCaching.m; sourceTree = ""; }; 531BCF392B28A83E00F5C573 /* MPIdentityCachingTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPIdentityCachingTests.m; sourceTree = ""; }; + 534C11B22D08A73100466F71 /* MParticleWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MParticleWebView.swift; sourceTree = ""; }; 534C11B52D08F53D00466F71 /* MPUserAttributeChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPUserAttributeChange.swift; sourceTree = ""; }; 534CD25D29CE2BF1008452B3 /* MParticleSwift.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MParticleSwift.h; sourceTree = ""; }; 534CD2AA29CE2CE1008452B3 /* mParticle-Apple-SDK-NoLocationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "mParticle-Apple-SDK-NoLocationTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -600,7 +599,6 @@ 53A79B0D29CDFB1F00E7489F /* MPApplication.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPApplication.m; sourceTree = ""; }; 53A79B1229CDFB1F00E7489F /* MPUploadBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUploadBuilder.m; sourceTree = ""; }; 53A79B1529CDFB1F00E7489F /* MPDevice.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPDevice.m; sourceTree = ""; }; - 53A79B1629CDFB1F00E7489F /* MParticleWebView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MParticleWebView.h; sourceTree = ""; }; 53A79B1729CDFB1F00E7489F /* MPLaunchInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPLaunchInfo.h; sourceTree = ""; }; 53A79B1A29CDFB1F00E7489F /* MPMessageBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPMessageBuilder.h; sourceTree = ""; }; 53A79B1D29CDFB1F00E7489F /* MPIUserDefaults.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPIUserDefaults.m; sourceTree = ""; }; @@ -609,7 +607,6 @@ 53A79B2429CDFB1F00E7489F /* MPApplication.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPApplication.h; sourceTree = ""; }; 53A79B2629CDFB1F00E7489F /* NSNumber+MPFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSNumber+MPFormatter.swift"; sourceTree = ""; }; 53A79B2929CDFB1F00E7489F /* MPLaunchInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPLaunchInfo.m; sourceTree = ""; }; - 53A79B2A29CDFB1F00E7489F /* MParticleWebView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MParticleWebView.m; sourceTree = ""; }; 53A79B2B29CDFB1F00E7489F /* MPDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPDevice.h; sourceTree = ""; }; 53A79B2C29CDFB1F00E7489F /* MPBracket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPBracket.h; sourceTree = ""; }; 53A79B2F29CDFB1F00E7489F /* MPCustomModulePreference.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPCustomModulePreference.h; sourceTree = ""; }; @@ -999,6 +996,7 @@ 53A79B0129CDFB1F00E7489F /* Utils */ = { isa = PBXGroup; children = ( + 534C11B22D08A73100466F71 /* MParticleWebView.swift */, 5399DDB72CA727E1006526E1 /* MPZip.swift */, 53A79B0229CDFB1F00E7489F /* MPMessageBuilder.m */, D3961CD52CB468DC003B3194 /* NSArray+MPCaseInsensitive.swift */, @@ -1011,7 +1009,6 @@ D3961CDD2CC0B7E4003B3194 /* NSString+MPPercentEscape.swift */, 53A79B1229CDFB1F00E7489F /* MPUploadBuilder.m */, 53A79B1529CDFB1F00E7489F /* MPDevice.m */, - 53A79B1629CDFB1F00E7489F /* MParticleWebView.h */, 53A79B1729CDFB1F00E7489F /* MPLaunchInfo.h */, 53A79B1A29CDFB1F00E7489F /* MPMessageBuilder.h */, 53A79B1D29CDFB1F00E7489F /* MPIUserDefaults.m */, @@ -1019,7 +1016,6 @@ 53A79B2329CDFB1F00E7489F /* MPUploadBuilder.h */, 53A79B2629CDFB1F00E7489F /* NSNumber+MPFormatter.swift */, 53A79B2929CDFB1F00E7489F /* MPLaunchInfo.m */, - 53A79B2A29CDFB1F00E7489F /* MParticleWebView.m */, 53A79B2B29CDFB1F00E7489F /* MPDevice.h */, 53A79B2C29CDFB1F00E7489F /* MPBracket.h */, 53FDD1BC2AE871AF003D5FA1 /* MPIHasher.swift */, @@ -1323,7 +1319,6 @@ 53A79BF329CDFB2000E7489F /* MPCustomModulePreference.h in Headers */, 53A79B8C29CDFB2000E7489F /* MPDataModelProtocol.h in Headers */, 53A79BFB29CDFB2100E7489F /* MPAppDelegateProxy.h in Headers */, - 53A79BDB29CDFB2000E7489F /* MParticleWebView.h in Headers */, 53A79B9929CDFB2000E7489F /* MPIConstants.h in Headers */, 53A79B7129CDFB2000E7489F /* MPBackendController.h in Headers */, 53A79C1D29CDFB2100E7489F /* MPKitConfiguration.h in Headers */, @@ -1415,7 +1410,6 @@ 53A79D2829CE23F700E7489F /* MPCustomModulePreference.h in Headers */, 53A79D2929CE23F700E7489F /* MPDataModelProtocol.h in Headers */, 53A79D2A29CE23F700E7489F /* MPAppDelegateProxy.h in Headers */, - 53A79D2B29CE23F700E7489F /* MParticleWebView.h in Headers */, 53A79D2C29CE23F700E7489F /* MPIConstants.h in Headers */, 53A79D2E29CE23F700E7489F /* MPBackendController.h in Headers */, 53A79D2F29CE23F700E7489F /* MPKitConfiguration.h in Headers */, @@ -1701,7 +1695,6 @@ 53A79C0F29CDFB2100E7489F /* MPForwardQueueParameters.m in Sources */, 53A79BDA29CDFB2000E7489F /* MPDevice.m in Sources */, 53A79BCE29CDFB2000E7489F /* NSDictionary+MPCaseInsensitive.m in Sources */, - 53A79BEF29CDFB2000E7489F /* MParticleWebView.m in Sources */, 53A79B7E29CDFB2000E7489F /* MPURLRequestBuilder.m in Sources */, 53A79BF529CDFB2000E7489F /* MPCustomModulePreference.m in Sources */, 530D24852CFF78EB000FE7E3 /* MPUserIdentityChange.swift in Sources */, @@ -1742,6 +1735,7 @@ 53A79B8429CDFB2000E7489F /* MPDatabaseMigrationController.m in Sources */, 53A79BC029CDFB2000E7489F /* MPConsentKitFilter.m in Sources */, 53A79C0A29CDFB2100E7489F /* MPKitRegister.m in Sources */, + 534C11B42D08A73700466F71 /* MParticleWebView.swift in Sources */, 53A79B9429CDFB2000E7489F /* MPForwardRecord.m in Sources */, 53A79B7A29CDFB2000E7489F /* MPConnector.m in Sources */, 53A79B8B29CDFB2000E7489F /* MPBreadcrumb.m in Sources */, @@ -1871,7 +1865,6 @@ 53A79D6729CE23F700E7489F /* MPForwardQueueParameters.m in Sources */, 53A79D6829CE23F700E7489F /* MPDevice.m in Sources */, 53A79D6929CE23F700E7489F /* NSDictionary+MPCaseInsensitive.m in Sources */, - 53A79D6B29CE23F700E7489F /* MParticleWebView.m in Sources */, 53A79D6C29CE23F700E7489F /* MPURLRequestBuilder.m in Sources */, 53A79D6D29CE23F700E7489F /* MPCustomModulePreference.m in Sources */, 530D24862CFF78EB000FE7E3 /* MPUserIdentityChange.swift in Sources */, @@ -1912,6 +1905,7 @@ D3CEDAC42C9DB0E0001B32DF /* MPDateFormatter.swift in Sources */, 53A79D9029CE23F700E7489F /* MPDatabaseMigrationController.m in Sources */, 53A79D9129CE23F700E7489F /* MPConsentKitFilter.m in Sources */, + 534C11B32D08A73700466F71 /* MParticleWebView.swift in Sources */, 53A79D9229CE23F700E7489F /* MPKitRegister.m in Sources */, 53A79D9329CE23F700E7489F /* MPForwardRecord.m in Sources */, 53A79D9429CE23F700E7489F /* MPConnector.m in Sources */, diff --git a/mParticle-Apple-SDK/Logger/MPLogger.swift b/mParticle-Apple-SDK/Logger/MPLogger.swift index 1bd34cf1a..b99fa524c 100644 --- a/mParticle-Apple-SDK/Logger/MPLogger.swift +++ b/mParticle-Apple-SDK/Logger/MPLogger.swift @@ -7,32 +7,32 @@ import Foundation -public struct MPLogger { +public struct MPLog { private static func MPLogger(loggerLevel: MPILogLevel, format: String, arguments: any CVarArg...) { if (MParticle.sharedInstance().logLevel.rawValue >= loggerLevel.rawValue && loggerLevel != .none) { - let msg = String.localizedStringWithFormat(format, arguments) + let msg = String.localizedStringWithFormat("mParticle -> \(format)", arguments) if let customLogger = MParticle.sharedInstance().customLogger { customLogger(msg) } else { - NSLog("%@", msg) + NSLog(msg) } } } - public static func MPLogError(format: String, arguments: any CVarArg...) { + public static func error(_ format: String, _ arguments: any CVarArg...) { MPLogger(loggerLevel: .error, format: format, arguments: arguments) } - public static func MPLogWarning(format: String, arguments: any CVarArg...) { + public static func warning(_ format: String, _ arguments: any CVarArg...) { MPLogger(loggerLevel: .warning, format: format, arguments: arguments) } - public static func MPLogDebug(format: String, arguments: any CVarArg...) { + public static func debug(_ format: String, _ arguments: any CVarArg...) { MPLogger(loggerLevel: .debug, format: format, arguments: arguments) } - public static func MPLogVerbose(format: String, arguments: any CVarArg...) { + public static func verbose(_ format: String, _ arguments: any CVarArg...) { MPLogger(loggerLevel: .verbose, format: format, arguments: arguments) } } diff --git a/mParticle-Apple-SDK/MPBackendController.m b/mParticle-Apple-SDK/MPBackendController.m index 6ffea3e4f..f3910b443 100644 --- a/mParticle-Apple-SDK/MPBackendController.m +++ b/mParticle-Apple-SDK/MPBackendController.m @@ -23,7 +23,6 @@ #import "MPKitContainer.h" #import "MPURLRequestBuilder.h" #import "MPListenerController.h" -#import "MParticleWebView.h" #import "MPDevice.h" #import "MPIdentityCaching.h" #import "MParticleSwift.h" @@ -54,7 +53,7 @@ @interface MParticle () @property (nonatomic, strong) MPPersistenceController *persistenceController; @property (nonatomic, strong) MPStateMachine_PRIVATE *stateMachine; @property (nonatomic, strong) MPKitContainer_PRIVATE *kitContainer_PRIVATE; -@property (nonatomic, strong) MParticleWebView *webView; +@property (nonatomic, strong) MParticleWebView_PRIVATE *webView; @property (nonatomic, strong, nullable) NSString *dataPlanId; @property (nonatomic, strong, nullable) NSNumber *dataPlanVersion; + (dispatch_queue_t)messageQueue; diff --git a/mParticle-Apple-SDK/MPConstants.swift b/mParticle-Apple-SDK/MPConstants.swift index 142203d39..ba3fc0f3f 100644 --- a/mParticle-Apple-SDK/MPConstants.swift +++ b/mParticle-Apple-SDK/MPConstants.swift @@ -14,6 +14,9 @@ func MPMilliseconds(timestamp: Double) -> Double { // NOTE: I kept the same naming here for clarity, but we should rename these // after we remove them from the MPIConstants.h file + +let kMParticleSDKVersion = "8.27.4" + struct MessageKeys { static let kMPMessagesKey = "msgs" static let kMPMessageIdKey = "id" diff --git a/mParticle-Apple-SDK/Network/MPURLRequestBuilder.m b/mParticle-Apple-SDK/Network/MPURLRequestBuilder.m index 9aa91924e..cca4ffa24 100644 --- a/mParticle-Apple-SDK/Network/MPURLRequestBuilder.m +++ b/mParticle-Apple-SDK/Network/MPURLRequestBuilder.m @@ -7,16 +7,16 @@ #import "MPExtensionProtocol.h" #import "MPILogger.h" #import "MPApplication.h" -#import "MParticleWebView.h" #import "MPURL.h" #import "mParticle.h" +#import "MParticleSwift.h" static NSDateFormatter *RFC1123DateFormatter; @interface MParticle () @property (nonatomic, strong, readonly) MPStateMachine_PRIVATE *stateMachine; -@property (nonatomic, strong, readonly) MParticleWebView *webView; +@property (nonatomic, strong, readonly) MParticleWebView_PRIVATE *webView; @end @@ -81,7 +81,7 @@ - (NSString *)hmacSha256Encode:(NSString *const)message key:(NSString *const)key - (NSString *)userAgent { BOOL isConfig = [[_url.defaultURL relativePath] rangeOfString:@"/config"].location != NSNotFound; if (isConfig) { - return MParticle.sharedInstance.webView.originalDefaultAgent; + return MParticle.sharedInstance.webView.originalDefaultUserAgent; } return MParticle.sharedInstance.webView.userAgent; } diff --git a/mParticle-Apple-SDK/Utils/MPConvertJS.swift b/mParticle-Apple-SDK/Utils/MPConvertJS.swift index 9053b10c9..5a8d5e156 100644 --- a/mParticle-Apple-SDK/Utils/MPConvertJS.swift +++ b/mParticle-Apple-SDK/Utils/MPConvertJS.swift @@ -46,14 +46,14 @@ import Foundation case MPJSCommerceEventAction.removeFromWishlist.rawValue: return .removeFromWishlist default: - MPLogger.MPLogError(format: "Invalid commerce event action received from webview: %@", arguments: json) + MPLog.error("Invalid commerce event action received from webview: %@", json) return .addToCart } } @objc public static func commerceEvent(_ json: [AnyHashable : Any]) -> MPCommerceEvent? { guard json["ProductAction"] == nil || json["ProductAction"] is [String: Any] else { - MPLogger.MPLogError(format: "Unexpected commerce event data received from webview") + MPLog.error("Unexpected commerce event data received from webview") return nil } @@ -64,14 +64,14 @@ import Foundation let isValid = isProductAction || isPromotion || isImpression if !isValid { - MPLogger.MPLogError(format: "Invalid commerce event dictionary received from webview: %@", arguments: json) + MPLog.error("Invalid commerce event dictionary received from webview: %@", json) return nil } let commerceEvent: MPCommerceEvent if isProductAction { guard let productActionType = productAction?["ProductActionType"] as? NSNumber else { - MPLogger.MPLogError(format: "Unexpected product action type received from webview") + MPLog.error("Unexpected product action type received from webview") return nil } let action = Self.commerceEventAction(productActionType) @@ -136,12 +136,12 @@ import Foundation @objc public static func promotionContainer(_ json: [AnyHashable : Any]) -> MPPromotionContainer? { guard let promotionDictionary = (json["PromotionAction"] as? [String: Any]) else { - MPLogger.MPLogError(format: "Unexpected promotion container action data received from webview") + MPLog.error("Unexpected promotion container action data received from webview") return nil } guard let promotionActionTypeNumber = (promotionDictionary["PromotionActionType"] as? NSNumber) else { - MPLogger.MPLogError(format: "Unexpected promotion container action type data received from webview") + MPLog.error("Unexpected promotion container action type data received from webview") return nil } @@ -152,7 +152,7 @@ import Foundation promotionContainer.addPromotion(MPConvertJS_PRIVATE.promotion(jsonObject)) } } else { - MPLogger.MPLogError(format: "Unexpected promotion container list data received from webview") + MPLog.error("Unexpected promotion container list data received from webview") return nil } @@ -252,7 +252,7 @@ import Foundation let request = MPIdentityApiRequest.withEmptyUser() guard let userIdentities = json?["UserIdentities"] as? [[AnyHashable : Any]] else { - MPLogger.MPLogError(format: "Unexpected user identity data received from webview") + MPLog.error("Unexpected user identity data received from webview") return nil } diff --git a/mParticle-Apple-SDK/Utils/MPIHasher.swift b/mParticle-Apple-SDK/Utils/MPIHasher.swift index 8fc350cfb..6e887931c 100644 --- a/mParticle-Apple-SDK/Utils/MPIHasher.swift +++ b/mParticle-Apple-SDK/Utils/MPIHasher.swift @@ -25,7 +25,7 @@ import Foundation let lowercaseStringToHash = stringToHash.lowercased() guard let dataToHash = lowercaseStringToHash.data(using: .utf8) else { - MPLogger.MPLogWarning(format: "Hash String Failed. Could not encode string as data") + MPLog.warning("Hash String Failed. Could not encode string as data") return "" } @@ -39,7 +39,7 @@ import Foundation @objc public class func hashStringUTF16(_ stringToHash: String) -> String { guard let data = stringToHash.data(using: .utf16LittleEndian) else { - MPLogger.MPLogWarning(format: "Hash String UTF16 Failed. Could not encode string as data") + MPLog.warning("Hash String UTF16 Failed. Could not encode string as data") return "" } let hash = hashFNV1a(data) diff --git a/mParticle-Apple-SDK/Utils/MParticleWebView.h b/mParticle-Apple-SDK/Utils/MParticleWebView.h deleted file mode 100644 index 03db283fe..000000000 --- a/mParticle-Apple-SDK/Utils/MParticleWebView.h +++ /dev/null @@ -1,15 +0,0 @@ -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface MParticleWebView : NSObject - -- (void)startWithCustomUserAgent:(nullable NSString *)customUserAgent shouldCollect:(BOOL)shouldCollect defaultAgentOverride:(nullable NSString *)defaultAgent; -- (BOOL)shouldDelayUpload:(NSTimeInterval)maxWaitTime; -- (nullable NSString *)userAgent; -- (nullable NSString *)originalDefaultAgent; - -@end - -NS_ASSUME_NONNULL_END diff --git a/mParticle-Apple-SDK/Utils/MParticleWebView.m b/mParticle-Apple-SDK/Utils/MParticleWebView.m deleted file mode 100644 index eac17a8b3..000000000 --- a/mParticle-Apple-SDK/Utils/MParticleWebView.m +++ /dev/null @@ -1,130 +0,0 @@ -#import "MParticleWebView.h" -#import "MPILogger.h" -#import "mParticle.h" -#import "MPStateMachine.h" -#import "MPApplication.h" - -@interface MParticle () -+ (dispatch_queue_t)messageQueue; -@end - -@interface MParticleWebView () - -// options -@property (nonatomic) NSString *customAgent; -@property (nonatomic) BOOL shouldCollect; -@property (nonatomic) NSString *defaultAgent; - -@property (nonatomic) NSDate *initializedDate; -@property (nonatomic) NSString *resolvedAgent; // final result -@property (nonatomic) BOOL isCollecting; -@property (nonatomic) int retryCount; - -#if TARGET_OS_IOS == 1 -@property (nonatomic) WKWebView *webView; -#endif - -@end - -@implementation MParticleWebView - -- (void)startWithCustomUserAgent:(nullable NSString *)customUserAgent shouldCollect:(BOOL)shouldCollect defaultAgentOverride:(nullable NSString *)defaultAgent { - self.initializedDate = [NSDate date]; - self.customAgent = customUserAgent; - self.shouldCollect = shouldCollect; - self.defaultAgent = defaultAgent ?: [self originalDefaultAgent]; - self.retryCount = 0; - [self startCollectionIfNecessary]; -} - -- (void)startCollectionIfNecessary { - if (self.customAgent != nil) { - self.resolvedAgent = self.customAgent; - } else if (![self canAndShouldCollect]) { - self.resolvedAgent = self.defaultAgent; - } - if (self.resolvedAgent != nil) { - return; - } - [self evaluateAgent]; -} - -- (BOOL)canAndShouldCollect { -#if TARGET_OS_IOS != 1 - return NO; -#else - return self.shouldCollect; -#endif -} - -- (void)evaluateAgent { -#if TARGET_OS_IOS == 1 - dispatch_async([MParticle messageQueue], ^{ - self.isCollecting = YES; - dispatch_async(dispatch_get_main_queue(), ^{ - if (!self.webView) { - self.webView = [[WKWebView alloc] initWithFrame:CGRectZero]; - } - MPILogVerbose(@"Getting user agent"); - [self.webView evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id _Nullable result, NSError * _Nullable error) { - if (result == nil || error != nil) { - MPILogVerbose(@"Error collecting user agent: %@", error); - } - if (result == nil) { - if (self.retryCount < 10) { - self.retryCount += 1; - MPILogVerbose(@"User agent collection failed (count=%@), retrying", @(self.retryCount)); - self.webView = nil; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [self evaluateAgent]; - }); - return; - } else { - MPILogVerbose(@"Falling back on default user agent"); - self.resolvedAgent = self.defaultAgent; - } - } else { - MPILogVerbose(@"Finished getting user agent"); - self.resolvedAgent = result; - } - self.webView = nil; - dispatch_async([MParticle messageQueue], ^{ - self.isCollecting = NO; - }); - }]; - }); - }); -#endif -} - -- (BOOL)shouldDelayUpload:(NSTimeInterval)maxWaitTime { - if (self.resolvedAgent || !self.isCollecting || !self.initializedDate) { - return NO; - } - - NSTimeInterval timeInterval = -1 * [self.initializedDate timeIntervalSinceNow]; - if (timeInterval > maxWaitTime) { - static BOOL printedMessage = NO; - if (!printedMessage) { - printedMessage = YES; - MPILogDebug(@"Max wait time exceeded for user agent"); - } - return NO; - } - static BOOL printedMessageDelay = NO; - if (!printedMessageDelay) { - printedMessageDelay = YES; - MPILogVerbose(@"Delaying initial upload for user agent"); - } - return YES; -} - -- (nullable NSString *)userAgent { - return self.resolvedAgent ?: self.defaultAgent; -} - -- (nullable NSString *)originalDefaultAgent { - return [NSString stringWithFormat:@"mParticle Apple SDK/%@", MParticle.sharedInstance.version]; -} - -@end diff --git a/mParticle-Apple-SDK/Utils/MParticleWebView.swift b/mParticle-Apple-SDK/Utils/MParticleWebView.swift new file mode 100644 index 000000000..7c6e0fe59 --- /dev/null +++ b/mParticle-Apple-SDK/Utils/MParticleWebView.swift @@ -0,0 +1,122 @@ +// +// MParticleWebView.swift +// mParticle-Apple-SDK +// +// Created by Ben Baron on 12/10/24. +// + +// NOTE: @objc specifier added to private properties to support existing Obj-C unit tests + +@objc public class MParticleWebView_PRIVATE : NSObject { + + @objc public var userAgent: String? { resolvedUserAgent ?? defaultUserAgent } + @objc public var originalDefaultUserAgent: String? { "mParticle Apple SDK/\(kMParticleSDKVersion)" } + + private var messageQueue: DispatchQueue + + private var customUserAgent: String? = nil + private var shouldCollect = false + private var defaultUserAgent: String? = nil + + @objc private var initializedDate: Date? = nil + @objc private var resolvedUserAgent: String? = nil // final result + @objc private var isCollecting: Bool = false + @objc private var retryCount: Int = 0 + +#if os(iOS) + @objc private var webView: WKWebView? = nil +#endif + + @objc public init(messageQueue: DispatchQueue) { + self.messageQueue = messageQueue + super.init() + } + + @objc public func start(customUserAgent: String?, shouldCollect: Bool, defaultUserAgentOverride: String?) { + self.initializedDate = Date() + self.customUserAgent = customUserAgent +#if os(iOS) + self.shouldCollect = shouldCollect +#endif + self.defaultUserAgent = defaultUserAgentOverride ?? originalDefaultUserAgent + self.retryCount = 0 + startCollectionIfNecessary() + } + + private func startCollectionIfNecessary() { + if let customUserAgent = customUserAgent { + resolvedUserAgent = customUserAgent; + } else if !shouldCollect { + resolvedUserAgent = defaultUserAgent; + } + + if let _ = resolvedUserAgent { + return + } + +#if os(iOS) + evaluateAgent() +#endif + } + +#if os(iOS) + private func evaluateAgent() { + messageQueue.async { + self.isCollecting = true + DispatchQueue.main.async { + if self.webView == nil { + self.webView = WKWebView(frame: .zero) + } + MPLog.verbose("Getting user agent") + self.webView?.evaluateJavaScript("navigator.userAgent") { result, error in + if result == nil, let error = error as? NSError { + MPLog.verbose("Error collecting user agent: %@", error) + } + if let result = result as? String { + MPLog.verbose("Finished getting user agent") + self.resolvedUserAgent = result + } else { + if self.retryCount < 10 { + self.retryCount += 1 + MPLog.verbose("User agent collection failed (count=%@), retrying", self.retryCount) + self.webView = nil + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + self.evaluateAgent() + } + return + } else { + MPLog.verbose("Falling back on default user agent") + self.resolvedUserAgent = self.defaultUserAgent + } + } + self.webView = nil + self.messageQueue.async { + self.isCollecting = false + } + } + } + } + } +#endif + + private var printedMessage = false + private var printedMessageDelay = false + @objc public func shouldDelayUpload(_ maxWaitTime: TimeInterval) -> Bool { + guard let initializedDate = initializedDate, resolvedUserAgent == nil, isCollecting else { + return false + } + + if -initializedDate.timeIntervalSinceNow > maxWaitTime { + if !printedMessage { + printedMessage = true + MPLog.debug("Max wait time exceeded for user agent") + } + return false + } + if !printedMessageDelay { + printedMessageDelay = true + MPLog.verbose("Delaying initial upload for user agent") + } + return true + } +} diff --git a/mParticle-Apple-SDK/mParticle.m b/mParticle-Apple-SDK/mParticle.m index 13aab4d06..5b3c37879 100644 --- a/mParticle-Apple-SDK/mParticle.m +++ b/mParticle-Apple-SDK/mParticle.m @@ -14,11 +14,10 @@ #import "MPSession.h" #import "MPIUserDefaults.h" #import "MPIdentityApi.h" -#import "MParticleWebView.h" #import "MPDataPlanFilter.h" -#import "MParticleSwift.h" #import "MPUpload.h" #import "MPKitContainer.h" +#import "MParticleSwift.h" static dispatch_queue_t messageQueue = nil; static void *messageQueueKey = "mparticle message queue key"; @@ -64,7 +63,7 @@ @interface MParticle()