Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions UnitTests/ObjCTests/MPKitConfigurationTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ - (void)tearDown {

- (void)testInstance {
XCTAssertNotNil(kitConfiguration);
XCTAssertEqualObjects(kitConfiguration.configurationHash, @(762651950));
XCTAssertEqualObjects(kitConfiguration.configurationHash, @(969680750));
XCTAssertEqualObjects(kitConfiguration.integrationId, @37);
XCTAssertEqualObjects(kitConfiguration.attributeValueFilteringHashedAttribute, @"12345");
XCTAssertEqualObjects(kitConfiguration.attributeValueFilteringHashedValue, @"54321");
Expand Down Expand Up @@ -183,7 +183,7 @@ - (void)testInvalidConfiguration {

kitConfig = [[MPKitConfiguration alloc] initWithDictionary:configuration];
XCTAssertNotNil(kitConfig);
XCTAssertEqualObjects(kitConfig.configurationHash, @(1495473349));
XCTAssertEqualObjects(kitConfig.configurationHash, @(-1872513399));
XCTAssertEqualObjects(kitConfig.integrationId, @80);

XCTAssertNil(kitConfig.filters);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@

#if MPARTICLE_LOCATION_DISABLE
import mParticle_Apple_SDK_NoLocation
#else
import mParticle_Apple_SDK
#endif
import XCTest

final class MParticleSceneDelegateTests: MParticleTestBase {

// MARK: - Properties

var testURL: URL!
var testUserActivity: NSUserActivity!

override func setUp() {
super.setUp()
testURL = URL(string: "myapp://test/path?param=value")!
testUserActivity = NSUserActivity(activityType: "com.test.activity")
testUserActivity.title = "Test Activity"
testUserActivity.userInfo = ["key": "value"]

// The implementation calls [MParticle sharedInstance], so we need to set the mock on the shared instance
MParticle.sharedInstance().appNotificationHandler = appNotificationHandler

// Reset mock state for each test
appNotificationHandler.continueUserActivityCalled = false
appNotificationHandler.continueUserActivityUserActivityParam = nil
appNotificationHandler.continueUserActivityRestorationHandlerParam = nil
appNotificationHandler.openURLWithOptionsCalled = false
appNotificationHandler.openURLWithOptionsURLParam = nil
appNotificationHandler.openURLWithOptionsOptionsParam = nil
}

// MARK: - handleUserActivity Tests

func test_handleUserActivity_invokesAppNotificationHandler() {
// Act
mparticle.handleUserActivity(testUserActivity)

// Assert - handleUserActivity directly calls the app notification handler
XCTAssertTrue(appNotificationHandler.continueUserActivityCalled)
XCTAssertEqual(appNotificationHandler.continueUserActivityUserActivityParam, testUserActivity)
XCTAssertNotNil(appNotificationHandler.continueUserActivityRestorationHandlerParam)
}

func test_handleUserActivity_withWebBrowsingActivity() {
// Arrange
let webActivity = NSUserActivity(activityType: NSUserActivityTypeBrowsingWeb)
webActivity.title = "Web Page"
webActivity.webpageURL = URL(string: "https://example.com/page")

// Act
mparticle.handleUserActivity(webActivity)

// Assert - Direct call to app notification handler
XCTAssertTrue(appNotificationHandler.continueUserActivityCalled)
XCTAssertEqual(appNotificationHandler.continueUserActivityUserActivityParam, webActivity)
}

func test_handleUserActivity_restorationHandlerIsEmpty() {
// Act
mparticle.handleUserActivity(testUserActivity)

// Assert
XCTAssertTrue(appNotificationHandler.continueUserActivityCalled)

// Verify the restoration handler is provided and safe to call
let restorationHandler = appNotificationHandler.continueUserActivityRestorationHandlerParam
XCTAssertNotNil(restorationHandler)

// Test that calling the restoration handler doesn't crash
XCTAssertNoThrow(restorationHandler?(nil))
XCTAssertNoThrow(restorationHandler?([]))
}
}
14 changes: 14 additions & 0 deletions mParticle-Apple-SDK.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,8 @@
7E0387842DB913D2003B7D5E /* MPRokt.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E0387802DB913D2003B7D5E /* MPRokt.m */; };
7E15B2062D94617900C1FF3E /* MPRoktTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E15B2052D94617900C1FF3E /* MPRoktTests.m */; };
7E15B2072D94617900C1FF3E /* MPRoktTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E15B2052D94617900C1FF3E /* MPRoktTests.m */; };
7E573D2A2ECB65D90087185D /* MParticleSceneDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E573D292ECB65D90087185D /* MParticleSceneDelegateTests.swift */; };
7E573D2B2ECB65D90087185D /* MParticleSceneDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E573D292ECB65D90087185D /* MParticleSceneDelegateTests.swift */; };
B31360F92E012760000DFBC9 /* MPRoktEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = B31360F72E012760000DFBC9 /* MPRoktEvent.swift */; };
B3D778622E02F55F00D887A4 /* MPRoktEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = B31360F72E012760000DFBC9 /* MPRoktEvent.swift */; };
D30CD0CB2CFF5FB100F5148A /* MPStateMachine.h in Headers */ = {isa = PBXBuildFile; fileRef = 53A79B0629CDFB1F00E7489F /* MPStateMachine.h */; settings = {ATTRIBUTES = (Public, ); }; };
Expand Down Expand Up @@ -923,6 +925,7 @@
7E03877F2DB913D2003B7D5E /* MPRokt.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPRokt.h; sourceTree = "<group>"; };
7E0387802DB913D2003B7D5E /* MPRokt.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPRokt.m; sourceTree = "<group>"; };
7E15B2052D94617900C1FF3E /* MPRoktTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPRoktTests.m; sourceTree = "<group>"; };
7E573D292ECB65D90087185D /* MParticleSceneDelegateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MParticleSceneDelegateTests.swift; sourceTree = "<group>"; };
B31360F72E012760000DFBC9 /* MPRoktEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPRoktEvent.swift; sourceTree = "<group>"; };
D33C8B312B8510C20012EDFD /* MPAudience.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPAudience.h; sourceTree = "<group>"; };
D33C8B322B8510C20012EDFD /* MPAudience.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPAudience.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1504,6 +1507,7 @@
isa = PBXGroup;
children = (
7231B8342EB95F9F001565E5 /* MParticleBreadcrumbTests.swift */,
7E573D292ECB65D90087185D /* MParticleSceneDelegateTests.swift */,
7231B83E2EB9627D001565E5 /* MParticleErrorTests.swift */,
7231B83B2EB961F2001565E5 /* MParticleLTVTests.swift */,
7231B84B2EB963B3001565E5 /* MParticleKitBatchTests.swift */,
Expand Down Expand Up @@ -1910,6 +1914,7 @@
534CD26929CE2CE1008452B3 /* MPSurrogateAppDelegateTests.m in Sources */,
534CD26A29CE2CE1008452B3 /* MPGDPRConsentTests.m in Sources */,
356D4A5A2E58B09D00CB69FE /* SettingsProviderMock.swift in Sources */,
7E573D2A2ECB65D90087185D /* MParticleSceneDelegateTests.swift in Sources */,
534CD26B29CE2CE1008452B3 /* MPBackendControllerTests.m in Sources */,
534CD26C29CE2CE1008452B3 /* MPKitConfigurationTests.mm in Sources */,
7231B80E2EB3C4AC001565E5 /* MPKitMock.swift in Sources */,
Expand Down Expand Up @@ -2129,6 +2134,7 @@
53A79CD129CE019F00E7489F /* MPSurrogateAppDelegateTests.m in Sources */,
53A79CE729CE019F00E7489F /* MPGDPRConsentTests.m in Sources */,
356D4A592E58B09D00CB69FE /* SettingsProviderMock.swift in Sources */,
7E573D2B2ECB65D90087185D /* MParticleSceneDelegateTests.swift in Sources */,
53A79CD229CE019F00E7489F /* MPBackendControllerTests.m in Sources */,
53A79CF329CE019F00E7489F /* MPKitConfigurationTests.mm in Sources */,
7231B80D2EB3C4AC001565E5 /* MPKitMock.swift in Sources */,
Expand Down Expand Up @@ -2362,6 +2368,7 @@
"$(inherited)",
);
INFOPLIST_FILE = ./UnitTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.mparticle.mParticle-Apple-SDK-NoLocationTests";
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All @@ -2374,6 +2381,7 @@
SWIFT_OBJC_BRIDGING_HEADER = "./UnitTests/mParticle_iOS_SDKTests-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,3";
TVOS_DEPLOYMENT_TARGET = 15.6;
};
name = Debug;
};
Expand All @@ -2390,6 +2398,7 @@
"$(inherited)",
);
INFOPLIST_FILE = ./UnitTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.mparticle.mParticle-Apple-SDK-NoLocationTests";
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All @@ -2402,6 +2411,7 @@
SWIFT_OBJC_BRIDGING_HEADER = "./UnitTests/mParticle_iOS_SDKTests-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,3";
TVOS_DEPLOYMENT_TARGET = 15.6;
};
name = Release;
};
Expand Down Expand Up @@ -2619,6 +2629,7 @@
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = DLD43Y3TRP;
INFOPLIST_FILE = ./UnitTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.mparticle.mParticle-Apple-SDKTests";
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All @@ -2630,6 +2641,7 @@
SWIFT_OBJC_BRIDGING_HEADER = "./UnitTests/mParticle_iOS_SDKTests-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,3";
TVOS_DEPLOYMENT_TARGET = 15.6;
};
name = Debug;
};
Expand All @@ -2642,6 +2654,7 @@
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = DLD43Y3TRP;
INFOPLIST_FILE = ./UnitTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.mparticle.mParticle-Apple-SDKTests";
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All @@ -2653,6 +2666,7 @@
SWIFT_OBJC_BRIDGING_HEADER = "./UnitTests/mParticle_iOS_SDKTests-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,3";
TVOS_DEPLOYMENT_TARGET = 15.6;
};
name = Release;
};
Expand Down
31 changes: 28 additions & 3 deletions mParticle-Apple-SDK/Include/mParticle.h
Original file line number Diff line number Diff line change
Expand Up @@ -754,32 +754,57 @@ Defaults to false. Prevents the eventsHost above from overwriting the alias endp
#endif

