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
16 changes: 13 additions & 3 deletions mParticle-Rokt.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@

/* Begin PBXBuildFile section */
2502325C2D7A7BF3004794A2 /* Rokt-Widget in Frameworks */ = {isa = PBXBuildFile; productRef = 2502325B2D7A7BF3004794A2 /* Rokt-Widget */; };
7E084C862E0C4B3A0098059B /* MPRoktLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E084C842E0C4B340098059B /* MPRoktLayout.swift */; };
7E084C8A2E12C4D30098059B /* mParticle_Rokt_SwiftTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E084C892E12C4D30098059B /* mParticle_Rokt_SwiftTests.swift */; };
7E15B20B2D9AE82600C1FF3E /* Rokt-Widget in Frameworks */ = {isa = PBXBuildFile; productRef = 7E15B20A2D9AE82600C1FF3E /* Rokt-Widget */; };
7EDDAAB02E05A88E00D089CF /* mParticle-Apple-SDK in Frameworks */ = {isa = PBXBuildFile; productRef = 7EDDAAAF2E05A88E00D089CF /* mParticle-Apple-SDK */; };
7EDDAAB22E05A89B00D089CF /* mParticle-Apple-SDK in Frameworks */ = {isa = PBXBuildFile; productRef = 7EDDAAB12E05A89B00D089CF /* mParticle-Apple-SDK */; };
7EE7F13E2DA95BEE006C5440 /* OCMock in Frameworks */ = {isa = PBXBuildFile; productRef = 7EE7F13D2DA95BEE006C5440 /* OCMock */; };
B34CE55A2E04356F00712DE1 /* MPRoktEventMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3D778512E02845700D887A4 /* MPRoktEventMapper.swift */; };
B3D778532E02845700D887A4 /* MPRoktEventMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3D778512E02845700D887A4 /* MPRoktEventMapper.swift */; };
DBB01A601DC1478A00A7B188 /* mParticle_Rokt.h in Headers */ = {isa = PBXBuildFile; fileRef = DBB01A5E1DC1478A00A7B188 /* mParticle_Rokt.h */; settings = {ATTRIBUTES = (Public, ); }; };
DBB01A681DC1480700A7B188 /* MPKitRokt.h in Headers */ = {isa = PBXBuildFile; fileRef = DBB01A661DC1480700A7B188 /* MPKitRokt.h */; };
DBB01A681DC1480700A7B188 /* MPKitRokt.h in Headers */ = {isa = PBXBuildFile; fileRef = DBB01A661DC1480700A7B188 /* MPKitRokt.h */; settings = {ATTRIBUTES = (Public, ); }; };
DBB01A691DC1480700A7B188 /* MPKitRokt.m in Sources */ = {isa = PBXBuildFile; fileRef = DBB01A671DC1480700A7B188 /* MPKitRokt.m */; };
FF0BB63E217A84E800B0556C /* mParticle_RoktTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FF0BB63D217A84E800B0556C /* mParticle_RoktTests.m */; };
FF0BB640217A84E800B0556C /* mParticle_Rokt.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DBB01A5B1DC1478A00A7B188 /* mParticle_Rokt.framework */; };
Expand All @@ -32,6 +33,9 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
7E084C842E0C4B340098059B /* MPRoktLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPRoktLayout.swift; sourceTree = "<group>"; };
7E084C882E12C4D30098059B /* mParticle_RoktTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "mParticle_RoktTests-Bridging-Header.h"; sourceTree = "<group>"; };
7E084C892E12C4D30098059B /* mParticle_Rokt_SwiftTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = mParticle_Rokt_SwiftTests.swift; sourceTree = "<group>"; };
B3D778512E02845700D887A4 /* MPRoktEventMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPRoktEventMapper.swift; sourceTree = "<group>"; };
DBB01A5B1DC1478A00A7B188 /* mParticle_Rokt.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = mParticle_Rokt.framework; sourceTree = BUILT_PRODUCTS_DIR; };
DBB01A5E1DC1478A00A7B188 /* mParticle_Rokt.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mParticle_Rokt.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -89,6 +93,7 @@
DBB01A5D1DC1478A00A7B188 /* mParticle-Rokt */ = {
isa = PBXGroup;
children = (
7E084C842E0C4B340098059B /* MPRoktLayout.swift */,
B3D778512E02845700D887A4 /* MPRoktEventMapper.swift */,
DBB01A661DC1480700A7B188 /* MPKitRokt.h */,
DBB01A671DC1480700A7B188 /* MPKitRokt.m */,
Expand All @@ -102,7 +107,9 @@
isa = PBXGroup;
children = (
FF0BB63D217A84E800B0556C /* mParticle_RoktTests.m */,
7E084C892E12C4D30098059B /* mParticle_Rokt_SwiftTests.swift */,
FF0BB63F217A84E800B0556C /* Info.plist */,
7E084C882E12C4D30098059B /* mParticle_RoktTests-Bridging-Header.h */,
);
path = mParticle_RoktTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -237,6 +244,7 @@
buildActionMask = 2147483647;
files = (
B34CE55A2E04356F00712DE1 /* MPRoktEventMapper.swift in Sources */,
7E084C862E0C4B3A0098059B /* MPRoktLayout.swift in Sources */,
DBB01A691DC1480700A7B188 /* MPKitRokt.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand All @@ -245,8 +253,8 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B3D778532E02845700D887A4 /* MPRoktEventMapper.swift in Sources */,
FF0BB63E217A84E800B0556C /* mParticle_RoktTests.m in Sources */,
7E084C8A2E12C4D30098059B /* mParticle_Rokt_SwiftTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -449,6 +457,7 @@
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.mparticle.mParticle-RoktTests";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "mParticle_RoktTests/mParticle_RoktTests-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
Expand Down Expand Up @@ -489,6 +498,7 @@
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.mparticle.mParticle-RoktTests";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "mParticle_RoktTests/mParticle_RoktTests-Bridging-Header.h";
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
Expand Down
2 changes: 2 additions & 0 deletions mParticle-Rokt/MPKitRokt.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@
@property (nonatomic, strong, nullable) NSDictionary *launchOptions;
@property (nonatomic, unsafe_unretained, readonly) BOOL started;

+ (NSDictionary<NSString *, NSString *> * _Nonnull)prepareAttributes:(NSDictionary<NSString *, NSString *> * _Nonnull)attributes filteredUser:(FilteredMParticleUser * _Nullable)filteredUser performMapping:(BOOL)performMapping;

@end
144 changes: 126 additions & 18 deletions mParticle-Rokt/MPKitRokt.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
NSString * const kMPRemoteConfigUserAttributeFilter = @"ua";
NSString * const MPKitRoktErrorDomain = @"com.mparticle.kits.rokt";
NSString * const MPKitRoktErrorMessageKey = @"mParticle-Rokt Error";
NSString * const kMPPlacementAttributesMapping = @"placementAttributesMapping";
static __weak MPKitRokt *roktKit = nil;

@interface MPKitRokt () <MPKitProtocol>

Expand Down Expand Up @@ -42,6 +44,7 @@ - (MPKitExecStatus *)didFinishLaunchingWithConfiguration:(NSDictionary *)configu
}

_configuration = configuration;
roktKit = self;

NSString *sdkVersion = [MParticle sharedInstance].version;
// https://go.mparticle.com/work/SQDSDKS-7379
Expand Down Expand Up @@ -93,23 +96,7 @@ - (MPKitExecStatus *)executeWithIdentifier:(NSString * _Nullable)identifier
config:(MPRoktConfig * _Nullable)mpRoktConfig
callbacks:(MPRoktEventCallback * _Nullable)callbacks
filteredUser:(FilteredMParticleUser * _Nonnull)filteredUser {
NSDictionary<NSString *, NSString *> *mpAttributes = [filteredUser.userAttributes transformValuesToString];
NSMutableDictionary<NSString *, NSString *> *finalAtt = [[NSMutableDictionary alloc] init];
[finalAtt addEntriesFromDictionary:mpAttributes];

// Add MPID to the attributes being passed to the Rokt SDK
if (filteredUser.userId.stringValue != nil) {
[finalAtt addEntriesFromDictionary:@{@"mpid": filteredUser.userId.stringValue}];
}

// Add all known user identities to the attributes being passed to the Rokt SDK
[self addIdentityAttributes:finalAtt filteredUser:filteredUser];

// The core SDK does not set sandbox on the user, but we must pass it to Rokt if provided
NSString *sandboxKey = @"sandbox";
if (attributes[sandboxKey] != nil) {
[finalAtt addEntriesFromDictionary:@{sandboxKey: attributes[sandboxKey]}];
}
NSDictionary<NSString *, NSString *> *finalAtt = [MPKitRokt prepareAttributes:attributes filteredUser:filteredUser performMapping:NO];

//Convert MPRoktConfig to RoktConfig
RoktConfig *roktConfig = [MPKitRokt convertMPRoktConfig:mpRoktConfig];
Expand Down Expand Up @@ -171,7 +158,128 @@ - (RoktFrameworkType)mapMPWrapperSdkToRoktFrameworkType:(MPWrapperSdk)wrapperSdk
return safePlacements;
}

- (void)addIdentityAttributes:(NSMutableDictionary<NSString *, NSString *> * _Nullable)attributes filteredUser:(FilteredMParticleUser * _Nonnull)filteredUser {
+ (NSDictionary<NSString *, NSString *> *)confirmSandboxAttribute:(NSDictionary<NSString *, NSString *> * _Nullable)attributes {
NSMutableDictionary<NSString *, NSString *> *finalAttributes = attributes.mutableCopy;
NSString *sandboxKey = @"sandbox";

// Determine the value of the sandbox attribute based off the current environment
NSString *sandboxValue = ([[MParticle sharedInstance] environment] == MPEnvironmentDevelopment) ? @"true" : @"false";

if (finalAttributes != nil) {
// Only set sandbox if it`s not set by the client
if (![finalAttributes.allKeys containsObject:sandboxKey]) {
finalAttributes[sandboxKey] = sandboxValue;
}
} else {
finalAttributes = [[NSMutableDictionary alloc] initWithDictionary:@{sandboxKey: sandboxValue}];
}

return finalAttributes;
}

+ (NSDictionary<NSString *, NSString *> * _Nonnull)prepareAttributes:(NSDictionary<NSString *, NSString *> * _Nonnull)attributes filteredUser:(FilteredMParticleUser * _Nullable)filteredUser performMapping:(BOOL)performMapping {
if (filteredUser == nil && roktKit != nil) {
filteredUser = [[[MPKitAPI alloc] init] getCurrentUserWithKit:roktKit];
}
NSDictionary<NSString *, NSString *> *mpAttributes = [filteredUser.userAttributes transformValuesToString];
if (performMapping) {
mpAttributes = [self mapAttributes:attributes filteredUser:filteredUser];
}

NSMutableDictionary<NSString *, NSString *> *finalAtt = [[NSMutableDictionary alloc] init];
[finalAtt addEntriesFromDictionary:mpAttributes];

// Add MPID to the attributes being passed to the Rokt SDK
if (filteredUser.userId.stringValue != nil) {
[finalAtt addEntriesFromDictionary:@{@"mpid": filteredUser.userId.stringValue}];
}

// Add all known user identities to the attributes being passed to the Rokt SDK
[self addIdentityAttributes:finalAtt filteredUser:filteredUser];

// The core SDK does not set sandbox on the user, but we must pass it to Rokt if provided
NSString *sandboxKey = @"sandbox";
if (attributes[sandboxKey] != nil) {
[finalAtt addEntriesFromDictionary:@{sandboxKey: attributes[sandboxKey]}];
}

return [self confirmSandboxAttribute:finalAtt];
}

+ (NSDictionary<NSString *, NSString *> *)mapAttributes:(NSDictionary<NSString *, NSString *> * _Nullable)attributes filteredUser:(FilteredMParticleUser * _Nonnull)filteredUser {
NSArray<NSDictionary<NSString *, NSString *> *> *attributeMap = nil;

// Get the kit configuration
NSArray<NSDictionary *> *kitConfigs = [MParticle sharedInstance].kitContainer_PRIVATE.originalConfig.copy;
NSDictionary *roktKitConfig;
for (NSDictionary *kitConfig in kitConfigs) {
if (kitConfig[@"id"] != nil && [kitConfig[@"id"] integerValue] == 181) {
roktKitConfig = kitConfig;
}
}

// Return nil if no Rokt Kit configuration found
if (!roktKitConfig) {
return attributes;
}

// Get the placement attributes map
NSString *strAttributeMap;
NSData *dataAttributeMap;
// Rokt Kit is available though there may not be an attribute map
attributeMap = @[];
if (roktKitConfig[kMPPlacementAttributesMapping] != [NSNull null]) {
strAttributeMap = [roktKitConfig[kMPPlacementAttributesMapping] stringByRemovingPercentEncoding];
dataAttributeMap = [strAttributeMap dataUsingEncoding:NSUTF8StringEncoding];
}

if (dataAttributeMap != nil) {
// Convert it to an array of dictionaries
NSError *error = nil;

@try {
attributeMap = [NSJSONSerialization JSONObjectWithData:dataAttributeMap options:kNilOptions error:&error];
} @catch (NSException *exception) {
}

if (attributeMap && !error) {
NSLog(@"%@", attributeMap);
} else {
NSLog(@"%@", error);
}
}

if (attributeMap) {
NSMutableDictionary *mappedAttributes = attributes.mutableCopy;
for (NSDictionary<NSString *, NSString *> *map in attributeMap) {
NSString *mapFrom = map[@"map"];
NSString *mapTo = map[@"value"];
if (mappedAttributes[mapFrom]) {
NSString * value = mappedAttributes[mapFrom];
[mappedAttributes removeObjectForKey:mapFrom];
mappedAttributes[mapTo] = value;
}
}
for (NSString *key in mappedAttributes) {
if (![key isEqual:@"sandbox"]) {
[[MParticle sharedInstance].identity.currentUser setUserAttribute:key value:mappedAttributes[key]];
}
}

// Add userAttributes to the attributes sent to Rokt
for (NSString *uaKey in filteredUser.userAttributes) {
if (![mappedAttributes.allKeys containsObject:uaKey]) {
mappedAttributes[uaKey] = filteredUser.userAttributes[uaKey];
}
}

return [mappedAttributes transformValuesToString];
} else {
return attributes;
}
}

+ (void)addIdentityAttributes:(NSMutableDictionary<NSString *, NSString *> * _Nullable)attributes filteredUser:(FilteredMParticleUser * _Nonnull)filteredUser {
NSMutableDictionary<NSString *, NSString *> *identityAttributes = [[NSMutableDictionary alloc] init];
for (NSNumber *identityNumberKey in filteredUser.userIdentities) {
NSString *identityStringKey = [MPKitRokt stringForIdentityType:identityNumberKey.unsignedIntegerValue];
Expand Down
45 changes: 45 additions & 0 deletions mParticle-Rokt/MPRoktLayout.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//
// MPRoktLayout.swift
// mParticle-Rokt
//
// Copyright 2025 Rokt Pte Ltd
//
// Licensed under the Rokt Software Development Kit (SDK) Terms of Use
// Version 2.0 (the "License");
//
// You may not use this file except in compliance with the License.
//
// You may obtain a copy of the License at https://rokt.com/sdk-license-2-0/

import SwiftUI
import Rokt_Widget
import mParticle_Apple_SDK

@available(iOS 15, *)
public struct MPRoktLayout: View {
private var roktLayout: RoktLayout

public init(
sdkTriggered: Binding<Bool>,
viewName: String? = nil,
locationName: String = "",
attributes: [String: String],
config: RoktConfig? = nil,
onEvent: ((RoktEvent) -> Void)? = nil
) {
let preparedAttributes = MPKitRokt.prepareAttributes(attributes, filteredUser: Optional<FilteredMParticleUser>.none, performMapping: true)

self.roktLayout = RoktLayout.init(
sdkTriggered: sdkTriggered,
viewName: viewName,
locationName: locationName,
attributes: preparedAttributes,
config: config,
onEvent: onEvent
)
}

public var body: some View {
return self.roktLayout.body
}
}
1 change: 1 addition & 0 deletions mParticle-Rokt/mParticle_Rokt.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#import <UIKit/UIKit.h>
#import "MPKitRokt.h"

FOUNDATION_EXPORT double mParticle_RoktVersionNumber;
FOUNDATION_EXPORT const unsigned char mParticle_RoktVersionString[];
8 changes: 8 additions & 0 deletions mParticle_RoktTests/mParticle_RoktTests-Bridging-Header.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import <OCMock/OCMock.h>
#import <Rokt_Widget/Rokt_Widget-Swift.h>
#import <mParticle_Rokt/mParticle_Rokt.h>
#import "MPKitRokt.h"
#import <mParticle_Rokt/mParticle_Rokt-Swift.h>
50 changes: 42 additions & 8 deletions mParticle_RoktTests/mParticle_RoktTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@ - (MPKitExecStatus *)purchaseFinalized:(NSString *)placementId

- (NSDictionary<NSString *, RoktEmbeddedView *> * _Nullable) confirmEmbeddedViews:(NSDictionary<NSString *, MPRoktEmbeddedView *> * _Nullable)embeddedViews;

- (NSDictionary<NSString *, NSString *> *) filteredUserAttributes:(NSDictionary<NSString *, NSString *> * _Nonnull)attributes kitConfiguration:(MPKitConfiguration *)kitConfiguration;

- (void)addIdentityAttributes:(NSMutableDictionary<NSString *, NSString *> * _Nullable)attributes filteredUser:(FilteredMParticleUser * _Nonnull)filteredUser;
+ (void)addIdentityAttributes:(NSMutableDictionary<NSString *, NSString *> * _Nullable)attributes filteredUser:(FilteredMParticleUser * _Nonnull)filteredUser;

+ (RoktConfig *)convertMPRoktConfig:(MPRoktConfig *)mpRoktConfig;

Expand Down Expand Up @@ -132,7 +130,45 @@ - (void)testExecuteWithIdentifier {
MPRoktEmbeddedView *view = [[MPRoktEmbeddedView alloc] init];
NSString *identifier = @"TestView";
NSDictionary *embeddedViews = @{@"placement1": view};
NSDictionary *attributes = @{@"attr1": @"value1", @"sandbox": @"true"};
NSDictionary *attributes = @{@"attr1": @"value1", @"sandbox": @"false"};
FilteredMParticleUser *user = [[FilteredMParticleUser alloc] init];

// Expected attributes in final call
NSDictionary *expectedAttributes = @{
@"sandbox": @"false"
};

// Expect Rokt execute call with correct parameters
OCMExpect([mockRoktSDK executeWithViewName:identifier
attributes:expectedAttributes
placements:OCMOCK_ANY
config: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];

// Verify
XCTAssertNotNil(status);
XCTAssertEqual(status.returnCode, MPKitReturnCodeSuccess);
OCMVerifyAll(mockRoktSDK);
}

- (void)testExecuteSandboxDetection {
id mockRoktSDK = OCMClassMock([Rokt class]);

MPRoktEmbeddedView *view = [[MPRoktEmbeddedView alloc] init];
NSString *identifier = @"TestView";
NSDictionary *embeddedViews = @{@"placement1": view};
NSDictionary *attributes = @{@"attr1": @"value1"};
FilteredMParticleUser *user = [[FilteredMParticleUser alloc] init];

// Expected attributes in final call
Expand Down Expand Up @@ -196,8 +232,7 @@ - (void)testAddIdentityAttributes {
id mockfilteredUser = OCMPartialMock(filteredUser);
[[[mockfilteredUser stub] andReturn:testIdentities] userIdentities];

MPKitRokt *kit = [[MPKitRokt alloc] init];
[kit addIdentityAttributes:passedAttributes filteredUser:filteredUser];
[MPKitRokt addIdentityAttributes:passedAttributes filteredUser:filteredUser];

XCTAssertEqualObjects(passedAttributes[@"customerid"], @"testCustomerID");
XCTAssertEqualObjects(passedAttributes[@"email"], @"testEmail@gmail.com");
Expand Down Expand Up @@ -259,8 +294,7 @@ - (void)testAddIdentityAttributesWithExistingAttributes {
id mockfilteredUser = OCMPartialMock(filteredUser);
[[[mockfilteredUser stub] andReturn:testIdentities] userIdentities];

MPKitRokt *kit = [[MPKitRokt alloc] init];
[kit addIdentityAttributes:passedAttributes filteredUser:filteredUser];
[MPKitRokt addIdentityAttributes:passedAttributes filteredUser:filteredUser];

XCTAssertEqualObjects(passedAttributes[@"foo"], @"bar");
XCTAssertEqualObjects(passedAttributes[@"customerid"], @"testCustomerID");
Expand Down
Loading
Loading