diff --git a/Package.swift b/Package.swift index a8c1d0a..8ac6160 100644 --- a/Package.swift +++ b/Package.swift @@ -17,7 +17,7 @@ let package = Package( .upToNextMajor(from: "8.0.0")), .package(name: "Rokt-Widget", url: "https://github.com/ROKT/rokt-sdk-ios", - .upToNextMajor(from: "4.16.0")), + .upToNextMajor(from: "4.16.1")), ], targets: [ .target( diff --git a/mParticle-Rokt-Swift/MPRoktLayout.swift b/mParticle-Rokt-Swift/MPRoktLayout.swift index 9c4bf42..2246472 100644 --- a/mParticle-Rokt-Swift/MPRoktLayout.swift +++ b/mParticle-Rokt-Swift/MPRoktLayout.swift @@ -29,6 +29,12 @@ public class MPRoktLayout { config: RoktConfig? = nil, onEvent: ((RoktEvent) -> Void)? = nil ) { + // Capture the timestamp when the SwiftUI component is rendered + let options = PlacementOptions( + jointSdkSelectPlacements: Int64(Date().timeIntervalSince1970 * 1000), + dynamicPerformanceMarkers: [:] + ) + MPRoktLayout.mpLog("Initializing MPRoktLayout with arguments sdkTriggered:\(sdkTriggered.wrappedValue), viewName:\(viewName ?? "nil"), locationName:\(locationName), attributes:\(attributes)") confirmUser(attributes: attributes) { identifyCalled in let preparedAttributes = MPKitRokt.prepareAttributes(attributes, filteredUser: Optional.none, performMapping: true) @@ -43,6 +49,7 @@ public class MPRoktLayout { locationName: locationName, attributes: preparedAttributes, config: config, + placementOptions: options, onEvent: onEvent ) // The Binding variable provided by the client allows us to trigger a re-render of the UI but we only want to do this if the value was true to start diff --git a/mParticle-Rokt.podspec b/mParticle-Rokt.podspec index 7eafc3a..24f2a1d 100644 --- a/mParticle-Rokt.podspec +++ b/mParticle-Rokt.podspec @@ -21,7 +21,7 @@ Pod::Spec.new do |s| objc.source_files = 'mParticle-Rokt/*.{h,m}' objc.public_header_files = 'mParticle-Rokt/*.h' objc.dependency 'mParticle-Apple-SDK', '~> 8.0' - objc.dependency 'Rokt-Widget', '~> 4.15' + objc.dependency 'Rokt-Widget', '~> 4.16' end # Swift subspec @@ -29,7 +29,7 @@ Pod::Spec.new do |s| swift.source_files = 'mParticle-Rokt-Swift/*.swift' swift.dependency 'mParticle-Rokt/ObjC' swift.dependency 'mParticle-Apple-SDK', '~> 8.0' - swift.dependency 'Rokt-Widget', '~> 4.15' + swift.dependency 'Rokt-Widget', '~> 4.16' end # Default includes both diff --git a/mParticle-Rokt/MPKitRokt.h b/mParticle-Rokt/MPKitRokt.h index b4a3f30..798feb6 100644 --- a/mParticle-Rokt/MPKitRokt.h +++ b/mParticle-Rokt/MPKitRokt.h @@ -10,6 +10,8 @@ #import "mParticle_Apple_SDK-Swift.h" #endif +@class MPRoktPlacementOptions; + @interface MPKitRokt : NSObject @property (nonatomic, strong, nonnull) NSDictionary *configuration; diff --git a/mParticle-Rokt/MPKitRokt.m b/mParticle-Rokt/MPKitRokt.m index 50e5f18..935247f 100644 --- a/mParticle-Rokt/MPKitRokt.m +++ b/mParticle-Rokt/MPKitRokt.m @@ -120,8 +120,9 @@ - (MPKitExecStatus *)executeWithIdentifier:(NSString * _Nullable)identifier embeddedViews:(NSDictionary * _Nullable)embeddedViews config:(MPRoktConfig * _Nullable)mpRoktConfig callbacks:(MPRoktEventCallback * _Nullable)callbacks - filteredUser:(FilteredMParticleUser * _Nonnull)filteredUser { - [MPKitRokt MPLog:[NSString stringWithFormat:@"Rokt Kit recieved `executeWithIdentifier` method with the following arguments: \n identifier: %@ \n attributes: %@ \n embeddedViews: %@ \n config: %@ \n callbacks: %@ \n filteredUser identities: %@", identifier, attributes, embeddedViews, mpRoktConfig, callbacks, filteredUser.userIdentities]]; + filteredUser:(FilteredMParticleUser * _Nonnull)filteredUser + options:(MPRoktPlacementOptions * _Nullable)options { + [MPKitRokt MPLog:[NSString stringWithFormat:@"Rokt Kit recieved `executeWithIdentifier` method with the following arguments: \n identifier: %@ \n attributes: %@ \n embeddedViews: %@ \n config: %@ \n callbacks: %@ \n filteredUser identities: %@ \n options: %@", identifier, attributes, embeddedViews, mpRoktConfig, callbacks, filteredUser.userIdentities, options]]; NSDictionary *finalAtt = [MPKitRokt prepareAttributes:attributes filteredUser:filteredUser performMapping:NO]; // Log custom event for selectPlacements call @@ -130,11 +131,17 @@ - (MPKitExecStatus *)executeWithIdentifier:(NSString * _Nullable)identifier //Convert MPRoktConfig to RoktConfig RoktConfig *roktConfig = [MPKitRokt convertMPRoktConfig:mpRoktConfig]; NSDictionary *confirmedViews = [self confirmEmbeddedViews:embeddedViews]; + + PlacementOptions *placementOptions = [[PlacementOptions alloc] initWithJointSdkSelectPlacements:0 dynamicPerformanceMarkers:@{}]; + if (options) { + placementOptions = [[PlacementOptions alloc] initWithJointSdkSelectPlacements:options.jointSdkSelectPlacements dynamicPerformanceMarkers:@{}]; + } [Rokt executeWithViewName:identifier attributes:finalAtt placements:confirmedViews config:roktConfig + placementOptions:placementOptions onLoad:callbacks.onLoad onUnLoad:callbacks.onUnLoad onShouldShowLoadingIndicator:callbacks.onShouldShowLoadingIndicator diff --git a/mParticle_RoktTests/mParticle_RoktTests.m b/mParticle_RoktTests/mParticle_RoktTests.m index a72fcd4..ef362a5 100644 --- a/mParticle_RoktTests/mParticle_RoktTests.m +++ b/mParticle_RoktTests/mParticle_RoktTests.m @@ -14,7 +14,8 @@ - (MPKitExecStatus *)executeWithIdentifier:(NSString * _Nullable)identifier embeddedViews:(NSDictionary * _Nullable)embeddedViews config:(MPRoktConfig * _Nullable)mpRoktConfig callbacks:(MPRoktEventCallback * _Nullable)callbacks - filteredUser:(FilteredMParticleUser * _Nonnull)filteredUser; + filteredUser:(FilteredMParticleUser * _Nonnull)filteredUser + options:(MPRoktPlacementOptions * _Nullable)options; - (MPKitExecStatus *)setWrapperSdk:(MPWrapperSdk)wrapperSdk version:(nonnull NSString *)wrapperSdkVersion; - (MPKitExecStatus *)purchaseFinalized:(NSString *)placementId @@ -164,6 +165,7 @@ - (void)testExecuteWithIdentifier { }] placements:OCMOCK_ANY config:nil + placementOptions:OCMOCK_ANY onLoad:nil onUnLoad:nil onShouldShowLoadingIndicator:nil @@ -175,7 +177,8 @@ - (void)testExecuteWithIdentifier { embeddedViews:embeddedViews config:nil callbacks:nil - filteredUser:user]; + filteredUser:user + options:nil]; // Verify XCTAssertNotNil(status); @@ -201,6 +204,7 @@ - (void)testExecuteSandboxDetection { }] placements:OCMOCK_ANY config:nil + placementOptions:OCMOCK_ANY onLoad:nil onUnLoad:nil onShouldShowLoadingIndicator:nil @@ -212,7 +216,83 @@ - (void)testExecuteSandboxDetection { embeddedViews:embeddedViews config:nil callbacks:nil - filteredUser:user]; + filteredUser:user + options:nil]; + + // Verify + XCTAssertNotNil(status); + XCTAssertEqual(status.returnCode, MPKitReturnCodeSuccess); + OCMVerifyAll(mockRoktSDK); +} + +- (void)testExecuteWithIdentifierWithOptions { + id mockRoktSDK = OCMClassMock([Rokt class]); + + MPRoktEmbeddedView *view = [[MPRoktEmbeddedView alloc] init]; + NSString *identifier = @"TestView"; + NSDictionary *embeddedViews = @{@"placement1": view}; + NSDictionary *attributes = @{@"attr1": @"value1", @"sandbox": @"false"}; + FilteredMParticleUser *user = [[FilteredMParticleUser alloc] init]; + + // Create placement options with a custom timestamp value + MPRoktPlacementOptions *options = [[MPRoktPlacementOptions alloc] initWithTimestamp:42]; + + // Expect Rokt execute call and verify placementOptions carries the jointSdkSelectPlacements value + OCMExpect([mockRoktSDK executeWithViewName:identifier + attributes:OCMOCK_ANY + placements:OCMOCK_ANY + config:nil + placementOptions:[OCMArg checkWithBlock:^BOOL(PlacementOptions *opts) { + return opts != nil; + }] + onLoad:nil + onUnLoad:nil + onShouldShowLoadingIndicator:nil + onShouldHideLoadingIndicator:nil + onEmbeddedSizeChange:nil]); + + MPKitExecStatus *status = [self.kitInstance executeWithIdentifier:identifier + attributes:attributes + embeddedViews:embeddedViews + config:nil + callbacks:nil + filteredUser:user + options:options]; + + // Verify + XCTAssertNotNil(status); + XCTAssertEqual(status.returnCode, MPKitReturnCodeSuccess); + OCMVerifyAll(mockRoktSDK); +} + +- (void)testExecuteWithIdentifierNilOptionsCreatesDefaultPlacementOptions { + id mockRoktSDK = OCMClassMock([Rokt class]); + + NSString *identifier = @"TestView"; + NSDictionary *attributes = @{@"attr1": @"value1", @"sandbox": @"false"}; + FilteredMParticleUser *user = [[FilteredMParticleUser alloc] init]; + + // When options is nil, a default PlacementOptions with jointSdkSelectPlacements=0 should be created + OCMExpect([mockRoktSDK executeWithViewName:identifier + attributes:OCMOCK_ANY + placements:OCMOCK_ANY + config:nil + placementOptions:[OCMArg checkWithBlock:^BOOL(PlacementOptions *opts) { + return opts != nil; + }] + onLoad:nil + onUnLoad:nil + onShouldShowLoadingIndicator:nil + onShouldHideLoadingIndicator:nil + onEmbeddedSizeChange:nil]); + + MPKitExecStatus *status = [self.kitInstance executeWithIdentifier:identifier + attributes:attributes + embeddedViews:nil + config:nil + callbacks:nil + filteredUser:user + options:nil]; // Verify XCTAssertNotNil(status); @@ -783,6 +863,7 @@ - (void)testExecuteWithIdentifierLogsSelectPlacementEventWithPreparedAttributes attributes:OCMOCK_ANY placements:OCMOCK_ANY config:OCMOCK_ANY + placementOptions:OCMOCK_ANY onLoad:OCMOCK_ANY onUnLoad:OCMOCK_ANY onShouldShowLoadingIndicator:OCMOCK_ANY @@ -795,7 +876,8 @@ - (void)testExecuteWithIdentifierLogsSelectPlacementEventWithPreparedAttributes embeddedViews:nil config:nil callbacks:nil - filteredUser:user]; + filteredUser:user + options:nil]; // Verify that logEvent was called with the correct MPEvent object OCMVerifyAll(mockMParticleInstance); diff --git a/mParticle_RoktTests/mParticle_Rokt_SwiftTests.swift b/mParticle_RoktTests/mParticle_Rokt_SwiftTests.swift index 81e06d8..58bd5fd 100644 --- a/mParticle_RoktTests/mParticle_Rokt_SwiftTests.swift +++ b/mParticle_RoktTests/mParticle_Rokt_SwiftTests.swift @@ -284,7 +284,7 @@ struct mParticle_Rokt_SwiftTests { // Then #expect(true, "logSelectPlacementEvent should handle MParticle instance state gracefully") } - + // MARK: - Integration Tests @MainActor @available(iOS 15, *)