/**
DEPRECATED: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/application(_:open:sourceapplication:annotation:)
Use a UIScene lifecycle, mParticle's handleURLContext: method, and scene(_:openURLContexts:) from UISceneDelegate instead.

Informs the mParticle SDK the app has been asked to open a resource identified by a URL.
This method should be called only if proxiedAppDelegate is disabled.
@param url The URL resource to open
@param sourceApplication The bundle ID of the requesting app
@param annotation A property list object supplied by the source app
@see proxiedAppDelegate
*/
- (void)openURL:(NSURL *)url sourceApplication:(nullable NSString *)sourceApplication annotation:(nullable id)annotation;
- (void)openURL:(NSURL *)url sourceApplication:(nullable NSString *)sourceApplication annotation:(nullable id)annotation DEPRECATED_MSG_ATTRIBUTE("iOS 27 will no longer support this protocol method");

/**
DEPRECATED: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/application(_:open:sourceapplication:annotation:)
Use a UIScene lifecycle, mParticle's handleURLContext: method, and scene(_:openURLContexts:) from UISceneDelegate instead.

Informs the mParticle SDK the app has been asked to open a resource identified by a URL.
This method should be called only if proxiedAppDelegate is disabled. This method is only available for iOS 9 and above.
@param url The URL resource to open
@param options The dictionary of launch options
@see proxiedAppDelegate
*/
- (void)openURL:(NSURL *)url options:(nullable NSDictionary *)options;
- (void)openURL:(NSURL *)url options:(nullable NSDictionary *)options DEPRECATED_MSG_ATTRIBUTE("iOS 27 will no longer support this protocol method");

