diff --git a/mParticle-Rokt-Swift/MPRoktLayout.swift b/mParticle-Rokt-Swift/MPRoktLayout.swift index 0695dca..3b8e51a 100644 --- a/mParticle-Rokt-Swift/MPRoktLayout.swift +++ b/mParticle-Rokt-Swift/MPRoktLayout.swift @@ -17,8 +17,9 @@ import mParticle_Apple_SDK import mParticle_Rokt @available(iOS 15, *) -public struct MPRoktLayout: View { - private var roktLayout: RoktLayout +public class MPRoktLayout { + public var roktLayout: RoktLayout? = nil + let mparticle = MParticle.sharedInstance() public init( sdkTriggered: Binding, @@ -28,19 +29,72 @@ public struct MPRoktLayout: View { config: RoktConfig? = nil, onEvent: ((RoktEvent) -> Void)? = nil ) { - let preparedAttributes = MPKitRokt.prepareAttributes(attributes, filteredUser: Optional.none, performMapping: true) + confirmUser(attributes: attributes) { + let preparedAttributes = MPKitRokt.prepareAttributes(attributes, filteredUser: Optional.none, performMapping: true) - self.roktLayout = RoktLayout.init( - sdkTriggered: sdkTriggered, - viewName: viewName, - locationName: locationName, - attributes: preparedAttributes, - config: config, - onEvent: onEvent - ) + 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 + + func confirmUser( + attributes: [String: String]?, + completion: @escaping () -> Void + ) { + guard let user = mparticle.identity.currentUser else { + completion() + return + } + let email = attributes?["email"] + let hashedEmail = attributes?["emailsha256"] + + let userEmailIdentity = user.identities[NSNumber(value: MPIdentity.email.rawValue)] + let userHashedEmailIdentity = user.identities[NSNumber(value: MPIdentity.other.rawValue)] + + let emailMismatch = email != nil && email != userEmailIdentity + let hashedEmailMismatch = hashedEmail != nil && hashedEmail != userHashedEmailIdentity + + if emailMismatch || hashedEmailMismatch { + // If there is an existing email or hashed email but it doesn't match what was passed in, warn the customer + if emailMismatch { + print("The existing email on the user (\(userEmailIdentity ?? "nil")) does not match the email passed in to `selectPlacements:` (\(email ?? "nil")). Please remember to sync the email identity to mParticle as soon as you receive it. We will now identify the user before creating the layout") + } + if hashedEmailMismatch { + print("The existing hashed email on the user (\(userHashedEmailIdentity ?? "nil")) does not match the email passed in to `selectPlacements:` (\(hashedEmail ?? "nil")). Please remember to sync the email identity to mParticle as soon as you receive it. We will now identify the user before creating the layout") + } + + syncIdentities(user: user, email: email, hashedEmail: hashedEmail, completion: completion) + } else { + completion() + } + } + + func syncIdentities( + user: MParticleUser, + email: String?, + hashedEmail: String?, + completion: @escaping () -> Void + ) { + let identityRequest = MPIdentityApiRequest(user: user) + identityRequest.setIdentity(email, identityType: .email) + identityRequest.setIdentity(hashedEmail, identityType: .other) + + mparticle.identity.identify(identityRequest) {apiResult, error in + if let error = error { + print("Failed to sync email from selectPlacement to user: \(error)") + completion() + } else { + if let identities = apiResult?.user.identities { + print("Updated user identity based off selectPlacement's attributes: \(identities)") + } + completion() + } + } } } diff --git a/mParticle_RoktTests/mParticle_Rokt_SwiftTests.swift b/mParticle_RoktTests/mParticle_Rokt_SwiftTests.swift index ea34e61..3a1dae7 100644 --- a/mParticle_RoktTests/mParticle_Rokt_SwiftTests.swift +++ b/mParticle_RoktTests/mParticle_Rokt_SwiftTests.swift @@ -31,7 +31,7 @@ struct mParticle_Rokt_SwiftTests { ) // Then - #expect(layout.body != nil, "Layout body should not be nil") + #expect(layout.roktLayout != nil, "Layout.roktLayout should not be nil") } @MainActor @available(iOS 15, *) @@ -58,7 +58,7 @@ struct mParticle_Rokt_SwiftTests { ) // Then - #expect(layout.body != nil, "Layout body should not be nil") + #expect(layout.roktLayout != nil, "Layout.roktLayout should not be nil") } @MainActor @available(iOS 15, *) @@ -76,7 +76,7 @@ struct mParticle_Rokt_SwiftTests { ) // Then - #expect(layout.body != nil, "Layout should handle empty attributes") + #expect(layout.roktLayout != nil, "Layout should handle empty attributes") } @MainActor @available(iOS 15, *) @@ -94,7 +94,7 @@ struct mParticle_Rokt_SwiftTests { ) // Then - #expect(layout.body != nil, "Layout should handle sandbox attribute") + #expect(layout.roktLayout != nil, "Layout should handle sandbox attribute") } // MARK: - Attribute Preparation Tests @@ -183,25 +183,7 @@ struct mParticle_Rokt_SwiftTests { ) // Then - #expect(layout is any View, "MPRoktLayout should conform to SwiftUI View protocol") - } - - @MainActor @available(iOS 15, *) - @Test func testMPRoktLayoutBodyProperty() { - // Given - let sdkTriggered = Binding.constant(false) - let attributes: [String: String] = ["test": "value"] - - // When - let layout = MPRoktLayout( - sdkTriggered: sdkTriggered, - locationName: "test", - attributes: attributes - ) - - // Then - let body = layout.body - #expect(body != nil, "Layout body should be accessible") + #expect(layout.roktLayout is any View, "MPRoktLayout should conform to SwiftUI View protocol") } // MARK: - Parameter Validation Tests @@ -212,16 +194,16 @@ struct mParticle_Rokt_SwiftTests { let sdkTriggered = Binding.constant(false) let longLocationName = String(repeating: "a", count: 1000) let attributes: [String: String] = ["test": "value"] - + // When let layout = MPRoktLayout( sdkTriggered: sdkTriggered, locationName: longLocationName, attributes: attributes ) - + // Then - #expect(layout.body != nil, "Layout should handle long location names") + #expect(layout.roktLayout != nil, "Layout should handle long location names") } @MainActor @available(iOS 15, *) @@ -242,7 +224,7 @@ struct mParticle_Rokt_SwiftTests { ) // Then - #expect(layout.body != nil, "Layout should handle special characters and unicode") + #expect(layout.roktLayout != nil, "Layout should handle special characters and unicode") } // MARK: - State Management Tests @@ -261,13 +243,13 @@ struct mParticle_Rokt_SwiftTests { ) // Then - #expect(layout.body != nil, "Layout should be created with initial state") + #expect(layout.roktLayout != nil, "Layout should be created with initial state") // When state changes sdkTriggered.wrappedValue = true // Then - #expect(layout.body != nil, "Layout should handle state changes") + #expect(layout.roktLayout != nil, "Layout should handle state changes") } // MARK: - Integration Tests @@ -291,7 +273,7 @@ struct mParticle_Rokt_SwiftTests { ) // Then - #expect(layout.body != nil, "Layout should properly integrate attribute processing") + #expect(layout.roktLayout == nil, "Layout should attempt to identify user attributes and fail") } @MainActor @available(iOS 15, *) @@ -325,6 +307,6 @@ struct mParticle_Rokt_SwiftTests { ) // Then - #expect(layout.body != nil, "Layout should handle complex configurations") + #expect(layout.roktLayout != nil, "Layout should handle complex configurations") } }