diff --git a/UnitTests/ObjCTests/MPConvertJSTests.m b/UnitTests/ObjCTests/MPConvertJSTests.m index f55139c97..ab3d52c03 100644 --- a/UnitTests/ObjCTests/MPConvertJSTests.m +++ b/UnitTests/ObjCTests/MPConvertJSTests.m @@ -1,4 +1,5 @@ #import +#import "MPConvertJS.h" #import "mParticle.h" #import "MPBaseTestCase.h" #import "MParticleSwift.h" diff --git a/mParticle-Apple-SDK.xcodeproj/project.pbxproj b/mParticle-Apple-SDK.xcodeproj/project.pbxproj index f89401945..83b4f7981 100644 --- a/mParticle-Apple-SDK.xcodeproj/project.pbxproj +++ b/mParticle-Apple-SDK.xcodeproj/project.pbxproj @@ -277,7 +277,8 @@ D34286102D419CB800FEC2C8 /* MPUploadSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D342860E2D419CA700FEC2C8 /* MPUploadSettings.swift */; }; D356E0252CFE08ED0020898D /* MPResponseConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = D356E0232CFE08ED0020898D /* MPResponseConfig.swift */; }; D3724C192AE02AF60074CD67 /* mParticle_Apple_SDK_NoLocation.h in Headers */ = {isa = PBXBuildFile; fileRef = D3724C182AE02AF60074CD67 /* mParticle_Apple_SDK_NoLocation.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D3CEDACC2CB027E1001B32DF /* MPConvertJS.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3CEDACA2CB027E1001B32DF /* MPConvertJS.swift */; }; + D3CEDACB2CB027E1001B32DE /* MPConvertJS.h in Headers */ = {isa = PBXBuildFile; fileRef = D3CEDAC92CB027E1001B32DE /* MPConvertJS.h */; }; + D3CEDACC2CB027E1001B32DF /* MPConvertJS.m in Sources */ = {isa = PBXBuildFile; fileRef = D3CEDACA2CB027E1001B32DF /* MPConvertJS.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -605,7 +606,8 @@ D3724C182AE02AF60074CD67 /* mParticle_Apple_SDK_NoLocation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mParticle_Apple_SDK_NoLocation.h; sourceTree = ""; }; D3B3E2062AE028EC001AB58C /* mParticle_Apple_SDK.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mParticle_Apple_SDK.h; sourceTree = ""; }; D3BA75152B614E3D008C3C65 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; - D3CEDACA2CB027E1001B32DF /* MPConvertJS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPConvertJS.swift; sourceTree = ""; }; + D3CEDAC92CB027E1001B32DE /* MPConvertJS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPConvertJS.h; sourceTree = ""; }; + D3CEDACA2CB027E1001B32DF /* MPConvertJS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPConvertJS.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ @@ -891,7 +893,8 @@ D34286082D3806CF00FEC2C8 /* MPUserDefaults.swift */, D342860E2D419CA700FEC2C8 /* MPUploadSettings.swift */, 53A79B0229CDFB1F00E7489F /* MPMessageBuilder.m */, - D3CEDACA2CB027E1001B32DF /* MPConvertJS.swift */, + D3CEDACA2CB027E1001B32DF /* MPConvertJS.m */, + D3CEDAC92CB027E1001B32DE /* MPConvertJS.h */, 53A79B0929CDFB1F00E7489F /* NSDictionary+MPCaseInsensitive.m */, D356E0232CFE08ED0020898D /* MPResponseConfig.swift */, 53A79B0D29CDFB1F00E7489F /* MPApplication.m */, @@ -1279,6 +1282,7 @@ 53A79D5529CE23F700E7489F /* MPPersistenceController.h in Headers */, 53A79D5829CE23F700E7489F /* MPIdentityDTO.h in Headers */, 53A79D5929CE23F700E7489F /* MPMessageBuilder.h in Headers */, + D3CEDACB2CB027E1001B32DE /* MPConvertJS.h in Headers */, 53A79D5A29CE23F700E7489F /* MPURLRequestBuilder.h in Headers */, D3724C192AE02AF60074CD67 /* mParticle_Apple_SDK_NoLocation.h in Headers */, 53A79D5B29CE23F700E7489F /* MPKitContainer.h in Headers */, @@ -1584,7 +1588,7 @@ 53A79D7729CE23F700E7489F /* MPKitConfiguration.m in Sources */, 35C3DE702F2919B40077C0FD /* MPCCPAConsent.m in Sources */, 53A79D7829CE23F700E7489F /* MPConsentState.m in Sources */, - D3CEDACC2CB027E1001B32DF /* MPConvertJS.swift in Sources */, + D3CEDACC2CB027E1001B32DF /* MPConvertJS.m in Sources */, 53A79D7929CE23F700E7489F /* MPBracket.m in Sources */, 53A79D7A29CE23F700E7489F /* MParticleUserNotification.m in Sources */, 531BCF372B28A23400F5C573 /* MPIdentityCaching.m in Sources */, diff --git a/mParticle-Apple-SDK/Ecommerce/MPPromotion.m b/mParticle-Apple-SDK/Ecommerce/MPPromotion.m index 89d24677e..738ce241b 100644 --- a/mParticle-Apple-SDK/Ecommerce/MPPromotion.m +++ b/mParticle-Apple-SDK/Ecommerce/MPPromotion.m @@ -1,6 +1,7 @@ #import "MPPromotion.h" #import "MPIConstants.h" #import "NSDictionary+MPCaseInsensitive.h" +#import "mParticle.h" #import "MParticleSwift.h" @import mParticle_Apple_SDK_Swift; diff --git a/mParticle-Apple-SDK/Kits/MPEventProjection.m b/mParticle-Apple-SDK/Kits/MPEventProjection.m index 4f027d77a..44bd4a21e 100644 --- a/mParticle-Apple-SDK/Kits/MPEventProjection.m +++ b/mParticle-Apple-SDK/Kits/MPEventProjection.m @@ -1,5 +1,6 @@ #import "MPEventProjection.h" #import "MPAttributeProjection.h" +#import "mParticle.h" #import "MParticleSwift.h" @import mParticle_Apple_SDK_Swift; diff --git a/mParticle-Apple-SDK/MPBackendController.m b/mParticle-Apple-SDK/MPBackendController.m index c3a87778f..bc5000d3e 100644 --- a/mParticle-Apple-SDK/MPBackendController.m +++ b/mParticle-Apple-SDK/MPBackendController.m @@ -22,6 +22,7 @@ #import "MPKitContainer.h" #import "MPURLRequestBuilder.h" #import "MPIdentityCaching.h" +#import "mParticle.h" #import "MParticleSwift.h" #import "MPNetworkCommunication.h" #if TARGET_OS_IOS == 1 diff --git a/mParticle-Apple-SDK/MParticleSession+MParticlePrivate.h b/mParticle-Apple-SDK/MParticleSession+MParticlePrivate.h index 7b07b9bc1..1b0fc5ce2 100644 --- a/mParticle-Apple-SDK/MParticleSession+MParticlePrivate.h +++ b/mParticle-Apple-SDK/MParticleSession+MParticlePrivate.h @@ -1,3 +1,6 @@ +#import +#import "mParticle.h" + @interface MParticleSession () - (instancetype)initWithUUID:(NSString *)uuid; diff --git a/mParticle-Apple-SDK/MParticleSession+MParticlePrivate.m b/mParticle-Apple-SDK/MParticleSession+MParticlePrivate.m index b07c0524c..f4e445081 100644 --- a/mParticle-Apple-SDK/MParticleSession+MParticlePrivate.m +++ b/mParticle-Apple-SDK/MParticleSession+MParticlePrivate.m @@ -1,4 +1,4 @@ -#import "MParticleSwift.h" +#import "mParticle.h" #import "MParticleSession+MParticlePrivate.h" @import mParticle_Apple_SDK_Swift; diff --git a/mParticle-Apple-SDK/Utils/MPConvertJS.h b/mParticle-Apple-SDK/Utils/MPConvertJS.h new file mode 100644 index 000000000..705c24f3c --- /dev/null +++ b/mParticle-Apple-SDK/Utils/MPConvertJS.h @@ -0,0 +1,31 @@ +#import +#import "MPCommerceEvent.h" +#import "MPPromotion.h" +#import "MPTransactionAttributes.h" +#import "MPProduct.h" +#import "MPIdentityApiRequest.h" + +typedef NS_ENUM(NSUInteger, MPJSCommerceEventAction) { + MPJSCommerceEventActionUnknown = 0, + MPJSCommerceEventActionAddToCart, + MPJSCommerceEventActionRemoveFromCart, + MPJSCommerceEventActionCheckout, + MPJSCommerceEventActionCheckoutOptions, + MPJSCommerceEventActionClick, + MPJSCommerceEventActionViewDetail, + MPJSCommerceEventActionPurchase, + MPJSCommerceEventActionRefund, + MPJSCommerceEventActionAddToWishList, + MPJSCommerceEventActionRemoveFromWishlist +}; + +@interface MPConvertJS_PRIVATE : NSObject + ++ (MPCommerceEvent *)commerceEvent:(NSDictionary *)json; ++ (MPPromotionContainer *)promotionContainer:(NSDictionary *)json; ++ (MPPromotion *)promotion:(NSDictionary *)json; ++ (MPTransactionAttributes *)transactionAttributes:(NSDictionary *)json; ++ (MPProduct *)product:(NSDictionary *)json; ++ (MPIdentityApiRequest *)identityApiRequest:(NSDictionary *)json; + +@end diff --git a/mParticle-Apple-SDK/Utils/MPConvertJS.m b/mParticle-Apple-SDK/Utils/MPConvertJS.m new file mode 100644 index 000000000..a428a437d --- /dev/null +++ b/mParticle-Apple-SDK/Utils/MPConvertJS.m @@ -0,0 +1,379 @@ +#import "MPConvertJS.h" +#import "MPCommerceEvent+Dictionary.h" +#import "mParticle.h" +#import "MPILogger.h" + +@implementation MPConvertJS_PRIVATE + ++ (MPCommerceEventAction)commerceEventAction:(NSNumber *)json { + int actionInt = [json intValue]; + switch (actionInt) { + case MPJSCommerceEventActionAddToCart: + return MPCommerceEventActionAddToCart; + case MPJSCommerceEventActionRemoveFromCart: + return MPCommerceEventActionRemoveFromCart; + case MPJSCommerceEventActionCheckout: + return MPCommerceEventActionCheckout; + case MPJSCommerceEventActionCheckoutOptions: + return MPCommerceEventActionCheckoutOptions; + case MPJSCommerceEventActionClick: + return MPCommerceEventActionClick; + case MPJSCommerceEventActionViewDetail: + return MPCommerceEventActionViewDetail; + case MPJSCommerceEventActionPurchase: + return MPCommerceEventActionPurchase; + case MPJSCommerceEventActionRefund: + return MPCommerceEventActionRefund; + case MPJSCommerceEventActionAddToWishList: + return MPCommerceEventActionAddToWishList; + case MPJSCommerceEventActionRemoveFromWishlist: + return MPCommerceEventActionRemoveFromWishlist; + default: + MPILogError(@"Invalid commerce event action received from webview: %@", json); + return MPCommerceEventActionAddToCart; + } +} + ++ (MPCommerceEvent *)commerceEvent:(NSDictionary *)json { + if (json[@"ProductAction"] != nil && ![json[@"ProductAction"] isKindOfClass:[NSDictionary class]]) { + MPILogError(@"Unexpected commerce event data received from webview"); + return nil; + } + + NSDictionary *productAction = json[@"ProductAction"]; + BOOL isProductAction = productAction[@"ProductActionType"] != nil; + BOOL isPromotion = json[@"PromotionAction"] != nil; + BOOL isImpression = json[@"ProductImpressions"] != nil; + BOOL isValid = isProductAction || isPromotion || isImpression; + + if (!isValid) { + MPILogError(@"Invalid commerce event dictionary received from webview: %@", json); + return nil; + } + + MPCommerceEvent *commerceEvent = nil; + if (isProductAction) { + id productActionType = productAction[@"ProductActionType"]; + if (!productActionType || ![productActionType isKindOfClass:[NSNumber class]]) { + MPILogError(@"Unexpected product action type received from webview"); + return nil; + } + MPCommerceEventAction action = [MPConvertJS_PRIVATE commerceEventAction:productActionType]; + commerceEvent = [[MPCommerceEvent alloc] initWithAction:action]; + } + else if (isPromotion) { + MPPromotionContainer *promotionContainer = [MPConvertJS_PRIVATE promotionContainer:json]; + commerceEvent = [[MPCommerceEvent alloc] initWithPromotionContainer:promotionContainer]; + } + else { + commerceEvent = [[MPCommerceEvent alloc] initWithImpressionName:nil product:nil]; + } + + id eventAttributes = json[@"EventAttributes"]; + if ([eventAttributes isKindOfClass:[NSDictionary class]]) { + commerceEvent.customAttributes = (NSDictionary *)eventAttributes; + } + + id checkoutOptionsObj = json[@"CheckoutOptions"]; + if ([checkoutOptionsObj isKindOfClass:[NSString class]]) { + commerceEvent.checkoutOptions = (NSString *)checkoutOptionsObj; + } + + id productActionListNameObj = json[@"productActionListName"]; + if ([productActionListNameObj isKindOfClass:[NSString class]]) { + commerceEvent.productListName = (NSString *)productActionListNameObj; + } + + id productActionListSourceObj = json[@"productActionListSource"]; + if ([productActionListSourceObj isKindOfClass:[NSString class]]) { + commerceEvent.productListSource = (NSString *)productActionListSourceObj; + } + + id currencyCodeObj = json[@"CurrencyCode"]; + if ([currencyCodeObj isKindOfClass:[NSString class]]) { + commerceEvent.currency = (NSString *)currencyCodeObj; + } + + if (productAction != nil) { + commerceEvent.transactionAttributes = [self transactionAttributes:productAction]; + } + + id checkoutStepObj = json[@"CheckoutStep"]; + if ([checkoutStepObj isKindOfClass:[NSNumber class]]) { + commerceEvent.checkoutStep = [(NSNumber *)checkoutStepObj intValue]; + } + + id customFlagsObj = json[@"CustomFlags"]; + if ([customFlagsObj isKindOfClass:[NSDictionary class]]) { + NSDictionary *customFlags = (NSDictionary *)customFlagsObj; + + for (id key in customFlags) { + id value = customFlags[key]; + + if ([value isKindOfClass:[NSArray class]]) { + BOOL allStrings = YES; + for (id item in (NSArray *)value) { + if (![item isKindOfClass:[NSString class]]) { allStrings = NO; break; } + } + if (allStrings && [key isKindOfClass:[NSString class]]) { + [commerceEvent addCustomFlags:(NSArray *)value withKey:(NSString *)key]; + } + + } else if ([value isKindOfClass:[NSString class]]) { + if ([key isKindOfClass:[NSString class]]) { + [commerceEvent addCustomFlag:(NSString *)value withKey:(NSString *)key]; + } + } + } + } + + id productListObj = productAction[@"ProductList"]; + if ([productListObj isKindOfClass:[NSArray class]]) { + NSArray *jsonProducts = (NSArray *)productListObj; + + NSMutableArray *products = [NSMutableArray arrayWithCapacity:jsonProducts.count]; + for (id item in jsonProducts) { + if (![item isKindOfClass:[NSDictionary class]]) { continue; } + + MPProduct *p = [self product:(NSDictionary *)item]; + if (p) { [products addObject:p]; } + } + [commerceEvent addProducts:products]; + } + + id impressionsObj = json[@"ProductImpressions"]; + if ([impressionsObj isKindOfClass:[NSArray class]]) { + NSArray *jsonImpressions = (NSArray *)impressionsObj; + + for (id impressionItem in jsonImpressions) { + if (![impressionItem isKindOfClass:[NSDictionary class]]) { continue; } + + NSDictionary *jsonImpression = (NSDictionary *)impressionItem; + id listNameObj = jsonImpression[@"ProductImpressionList"]; + id impressionProductsObj = jsonImpression[@"ProductList"]; + + if ([listNameObj isKindOfClass:[NSString class]] && + [impressionProductsObj isKindOfClass:[NSArray class]]) { + + NSString *listName = (NSString *)listNameObj; + NSArray *impressionProducts = (NSArray *)impressionProductsObj; + + for (id prodItem in impressionProducts) { + if (![prodItem isKindOfClass:[NSDictionary class]]) { continue; } + + MPProduct *product = [MPConvertJS_PRIVATE product:(NSDictionary *)prodItem]; + [commerceEvent addImpression:product listName:listName]; + } + } + } + } + + return commerceEvent; +} + ++ (MPPromotionContainer *)promotionContainer:(NSDictionary *)json { + NSDictionary *promotionActionDictionary = json[@"PromotionAction"]; + if (!promotionActionDictionary || ![promotionActionDictionary isKindOfClass:[NSDictionary class]]) { + MPILogError(@"Unexpected promotion container action data received from webview"); + return nil; + } + + NSNumber *promotionActionTypeNumber = promotionActionDictionary[@"PromotionActionType"]; + if (promotionActionTypeNumber == nil || ![promotionActionTypeNumber isKindOfClass:[NSNumber class]]) { + MPILogError(@"Unexpected promotion container action type data received from webview"); + return nil; + } + + int promotionActionInt = [promotionActionTypeNumber intValue]; + MPPromotionAction promotionAction = promotionActionInt == 1 ? MPPromotionActionView : MPPromotionActionClick; + MPPromotionContainer *promotionContainer = [[MPPromotionContainer alloc] initWithAction:promotionAction promotion:nil]; + + NSArray *jsonPromotions = promotionActionDictionary[@"PromotionList"]; + if (!jsonPromotions || ![jsonPromotions isKindOfClass:[NSArray class]]) { + MPILogError(@"Unexpected promotion container list data received from webview"); + return nil; + } + + [jsonPromotions enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + MPPromotion *promotion = [MPConvertJS_PRIVATE promotion:obj]; + [promotionContainer addPromotion:promotion]; + }]; + + return promotionContainer; +} + ++ (MPPromotion *)promotion:(NSDictionary *)json { + MPPromotion *promotion = [[MPPromotion alloc] init]; + + id creative = json[@"Creative"]; + if ([creative isKindOfClass:[NSString class]]) { + promotion.creative = (NSString *)creative; + } + + id name = json[@"Name"]; + if ([name isKindOfClass:[NSString class]]) { + promotion.name = (NSString *)name; + } + + id position = json[@"Position"]; + if ([position isKindOfClass:[NSString class]]) { + promotion.position = (NSString *)position; + } + + id promoId = json[@"Id"]; + if ([promoId isKindOfClass:[NSString class]]) { + promotion.promotionId = (NSString *)promoId; + } + + return promotion; +} + ++ (MPTransactionAttributes *)transactionAttributes:(NSDictionary *)json { + MPTransactionAttributes *transactionAttributes = [[MPTransactionAttributes alloc] init]; + + id affiliation = json[@"Affiliation"]; + if ([affiliation isKindOfClass:[NSString class]]) { + transactionAttributes.affiliation = (NSString *)affiliation; + } + + id couponCode = json[@"CouponCode"]; + if ([couponCode isKindOfClass:[NSString class]]) { + transactionAttributes.couponCode = (NSString *)couponCode; + } + + id shippingAmount = json[@"ShippingAmount"]; + if ([shippingAmount isKindOfClass:[NSNumber class]]) { + transactionAttributes.shipping = (NSNumber *)shippingAmount; + } + + id taxAmount = json[@"TaxAmount"]; + if ([taxAmount isKindOfClass:[NSNumber class]]) { + transactionAttributes.tax = (NSNumber *)taxAmount; + } + + id totalAmount = json[@"TotalAmount"]; + if ([totalAmount isKindOfClass:[NSNumber class]]) { + transactionAttributes.revenue = (NSNumber *)totalAmount; + } + + id transactionId = json[@"TransactionId"]; + if ([transactionId isKindOfClass:[NSString class]]) { + transactionAttributes.transactionId = (NSString *)transactionId; + } + + return transactionAttributes; +} + ++ (MPProduct *)product:(NSDictionary *)json { + MPProduct *product = [[MPProduct alloc] init]; + + id brand = json[@"Brand"]; + if ([brand isKindOfClass:[NSString class]]) { + product.brand = (NSString *)brand; + } + + id category = json[@"Category"]; + if ([category isKindOfClass:[NSString class]]) { + product.category = (NSString *)category; + } + + id couponCode = json[@"CouponCode"]; + if ([couponCode isKindOfClass:[NSString class]]) { + product.couponCode = (NSString *)couponCode; + } + + id name = json[@"Name"]; + if ([name isKindOfClass:[NSString class]]) { + product.name = (NSString *)name; + } + + id price = json[@"Price"]; + if ([price isKindOfClass:[NSNumber class]]) { + product.price = (NSNumber *)price; + } else if ([price isKindOfClass:[NSString class]]) { + product.price = @([(NSString *)price doubleValue]); + } + + id sku = json[@"Sku"]; + if ([sku isKindOfClass:[NSString class]]) { + product.sku = (NSString *)sku; + } + + id variant = json[@"Variant"]; + if ([variant isKindOfClass:[NSString class]]) { + product.variant = (NSString *)variant; + } + + id position = json[@"Position"]; + if ([position isKindOfClass:[NSNumber class]]) { + product.position = [(NSNumber *)position unsignedIntValue]; + } + + id quantity = json[@"Quantity"]; + if ([quantity isKindOfClass:[NSNumber class]]) { + product.quantity = (NSNumber *)quantity; + } + + id attributes = json[@"Attributes"]; + if ([attributes isKindOfClass:[NSDictionary class]]) { + NSDictionary *jsonAttributes = (NSDictionary *)attributes; + [jsonAttributes enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + if ([key isKindOfClass:[NSString class]] && [obj isKindOfClass:[NSString class]]) { + [product setValue:obj forKey:key]; + } + }]; + } + + return product; +} + ++ (MPIdentityApiRequest *)identityApiRequest:(NSDictionary *)json { + MPIdentityApiRequest *request = [MPIdentityApiRequest requestWithEmptyUser]; + + NSArray *userIdentities = json[@"UserIdentities"]; + if (!userIdentities || ![userIdentities isKindOfClass:[NSArray class]]) { + MPILogError(@"Unexpected user identity data received from webview"); + return nil; + } + + if (userIdentities.count > 0) { + __block BOOL allSuccess = YES; + + [userIdentities enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull identityDictionary, NSUInteger idx, BOOL * _Nonnull stop) { + NSString *identity = identityDictionary[@"Identity"]; + NSNumber *identityTypeNumber = identityDictionary[@"Type"]; + + if (!identity || ![identity isKindOfClass:[NSString class]]) { + allSuccess = NO; + *stop = YES; + return; + } + + if (identityTypeNumber == nil || ![identityTypeNumber isKindOfClass:[NSNumber class]]) { + allSuccess = NO; + *stop = YES; + return; + } + + MPIdentity identityType = (MPIdentity)[identityTypeNumber unsignedIntegerValue]; + [request setIdentity:identity identityType:identityType]; + }]; + + if (!allSuccess) { + return nil; + } + } + + NSString *identity = json[@"Identity"]; + NSNumber *identityTypeNumber = json[@"Type"]; + + if (identity && [identity isKindOfClass:[NSString class]] && + identityTypeNumber && [identityTypeNumber isKindOfClass:[NSNumber class]]) { + MPIdentity identityType = (MPIdentity)[identityTypeNumber unsignedIntegerValue]; + [request setIdentity:identity identityType:identityType]; + } + + return request; +} + +@end diff --git a/mParticle-Apple-SDK/Utils/MPConvertJS.swift b/mParticle-Apple-SDK/Utils/MPConvertJS.swift deleted file mode 100644 index 71472c495..000000000 --- a/mParticle-Apple-SDK/Utils/MPConvertJS.swift +++ /dev/null @@ -1,285 +0,0 @@ -internal import mParticle_Apple_SDK_Swift - -@objc public enum MPJSCommerceEventAction: UInt, @unchecked Sendable { - case unknown = 0 - case addToCart = 1 - case removeFromCart = 2 - case checkout = 3 - case checkoutOptions = 4 - case click = 5 - case viewDetail = 6 - case purchase = 7 - case refund = 8 - case addToWishList = 9 - case removeFromWishlist = 10 -} - -@objc public final class MPConvertJS_PRIVATE: NSObject { - @objc public static func commerceEventAction(_ json: NSNumber) -> MPCommerceEventAction { - switch json.uintValue { - case MPJSCommerceEventAction.addToCart.rawValue: - return .addToCart - case MPJSCommerceEventAction.removeFromCart.rawValue: - return .removeFromCart - case MPJSCommerceEventAction.checkout.rawValue: - return .checkout - case MPJSCommerceEventAction.checkoutOptions.rawValue: - return .checkoutOptions - case MPJSCommerceEventAction.click.rawValue: - return .click - case MPJSCommerceEventAction.viewDetail.rawValue: - return .viewDetail - case MPJSCommerceEventAction.purchase.rawValue: - return .purchase - case MPJSCommerceEventAction.refund.rawValue: - return .refund - case MPJSCommerceEventAction.addToWishList.rawValue: - return .addToWishList - case MPJSCommerceEventAction.removeFromWishlist.rawValue: - return .removeFromWishlist - default: - let mparticle = MParticle.sharedInstance() - let logger = MPLog(logLevel: MPLog.from(rawValue: mparticle.logLevel.rawValue)) - logger.customLogger = mparticle.customLogger - - logger.error("Invalid commerce event action received from webview: \(json)") - return .addToCart - } - } - - @objc public static func commerceEvent(_ json: [AnyHashable: Any]) -> MPCommerceEvent? { - let mparticle = MParticle.sharedInstance() - let logger = MPLog(logLevel: MPLog.from(rawValue: mparticle.logLevel.rawValue)) - logger.customLogger = mparticle.customLogger - - guard json["ProductAction"] == nil || json["ProductAction"] is [String: Any] else { - logger.error("Unexpected commerce event data received from webview") - return nil - } - - let productAction = json["ProductAction"] as? [String: Any] - let isProductAction = productAction?["ProductActionType"] != nil - let isPromotion = json["PromotionAction"] != nil - let isImpression = json["ProductImpressions"] != nil - let isValid = isProductAction || isPromotion || isImpression - - if !isValid { - logger.error("Invalid commerce event dictionary received from webview: \(json)") - return nil - } - - let commerceEvent: MPCommerceEvent - if isProductAction { - guard let productActionType = productAction?["ProductActionType"] as? NSNumber else { - logger.error("Unexpected product action type received from webview") - return nil - } - let action = Self.commerceEventAction(productActionType) - commerceEvent = MPCommerceEvent(action: action) - } else if isPromotion { - let promotionContainer = Self.promotionContainer(json) - commerceEvent = MPCommerceEvent(promotionContainer: promotionContainer) - } else { - commerceEvent = MPCommerceEvent(impressionName: nil, product: nil) - } - - if let eventAttributes = json["EventAttributes"] as? [String: Any] { - commerceEvent.customAttributes = eventAttributes - } - if let checkoutOptions = json["CheckoutOptions"] as? String { - commerceEvent.checkoutOptions = checkoutOptions - } - if let productActionListName = json["productActionListName"] as? String { - commerceEvent.productListName = productActionListName - } - if let productActionListSource = json["productActionListSource"] as? String { - commerceEvent.productListSource = productActionListSource - } - if let currencyCode = json["CurrencyCode"] as? String { - commerceEvent.currency = currencyCode - } - if let productAction = productAction { - commerceEvent.transactionAttributes = Self.transactionAttributes(productAction) - } - if let checkoutStep = json["CheckoutStep"] as? NSNumber { - commerceEvent.checkoutStep = checkoutStep.intValue - } - if let customFlags = json["CustomFlags"] as? [String: Any] { - for key in customFlags.keys { - if let valueArray = customFlags[key] as? [String] { - commerceEvent.addCustomFlags(valueArray, withKey: key) - } else if let valueString = customFlags[key] as? String { - commerceEvent.addCustomFlag(valueString, withKey: key) - } - } - } - - if let jsonProducts = productAction?["ProductList"] as? [[AnyHashable: Any]] { - let products = jsonProducts.map { Self.product($0) } - commerceEvent.addProducts(products) - } - - if let jsonImpressions = json["ProductImpressions"] as? [[AnyHashable: Any]] { - for jsonImpression in jsonImpressions { - if let listName = jsonImpression["ProductImpressionList"] as? String, - let jsonProducts = jsonImpression["ProductList"] as? [[AnyHashable: Any]] { - for jsonObject in jsonProducts { - let product = MPConvertJS_PRIVATE.product(jsonObject) - commerceEvent.addImpression(product, listName: listName) - } - } - } - } - - return commerceEvent - } - - @objc public static func promotionContainer(_ json: [AnyHashable: Any]) -> MPPromotionContainer? { - let mparticle = MParticle.sharedInstance() - let logger = MPLog(logLevel: MPLog.from(rawValue: mparticle.logLevel.rawValue)) - logger.customLogger = mparticle.customLogger - - guard let promotionDictionary = (json["PromotionAction"] as? [String: Any]) else { - logger.error("Unexpected promotion container action data received from webview") - return nil - } - - guard let promotionActionTypeNumber = (promotionDictionary["PromotionActionType"] as? NSNumber) else { - logger.error("Unexpected promotion container action type data received from webview") - return nil - } - - let promotionAction = promotionActionTypeNumber.intValue == 1 ? MPPromotionAction.view : MPPromotionAction.click - let promotionContainer = MPPromotionContainer(action: promotionAction, promotion: nil) - if let jsonPromotions = promotionDictionary["PromotionList"] as? [[AnyHashable: Any]] { - for jsonObject in jsonPromotions { - promotionContainer.addPromotion(MPConvertJS_PRIVATE.promotion(jsonObject)) - } - } else { - logger.error("Unexpected promotion container list data received from webview") - return nil - } - - return promotionContainer - } - - @objc public static func promotion(_ json: [AnyHashable: Any]) -> MPPromotion { - let promotion = MPPromotion() - - if let creative = json["Creative"] as? String { - promotion.creative = creative - } - if let name = json["Name"] as? String { - promotion.name = name - } - if let position = json["Position"] as? String { - promotion.position = position - } - if let id = json["Id"] as? String { - promotion.promotionId = id - } - - return promotion - } - - @objc public static func transactionAttributes(_ json: [AnyHashable: Any] = [:]) -> MPTransactionAttributes! { - let transactionAttributes = MPTransactionAttributes() - - if let affiliation = json["Affiliation"] as? String { - transactionAttributes.affiliation = affiliation - } - if let couponCode = json["CouponCode"] as? String { - transactionAttributes.couponCode = couponCode - } - if let shippingAmount = json["ShippingAmount"] as? NSNumber { - transactionAttributes.shipping = shippingAmount - } - if let taxAmount = json["TaxAmount"] as? NSNumber { - transactionAttributes.tax = taxAmount - } - if let totalAmount = json["TotalAmount"] as? NSNumber { - transactionAttributes.revenue = totalAmount - } - if let transactionId = json["TransactionId"] as? String { - transactionAttributes.transactionId = transactionId - } - - return transactionAttributes - } - - @objc public static func product(_ json: [AnyHashable: Any]) -> MPProduct { - let product = MPProduct() - - if let brand = json["Brand"] as? String { - product.brand = brand - } - if let category = json["Category"] as? String { - product.category = category - } - if let couponCode = json["CouponCode"] as? String { - product.couponCode = couponCode - } - if let name = json["Name"] as? String { - product.name = name - } - if let price = json["Price"] as? NSNumber { - product.price = price - } else if let price = json["Price"] as? String, let double = double_t(price) { - product.price = NSNumber(value: double) - } - if let sku = json["Sku"] as? String { - product.sku = sku - } - if let variant = json["Variant"] as? String { - product.variant = variant - } - if let position = json["Position"] as? NSNumber { - product.position = position.uintValue - } - if let quantity = json["Quantity"] as? NSNumber { - product.quantity = quantity - } - if let jsonAttributes = json["Attributes"] as? [String: String] { - for (key, value) in jsonAttributes { - product.setValue(value, forKey: key) - } - } - - return product - } - - @objc private static func identityFromNumber(_ identityTypeNumber: NSNumber) -> MPIdentity { - return MPIdentity(rawValue: identityTypeNumber.uintValue) ?? .other - } - - @objc public static func identityApiRequest(_ json: [AnyHashable: Any]?) -> MPIdentityApiRequest? { - let request = MPIdentityApiRequest.withEmptyUser() - - guard let userIdentities = json?["UserIdentities"] as? [[AnyHashable: Any]] else { - let mparticle = MParticle.sharedInstance() - let logger = MPLog(logLevel: MPLog.from(rawValue: mparticle.logLevel.rawValue)) - logger.customLogger = mparticle.customLogger - - logger.error("Unexpected user identity data received from webview") - return nil - } - - for identityDictionary in userIdentities { - if let identity = identityDictionary["Identity"] as? String, - let identityTypeNumber = identityDictionary["Type"] as? NSNumber, - let identityType = MPIdentity(rawValue: identityTypeNumber.uintValue) { - request.setIdentity(identity, identityType: identityType) - } else { - return nil - } - } - - if let identity = json?["Identity"] as? String, - let identityTypeNumber = json?["Type"] as? NSNumber, - let identityType = MPIdentity(rawValue: identityTypeNumber.uintValue) { - request.setIdentity(identity, identityType: identityType) - } - - return request - } -} diff --git a/mParticle-Apple-SDK/mParticle.m b/mParticle-Apple-SDK/mParticle.m index a7d18bae0..d5cf60ec8 100644 --- a/mParticle-Apple-SDK/mParticle.m +++ b/mParticle-Apple-SDK/mParticle.m @@ -19,6 +19,7 @@ #import "SettingsProvider.h" #import "Executor.h" #import "AppEnvironmentProvider.h" +#import "MPConvertJS.h" @import mParticle_Apple_SDK_Swift; static NSArray *eventTypeStrings = nil;