/**
DEPRECATED: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/application(_:continue:restorationhandler:)
Use UIScene lifecycle, mParticle's handleUserActivity: method, and scene(_:continue:) from UISceneDelegate instead.

Informs the mParticle SDK the app has been asked to open to continue an NSUserActivity.
This method should be called only if proxiedAppDelegate is disabled. This method is only available for iOS 9 and above.
@param userActivity The NSUserActivity that caused the app to be opened
@param restorationHandler A block to execute if your app creates objects to perform the task.
@see proxiedAppDelegate
*/
- (BOOL)continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(void(^ _Nonnull)(NSArray<id<UIUserActivityRestoring>> * __nullable restorableObjects))restorationHandler;
- (BOOL)continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(void(^ _Nonnull)(NSArray<id<UIUserActivityRestoring>> * __nullable restorableObjects))restorationHandler DEPRECATED_MSG_ATTRIBUTE("iOS 27 will no longer support this protocol method");

/**
Informs the mParticle SDK the app has been asked to open a resource identified by a URL.
This method should be called only if proxiedAppDelegate is disabled. This method is only available for iOS 13 and above.
@param urlContext The UIOpenURLContext provided by the SceneDelegate
@see proxiedAppDelegate
*/
- (void)handleURLContext:(UIOpenURLContext *)urlContext NS_SWIFT_NAME(handleURLContext(_:)) API_AVAILABLE(ios(13.0));

/**
Informs the mParticle SDK the app has been asked to open to continue an NSUserActivity.
This method should be called only if proxiedAppDelegate is disabled.
@param userActivity The NSUserActivity that caused the app to be opened
@see proxiedAppDelegate
*/
- (void)handleUserActivity:(NSUserActivity *)userActivity NS_SWIFT_NAME(handleUserActivity(_:));

/**
DEPRECATED: This method will permanently remove ALL MParticle data from the device, including MParticle UserDefaults and Database, it will also halt any further upload or download behavior that may be prepared
Expand Down
11 changes: 5 additions & 6 deletions mParticle-Apple-SDK/Kits/MPKitConfiguration.mm
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ - (instancetype)initWithDictionary:(NSDictionary *)configurationDictionary {
return nil;
}

NSData *ekConfigData = [NSJSONSerialization dataWithJSONObject:configurationDictionary options:0 error:nil];
NSJSONWritingOptions options = 0;
if (@available(iOS 11.0, tvOS 11.0, *)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we need this check? Our min iOS version is 15.6

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current minimum deployment target is 9.0 though I do plan on upping that in future PRs/tickets. Would you like me to bring that into this ticket? @denischilik

options = NSJSONWritingSortedKeys;
}
NSData *ekConfigData = [NSJSONSerialization dataWithJSONObject:configurationDictionary options:options error:nil];
NSString *ekConfigString = [[NSString alloc] initWithData:ekConfigData encoding:NSUTF8StringEncoding];
_configurationHash = @([[MPIHasher hashString:ekConfigString] intValue]);

Expand Down Expand Up @@ -114,16 +118,11 @@ - (id)initWithCoder:(NSCoder *)coder {
@try {
configurationDictionary = [coder decodeObjectOfClass:[NSDictionary class] forKey:@"configurationDictionary"];
}

@catch ( NSException *e) {
configurationDictionary = nil;
MPILogError(@"Exception decoding MPKitConfiguration Attributes: %@", [e reason]);
}

@finally {
self = [self initWithDictionary:configurationDictionary];
}

self = [self initWithDictionary:configurationDictionary];
if (!self) {
return nil;
Expand Down
40 changes: 40 additions & 0 deletions mParticle-Apple-SDK/mParticle.m
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,46 @@ - (BOOL)continueUserActivity:(nonnull NSUserActivity *)userActivity restorationH
return [self.appNotificationHandler continueUserActivity:userActivity restorationHandler:restorationHandler];
}

- (void)handleURLContext:(UIOpenURLContext *)urlContext API_AVAILABLE(ios(13.0)){
NSString *messageURL = [NSString stringWithFormat:@"Opening URLContext URL: %@", urlContext.URL];
[logger debug:messageURL];
NSString *messageSource = [NSString stringWithFormat:@"Source: %@", urlContext.options.sourceApplication ? urlContext.options.sourceApplication : @"unknown"];
[logger debug:messageSource];
NSString *messageAnnotation = [NSString stringWithFormat:@"Annotation: %@", urlContext.options.annotation];
[logger debug:messageAnnotation];
#if TARGET_OS_IOS == 1
if (@available(iOS 14.5, *)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we also don't need this check

NSString *messageEventAttribution = [NSString stringWithFormat:@"Event Attribution: %@", urlContext.options.eventAttribution];
[logger debug:messageEventAttribution];
}
#endif
NSString *messageOpenInPlace = [NSString stringWithFormat:@"Open in place: %@", urlContext.options.openInPlace ? @"True" : @"False"];
[logger debug:messageOpenInPlace];

// Currently only one kit integration uses this dictionary for this key
// https://github.com/mparticle-integrations/mparticle-apple-integration-flurry/blob/a0856e271aa9a63a6668805582395dea63f96af5/mParticle-Flurry/MPKitFlurry.m#L148C38-L148C85
NSDictionary *options = @{@"UIApplicationOpenURLOptionsSourceApplicationKey": urlContext.options.sourceApplication};

[self.appNotificationHandler openURL:urlContext.URL options:options];
}

- (void)handleUserActivity:(NSUserActivity *)userActivity {
NSString *message = [NSString stringWithFormat:@"User Activity Received"];
[logger debug:message];
NSString *messageType = [NSString stringWithFormat:@"User Activity Type: %@", userActivity.activityType];
[logger debug:messageType];
NSString *messageTitle = [NSString stringWithFormat:@"User Activity Title: %@", userActivity.title];
[logger debug:messageTitle];
NSString *messageUserInfo = [NSString stringWithFormat:@"User Activity User Info: %@", userActivity.userInfo];
[logger debug:messageUserInfo];
if (userActivity.activityType == NSUserActivityTypeBrowsingWeb) {
NSString *messageURL = [NSString stringWithFormat:@"Opening UserActivity URL: %@", userActivity.webpageURL];
[logger debug:messageURL];
}
// When provided by the SceneDelegate NSUserActivity is not paired with a restorationHandler
[self.appNotificationHandler continueUserActivity: userActivity restorationHandler:^(NSArray<id<UIUserActivityRestoring>> * _Nullable restorableObjects) {}];
}

- (void)reset:(void (^)(void))completion {
[executor executeOnMessage:^{
[self.kitContainer flushSerializedKits];
Expand Down
Loading