diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 2f9171511..a7d72f3e4 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -72,6 +72,7 @@ lint: - linters: [markdownlint] paths: - CHANGELOG.md # Standard changelog format violates MD001, MD024, MD025 + - kits/braze/braze-12/CHANGELOG.md actions: enabled: diff --git a/kits/braze/braze-12/CHANGELOG.md b/kits/braze/braze-12/CHANGELOG.md new file mode 100644 index 000000000..6ee59f592 --- /dev/null +++ b/kits/braze/braze-12/CHANGELOG.md @@ -0,0 +1,111 @@ +# [8.11.0](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/compare/v8.10.2...v8.11.0) (2024-11-19) + +### Bug Fixes + +- Bump min Braze SDK to 11.2 ([#102](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/issues/102)) ([03c3ab2](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/commit/03c3ab265131364e7caf00f7b67e017698916bb7)) + +### Features + +- Add sendProductName Option to Braze ([#100](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/issues/100)) ([f8deb1e](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/commit/f8deb1ea38ed016939e1ea9f25cfeacbaf8d7834)) + +## [8.10.2](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/compare/v8.10.1...v8.10.2) (2024-11-06) + +### Bug Fixes + +- prevent mpid with value 0 to forward to Braze ([#99](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/issues/99)) ([69255c2](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/commit/69255c28df2376fdf47b5fff25c3d67a6383069d)) + +## [8.10.1](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/compare/v8.10.0...v8.10.1) (2024-10-18) + +### Bug Fixes + +- Update consent attributes after kit is initialized ([#98](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/issues/98)) ([0d74b3c](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/commit/0d74b3c925e7c014c8d236a30bcf335d4dc4a4d5)) + +# [8.10.0](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/compare/v8.9.0...v8.10.0) (2024-10-16) + +### Features + +- Update Braze Kit to V11 ([#97](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/issues/97)) ([d93e071](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/commit/d93e0718f420c5a175c9e78d9387d021684cd351)) + +# [8.9.0](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/compare/v8.8.1...v8.9.0) (2024-10-02) + +### Features + +- Support workspace switching ([#95](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/issues/95)) ([a80ce1d](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/commit/a80ce1d35ee3b42e971849bf09e68c922901876c)) + +## [8.8.1](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/compare/v8.8.0...v8.8.1) (2024-09-30) + +### Bug Fixes + +- Update Package.swift to match Podspec ([#96](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/issues/96)) ([eddc5e5](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/commit/eddc5e57dfa24841fc2228109931994ff039bff0)) + +# [8.8.0](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/compare/v8.7.2...v8.8.0) (2024-09-09) + +### Features + +- Implement Google EU Consent ([#94](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/issues/94)) ([2f97151](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/commit/2f971517f1b6c7f25d0f489539c765510e1931d9)) + +## [8.7.2](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/compare/v8.7.1...v8.7.2) (2024-06-24) + +### Bug Fixes + +- Kit fails to start due to missing kitApi property ([#93](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/issues/93)) ([10bf509](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/commit/10bf5091b2e29b7e60a0b214b1b205fa8b360c05)) + +## [8.7.1](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/compare/v8.7.0...v8.7.1) (2024-06-18) + +### Bug Fixes + +- Forward screen events and collect IDFA configs when using an external Braze instance ([#92](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/issues/92)) ([a8c4706](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/commit/a8c4706518c804b977831b33d071f8fbbce61d54)) + +# [8.7.0](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/compare/v8.6.1...v8.7.0) (2024-06-12) + +### Features + +- Update External iD on Initialization ([#91](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/issues/91)) ([5e1d301](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/commit/5e1d301aacb2efa1a5c4dcd1686012da5bc24654)) + +## [8.6.1](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/compare/v8.6.0...v8.6.1) (2024-05-28) + +### Bug Fixes + +- Setting urlDelegate on Braze instance ([#90](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/issues/90)) ([596c250](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/commit/596c250ba2b6543cfbb534af4bc74781eed2b12d)) + +# [8.6.0](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/compare/v8.5.0...v8.6.0) (2024-05-01) + +### Features + +- Update to Braze 9.x.x SDK, improve privacy manifest support ([#89](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/issues/89)) ([979b259](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/commit/979b259de22c31fe4b1a8686756bcf5093b9642f)) + +# [8.5.0](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/compare/v8.4.0...v8.5.0) (2024-03-07) + +### Features + +- Update Braze with Privacy Manifest ([#88](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/issues/88)) ([8eb56d3](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/commit/8eb56d3a039ae3d7dd6ad33071743c084c5f585a)) + +# [8.4.0](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/compare/v8.3.1...v8.4.0) (2024-02-21) + +### Features + +- Allow setting Braze instance and override notification handling ([#87](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/issues/87)) ([c3c76c1](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/commit/c3c76c1724ce3c822b9c62cb40582871c7e032fe)) + +## [8.3.1](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/compare/v8.3.0...v8.3.1) (2024-02-07) + +### Bug Fixes + +- Deleting property stripping from product when sent unbundled ([#86](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/issues/86)) ([d0b477b](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/commit/d0b477b78b6e3364de9b5f0eaeabdf64c9ec45a0)) + +# [8.3.0](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/compare/v8.2.5...v8.3.0) (2023-12-13) + +### Features + +- Update Braze to Latest Version ([#85](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/issues/85)) ([559937b](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/commit/559937be481b2018d0a549efc6d077178e2e4aaf)) + +## [8.2.5](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/compare/v8.2.4...v8.2.5) (2023-12-08) + +### Bug Fixes + +- Use Umbrella Imports ([#84](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/issues/84)) ([2782055](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/commit/278205520c7bcfbadd9d08b40555cb422a316490)) + +## [8.2.4](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/compare/v8.2.3...v8.2.4) (2023-11-02) + +### Bug Fixes + +- Bundle commerce event setting ([#83](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/issues/83)) ([1fc5f40](https://github.com/mparticle-integrations/mparticle-apple-integration-appboy/commit/1fc5f401b4eed836c47d9e0705a5b66d38c9df1f)) diff --git a/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example.xcodeproj/project.pbxproj b/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example.xcodeproj/project.pbxproj new file mode 100644 index 000000000..3fc02feba --- /dev/null +++ b/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example.xcodeproj/project.pbxproj @@ -0,0 +1,359 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + 35CE10952F44CE6A00F2EAB7 /* mParticle-Braze in Frameworks */ = {isa = PBXBuildFile; productRef = 35CE10942F44CE6A00F2EAB7 /* mParticle-Braze */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 35CE10762F44CE3F00F2EAB7 /* SPM-Objc-Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "SPM-Objc-Example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 35CE108D2F44CE4000F2EAB7 /* Exceptions for "SPM-Objc-Example" folder in "SPM-Objc-Example" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = 35CE10752F44CE3F00F2EAB7 /* SPM-Objc-Example */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 35CE10782F44CE3F00F2EAB7 /* SPM-Objc-Example */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + 35CE108D2F44CE4000F2EAB7 /* Exceptions for "SPM-Objc-Example" folder in "SPM-Objc-Example" target */, + ); + path = "SPM-Objc-Example"; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + 35CE10732F44CE3F00F2EAB7 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 35CE10952F44CE6A00F2EAB7 /* mParticle-Braze in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 35CE106D2F44CE3F00F2EAB7 = { + isa = PBXGroup; + children = ( + 35CE10782F44CE3F00F2EAB7 /* SPM-Objc-Example */, + 35CE10772F44CE3F00F2EAB7 /* Products */, + ); + sourceTree = ""; + }; + 35CE10772F44CE3F00F2EAB7 /* Products */ = { + isa = PBXGroup; + children = ( + 35CE10762F44CE3F00F2EAB7 /* SPM-Objc-Example.app */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 35CE10752F44CE3F00F2EAB7 /* SPM-Objc-Example */ = { + isa = PBXNativeTarget; + buildConfigurationList = 35CE108E2F44CE4000F2EAB7 /* Build configuration list for PBXNativeTarget "SPM-Objc-Example" */; + buildPhases = ( + 35CE10722F44CE3F00F2EAB7 /* Sources */, + 35CE10732F44CE3F00F2EAB7 /* Frameworks */, + 35CE10742F44CE3F00F2EAB7 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 35CE10782F44CE3F00F2EAB7 /* SPM-Objc-Example */, + ); + name = "SPM-Objc-Example"; + packageProductDependencies = ( + 35CE10942F44CE6A00F2EAB7 /* mParticle-Braze */, + ); + productName = "SPM-Objc-Example"; + productReference = 35CE10762F44CE3F00F2EAB7 /* SPM-Objc-Example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 35CE106E2F44CE3F00F2EAB7 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastUpgradeCheck = 2620; + TargetAttributes = { + 35CE10752F44CE3F00F2EAB7 = { + CreatedOnToolsVersion = 26.2; + }; + }; + }; + buildConfigurationList = 35CE10712F44CE3F00F2EAB7 /* Build configuration list for PBXProject "SPM-Objc-Example" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 35CE106D2F44CE3F00F2EAB7; + minimizedProjectReferenceProxies = 1; + packageReferences = ( + 35CE10932F44CE6A00F2EAB7 /* XCLocalSwiftPackageReference "../../../braze-12" */, + ); + preferredProjectObjectVersion = 77; + productRefGroup = 35CE10772F44CE3F00F2EAB7 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 35CE10752F44CE3F00F2EAB7 /* SPM-Objc-Example */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 35CE10742F44CE3F00F2EAB7 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 35CE10722F44CE3F00F2EAB7 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 35CE108F2F44CE4000F2EAB7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "SPM-Objc-Example/Info.plist"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.mparticle.mParticle.SPM-Objc-Example"; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 35CE10902F44CE4000F2EAB7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "SPM-Objc-Example/Info.plist"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.mparticle.mParticle.SPM-Objc-Example"; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 35CE10912F44CE4000F2EAB7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 26.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 35CE10922F44CE4000F2EAB7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 26.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 35CE10712F44CE3F00F2EAB7 /* Build configuration list for PBXProject "SPM-Objc-Example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 35CE10912F44CE4000F2EAB7 /* Debug */, + 35CE10922F44CE4000F2EAB7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 35CE108E2F44CE4000F2EAB7 /* Build configuration list for PBXNativeTarget "SPM-Objc-Example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 35CE108F2F44CE4000F2EAB7 /* Debug */, + 35CE10902F44CE4000F2EAB7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 35CE10932F44CE6A00F2EAB7 /* XCLocalSwiftPackageReference "../../../braze-12" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = "../../../braze-12"; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 35CE10942F44CE6A00F2EAB7 /* mParticle-Braze */ = { + isa = XCSwiftPackageProductDependency; + productName = "mParticle-Braze"; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 35CE106E2F44CE3F00F2EAB7 /* Project object */; +} diff --git a/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/AppDelegate.h b/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/AppDelegate.h new file mode 100644 index 000000000..d79858b97 --- /dev/null +++ b/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/AppDelegate.h @@ -0,0 +1,7 @@ +#import + +@interface AppDelegate : UIResponder + + +@end + diff --git a/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/AppDelegate.m b/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/AppDelegate.m new file mode 100644 index 000000000..f6bd5d7d2 --- /dev/null +++ b/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/AppDelegate.m @@ -0,0 +1,38 @@ +#import "AppDelegate.h" +#import "mParticle.h" + +@interface AppDelegate () + +@end + +@implementation AppDelegate + + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [[MParticle sharedInstance] startWithOptions:[MParticleOptions optionsWithKey:@"REPLACE WITH YOUR MPARTICLE API KEY" secret:@"REPLACE WITH YOUR MPARTICLE API SECRET"]]; + [MParticle sharedInstance].logLevel = MPILogLevelVerbose; + + [[MParticle sharedInstance] logEvent:[[MPEvent alloc] initWithName:@"foo" type:MPEventTypeOther]]; + + return YES; +} + + +#pragma mark - UISceneSession lifecycle + + +- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role]; +} + + +- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet *)sceneSessions { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. +} + + +@end diff --git a/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/Assets.xcassets/AccentColor.colorset/Contents.json b/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 000000000..0afb3cf0e --- /dev/null +++ b/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors": [ + { + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/Assets.xcassets/AppIcon.appiconset/Contents.json b/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..c70a5bff1 --- /dev/null +++ b/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,35 @@ +{ + "images": [ + { + "idiom": "universal", + "platform": "ios", + "size": "1024x1024" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "idiom": "universal", + "platform": "ios", + "size": "1024x1024" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "tinted" + } + ], + "idiom": "universal", + "platform": "ios", + "size": "1024x1024" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/Assets.xcassets/Contents.json b/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/Assets.xcassets/Contents.json new file mode 100644 index 000000000..74d6a722c --- /dev/null +++ b/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/Base.lproj/LaunchScreen.storyboard b/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..865e9329f --- /dev/null +++ b/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/Base.lproj/Main.storyboard b/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/Base.lproj/Main.storyboard new file mode 100644 index 000000000..808a21ce7 --- /dev/null +++ b/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/Base.lproj/Main.storyboard @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/Info.plist b/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/Info.plist new file mode 100644 index 000000000..81ed29b76 --- /dev/null +++ b/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/Info.plist @@ -0,0 +1,25 @@ + + + + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + SceneDelegate + UISceneStoryboardFile + Main + + + + + + diff --git a/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/SceneDelegate.h b/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/SceneDelegate.h new file mode 100644 index 000000000..a19a1ce55 --- /dev/null +++ b/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/SceneDelegate.h @@ -0,0 +1,8 @@ +#import + +@interface SceneDelegate : UIResponder + +@property (strong, nonatomic) UIWindow * window; + +@end + diff --git a/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/SceneDelegate.m b/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/SceneDelegate.m new file mode 100644 index 000000000..e5ae2c95c --- /dev/null +++ b/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/SceneDelegate.m @@ -0,0 +1,50 @@ +#import "SceneDelegate.h" + +@interface SceneDelegate () + +@end + +@implementation SceneDelegate + + +- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). +} + + +- (void)sceneDidDisconnect:(UIScene *)scene { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). +} + + +- (void)sceneDidBecomeActive:(UIScene *)scene { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. +} + + +- (void)sceneWillResignActive:(UIScene *)scene { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). +} + + +- (void)sceneWillEnterForeground:(UIScene *)scene { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. +} + + +- (void)sceneDidEnterBackground:(UIScene *)scene { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. +} + + +@end diff --git a/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/ViewController.h b/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/ViewController.h new file mode 100644 index 000000000..a79652b56 --- /dev/null +++ b/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/ViewController.h @@ -0,0 +1,7 @@ +#import + +@interface ViewController : UIViewController + + +@end + diff --git a/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/ViewController.m b/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/ViewController.m new file mode 100644 index 000000000..19161aa7a --- /dev/null +++ b/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/ViewController.m @@ -0,0 +1,15 @@ +#import "ViewController.h" + +@interface ViewController () + +@end + +@implementation ViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do any additional setup after loading the view. +} + + +@end diff --git a/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/main.m b/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/main.m new file mode 100644 index 000000000..dba295ebe --- /dev/null +++ b/kits/braze/braze-12/Example/SPM-Objc-Example/SPM-Objc-Example/main.m @@ -0,0 +1,11 @@ +#import +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + NSString * appDelegateClassName; + @autoreleasepool { + // Setup code that might create autoreleased objects goes here. + appDelegateClassName = NSStringFromClass([AppDelegate class]); + } + return UIApplicationMain(argc, argv, nil, appDelegateClassName); +} diff --git a/kits/braze/braze-12/Example/SPM-Swift-Example/SPM-Swift-Example.xcodeproj/project.pbxproj b/kits/braze/braze-12/Example/SPM-Swift-Example/SPM-Swift-Example.xcodeproj/project.pbxproj new file mode 100644 index 000000000..2c831ead7 --- /dev/null +++ b/kits/braze/braze-12/Example/SPM-Swift-Example/SPM-Swift-Example.xcodeproj/project.pbxproj @@ -0,0 +1,369 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + 35CCA5FB2F44CF5200550EBC /* mParticle-Braze in Frameworks */ = {isa = PBXBuildFile; productRef = 35CCA5FA2F44CF5200550EBC /* mParticle-Braze */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 35CCA5E12F44CF3900550EBC /* SPM-Swift-Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "SPM-Swift-Example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 35CCA5F32F44CF3A00550EBC /* Exceptions for "SPM-Swift-Example" folder in "SPM-Swift-Example" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = 35CCA5E02F44CF3900550EBC /* SPM-Swift-Example */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 35CCA5E32F44CF3900550EBC /* SPM-Swift-Example */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + 35CCA5F32F44CF3A00550EBC /* Exceptions for "SPM-Swift-Example" folder in "SPM-Swift-Example" target */, + ); + path = "SPM-Swift-Example"; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + 35CCA5DE2F44CF3900550EBC /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 35CCA5FB2F44CF5200550EBC /* mParticle-Braze in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 35CCA5D82F44CF3900550EBC = { + isa = PBXGroup; + children = ( + 35CCA5E32F44CF3900550EBC /* SPM-Swift-Example */, + 35CCA5E22F44CF3900550EBC /* Products */, + ); + sourceTree = ""; + }; + 35CCA5E22F44CF3900550EBC /* Products */ = { + isa = PBXGroup; + children = ( + 35CCA5E12F44CF3900550EBC /* SPM-Swift-Example.app */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 35CCA5E02F44CF3900550EBC /* SPM-Swift-Example */ = { + isa = PBXNativeTarget; + buildConfigurationList = 35CCA5F42F44CF3A00550EBC /* Build configuration list for PBXNativeTarget "SPM-Swift-Example" */; + buildPhases = ( + 35CCA5DD2F44CF3900550EBC /* Sources */, + 35CCA5DE2F44CF3900550EBC /* Frameworks */, + 35CCA5DF2F44CF3900550EBC /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 35CCA5E32F44CF3900550EBC /* SPM-Swift-Example */, + ); + name = "SPM-Swift-Example"; + packageProductDependencies = ( + 35CCA5FA2F44CF5200550EBC /* mParticle-Braze */, + ); + productName = "SPM-Swift-Example"; + productReference = 35CCA5E12F44CF3900550EBC /* SPM-Swift-Example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 35CCA5D92F44CF3900550EBC /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 2620; + LastUpgradeCheck = 2620; + TargetAttributes = { + 35CCA5E02F44CF3900550EBC = { + CreatedOnToolsVersion = 26.2; + }; + }; + }; + buildConfigurationList = 35CCA5DC2F44CF3900550EBC /* Build configuration list for PBXProject "SPM-Swift-Example" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 35CCA5D82F44CF3900550EBC; + minimizedProjectReferenceProxies = 1; + packageReferences = ( + 35CCA5F92F44CF5200550EBC /* XCLocalSwiftPackageReference "../../../braze-12" */, + ); + preferredProjectObjectVersion = 77; + productRefGroup = 35CCA5E22F44CF3900550EBC /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 35CCA5E02F44CF3900550EBC /* SPM-Swift-Example */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 35CCA5DF2F44CF3900550EBC /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 35CCA5DD2F44CF3900550EBC /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 35CCA5F52F44CF3A00550EBC /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "SPM-Swift-Example/Info.plist"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.mparticle.mParticle.SPM-Swift-Example"; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 35CCA5F62F44CF3A00550EBC /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "SPM-Swift-Example/Info.plist"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.mparticle.mParticle.SPM-Swift-Example"; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 35CCA5F72F44CF3A00550EBC /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 26.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 35CCA5F82F44CF3A00550EBC /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 26.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 35CCA5DC2F44CF3900550EBC /* Build configuration list for PBXProject "SPM-Swift-Example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 35CCA5F72F44CF3A00550EBC /* Debug */, + 35CCA5F82F44CF3A00550EBC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 35CCA5F42F44CF3A00550EBC /* Build configuration list for PBXNativeTarget "SPM-Swift-Example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 35CCA5F52F44CF3A00550EBC /* Debug */, + 35CCA5F62F44CF3A00550EBC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 35CCA5F92F44CF5200550EBC /* XCLocalSwiftPackageReference "../../../braze-12" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = "../../../braze-12"; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 35CCA5FA2F44CF5200550EBC /* mParticle-Braze */ = { + isa = XCSwiftPackageProductDependency; + productName = "mParticle-Braze"; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 35CCA5D92F44CF3900550EBC /* Project object */; +} diff --git a/kits/braze/braze-12/Example/SPM-Swift-Example/SPM-Swift-Example/AppDelegate.swift b/kits/braze/braze-12/Example/SPM-Swift-Example/SPM-Swift-Example/AppDelegate.swift new file mode 100644 index 000000000..1d78ad17b --- /dev/null +++ b/kits/braze/braze-12/Example/SPM-Swift-Example/SPM-Swift-Example/AppDelegate.swift @@ -0,0 +1,41 @@ +import UIKit +import mParticle_Apple_SDK + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + let mparticle = MParticle.sharedInstance() + mparticle.start(with: .init( + key: "REPLACE WITH YOUR MPARTICLE API KEY", + secret: "REPLACE WITH YOUR MPARTICLE API SECRET" + )) + mparticle.logLevel = .verbose + if let event = MPEvent(name: "foo", type: .other) { + mparticle.logEvent(event) + } + + return true + } + + // MARK: UISceneSession Lifecycle + + func application( + _ application: UIApplication, + configurationForConnecting connectingSceneSession: UISceneSession, + options: UIScene.ConnectionOptions + ) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + } +} diff --git a/kits/braze/braze-12/Example/SPM-Swift-Example/SPM-Swift-Example/Assets.xcassets/AccentColor.colorset/Contents.json b/kits/braze/braze-12/Example/SPM-Swift-Example/SPM-Swift-Example/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 000000000..0afb3cf0e --- /dev/null +++ b/kits/braze/braze-12/Example/SPM-Swift-Example/SPM-Swift-Example/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors": [ + { + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/kits/braze/braze-12/Example/SPM-Swift-Example/SPM-Swift-Example/Assets.xcassets/AppIcon.appiconset/Contents.json b/kits/braze/braze-12/Example/SPM-Swift-Example/SPM-Swift-Example/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..c70a5bff1 --- /dev/null +++ b/kits/braze/braze-12/Example/SPM-Swift-Example/SPM-Swift-Example/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,35 @@ +{ + "images": [ + { + "idiom": "universal", + "platform": "ios", + "size": "1024x1024" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "idiom": "universal", + "platform": "ios", + "size": "1024x1024" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "tinted" + } + ], + "idiom": "universal", + "platform": "ios", + "size": "1024x1024" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/kits/braze/braze-12/Example/SPM-Swift-Example/SPM-Swift-Example/Assets.xcassets/Contents.json b/kits/braze/braze-12/Example/SPM-Swift-Example/SPM-Swift-Example/Assets.xcassets/Contents.json new file mode 100644 index 000000000..74d6a722c --- /dev/null +++ b/kits/braze/braze-12/Example/SPM-Swift-Example/SPM-Swift-Example/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/kits/braze/braze-12/Example/SPM-Swift-Example/SPM-Swift-Example/Base.lproj/LaunchScreen.storyboard b/kits/braze/braze-12/Example/SPM-Swift-Example/SPM-Swift-Example/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..865e9329f --- /dev/null +++ b/kits/braze/braze-12/Example/SPM-Swift-Example/SPM-Swift-Example/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kits/braze/braze-12/Example/SPM-Swift-Example/SPM-Swift-Example/Base.lproj/Main.storyboard b/kits/braze/braze-12/Example/SPM-Swift-Example/SPM-Swift-Example/Base.lproj/Main.storyboard new file mode 100644 index 000000000..25a763858 --- /dev/null +++ b/kits/braze/braze-12/Example/SPM-Swift-Example/SPM-Swift-Example/Base.lproj/Main.storyboard @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kits/braze/braze-12/Example/SPM-Swift-Example/SPM-Swift-Example/Info.plist b/kits/braze/braze-12/Example/SPM-Swift-Example/SPM-Swift-Example/Info.plist new file mode 100644 index 000000000..dd3c9afda --- /dev/null +++ b/kits/braze/braze-12/Example/SPM-Swift-Example/SPM-Swift-Example/Info.plist @@ -0,0 +1,25 @@ + + + + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + + diff --git a/kits/braze/braze-12/Example/SPM-Swift-Example/SPM-Swift-Example/SceneDelegate.swift b/kits/braze/braze-12/Example/SPM-Swift-Example/SPM-Swift-Example/SceneDelegate.swift new file mode 100644 index 000000000..ff939c704 --- /dev/null +++ b/kits/braze/braze-12/Example/SPM-Swift-Example/SPM-Swift-Example/SceneDelegate.swift @@ -0,0 +1,42 @@ +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + guard (scene as? UIWindowScene) != nil else { return } + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } + +} diff --git a/kits/braze/braze-12/Example/SPM-Swift-Example/SPM-Swift-Example/ViewController.swift b/kits/braze/braze-12/Example/SPM-Swift-Example/SPM-Swift-Example/ViewController.swift new file mode 100644 index 000000000..b9a63bbde --- /dev/null +++ b/kits/braze/braze-12/Example/SPM-Swift-Example/SPM-Swift-Example/ViewController.swift @@ -0,0 +1,4 @@ +import UIKit + +class ViewController: UIViewController { +} diff --git a/kits/braze/braze-12/LICENSE b/kits/braze/braze-12/LICENSE new file mode 100644 index 000000000..1bccc65a9 --- /dev/null +++ b/kits/braze/braze-12/LICENSE @@ -0,0 +1,191 @@ +Copyright 2019 mParticle, Inc. + +Licensed under the Apache License, 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 + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/kits/braze/braze-12/Package.resolved b/kits/braze/braze-12/Package.resolved new file mode 100644 index 000000000..b8450911a --- /dev/null +++ b/kits/braze/braze-12/Package.resolved @@ -0,0 +1,41 @@ +{ + "pins" : [ + { + "identity" : "braze-swift-sdk", + "kind" : "remoteSourceControl", + "location" : "https://github.com/braze-inc/braze-swift-sdk", + "state" : { + "revision" : "c630122e490fb156be2971b1f24d790488d33ac5", + "version" : "12.1.0" + } + }, + { + "identity" : "mparticle-apple-sdk", + "kind" : "remoteSourceControl", + "location" : "https://github.com/mParticle/mparticle-apple-sdk", + "state" : { + "branch" : "workstation/9.0-Release", + "revision" : "d26eb54c9a862a996e8ff9e7515be87115030ba5" + } + }, + { + "identity" : "ocmock", + "kind" : "remoteSourceControl", + "location" : "https://github.com/erikdoe/ocmock", + "state" : { + "branch" : "master", + "revision" : "7f6e9e9b6c9ffa19a9040860da9f7a1f202b9f4d" + } + }, + { + "identity" : "sdwebimage", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SDWebImage/SDWebImage.git", + "state" : { + "revision" : "449e8f8f10377f620db8ad22ea81208eecf6325f", + "version" : "5.21.6" + } + } + ], + "version" : 2 +} diff --git a/kits/braze/braze-12/Package.swift b/kits/braze/braze-12/Package.swift new file mode 100644 index 000000000..d1d4295af --- /dev/null +++ b/kits/braze/braze-12/Package.swift @@ -0,0 +1,48 @@ +// swift-tools-version:5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "mParticle-Braze", + platforms: [ .iOS(.v15), .tvOS(.v15) ], + products: [ + .library( + name: "mParticle-Braze", + targets: ["mParticle-Braze"] + ) + ], + dependencies: [ + .package( + url: "https://github.com/mParticle/mparticle-apple-sdk", + branch: "workstation/9.0-Release" + ), + .package( + url: "https://github.com/braze-inc/braze-swift-sdk", + .upToNextMajor(from: "12.0.0") + ), + .package( + url: "https://github.com/erikdoe/ocmock", + branch: "master" + ) + ], + targets: [ + .target( + name: "mParticle-Braze", + dependencies: [ + .product(name: "mParticle-Apple-SDK", package: "mParticle-Apple-SDK"), + .product(name: "BrazeUI", package: "braze-swift-sdk", condition: .when(platforms: [.iOS])), + .product(name: "BrazeKit", package: "braze-swift-sdk"), + .product(name: "BrazeKitCompat", package: "braze-swift-sdk") + ], + resources: [.process("PrivacyInfo.xcprivacy")] + ), + .testTarget( + name: "mParticle-BrazeTests", + dependencies: [ + "mParticle-Braze", + .product(name: "OCMock", package: "ocmock") + ] + ) + ] +) diff --git a/kits/braze/braze-12/README.md b/kits/braze/braze-12/README.md new file mode 100644 index 000000000..48b2e8b49 --- /dev/null +++ b/kits/braze/braze-12/README.md @@ -0,0 +1,27 @@ +# Braze (formerly Appboy) Kit Integration + +This repository contains the [Braze](https://www.braze.com) integration for the [mParticle Apple SDK](https://github.com/mParticle/mparticle-apple-sdk) using the latest [Braze Swift SDK](https://github.com/braze-inc/braze-swift-sdk/). + +## Adding the integration + +1. Add the kit dependency using SPM: + + ```swift + github "mparticle-integrations/mparticle-apple-integration-braze-12" ~> 8.0 + ``` + +2. If using SPM, make sure to add the `-ObjC` flag to the target's `Other Linker Flags` setting in Xcode, according to the [Braze documentation](https://www.braze.com/docs/developer_guide/platform_integration_guides/ios/initial_sdk_setup/installation_methods/swift_package_manager#step-2-configuring-your-project). + +3. Follow the mParticle iOS SDK [quick-start](https://github.com/mParticle/mparticle-apple-sdk), then rebuild and launch your app, and verify that you see `"Included kits: { Appboy }"` in your Xcode console + +> (This requires your mParticle log level to be at least Debug) + +4. Reference mParticle's integration docs below to enable the integration. + +## Documentation + +[Braze integration](https://docs.mparticle.com/integrations/braze/event/) + +## License + +[Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0) diff --git a/kits/braze/braze-12/Sources/Info.plist b/kits/braze/braze-12/Sources/Info.plist new file mode 100644 index 000000000..d3de8eefb --- /dev/null +++ b/kits/braze/braze-12/Sources/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/kits/braze/braze-12/Sources/mParticle-Braze/MPKitAppboy.m b/kits/braze/braze-12/Sources/mParticle-Braze/MPKitAppboy.m new file mode 100644 index 000000000..27fcf20b0 --- /dev/null +++ b/kits/braze/braze-12/Sources/mParticle-Braze/MPKitAppboy.m @@ -0,0 +1,1308 @@ +#import "MPKitAppboy.h" + +#if TARGET_OS_IOS + @import BrazeKit; + @import BrazeKitCompat; + @import BrazeUI; +#else + @import BrazeKit; + @import BrazeKitCompat; +#endif + +static NSString *const eabAPIKey = @"apiKey"; +static NSString *const eabOptions = @"options"; +static NSString *const hostConfigKey = @"host"; +static NSString *const userIdTypeKey = @"userIdentificationType"; +static NSString *const emailIdTypeKey = @"emailIdentificationType"; +static NSString *const enableTypeDetectionKey = @"enableTypeDetection"; +static NSString *const bundleCommerceEventData = @"bundleCommerceEventData"; +static NSString *const replaceSkuWithProductName = @"replaceSkuWithProductName"; +static NSString *const subscriptionGroupMapping = @"subscriptionGroupMapping"; + +// The possible values for userIdentificationType +static NSString *const userIdValueOther = @"Other"; +static NSString *const userIdValueOther2 = @"Other2"; +static NSString *const userIdValueOther3 = @"Other3"; +static NSString *const userIdValueOther4 = @"Other4"; +static NSString *const userIdValueOther5 = @"Other5"; +static NSString *const userIdValueOther6 = @"Other6"; +static NSString *const userIdValueOther7 = @"Other7"; +static NSString *const userIdValueOther8 = @"Other8"; +static NSString *const userIdValueOther9 = @"Other9"; +static NSString *const userIdValueOther10 = @"Other10"; +static NSString *const userIdValueCustomerId = @"CustomerId"; +static NSString *const userIdValueFacebook = @"Facebook"; +static NSString *const userIdValueTwitter = @"Twitter"; +static NSString *const userIdValueGoogle = @"Google"; +static NSString *const userIdValueMicrosoft = @"Microsoft"; +static NSString *const userIdValueYahoo = @"Yahoo"; +static NSString *const userIdValueEmail = @"Email"; +static NSString *const userIdValueAlias = @"Alias"; +static NSString *const userIdValueMPID = @"MPID"; + +// User Attribute key with reserved functionality for Braze kit +static NSString *const brazeUserAttributeDob = @"dob"; +static NSString *const brazeUserAttributeEmailSubscribe = @"email_subscribe"; +static NSString *const brazeUserAttributePushSubscribe = @"push_subscribe"; + +// Strings used when sending enhanced commerce events +static NSString *const attributesKey = @"Attributes"; +static NSString *const productKey = @"products"; +static NSString *const promotionKey = @"promotions"; +static NSString *const impressionKey = @"impressions"; + +// Strings used for Google Consent +static NSString *const MPMapKey = @"map"; +static NSString *const MPValueKey = @"value"; +static NSString *const MPConsentMappingSDKKey = @"consentMappingSDK"; +static NSString *const MPGoogleAdUserDataKey = @"google_ad_user_data"; +static NSString *const MPGoogleAdPersonalizationKey = @"google_ad_personalization"; +static NSString *const BGoogleAdUserDataKey = @"$google_ad_user_data"; +static NSString *const BGoogleAdPersonalizationKey = @"$google_ad_personalization"; + +#if TARGET_OS_IOS +static id inAppMessageControllerDelegate = nil; +static BOOL shouldDisableNotificationHandling = NO; +#endif +static id urlDelegate = nil; +static Braze *brazeInstance = nil; +static id brazeLocationProvider = nil; +static NSSet *brazeTrackingPropertyAllowList; + +@interface MPKitAppboy() { + Braze *appboyInstance; + BOOL collectIDFA; + BOOL forwardScreenViews; + NSMutableDictionary *subscriptionGroupDictionary; +} + +@property (nonatomic) NSString *host; +@property (nonatomic) BOOL enableTypeDetection; + +@end + + +@implementation MPKitAppboy + ++ (NSNumber *)kitCode { + return @28; +} + ++ (void)load { + MPKitRegister *kitRegister = [[MPKitRegister alloc] initWithName:@"Appboy" className:@"MPKitAppboy"]; + [MParticle registerExtension:kitRegister]; +} + +#if TARGET_OS_IOS ++ (void)setInAppMessageControllerDelegate:(id)delegate { + inAppMessageControllerDelegate = (id)delegate; +} + ++ (id)inAppMessageControllerDelegate { + return inAppMessageControllerDelegate; +} + ++ (void)setShouldDisableNotificationHandling:(BOOL)isDisabled { + shouldDisableNotificationHandling = isDisabled; +} + ++ (BOOL)shouldDisableNotificationHandling { + return shouldDisableNotificationHandling; +} + +#endif + ++ (void)setURLDelegate:(id)delegate { + urlDelegate = (id)delegate; +} + ++ (id)urlDelegate { + return urlDelegate; +} + ++ (void)setBrazeInstance:(id)instance { + if ([instance isKindOfClass:[Braze class]]) { + brazeInstance = instance; + } +} + ++ (Braze *)brazeInstance { + return brazeInstance; +} + ++ (void)setBrazeLocationProvider:(nonnull id)instance { + brazeLocationProvider = instance; +} + ++ (void)setBrazeTrackingPropertyAllowList:(nonnull NSSet *)allowList { + for (id property in allowList) { + if (![property isKindOfClass:[BRZTrackingProperty class]]) { + return; + } + } + brazeTrackingPropertyAllowList = allowList; +} + +#pragma mark Private methods +- (NSString *)stringRepresentation:(id)value { + NSString *stringRepresentation = nil; + + if ([value isKindOfClass:[NSString class]]) { + stringRepresentation = value; + } else if ([value isKindOfClass:[NSNumber class]]) { + stringRepresentation = [(NSNumber *)value stringValue]; + } else if ([value isKindOfClass:[NSDate class]]) { + stringRepresentation = [MPKitAPI stringFromDateRFC3339:value]; + } else if ([value isKindOfClass:[NSData class]]) { + stringRepresentation = [[NSString alloc] initWithData:value encoding:NSUTF8StringEncoding]; + } else { + return nil; + } + + return stringRepresentation; +} + +- (Braze *)appboyInstance { + return self->appboyInstance; +} + +- (void)setAppboyInstance:(Braze *)instance { + self->appboyInstance = instance; +} + +- (NSString *)stripCharacter:(NSString *)character fromString:(NSString *)originalString { + NSRange range = [originalString rangeOfString:character]; + + if (range.location == 0) { + NSMutableString *strippedString = [originalString mutableCopy]; + [strippedString replaceOccurrencesOfString:character withString:@"" options:NSCaseInsensitiveSearch range:range]; + return [strippedString copy]; + } else { + return originalString; + } +} + +- (NSMutableDictionary *)getSubscriptionGroupIds:(NSString *)subscriptionGroupMap { + NSMutableDictionary *subscriptionGroupDictionary = [NSMutableDictionary dictionary]; + + if (!subscriptionGroupMap.length) { + return subscriptionGroupDictionary; + } + + NSData *subsctiprionGroupData = [subscriptionGroupMap dataUsingEncoding:NSUTF8StringEncoding]; + + NSError *error = nil; + NSArray *subsctiprionGroupDataArray = [NSJSONSerialization JSONObjectWithData:subsctiprionGroupData options:0 error:&error]; + + for (NSDictionary *item in subsctiprionGroupDataArray) { + NSString *key = item[@"map"]; + NSString *value = item[@"value"]; + subscriptionGroupDictionary[key] = value; + } + + return subscriptionGroupDictionary; +} + +- (MPKitExecStatus *)logAppboyCustomEvent:(MPEvent *)event eventType:(NSUInteger)eventType { + void (^logCustomEvent)(void) = ^{ + NSDictionary *transformedEventInfo = [event.customAttributes transformValuesToString]; + + NSMutableDictionary *eventInfo = [[NSMutableDictionary alloc] initWithCapacity:event.customAttributes.count]; + [transformedEventInfo enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { + NSString *strippedKey = [self stripCharacter:@"$" fromString:key]; + eventInfo[strippedKey] = obj; + }]; + + NSDictionary *detectedEventInfo = eventInfo; + if (self->_enableTypeDetection) { + detectedEventInfo = [self simplifiedDictionary:eventInfo]; + } + + // Appboy expects that the properties are non empty when present. + if (detectedEventInfo && detectedEventInfo.count > 0) { + [self->appboyInstance logCustomEvent:event.name properties:detectedEventInfo]; + } else { + [self->appboyInstance logCustomEvent:event.name]; + } + + NSString *eventTypeString = [@(eventType) stringValue]; + + for (NSString *key in eventInfo) { + NSString *eventTypePlusNamePlusKey = [[NSString stringWithFormat:@"%@%@%@", eventTypeString, event.name, key] lowercaseString]; + NSString *hashValue = [MPKitAPI hashString:eventTypePlusNamePlusKey]; + + NSDictionary *forwardUserAttributes; + + // Delete from array + forwardUserAttributes = self.configuration[@"ear"]; + if (forwardUserAttributes[hashValue]) { + [self->appboyInstance.user removeFromCustomAttributeStringArrayWithKey:forwardUserAttributes[hashValue] value:eventInfo[key]]; + } + + // Add to array + forwardUserAttributes = self.configuration[@"eaa"]; + if (forwardUserAttributes[hashValue]) { + [self->appboyInstance.user addToCustomAttributeStringArrayWithKey:forwardUserAttributes[hashValue] value:eventInfo[key]]; + } + + // Add key/value pair + forwardUserAttributes = self.configuration[@"eas"]; + if (forwardUserAttributes[hashValue]) { + [self setUserAttribute:forwardUserAttributes[hashValue] value:eventInfo[key]]; + } + } + }; + + if ([NSThread isMainThread]) { + logCustomEvent(); + } else { + dispatch_async(dispatch_get_main_queue(), logCustomEvent); + } + + MPKitExecStatus *execStatus = [[MPKitExecStatus alloc] initWithSDKCode:@(MPKitInstanceAppboy) returnCode:MPKitReturnCodeSuccess]; + return execStatus; +} + +- (BOOL)isAdvertisingTrackingEnabled { + BOOL advertisingTrackingEnabled = NO; + Class MPIdentifierManager = NSClassFromString(@"ASIdentifierManager"); + + if (MPIdentifierManager) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wundeclared-selector" +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + SEL selector = NSSelectorFromString(@"sharedManager"); + id adIdentityManager = [MPIdentifierManager performSelector:selector]; + selector = NSSelectorFromString(@"isAdvertisingTrackingEnabled"); + advertisingTrackingEnabled = (BOOL)[adIdentityManager performSelector:selector]; +#pragma clang diagnostic pop +#pragma clang diagnostic pop + } + + return advertisingTrackingEnabled && collectIDFA; +} + +- (NSString *)advertisingIdentifierString { + NSString *_advertiserId = nil; + Class MPIdentifierManager = NSClassFromString(@"ASIdentifierManager"); + + if (MPIdentifierManager) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wundeclared-selector" +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + SEL selector = NSSelectorFromString(@"sharedManager"); + id adIdentityManager = [MPIdentifierManager performSelector:selector]; + + selector = NSSelectorFromString(@"isAdvertisingTrackingEnabled"); + BOOL advertisingTrackingEnabled = (BOOL)[adIdentityManager performSelector:selector]; + if (advertisingTrackingEnabled) { + selector = NSSelectorFromString(@"advertisingIdentifier"); + _advertiserId = [[adIdentityManager performSelector:selector] UUIDString]; + } +#pragma clang diagnostic pop +#pragma clang diagnostic pop + } + + return _advertiserId; +} + +- (BOOL)isAppTrackingEnabled { + BOOL appTrackingEnabled = NO; + Class ATTrackingManager = NSClassFromString(@"ATTrackingManager"); + + if (ATTrackingManager) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wundeclared-selector" +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + SEL selector = NSSelectorFromString(@"trackingAuthorizationStatus"); + NSUInteger trackingAuthorizationStatus = (NSUInteger)[ATTrackingManager performSelector:selector]; + appTrackingEnabled = (trackingAuthorizationStatus == 3); // ATTrackingManagerAuthorizationStatusAuthorized +#pragma clang diagnostic pop +#pragma clang diagnostic pop + } + + return appTrackingEnabled; +} + +#pragma mark MPKitInstanceProtocol methods +- (MPKitExecStatus *)didFinishLaunchingWithConfiguration:(NSDictionary *)configuration { + + // Use the static braze instance if set + [self setAppboyInstance:brazeInstance]; + + MPKitExecStatus *execStatus = nil; + + if (!configuration[eabAPIKey]) { + execStatus = [[MPKitExecStatus alloc] initWithSDKCode:[[self class] kitCode] returnCode:MPKitReturnCodeRequirementsNotMet]; + return execStatus; + } + + _configuration = configuration; + + collectIDFA = NO; + forwardScreenViews = NO; + + _host = configuration[hostConfigKey]; + _enableTypeDetection = [configuration[enableTypeDetectionKey] boolValue]; + + //If Braze is already initialized, immediately "start" the kit, this + //is here for: + // 1. Apps that initialize Braze prior to mParticle, and/or + // 2. Apps that initialize mParticle too late, causing the SDK to miss + // the launch notification which would otherwise trigger start(). + if (self->appboyInstance) { + NSLog(@"mParticle -> Warning: Braze SDK initialized outside of mParticle kit, this will mean Braze settings within the mParticle dashboard such as API key, endpoint URL, flush interval and others will not be respected."); + [self start]; + } else { + _started = NO; + } + + execStatus = [[MPKitExecStatus alloc] initWithSDKCode:[[self class] kitCode] returnCode:MPKitReturnCodeSuccess]; + return execStatus; +} + +- (id const)providerKitInstance { + return [self started] ? appboyInstance : nil; +} + +- (void)start { + if (!self->appboyInstance) { + NSDictionary *optionsDict = [self optionsDictionary]; + BRZConfiguration *configuration = [[BRZConfiguration alloc] initWithApiKey:self.configuration[eabAPIKey] endpoint:optionsDict[ABKEndpointKey]]; + + [configuration.api addSDKMetadata:@[BRZSDKMetadata.mparticle]]; + configuration.api.sdkFlavor = ((NSNumber *)optionsDict[ABKSDKFlavorKey]).intValue; + configuration.api.requestPolicy = ((NSNumber *)optionsDict[ABKRequestProcessingPolicyOptionKey]).intValue; + NSNumber *flushIntervalOption = (NSNumber *)optionsDict[ABKFlushIntervalOptionKey] ?: @10; // If not set, use the default 10 seconds specified in Braze SDK header + configuration.api.flushInterval = flushIntervalOption.doubleValue < 1.0 ? 1.0 : flushIntervalOption.doubleValue; // Ensure value is above the minimum of 1.0 per run time warning from Braze SDK + configuration.api.trackingPropertyAllowList = brazeTrackingPropertyAllowList; + + configuration.sessionTimeout = ((NSNumber *)optionsDict[ABKSessionTimeoutKey]).doubleValue; + + configuration.triggerMinimumTimeInterval = ((NSNumber *)optionsDict[ABKMinimumTriggerTimeIntervalKey]).doubleValue; + + NSNumber *automaticLocationTrackingOption = (NSNumber *)optionsDict[ABKEnableAutomaticLocationCollectionKey]; + if (automaticLocationTrackingOption != nil && automaticLocationTrackingOption.boolValue && brazeLocationProvider) { + configuration.location.automaticLocationCollection = YES; + configuration.location.brazeLocationProvider = brazeLocationProvider; + } + + self->appboyInstance = [[Braze alloc] initWithConfiguration:configuration]; + } + + if (!self->appboyInstance) { + return; + } + + self->forwardScreenViews = self.configuration[@"forwardScreenViews"] && [self.configuration[@"forwardScreenViews"] caseInsensitiveCompare:@"true"] == NSOrderedSame; + + self->collectIDFA = self.configuration[@"ABKCollectIDFA"] && [self.configuration[@"ABKCollectIDFA"] caseInsensitiveCompare:@"true"] == NSOrderedSame; + + if (self->collectIDFA) { + [self->appboyInstance setIdentifierForAdvertiser:[self advertisingIdentifierString]]; + } + [self->appboyInstance setAdTrackingEnabled:[self isAppTrackingEnabled]]; + + if ([MPKitAppboy urlDelegate]) { + self->appboyInstance.delegate = [MPKitAppboy urlDelegate]; + } + + self->subscriptionGroupDictionary = [self getSubscriptionGroupIds:self.configuration[subscriptionGroupMapping]]; + +#if TARGET_OS_IOS + BrazeInAppMessageUI *inAppMessageUI = [[BrazeInAppMessageUI alloc] init]; + inAppMessageUI.delegate = [MPKitAppboy inAppMessageControllerDelegate]; + [self->appboyInstance setInAppMessagePresenter:inAppMessageUI]; +#endif + + FilteredMParticleUser *currentUser = [[self kitApi] getCurrentUserWithKit:self]; + if (currentUser.userId.integerValue != 0) { + [self updateUser:currentUser request:currentUser.userIdentities]; + } + + self->_started = YES; + + // Update Consent on start + [self updateConsent]; + + dispatch_async(dispatch_get_main_queue(), ^{ + NSDictionary *userInfo = @{mParticleKitInstanceKey:[[self class] kitCode]}; + + [[NSNotificationCenter defaultCenter] postNotificationName:mParticleKitDidBecomeActiveNotification + object:nil + userInfo:userInfo]; + }); +} + +- (void)stop { + self->appboyInstance = nil; + _started = NO; + _configuration = nil; +} + +- (NSMutableDictionary *)optionsDictionary { + NSArray *serverKeys = @[@"ABKRequestProcessingPolicyOptionKey", @"ABKFlushIntervalOptionKey", @"ABKSessionTimeoutKey", @"ABKMinimumTriggerTimeIntervalKey"]; + NSArray *appboyKeys = @[ABKRequestProcessingPolicyOptionKey, ABKFlushIntervalOptionKey, ABKSessionTimeoutKey, ABKMinimumTriggerTimeIntervalKey]; + NSMutableDictionary *optionsDictionary = [[NSMutableDictionary alloc] initWithCapacity:serverKeys.count]; + NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; + numberFormatter.numberStyle = NSNumberFormatterNoStyle; + + [serverKeys enumerateObjectsUsingBlock:^(NSString * _Nonnull serverKey, NSUInteger idx, BOOL * _Nonnull stop) { + NSString *optionValue = self.configuration[serverKey]; + + if (optionValue != nil && (NSNull *)optionValue != [NSNull null]) { + NSString *appboyKey = appboyKeys[idx]; + NSNumber *numberValue = nil; + @try { + numberValue = [numberFormatter numberFromString:optionValue]; + } @catch (NSException *exception) { + numberValue = nil; + } + if (numberValue != nil) { + optionsDictionary[appboyKey] = numberValue; + } + } + }]; + + if (self.host.length) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wincompatible-pointer-types" + optionsDictionary[ABKEndpointKey] = self.host; +#pragma clang diagnostic pop + } + + if (optionsDictionary.count == 0) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wincompatible-pointer-types" + optionsDictionary = [[NSMutableDictionary alloc] initWithCapacity:serverKeys.count]; + } + optionsDictionary[ABKSDKFlavorKey] = @(MPARTICLE); +#pragma clang diagnostic pop + +#if TARGET_OS_IOS + optionsDictionary[ABKEnableAutomaticLocationCollectionKey] = @(YES); + if (self.configuration[@"ABKDisableAutomaticLocationCollectionKey"]) { + if ([self.configuration[@"ABKDisableAutomaticLocationCollectionKey"] caseInsensitiveCompare:@"true"] == NSOrderedSame) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wincompatible-pointer-types" + optionsDictionary[ABKEnableAutomaticLocationCollectionKey] = @(NO); +#pragma clang diagnostic pop + } + } +#endif + + return optionsDictionary; +} + +- (MPKitExecStatus *)incrementUserAttribute:(NSString *)key byValue:(NSNumber *)value { + [appboyInstance.user incrementCustomUserAttribute:key by:[value integerValue]]; + + MPKitExecStatus *execStatus = [[MPKitExecStatus alloc] initWithSDKCode:@(MPKitInstanceAppboy) returnCode:MPKitReturnCodeSuccess]; + return execStatus; +} + +- (nonnull MPKitExecStatus *)logBaseEvent:(nonnull MPBaseEvent *)event { + if ([event isKindOfClass:[MPEvent class]]) { + return [self routeEvent:(MPEvent *)event]; + } else if ([event isKindOfClass:[MPCommerceEvent class]]) { + return [self routeCommerceEvent:(MPCommerceEvent *)event]; + } else { + return [[MPKitExecStatus alloc] initWithSDKCode:@(MPKitInstanceAppboy) returnCode:MPKitReturnCodeUnavailable]; + } +} + +- (MPKitExecStatus *)routeCommerceEvent:(MPCommerceEvent *)commerceEvent { + MPKitExecStatus *execStatus = [[MPKitExecStatus alloc] initWithSDKCode:@(MPKitInstanceAppboy) returnCode:MPKitReturnCodeSuccess forwardCount:0]; + + if (commerceEvent.action == MPCommerceEventActionPurchase) { + NSMutableDictionary *baseProductAttributes = [[NSMutableDictionary alloc] init]; + NSDictionary *transactionAttributes = [self simplifiedDictionary:[commerceEvent.transactionAttributes beautifiedDictionaryRepresentation]]; + + if (transactionAttributes) { + [baseProductAttributes addEntriesFromDictionary:transactionAttributes]; + } + + NSDictionary *commerceEventAttributes = [commerceEvent beautifiedAttributes]; + NSArray *keys = @[kMPExpCECheckoutOptions, kMPExpCECheckoutStep, kMPExpCEProductListName, kMPExpCEProductListSource]; + + for (NSString *key in keys) { + if (commerceEventAttributes[key]) { + baseProductAttributes[key] = commerceEventAttributes[key]; + } + } + + NSDictionary *commerceCustomAttribues = [commerceEvent.customAttributes transformValuesToString]; + for (NSString *key in commerceCustomAttribues) { + baseProductAttributes[key] = commerceCustomAttribues[key]; + } + + NSArray *products = commerceEvent.products; + NSString *currency = commerceEvent.currency ? : @"USD"; + NSMutableDictionary *properties; + + // Add relevant attributes from the commerce event + properties = [[NSMutableDictionary alloc] init]; + if (baseProductAttributes.count > 0) { + [properties addEntriesFromDictionary:baseProductAttributes]; + } + + if ([_configuration[bundleCommerceEventData] boolValue]) { + if (commerceEvent.customAttributes.count > 0) { + [properties removeObjectsForKeys:[commerceEvent.customAttributes allKeys]]; + [properties setValue:commerceEvent.customAttributes forKey:attributesKey]; + } + NSArray *productArray = [self getProductListParameters:products]; + if (productArray.count > 0) { + [properties setValue:productArray forKey:productKey]; + } + NSArray *promotionArray = [self getPromotionListParameters:commerceEvent.promotionContainer.promotions]; + if (promotionArray.count > 0) { + [properties setValue:promotionArray forKey:promotionKey]; + } + NSArray *impressionArray = [self getImpressionListParameters:commerceEvent.impressions]; + if (impressionArray.count > 0) { + [properties setValue:impressionArray forKey:impressionKey]; + } + + NSString *eventName = [NSString stringWithFormat:@"eCommerce - %@", [self eventNameForAction:commerceEvent.action]]; + + [appboyInstance logPurchase:eventName + currency:currency + price:[commerceEvent.transactionAttributes.revenue doubleValue] + properties:properties]; + + [execStatus incrementForwardCount]; + } else { + for (MPProduct *product in products) { + // Add attributes from the product itself + NSDictionary *productDictionary = [product beautifiedDictionaryRepresentation]; + if (productDictionary) { + [properties addEntriesFromDictionary:productDictionary]; + } + + NSString *sanitizedProductName = product.sku; + if ([@"True" isEqualToString:_configuration[replaceSkuWithProductName]]) { + sanitizedProductName = product.name; + } + + // Strips key/values already being passed to Appboy, plus key/values initialized to default values + keys = @[kMPExpProductSKU, kMPProductCurrency, kMPExpProductUnitPrice, kMPExpProductQuantity]; + [properties removeObjectsForKeys:keys]; + + [appboyInstance logPurchase:sanitizedProductName + currency:currency + price:[product.price doubleValue] + quantity:[product.quantity integerValue] + properties:properties]; + + [execStatus incrementForwardCount]; + } + } + } else { + if ([_configuration[bundleCommerceEventData] boolValue]) { + NSDictionary *transformedEventInfo = [commerceEvent.customAttributes transformValuesToString]; + + NSMutableDictionary *eventInfo = [[NSMutableDictionary alloc] initWithCapacity:commerceEvent.customAttributes.count]; + [transformedEventInfo enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { + NSString *strippedKey = [self stripCharacter:@"$" fromString:key]; + eventInfo[strippedKey] = obj; + }]; + + if (self->_enableTypeDetection) { + eventInfo = [[self simplifiedDictionary:eventInfo] mutableCopy]; + } + + if (commerceEvent.customAttributes.count > 0) { + [eventInfo removeObjectsForKeys:[commerceEvent.customAttributes allKeys]]; + [eventInfo setValue:commerceEvent.customAttributes forKey:attributesKey]; + } + NSArray *productArray = [self getProductListParameters:commerceEvent.products]; + if (productArray.count > 0) { + [eventInfo setValue:productArray forKey:productKey]; + } + NSArray *promotionArray = [self getPromotionListParameters:commerceEvent.promotionContainer.promotions]; + if (promotionArray.count > 0) { + [eventInfo setValue:promotionArray forKey:promotionKey]; + } + NSArray *impressionArray = [self getImpressionListParameters:commerceEvent.impressions]; + if (impressionArray.count > 0) { + [eventInfo setValue:impressionArray forKey:impressionKey]; + } + + NSString *eventName = [NSString stringWithFormat:@"eCommerce - %@", [self eventNameForAction:commerceEvent.action]]; + if ([eventName isEqualToString:@"eCommerce - unknown"]) { + if (commerceEvent.impressions) { + eventName = @"eCommerce - impression"; + } else if (commerceEvent.promotionContainer.action) { + eventName = [NSString stringWithFormat:@"eCommerce - %@", [self eventNameForPromotionAction:commerceEvent.promotionContainer.action]]; + } + } + + // Appboy expects that the properties are non empty when present. + if (eventInfo.count > 0) { + [self->appboyInstance logCustomEvent:eventName properties:eventInfo]; + } else { + [self->appboyInstance logCustomEvent:eventName]; + } + [execStatus incrementForwardCount]; + } else { + NSArray *expandedInstructions = [commerceEvent expandedInstructions]; + + for (MPCommerceEventInstruction *commerceEventInstruction in expandedInstructions) { + [self logBaseEvent:commerceEventInstruction.event]; + [execStatus incrementForwardCount]; + } + } + } + + return execStatus; +} + +- (MPKitExecStatus *)routeEvent:(MPEvent *)event { + return [self logAppboyCustomEvent:event eventType:event.type]; +} + +- (MPKitExecStatus *)logScreen:(MPEvent *)event { + MPKitExecStatus *execStatus = nil; + + if (forwardScreenViews) { + execStatus = [self logAppboyCustomEvent:event eventType:0]; + } else { + execStatus = [[MPKitExecStatus alloc] initWithSDKCode:@(MPKitInstanceAppboy) returnCode:MPKitReturnCodeCannotExecute]; + } + + return execStatus; +} + +- (MPKitExecStatus *)receivedUserNotification:(NSDictionary *)userInfo { + MPKitExecStatus *execStatus = [[MPKitExecStatus alloc] initWithSDKCode:@(MPKitInstanceAppboy) returnCode:MPKitReturnCodeSuccess]; + +#if TARGET_OS_IOS + if (shouldDisableNotificationHandling) { + return execStatus; + } + + if (![appboyInstance.notifications handleBackgroundNotificationWithUserInfo:userInfo fetchCompletionHandler:^(UIBackgroundFetchResult fetchResult) {}]) { + NSLog(@"mParticle -> Invalid Braze remote notification: %@", userInfo); + } +#endif + + return execStatus; +} + +- (MPKitExecStatus *)removeUserAttribute:(NSString *)key { + [appboyInstance.user unsetCustomAttributeWithKey:key]; + + MPKitExecStatus *execStatus = [[MPKitExecStatus alloc] initWithSDKCode:@(MPKitInstanceAppboy) returnCode:MPKitReturnCodeSuccess]; + return execStatus; +} + +- (MPKitExecStatus *)setDeviceToken:(NSData *)deviceToken { + MPKitExecStatus *execStatus = [[MPKitExecStatus alloc] initWithSDKCode:@(MPKitInstanceAppboy) returnCode:MPKitReturnCodeSuccess]; + +#if TARGET_OS_IOS + if (shouldDisableNotificationHandling) { + return execStatus; + } + + [appboyInstance.notifications registerDeviceToken:deviceToken]; +#endif + + return execStatus; +} + +- (MPKitExecStatus *)setOptOut:(BOOL)optOut { + MPKitReturnCode returnCode; + + if (optOut) { + [appboyInstance.user setEmailSubscriptionState:BRZUserSubscriptionStateSubscribed]; + returnCode = MPKitReturnCodeSuccess; + } else { + returnCode = MPKitReturnCodeCannotExecute; + } + + MPKitExecStatus *execStatus = [[MPKitExecStatus alloc] initWithSDKCode:@(MPKitInstanceAppboy) returnCode:returnCode]; + return execStatus; +} + +- (MPKitExecStatus *)setUserAttribute:(NSString *)key value:(NSString *)value { + MPKitExecStatus *execStatus; + + if (!value) { + [appboyInstance.user unsetCustomAttributeWithKey:key]; + execStatus = [[MPKitExecStatus alloc] initWithSDKCode:@(MPKitInstanceAppboy) returnCode:MPKitReturnCodeSuccess]; + return execStatus; + } + + value = [self stringRepresentation:value]; + + if (!value) { + execStatus = [[MPKitExecStatus alloc] initWithSDKCode:@(MPKitInstanceAppboy) returnCode:MPKitReturnCodeFail]; + return execStatus; + } + + if ([key isEqualToString:mParticleUserAttributeFirstName]) { + [appboyInstance.user setFirstName:value]; + } else if ([key isEqualToString:mParticleUserAttributeLastName]) { + [appboyInstance.user setLastName:value]; + } else if ([key isEqualToString:mParticleUserAttributeAge]) { + NSDate *now = [NSDate date]; + NSCalendar *calendar = [NSCalendar currentCalendar]; + NSDateComponents *dateComponents = [calendar components:NSCalendarUnitYear fromDate:now]; + NSInteger age = 0; + + @try { + age = [value integerValue]; + } @catch (NSException *exception) { + NSLog(@"mParticle -> Invalid age: %@", value); + execStatus = [[MPKitExecStatus alloc] initWithSDKCode:@(MPKitInstanceAppboy) returnCode:MPKitReturnCodeFail]; + return execStatus; + } + + NSDateComponents *birthComponents = [[NSDateComponents alloc] init]; + birthComponents.year = dateComponents.year - age; + birthComponents.month = 01; + birthComponents.day = 01; + + [appboyInstance.user setDateOfBirth:[calendar dateFromComponents:birthComponents]]; + } else if ([key isEqualToString:brazeUserAttributeDob]) { + // Expected Date Format @"yyyy'-'MM'-'dd" + NSCalendar *calendar = [NSCalendar currentCalendar]; + + NSString *yearString = [value substringToIndex:4]; + NSRange monthRange = NSMakeRange(5, 2); + NSString *monthString = [value substringWithRange:monthRange]; + NSRange dayRange = NSMakeRange(8, 2); + NSString *dayString = [value substringWithRange:dayRange]; + + NSInteger year = 0; + NSInteger month = 0; + NSInteger day = 0; + + @try { + year = [yearString integerValue]; + } @catch (NSException *exception) { + NSLog(@"mParticle -> Invalid dob year: %@ \nPlease use this date format @\"yyyy'-'MM'-'dd\"", yearString); + execStatus = [[MPKitExecStatus alloc] initWithSDKCode:@(MPKitInstanceAppboy) returnCode:MPKitReturnCodeFail]; + return execStatus; + } + + @try { + month = [monthString integerValue]; + } @catch (NSException *exception) { + NSLog(@"mParticle -> Invalid dob month: %@ \nPlease use this date format @\"yyyy'-'MM'-'dd\"", monthString); + execStatus = [[MPKitExecStatus alloc] initWithSDKCode:@(MPKitInstanceAppboy) returnCode:MPKitReturnCodeFail]; + return execStatus; + } + + @try { + day = [dayString integerValue]; + } @catch (NSException *exception) { + NSLog(@"mParticle -> Invalid dob day: %@ \nPlease use this date format @\"yyyy'-'MM'-'dd\"", dayString); + execStatus = [[MPKitExecStatus alloc] initWithSDKCode:@(MPKitInstanceAppboy) returnCode:MPKitReturnCodeFail]; + return execStatus; + } + + NSDateComponents *birthComponents = [[NSDateComponents alloc] init]; + birthComponents.year = year; + birthComponents.month = month; + birthComponents.day = day; + + [appboyInstance.user setDateOfBirth:[calendar dateFromComponents:birthComponents]]; + } else if ([key isEqualToString:mParticleUserAttributeCountry]) { + [appboyInstance.user setCountry:value]; + } else if ([key isEqualToString:mParticleUserAttributeCity]) { + [appboyInstance.user setHomeCity:value]; + } else if ([key isEqualToString:mParticleUserAttributeGender]) { + [appboyInstance.user setGender:BRZUserGender.other]; + if ([value isEqualToString:mParticleGenderMale]) { + [appboyInstance.user setGender:BRZUserGender.male]; + } else if ([value isEqualToString:mParticleGenderFemale]) { + [appboyInstance.user setGender:BRZUserGender.female]; + } else if ([value isEqualToString:mParticleGenderNotAvailable]) { + [appboyInstance.user setGender:BRZUserGender.notApplicable]; + } + } else if ([key isEqualToString:mParticleUserAttributeMobileNumber] || [key isEqualToString:@"$MPUserMobile"]) { + [appboyInstance.user setPhoneNumber:value]; + } else if ([key isEqualToString:mParticleUserAttributeZip]){ + [appboyInstance.user setCustomAttributeWithKey:@"Zip" stringValue:value]; + } else if ([key isEqualToString:brazeUserAttributeEmailSubscribe]) { + if([value isEqualToString:@"opted_in"]) { + [appboyInstance.user setEmailSubscriptionState:BRZUserSubscriptionStateOptedIn]; + } else if ([value isEqualToString:@"unsubscribed"]) { + [appboyInstance.user setEmailSubscriptionState:BRZUserSubscriptionStateUnsubscribed]; + } else if ([value isEqualToString:@"subscribed"]) { + [appboyInstance.user setEmailSubscriptionState:BRZUserSubscriptionStateSubscribed]; + } else { + NSLog(@"mParticle -> Invalid email_subscribe value: %@", value); + execStatus = [[MPKitExecStatus alloc] initWithSDKCode:@(MPKitInstanceAppboy) returnCode:MPKitReturnCodeFail]; + return execStatus; + } + } else if ([key isEqualToString:brazeUserAttributePushSubscribe]) { + if([value isEqualToString:@"opted_in"]) { + [appboyInstance.user setPushNotificationSubscriptionState:BRZUserSubscriptionStateOptedIn]; + } else if ([value isEqualToString:@"unsubscribed"]) { + [appboyInstance.user setPushNotificationSubscriptionState:BRZUserSubscriptionStateUnsubscribed]; + } else if ([value isEqualToString:@"subscribed"]) { + [appboyInstance.user setPushNotificationSubscriptionState:BRZUserSubscriptionStateSubscribed]; + } else { + NSLog(@"mParticle -> Invalid push_subscribe value: %@", value); + execStatus = [[MPKitExecStatus alloc] initWithSDKCode:@(MPKitInstanceAppboy) returnCode:MPKitReturnCodeFail]; + return execStatus; + } + } else if (subscriptionGroupDictionary[key]){ + NSString *subscriptionGroupId = subscriptionGroupDictionary[key]; + if ([value isEqualToString:@"1"]) { + [appboyInstance.user addToSubscriptionGroupWithGroupId:subscriptionGroupId]; + } else if ([value isEqualToString:@"0"]) { + [appboyInstance.user removeFromSubscriptionGroupWithGroupId:subscriptionGroupId]; + } else { + NSLog(@"mParticle -> Invalid value type for subscriptionGroupId mapped user attribute key: %@, expected value should be of type BOOL", key); + execStatus = [[MPKitExecStatus alloc] initWithSDKCode:@(MPKitInstanceAppboy) returnCode:MPKitReturnCodeFail]; + return execStatus; + } + } else { + key = [self stripCharacter:@"$" fromString:key]; + + if (!_enableTypeDetection) { + [appboyInstance.user setCustomAttributeWithKey:key stringValue:value]; + } else { + NSDictionary *tempConversionDictionary = @{key: value}; + tempConversionDictionary = [self simplifiedDictionary:tempConversionDictionary]; + id obj = tempConversionDictionary[key]; + if ([obj isKindOfClass:[NSString class]]) { + [appboyInstance.user setCustomAttributeWithKey:key stringValue:obj]; + } else if ([obj isKindOfClass:[NSNumber class]]) { + if ([self isBoolNumber:obj]) { + [appboyInstance.user setCustomAttributeWithKey:key boolValue:((NSNumber *)obj).boolValue]; + } else if ([self isInteger:value]) { + [appboyInstance.user setCustomAttributeWithKey:key intValue:((NSNumber *)obj).intValue]; + } else if ([self isFloat:value]) { + [appboyInstance.user setCustomAttributeWithKey:key doubleValue:((NSNumber *)obj).doubleValue]; + } + } else if ([obj isKindOfClass:[NSDate class]]) { + [appboyInstance.user setCustomAttributeWithKey:key dateValue:obj]; + } + } + } + + execStatus = [[MPKitExecStatus alloc] initWithSDKCode:@(MPKitInstanceAppboy) returnCode:MPKitReturnCodeSuccess]; + return execStatus; +} + +- (nonnull MPKitExecStatus *)setUserAttribute:(nonnull NSString *)key values:(nonnull NSArray *)values { + MPKitExecStatus *execStatus; + + if (!values) { + execStatus = [[MPKitExecStatus alloc] initWithSDKCode:@(MPKitInstanceAppboy) returnCode:MPKitReturnCodeFail]; + } else { + [appboyInstance.user setCustomAttributeArrayWithKey:key array:values]; + execStatus = [[MPKitExecStatus alloc] initWithSDKCode:@(MPKitInstanceAppboy) returnCode:MPKitReturnCodeSuccess]; + } + + return execStatus; +} + +- (nonnull MPKitExecStatus *)onIdentifyComplete:(FilteredMParticleUser *)user request:(FilteredMPIdentityApiRequest *)request { + return [self updateUser:user request:request.userIdentities]; +} + +- (nonnull MPKitExecStatus *)onLoginComplete:(FilteredMParticleUser *)user request:(FilteredMPIdentityApiRequest *)request { + return [self updateUser:user request:request.userIdentities]; +} + +- (nonnull MPKitExecStatus *)onLogoutComplete:(FilteredMParticleUser *)user request:(FilteredMPIdentityApiRequest *)request { + return [self updateUser:user request:request.userIdentities]; +} + +- (nonnull MPKitExecStatus *)onModifyComplete:(FilteredMParticleUser *)user request:(FilteredMPIdentityApiRequest *)request { + return [self updateUser:user request:request.userIdentities]; +} + +- (nonnull MPKitExecStatus *)updateUser:(FilteredMParticleUser *)user request:(NSDictionary *)userIdentities { + MPKitExecStatus *execStatus = nil; + + if (userIdentities) { + NSMutableDictionary *userIDsCopy = [userIdentities copy]; + NSString *userId; + + if (_configuration[userIdTypeKey]) { + NSString *userIdKey = _configuration[userIdTypeKey]; + if ([userIdKey isEqualToString:userIdValueOther]) { + if (userIDsCopy[@(MPUserIdentityOther)]) { + userId = userIDsCopy[@(MPUserIdentityOther)]; + } + } else if ([userIdKey isEqualToString:userIdValueOther2]) { + if (userIDsCopy[@(MPUserIdentityOther2)]) { + userId = userIDsCopy[@(MPUserIdentityOther2)]; + } + } else if ([userIdKey isEqualToString:userIdValueOther3]) { + if (userIDsCopy[@(MPUserIdentityOther3)]) { + userId = userIDsCopy[@(MPUserIdentityOther3)]; + } + } else if ([userIdKey isEqualToString:userIdValueOther4]) { + if (userIDsCopy[@(MPUserIdentityOther4)]) { + userId = userIDsCopy[@(MPUserIdentityOther4)]; + } + } else if ([userIdKey isEqualToString:userIdValueOther5]) { + if (userIDsCopy[@(MPUserIdentityOther5)]) { + userId = userIDsCopy[@(MPUserIdentityOther5)]; + } + } else if ([userIdKey isEqualToString:userIdValueOther6]) { + if (userIDsCopy[@(MPUserIdentityOther6)]) { + userId = userIDsCopy[@(MPUserIdentityOther6)]; + } + } else if ([userIdKey isEqualToString:userIdValueOther7]) { + if (userIDsCopy[@(MPUserIdentityOther7)]) { + userId = userIDsCopy[@(MPUserIdentityOther7)]; + } + } else if ([userIdKey isEqualToString:userIdValueOther8]) { + if (userIDsCopy[@(MPUserIdentityOther8)]) { + userId = userIDsCopy[@(MPUserIdentityOther8)]; + } + } else if ([userIdKey isEqualToString:userIdValueOther9]) { + if (userIDsCopy[@(MPUserIdentityOther9)]) { + userId = userIDsCopy[@(MPUserIdentityOther9)]; + } + } else if ([userIdKey isEqualToString:userIdValueOther10]) { + if (userIDsCopy[@(MPUserIdentityOther10)]) { + userId = userIDsCopy[@(MPUserIdentityOther10)]; + } + } else if ([userIdKey isEqualToString:userIdValueCustomerId]) { + if (userIDsCopy[@(MPUserIdentityCustomerId)]) { + userId = userIDsCopy[@(MPUserIdentityCustomerId)]; + } + } else if ([userIdKey isEqualToString:userIdValueFacebook]) { + if (userIDsCopy[@(MPUserIdentityFacebook)]) { + userId = userIDsCopy[@(MPUserIdentityFacebook)]; + } + } else if ([userIdKey isEqualToString:userIdValueTwitter]) { + if (userIDsCopy[@(MPUserIdentityTwitter)]) { + userId = userIDsCopy[@(MPUserIdentityTwitter)]; + } + } else if ([userIdKey isEqualToString:userIdValueGoogle]) { + if (userIDsCopy[@(MPUserIdentityGoogle)]) { + userId = userIDsCopy[@(MPUserIdentityGoogle)]; + } + } else if ([userIdKey isEqualToString:userIdValueMicrosoft]) { + if (userIDsCopy[@(MPUserIdentityMicrosoft)]) { + userId = userIDsCopy[@(MPUserIdentityMicrosoft)]; + } + } else if ([userIdKey isEqualToString:userIdValueYahoo]) { + if (userIDsCopy[@(MPUserIdentityYahoo)]) { + userId = userIDsCopy[@(MPUserIdentityYahoo)]; + } + } else if ([userIdKey isEqualToString:userIdValueEmail]) { + if (userIDsCopy[@(MPUserIdentityEmail)]) { + userId = userIDsCopy[@(MPUserIdentityEmail)]; + } + } else if ([userIdKey isEqualToString:userIdValueAlias]) { + if (userIDsCopy[@(MPUserIdentityAlias)]) { + userId = userIDsCopy[@(MPUserIdentityAlias)]; + } + } else if ([userIdKey isEqualToString:userIdValueMPID]) { + if (user != nil) { + userId = user.userId.stringValue; + } + } else { + if (userIDsCopy[@(MPUserIdentityCustomerId)]) { + userId = userIDsCopy[@(MPUserIdentityCustomerId)]; + } + } + } + + if (userId && ![userId isKindOfClass: [NSNull class]]) { + void (^changeUser)(void) = ^ { + [self->appboyInstance changeUser:userId]; + }; + + if ([NSThread isMainThread]) { + changeUser(); + } else { + dispatch_async(dispatch_get_main_queue(), changeUser); + } + execStatus = [[MPKitExecStatus alloc] initWithSDKCode:@(MPKitInstanceAppboy) returnCode:MPKitReturnCodeSuccess]; + } + + NSString *userEmail; + + if (_configuration[emailIdTypeKey]) { + NSString *emailIdKey = _configuration[emailIdTypeKey]; + if ([emailIdKey isEqualToString:userIdValueOther]) { + if (userIDsCopy[@(MPUserIdentityOther)]) { + userEmail = userIDsCopy[@(MPUserIdentityOther)]; + } + } else if ([emailIdKey isEqualToString:userIdValueOther2]) { + if (userIDsCopy[@(MPUserIdentityOther2)]) { + userEmail = userIDsCopy[@(MPUserIdentityOther2)]; + } + } else if ([emailIdKey isEqualToString:userIdValueOther3]) { + if (userIDsCopy[@(MPUserIdentityOther3)]) { + userEmail = userIDsCopy[@(MPUserIdentityOther3)]; + } + } else if ([emailIdKey isEqualToString:userIdValueOther4]) { + if (userIDsCopy[@(MPUserIdentityOther4)]) { + userEmail = userIDsCopy[@(MPUserIdentityOther4)]; + } + } else if ([emailIdKey isEqualToString:userIdValueOther5]) { + if (userIDsCopy[@(MPUserIdentityOther5)]) { + userEmail = userIDsCopy[@(MPUserIdentityOther5)]; + } + } else if ([emailIdKey isEqualToString:userIdValueOther6]) { + if (userIDsCopy[@(MPUserIdentityOther6)]) { + userEmail = userIDsCopy[@(MPUserIdentityOther6)]; + } + } else if ([emailIdKey isEqualToString:userIdValueOther7]) { + if (userIDsCopy[@(MPUserIdentityOther7)]) { + userEmail = userIDsCopy[@(MPUserIdentityOther7)]; + } + } else if ([emailIdKey isEqualToString:userIdValueOther8]) { + if (userIDsCopy[@(MPUserIdentityOther8)]) { + userEmail = userIDsCopy[@(MPUserIdentityOther8)]; + } + } else if ([emailIdKey isEqualToString:userIdValueOther9]) { + if (userIDsCopy[@(MPUserIdentityOther9)]) { + userEmail = userIDsCopy[@(MPUserIdentityOther9)]; + } + } else if ([emailIdKey isEqualToString:userIdValueOther10]) { + if (userIDsCopy[@(MPUserIdentityOther10)]) { + userEmail = userIDsCopy[@(MPUserIdentityOther10)]; + } + } else if ([emailIdKey isEqualToString:userIdValueEmail]) { + if (userIDsCopy[@(MPUserIdentityEmail)]) { + userEmail = userIDsCopy[@(MPUserIdentityEmail)]; + } + } else { + if (userIDsCopy[@(MPUserIdentityEmail)]) { + userEmail = userIDsCopy[@(MPUserIdentityEmail)]; + } + } + } + + if (userEmail && ![userEmail isKindOfClass: [NSNull class]]) { + [appboyInstance.user setEmail:userEmail]; + execStatus = [[MPKitExecStatus alloc] initWithSDKCode:@(MPKitInstanceAppboy) returnCode:MPKitReturnCodeSuccess]; + } + } + + return execStatus; +} + +- (MPKitExecStatus *)setUserIdentity:(NSString *)identityString identityType:(MPUserIdentity)identityType { + return [[MPKitExecStatus alloc] initWithSDKCode:@(MPKitInstanceAppboy) returnCode:MPKitReturnCodeSuccess]; +} + +#if TARGET_OS_IOS +- (nonnull MPKitExecStatus *)userNotificationCenter:(nonnull UNUserNotificationCenter *)center willPresentNotification:(nonnull UNNotification *)notification { + MPKitExecStatus *execStatus = [[MPKitExecStatus alloc] initWithSDKCode:@(MPKitInstanceAppboy) returnCode:MPKitReturnCodeSuccess]; + + if (shouldDisableNotificationHandling) { + return execStatus; + } + + if (![appboyInstance.notifications handleBackgroundNotificationWithUserInfo:notification.request.content.userInfo fetchCompletionHandler:^(UIBackgroundFetchResult fetchResult) {}]) { + NSLog(@"mParticle -> Invalid Braze remote notification: %@", notification.request.content.userInfo); + } + + return execStatus; +} + +- (nonnull MPKitExecStatus *)userNotificationCenter:(nonnull UNUserNotificationCenter *)center didReceiveNotificationResponse:(nonnull UNNotificationResponse *)response API_AVAILABLE(ios(10.0)) { + MPKitExecStatus *execStatus = [[MPKitExecStatus alloc] initWithSDKCode:@(MPKitInstanceAppboy) returnCode:MPKitReturnCodeSuccess]; + + if (shouldDisableNotificationHandling) { + return execStatus; + } + + if (![appboyInstance.notifications handleUserNotificationWithResponse:response withCompletionHandler:^{}]) { + NSLog(@"mParticle -> Notification Response rejected by Braze: %@", response); + } + + return execStatus; +} +#endif + +- (MPKitExecStatus *)setATTStatus:(MPATTAuthorizationStatus)status withATTStatusTimestampMillis:(NSNumber *)attStatusTimestampMillis { + BOOL isEnabled = status == MPATTAuthorizationStatusAuthorized; + [appboyInstance setAdTrackingEnabled:isEnabled]; + return [[MPKitExecStatus alloc] initWithSDKCode:@(MPKitInstanceAppboy) returnCode:MPKitReturnCodeSuccess]; +} + +- (MPKitExecStatus *)setConsentState:(nullable MPConsentState *)state { + [self updateConsent]; + + return [[MPKitExecStatus alloc] initWithSDKCode:@(MPKitInstanceAppboy) returnCode:MPKitReturnCodeSuccess]; +} + +- (void)updateConsent { + MParticleUser *currentUser = [[[MParticle sharedInstance] identity] currentUser]; + NSDictionary *userConsentMap = currentUser.consentState.gdprConsentState; + + // Update from mParticle consent + if (self.configuration && self.configuration[MPConsentMappingSDKKey]) { + // Retrieve the array of Consent Map Dictionaries from the Config + NSData *objectData = [self.configuration[MPConsentMappingSDKKey] dataUsingEncoding:NSUTF8StringEncoding]; + NSArray *consentMappingArray = [NSJSONSerialization JSONObjectWithData:objectData + options:NSJSONReadingMutableContainers + error:nil]; + + // For each valid Consent Map check if mParticle has a corresponding consent setting and, if so, send to Braze + for (NSDictionary *consentMappingDict in consentMappingArray) { + NSString *consentPurpose = consentMappingDict[MPMapKey]; + if (consentMappingDict[MPValueKey] && userConsentMap[consentPurpose.lowercaseString]) { + NSString *brazeConsentName = consentMappingDict[MPValueKey]; + MPGDPRConsent *consent = userConsentMap[consentPurpose.lowercaseString]; + if ([brazeConsentName isEqualToString:MPGoogleAdUserDataKey]) { + [appboyInstance.user setCustomAttributeWithKey:BGoogleAdUserDataKey boolValue:consent.consented]; + } else if ([brazeConsentName isEqualToString:MPGoogleAdPersonalizationKey]) { + [appboyInstance.user setCustomAttributeWithKey:BGoogleAdPersonalizationKey boolValue:consent.consented]; + } + } + } + } +} + +#pragma mark Configuration Dictionary + +- (NSMutableDictionary *)simplifiedDictionary:(NSDictionary *)originalDictionary { + __block NSMutableDictionary *transformedDictionary = [[NSMutableDictionary alloc] init]; + + [originalDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) { + if ([value isKindOfClass:[NSString class]]) { + NSString *stringValue = (NSString *)value; + NSDate *dateValue = [MPKitAPI dateFromStringRFC3339:stringValue]; + if (dateValue) { + transformedDictionary[key] = dateValue; + } else if ([self isInteger:stringValue]) { + transformedDictionary[key] = [NSNumber numberWithInteger:[stringValue integerValue]]; + } else if ([self isFloat:stringValue]) { + transformedDictionary[key] = [NSNumber numberWithFloat:[stringValue floatValue]]; + } else if ([stringValue caseInsensitiveCompare:@"true"] == NSOrderedSame) { + transformedDictionary[key] = @YES; + } else if ([stringValue caseInsensitiveCompare:@"false"] == NSOrderedSame) { + transformedDictionary[key] = @NO; + } + else { + transformedDictionary[key] = stringValue; + } + } else if ([value isKindOfClass:[NSNumber class]]) { + transformedDictionary[key] = (NSNumber *)value; + } else if ([value isKindOfClass:[NSDate class]]) { + transformedDictionary[key] = (NSDate *)value; + } + }]; + + return transformedDictionary; +} + +- (BOOL) isInteger:(NSString *)string { + NSCharacterSet* nonNumbers = [[NSCharacterSet decimalDigitCharacterSet] invertedSet]; + + if([string hasPrefix:@"-"]) { + NSString *absoluteString = [string stringByReplacingOccurrencesOfString:@"-" withString:@""]; + NSRange r = [absoluteString rangeOfCharacterFromSet: nonNumbers]; + + return r.location == NSNotFound && absoluteString.length > 0; + } else { + NSRange r = [string rangeOfCharacterFromSet: nonNumbers]; + + return r.location == NSNotFound && string.length > 0; + } +} + +- (BOOL) isFloat:(NSString *)string { + NSArray *numList = [string componentsSeparatedByString:@"."]; + + if (numList.count == 2) { + if ([self isInteger:numList[0]] && [self isInteger:numList[1]]) { + return true; + } + } + + return false; +} + +- (BOOL) isBoolNumber:(NSNumber *)num { + CFTypeID boolID = CFBooleanGetTypeID(); + CFTypeID numID = CFGetTypeID((__bridge CFTypeRef)(num)); + return numID == boolID; +} + +- (void)setEnableTypeDetection:(BOOL)enableTypeDetection { + _enableTypeDetection = enableTypeDetection; +} + +- (NSArray *)getProductListParameters:(NSArray *)products { + NSMutableArray *productArray = [[NSMutableArray alloc] init]; + for (MPProduct *product in products) { + // Add attributes from the products themselves + NSMutableDictionary *productDictionary = [[product beautifiedDictionaryRepresentation] mutableCopy]; + + if (product.userDefinedAttributes.count > 0) { + [productDictionary removeObjectsForKeys:[product.userDefinedAttributes allKeys]]; + [productDictionary setValue:product.userDefinedAttributes forKey:attributesKey]; + } + + // Adds the product dictionary to the product array being supplied to Braze + if (productDictionary) { + [productArray addObject:productDictionary]; + } + } + return productArray; +} + +- (NSArray *)getPromotionListParameters:(NSArray *)promotions { + NSMutableArray *promotionArray = [[NSMutableArray alloc] init]; + for (MPPromotion *promotion in promotions) { + // Add attributes from the promotions themselves + NSMutableDictionary *promotionDictionary = [[NSMutableDictionary alloc] init]; + promotionDictionary[@"Creative"] = promotion.creative; + promotionDictionary[@"Name"] = promotion.name; + promotionDictionary[@"Position"] = promotion.position; + promotionDictionary[@"Id"] = promotion.promotionId; + + // Adds the promotion dictionary to the promotion array being supplied to Braze + [promotionArray addObject:promotionDictionary]; + } + return promotionArray; +} + +- (NSArray *)getImpressionListParameters:(NSDictionary *> *)impressions { + NSMutableArray *impressionArray = [[NSMutableArray alloc] init]; + for (NSString *impressionName in impressions.allKeys) { + // Add attributes from the products themselves + NSMutableDictionary *impressionDictionary = [[NSMutableDictionary alloc] init]; + impressionDictionary[@"Product Impression List"] = impressionName; + NSArray *impressionProducts = [[impressions[impressionName] allObjects] copy]; + impressionDictionary[productKey] = [self getProductListParameters:impressionProducts]; + + // Adds the impression dictionary to the impression array being supplied to Braze + [impressionArray addObject:impressionDictionary]; + } + return impressionArray; +} + +- (NSString *)eventNameForAction:(MPCommerceEventAction)action { + NSArray *actionNames = @[@"add_to_cart", @"remove_from_cart", @"add_to_wishlist", @"remove_from_wishlist", @"checkout", @"checkout_option", @"click", @"view_detail", @"purchase", @"refund"]; + + if (action >= actionNames.count) { + return @"unknown"; + } + + return actionNames[(NSUInteger)action]; +} + +- (NSString *)eventNameForPromotionAction:(MPPromotionAction)action { + NSArray *actionNames = @[@"click", @"view"]; + + if (action >= actionNames.count) { + return @"unknown"; + } + + return actionNames[(NSUInteger)action]; +} + +@end diff --git a/kits/braze/braze-12/Sources/mParticle-Braze/PrivacyInfo.xcprivacy b/kits/braze/braze-12/Sources/mParticle-Braze/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..e08a130bc --- /dev/null +++ b/kits/braze/braze-12/Sources/mParticle-Braze/PrivacyInfo.xcprivacy @@ -0,0 +1,14 @@ + + + + + NSPrivacyTracking + + NSPrivacyTrackingDomains + + NSPrivacyCollectedDataTypes + + NSPrivacyAccessedAPITypes + + + diff --git a/kits/braze/braze-12/Sources/mParticle-Braze/include/MPKitAppboy.h b/kits/braze/braze-12/Sources/mParticle-Braze/include/MPKitAppboy.h new file mode 100644 index 000000000..26b7c78e2 --- /dev/null +++ b/kits/braze/braze-12/Sources/mParticle-Braze/include/MPKitAppboy.h @@ -0,0 +1,29 @@ +#import +#if defined(__has_include) && __has_include() + #import +#else + #import "mParticle.h" +#endif + +#if defined(__has_include) && __has_include() + #import +#else + #import BrazeKit-Swift.h +#endif + +@interface MPKitAppboy : NSObject + +@property (nonatomic, strong, nonnull) NSDictionary *configuration; +@property (nonatomic, strong, nullable) NSDictionary *launchOptions; +@property (nonatomic, unsafe_unretained, readonly) BOOL started; +@property (nonatomic, strong, nullable) MPKitAPI *kitApi; + +#if TARGET_OS_IOS ++ (void)setInAppMessageControllerDelegate:(nonnull id)delegate; ++ (void)setShouldDisableNotificationHandling:(BOOL)isDisabled; +#endif ++ (void)setURLDelegate:(nonnull id)delegate; ++ (void)setBrazeInstance:(nonnull id)instance; ++ (void)setBrazeLocationProvider:(nonnull id)instance; ++ (void)setBrazeTrackingPropertyAllowList:(nonnull NSSet *)allowList; +@end diff --git a/kits/braze/braze-12/Sources/mParticle-Braze/include/mParticle_Appboy.h b/kits/braze/braze-12/Sources/mParticle-Braze/include/mParticle_Appboy.h new file mode 100644 index 000000000..829f2c83e --- /dev/null +++ b/kits/braze/braze-12/Sources/mParticle-Braze/include/mParticle_Appboy.h @@ -0,0 +1,17 @@ +#import + +//! Project version number for mParticle-Appboy. +FOUNDATION_EXPORT double mParticle_AppboyVersionNumber; + +//! Project version string for mParticle-Appboy. +FOUNDATION_EXPORT const unsigned char mParticle_AppboyVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + +#if defined(__has_include) && __has_include() + #import +#elif defined(__has_include) && __has_include() + #import +#else + #import "MPKitAppboy.h" +#endif diff --git a/kits/braze/braze-12/Tests/mParticle-BrazeTest/mParticle_AppboyTests.m b/kits/braze/braze-12/Tests/mParticle-BrazeTest/mParticle_AppboyTests.m new file mode 100644 index 000000000..e5efe84bb --- /dev/null +++ b/kits/braze/braze-12/Tests/mParticle-BrazeTest/mParticle_AppboyTests.m @@ -0,0 +1,1065 @@ +@import mParticle_Apple_SDK; +@import mParticle_Braze; +@import XCTest; +@import OCMock; +#if TARGET_OS_IOS + @import BrazeKitCompat; + @import BrazeUI; +#else + @import BrazeKitCompat; +#endif + +@interface MPKitAppboy () + +- (Braze *)appboyInstance; +- (void)setAppboyInstance:(Braze *)instance; +- (NSMutableDictionary *)optionsDictionary; ++ (id)inAppMessageControllerDelegate; +- (void)setEnableTypeDetection:(BOOL)enableTypeDetection; ++ (BOOL)shouldDisableNotificationHandling; ++ (Braze *)brazeInstance; ++ (MPKitExecStatus *)updateUser:(FilteredMParticleUser *)user request:(NSDictionary *)userIdentities; ++ (MPKitExecStatus *)setUserAttribute:(NSString *)key value:(NSString *)value; + +@end + +@interface mParticle_AppboyTests : XCTestCase + +@end + +@implementation mParticle_AppboyTests + +- (void)setUp { + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. + [MPKitAppboy setBrazeInstance:nil]; + [MPKitAppboy setURLDelegate:nil]; +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testStartwithSimpleConfig { + MPKitAppboy *appBoy = [[MPKitAppboy alloc] init]; + + NSDictionary *kitConfiguration = @{@"apiKey":@"BrazeID", + @"id":@42 + }; + + [appBoy didFinishLaunchingWithConfiguration:kitConfiguration]; + + NSDictionary *testOptionsDictionary = @{ABKEnableAutomaticLocationCollectionKey:@(YES), + ABKSDKFlavorKey:@7 + }; + + NSDictionary *optionsDictionary = [appBoy optionsDictionary]; + XCTAssertEqualObjects(optionsDictionary, testOptionsDictionary); +} + +- (void)testStartwithAdvancedConfig { + MPKitAppboy *appBoy = [[MPKitAppboy alloc] init]; + + NSDictionary *kitConfiguration = @{@"apiKey":@"BrazeID", + @"id":@42, + @"ABKCollectIDFA":@"true", + @"ABKRequestProcessingPolicyOptionKey": @"1", + @"ABKFlushIntervalOptionKey":@"2", + @"ABKSessionTimeoutKey":@"3", + @"ABKMinimumTriggerTimeIntervalKey":@"4", + @"userIdentificationType":@"CustomerId" + }; + + [appBoy didFinishLaunchingWithConfiguration:kitConfiguration]; + + NSDictionary *testOptionsDictionary = @{ABKEnableAutomaticLocationCollectionKey:@(YES), + ABKSDKFlavorKey:@7, + @"ABKRquestProcessingPolicy": @(1), + @"ABKFlushInterval":@(2), + @"ABKSessionTimeout":@(3), + @"ABKMinimumTriggerTimeInterval":@(4) + }; + + NSDictionary *optionsDictionary = [appBoy optionsDictionary]; + XCTAssertEqualObjects(optionsDictionary, testOptionsDictionary); +} + +- (void)testMpidForwardingOnStartUserIdZero { + NSDictionary *kitConfiguration = @{@"apiKey":@"BrazeID", + @"id":@42, + @"ABKCollectIDFA":@"true", + @"ABKRequestProcessingPolicyOptionKey": @"1", + @"ABKFlushIntervalOptionKey":@"2", + @"ABKSessionTimeoutKey":@"3", + @"ABKMinimumTriggerTimeIntervalKey":@"4", + @"userIdentificationType":@"MPID" + }; + + MPKitAppboy *kitInstance = [[MPKitAppboy alloc] init]; + + [kitInstance didFinishLaunchingWithConfiguration:kitConfiguration]; + + MParticleUser *testUser = [[MParticleUser alloc] init]; + [testUser setValue:@(0) forKey:@"userId"]; + + FilteredMParticleUser *filteredUser = [[FilteredMParticleUser alloc] initWithMParticleUser:testUser kitConfiguration:kitConfiguration]; + id mockKitApi = OCMClassMock([MPKitAPI class]); + OCMStub([mockKitApi getCurrentUserWithKit:kitInstance]).andReturn(filteredUser); + kitInstance.kitApi = mockKitApi; + + id mockKitInstance = OCMPartialMock(kitInstance); + [[mockKitInstance reject] updateUser:[OCMArg any] request:[OCMArg any]]; + [kitInstance start]; + [mockKitInstance verify]; +} + +- (void)testMpidForwardingOnStartUserIdPositive { + NSDictionary *kitConfiguration = @{@"apiKey":@"BrazeID", + @"id":@42, + @"ABKCollectIDFA":@"true", + @"ABKRequestProcessingPolicyOptionKey": @"1", + @"ABKFlushIntervalOptionKey":@"2", + @"ABKSessionTimeoutKey":@"3", + @"ABKMinimumTriggerTimeIntervalKey":@"4", + @"userIdentificationType":@"MPID" + }; + + MPKitAppboy *kitInstance = [[MPKitAppboy alloc] init]; + + [kitInstance didFinishLaunchingWithConfiguration:kitConfiguration]; + + MParticleUser *testUser = [[MParticleUser alloc] init]; + [testUser setValue:@(1) forKey:@"userId"]; + + FilteredMParticleUser *filteredUser = [[FilteredMParticleUser alloc] initWithMParticleUser:testUser kitConfiguration:kitConfiguration]; + id mockKitApi = OCMClassMock([MPKitAPI class]); + OCMStub([mockKitApi getCurrentUserWithKit:kitInstance]).andReturn(filteredUser); + kitInstance.kitApi = mockKitApi; + + id mockKitInstance = OCMPartialMock(kitInstance); + [[mockKitInstance expect] updateUser:[OCMArg any] request:[OCMArg any]]; + [kitInstance start]; + [mockKitInstance verify]; +} + +- (void)testMpidForwardingOnStartUserIdNegative { + NSDictionary *kitConfiguration = @{@"apiKey":@"BrazeID", + @"id":@42, + @"ABKCollectIDFA":@"true", + @"ABKRequestProcessingPolicyOptionKey": @"1", + @"ABKFlushIntervalOptionKey":@"2", + @"ABKSessionTimeoutKey":@"3", + @"ABKMinimumTriggerTimeIntervalKey":@"4", + @"userIdentificationType":@"MPID" + }; + + MPKitAppboy *kitInstance = [[MPKitAppboy alloc] init]; + + [kitInstance didFinishLaunchingWithConfiguration:kitConfiguration]; + + MParticleUser *testUser = [[MParticleUser alloc] init]; + [testUser setValue:@(-1) forKey:@"userId"]; + + FilteredMParticleUser *filteredUser = [[FilteredMParticleUser alloc] initWithMParticleUser:testUser kitConfiguration:kitConfiguration]; + id mockKitApi = OCMClassMock([MPKitAPI class]); + OCMStub([mockKitApi getCurrentUserWithKit:kitInstance]).andReturn(filteredUser); + kitInstance.kitApi = mockKitApi; + + id mockKitInstance = OCMPartialMock(kitInstance); + [[mockKitInstance expect] updateUser:[OCMArg any] request:[OCMArg any]]; + [kitInstance start]; + [mockKitInstance verify]; +} + +- (void)testEmailSubscribtionUserAttribute { + NSDictionary *kitConfiguration = @{@"apiKey":@"BrazeID", + @"id":@42, + @"ABKCollectIDFA":@"true", + @"ABKRequestProcessingPolicyOptionKey": @"1", + @"ABKFlushIntervalOptionKey":@"2", + @"ABKSessionTimeoutKey":@"3", + @"ABKMinimumTriggerTimeIntervalKey":@"4", + @"userIdentificationType":@"MPID" + }; + MPKitAppboy *kitInstance = [[MPKitAppboy alloc] init]; + [kitInstance didFinishLaunchingWithConfiguration:kitConfiguration]; + + BRZConfiguration *configuration = [[BRZConfiguration alloc] init]; + Braze *testClient = [[Braze alloc] initWithConfiguration:configuration]; + id mockClient = OCMPartialMock(testClient); + [kitInstance setAppboyInstance:mockClient]; + XCTAssertEqualObjects(mockClient, [kitInstance appboyInstance]); + + // Should succeed since opted_in is a valid value + MPKitExecStatus *execStatus1 = [kitInstance setUserAttribute:@"email_subscribe" value:@"opted_in"]; + XCTAssertEqual(execStatus1.returnCode, MPKitReturnCodeSuccess); + // Should fail since testValue is an invalid value + MPKitExecStatus *execStatus2 = [kitInstance setUserAttribute:@"email_subscribe" value:@"testValue"]; + XCTAssertEqual(execStatus2.returnCode, MPKitReturnCodeFail); + + [mockClient verify]; + + [mockClient stopMocking]; +} + +- (void)testPushSubscribtionUserAttribute { + NSDictionary *kitConfiguration = @{@"apiKey":@"BrazeID", + @"id":@42, + @"ABKCollectIDFA":@"true", + @"ABKRequestProcessingPolicyOptionKey": @"1", + @"ABKFlushIntervalOptionKey":@"2", + @"ABKSessionTimeoutKey":@"3", + @"ABKMinimumTriggerTimeIntervalKey":@"4", + @"userIdentificationType":@"MPID" + }; + MPKitAppboy *kitInstance = [[MPKitAppboy alloc] init]; + [kitInstance didFinishLaunchingWithConfiguration:kitConfiguration]; + + BRZConfiguration *configuration = [[BRZConfiguration alloc] init]; + Braze *testClient = [[Braze alloc] initWithConfiguration:configuration]; + id mockClient = OCMPartialMock(testClient); + [kitInstance setAppboyInstance:mockClient]; + XCTAssertEqualObjects(mockClient, [kitInstance appboyInstance]); + + // Should succeed since opted_in is a valid value + MPKitExecStatus *execStatus1 = [kitInstance setUserAttribute:@"push_subscribe" value:@"opted_in"]; + XCTAssertEqual(execStatus1.returnCode, MPKitReturnCodeSuccess); + // Should fail since testValue is an invalid value + MPKitExecStatus *execStatus2 = [kitInstance setUserAttribute:@"push_subscribe" value:@"testValue"]; + XCTAssertEqual(execStatus2.returnCode, MPKitReturnCodeFail); + + [mockClient verify]; + + [mockClient stopMocking]; +} + +- (void)testSubscriptionGroupIdsMappedUserAttributes { + NSDictionary *kitConfiguration = @{@"apiKey":@"BrazeID", + @"id":@42, + @"ABKCollectIDFA":@"true", + @"ABKRequestProcessingPolicyOptionKey": @"1", + @"ABKFlushIntervalOptionKey":@"2", + @"ABKSessionTimeoutKey":@"3", + @"ABKMinimumTriggerTimeIntervalKey":@"4", + @"userIdentificationType":@"MPID", + @"subscriptionGroupMapping" : @"[{\"jsmap\":null,\"map\":\"testAttribute1\",\"maptype\":\"UserAttributeClass.Name\",\"value\":\"00000000-0000-0000-0000-00000000000\"},{\"jsmap\":null,\"map\":\"testAttribute2\",\"maptype\":\"UserAttributeClass.Name\",\"value\":\"00000000-0000-0000-0000-00000000001\"}]" + }; + + MPKitAppboy *kitInstance = [[MPKitAppboy alloc] init]; + [kitInstance didFinishLaunchingWithConfiguration:kitConfiguration]; + + BRZConfiguration *configuration = [[BRZConfiguration alloc] init]; + Braze *testClient = [[Braze alloc] initWithConfiguration:configuration]; + id mockClient = OCMPartialMock(testClient); + [kitInstance setAppboyInstance:mockClient]; + XCTAssertEqualObjects(mockClient, [kitInstance appboyInstance]); + + // Should succeed since Bool false is a valid value + MPKitExecStatus *execStatus1 = [kitInstance setUserAttribute:@"testAttribute1" value:@NO]; + XCTAssertEqual(execStatus1.returnCode, MPKitReturnCodeSuccess); + // Should succeed since Bool true is a valid value + MPKitExecStatus *execStatus2 = [kitInstance setUserAttribute:@"testAttribute2" value:@YES]; + XCTAssertEqual(execStatus2.returnCode, MPKitReturnCodeSuccess); + // Should fail since testValue is not type BOOL + MPKitExecStatus *execStatus3 = [kitInstance setUserAttribute:@"testAttribute2" value:@"testValue"]; + XCTAssertEqual(execStatus3.returnCode, MPKitReturnCodeFail); + + [mockClient verify]; + + [mockClient stopMocking]; +} + + +//- (void)testEndpointOverride { +// MPKitAppboy *appBoy = [[MPKitAppboy alloc] init]; +// +// NSDictionary *kitConfiguration = @{@"apiKey":@"BrazeID", +// @"host":@"https://foo.bar.com", +// @"id":@42, +// @"ABKCollectIDFA":@"true", +// @"ABKRequestProcessingPolicyOptionKey": @"1", +// @"ABKFlushIntervalOptionKey":@"2", +// @"ABKSessionTimeoutKey":@"3", +// @"ABKMinimumTriggerTimeIntervalKey":@"4", +// @"ABKCollectIDFA":@"true" +// }; +// +// [appBoy didFinishLaunchingWithConfiguration:kitConfiguration]; +// +// XCTAssertEqualObjects(@"https://foo.bar.com", [appBoy getApiEndpoint:@"https://original.com"]); +// XCTAssertEqualObjects(@"https://foo.bar.com/param1", [appBoy getApiEndpoint:@"https://original.com/param1"]); +// XCTAssertEqualObjects(@"https://foo.bar.com/param1/param2", [appBoy getApiEndpoint:@"https://original.com/param1/param2"]); +// +// NSString *testEndpoint; +// XCTAssertNil([appBoy getApiEndpoint:testEndpoint]); +// XCTAssertEqualObjects(@"https://moo.far.com", [appBoy getApiEndpoint:@"moo.far.com"]); +// XCTAssertEqualObjects(@"http://moo.far.com", [appBoy getApiEndpoint:@"http://moo.far.com"]); +//} +// +//- (void)testEndpointOverride2 { +// MPKitAppboy *appBoy = [[MPKitAppboy alloc] init]; +// +// NSDictionary *kitConfiguration = @{@"apiKey":@"BrazeID", +// @"host":@"http://foo.bar.com", +// @"id":@42, +// @"ABKCollectIDFA":@"true", +// @"ABKRequestProcessingPolicyOptionKey": @"1", +// @"ABKFlushIntervalOptionKey":@"2", +// @"ABKSessionTimeoutKey":@"3", +// @"ABKMinimumTriggerTimeIntervalKey":@"4", +// @"ABKCollectIDFA":@"true" +// }; +// +// [appBoy didFinishLaunchingWithConfiguration:kitConfiguration]; +// +// XCTAssertEqualObjects(@"http://foo.bar.com", [appBoy getApiEndpoint:@"https://original.com"]); +// XCTAssertEqualObjects(@"http://foo.bar.com/param1", [appBoy getApiEndpoint:@"https://original.com/param1"]); +// XCTAssertEqualObjects(@"http://foo.bar.com/param1/param2", [appBoy getApiEndpoint:@"https://original.com/param1/param2"]); +// +// NSString *testEndpoint; +// XCTAssertNil([appBoy getApiEndpoint:testEndpoint]); +// XCTAssertEqualObjects(@"https://moo.far.com", [appBoy getApiEndpoint:@"moo.far.com"]); +// XCTAssertEqualObjects(@"http://moo.far.com", [appBoy getApiEndpoint:@"http://moo.far.com"]); +//} +// +//- (void)testEndpointOverride3 { +// MPKitAppboy *appBoy = [[MPKitAppboy alloc] init]; +// +// NSDictionary *kitConfiguration = @{@"apiKey":@"BrazeID", +// @"host":@"foo.bar.com", +// @"id":@42, +// @"ABKCollectIDFA":@"true", +// @"ABKRequestProcessingPolicyOptionKey": @"1", +// @"ABKFlushIntervalOptionKey":@"2", +// @"ABKSessionTimeoutKey":@"3", +// @"ABKMinimumTriggerTimeIntervalKey":@"4", +// @"ABKCollectIDFA":@"true" +// }; +// +// [appBoy didFinishLaunchingWithConfiguration:kitConfiguration]; +// +// XCTAssertEqualObjects(@"https://foo.bar.com", [appBoy getApiEndpoint:@"https://original.com"]); +// XCTAssertEqualObjects(@"https://foo.bar.com/param1", [appBoy getApiEndpoint:@"https://original.com/param1"]); +// XCTAssertEqualObjects(@"https://foo.bar.com/param1/param2", [appBoy getApiEndpoint:@"https://original.com/param1/param2"]); +// +// +// NSString *testEndpoint; +// XCTAssertNil([appBoy getApiEndpoint:testEndpoint]); +// XCTAssertEqualObjects(@"https://moo.far.com", [appBoy getApiEndpoint:@"moo.far.com"]); +// XCTAssertEqualObjects(@"http://moo.far.com", [appBoy getApiEndpoint:@"http://moo.far.com"]); +//} +// +//- (void)testEndpointOverride4 { +// MPKitAppboy *appBoy = [[MPKitAppboy alloc] init]; +// +// NSDictionary *kitConfiguration = @{@"apiKey":@"BrazeID", +// @"host":@"https://foo.bar.com/baz", +// @"id":@42, +// @"ABKCollectIDFA":@"true", +// @"ABKRequestProcessingPolicyOptionKey": @"1", +// @"ABKFlushIntervalOptionKey":@"2", +// @"ABKSessionTimeoutKey":@"3", +// @"ABKMinimumTriggerTimeIntervalKey":@"4", +// @"ABKCollectIDFA":@"true" +// }; +// +// [appBoy didFinishLaunchingWithConfiguration:kitConfiguration]; +// +// XCTAssertEqualObjects(@"https://foo.bar.com/baz", [appBoy getApiEndpoint:@"https://original.com"]); +// XCTAssertEqualObjects(@"https://foo.bar.com/baz/param1", [appBoy getApiEndpoint:@"https://original.com/param1"]); +// XCTAssertEqualObjects(@"https://foo.bar.com/baz/param1/param2", [appBoy getApiEndpoint:@"https://original.com/param1/param2"]); +// +// +// NSString *testEndpoint; +// XCTAssertNil([appBoy getApiEndpoint:testEndpoint]); +// XCTAssertEqualObjects(@"https://moo.far.com", [appBoy getApiEndpoint:@"moo.far.com"]); +// XCTAssertEqualObjects(@"http://moo.far.com", [appBoy getApiEndpoint:@"http://moo.far.com"]); +//} +// +//- (void)testEndpointOverride5 { +// MPKitAppboy *appBoy = [[MPKitAppboy alloc] init]; +// +// NSDictionary *kitConfiguration = @{@"apiKey":@"BrazeID", +// @"host":@"https://foo.bar.com/baz/baz", +// @"id":@42, +// @"ABKCollectIDFA":@"true", +// @"ABKRequestProcessingPolicyOptionKey": @"1", +// @"ABKFlushIntervalOptionKey":@"2", +// @"ABKSessionTimeoutKey":@"3", +// @"ABKMinimumTriggerTimeIntervalKey":@"4", +// @"ABKCollectIDFA":@"true" +// }; +// +// [appBoy didFinishLaunchingWithConfiguration:kitConfiguration]; +// +// XCTAssertEqualObjects(@"https://foo.bar.com/baz/baz", [appBoy getApiEndpoint:@"https://original.com"]); +// XCTAssertEqualObjects(@"https://foo.bar.com/baz/baz/param1", [appBoy getApiEndpoint:@"https://original.com/param1"]); +// XCTAssertEqualObjects(@"https://foo.bar.com/baz/baz/param1/param2", [appBoy getApiEndpoint:@"https://original.com/param1/param2"]); +// +// +// NSString *testEndpoint; +// XCTAssertNil([appBoy getApiEndpoint:testEndpoint]); +// XCTAssertEqualObjects(@"https://moo.far.com", [appBoy getApiEndpoint:@"moo.far.com"]); +// XCTAssertEqualObjects(@"http://moo.far.com", [appBoy getApiEndpoint:@"http://moo.far.com"]); +//} +// +//- (void)testEndpointOverrideNilHost { +// MPKitAppboy *appBoy = [[MPKitAppboy alloc] init]; +// +// NSDictionary *kitConfiguration = @{@"apiKey":@"BrazeID", +// @"id":@42, +// @"ABKCollectIDFA":@"true", +// @"ABKRequestProcessingPolicyOptionKey": @"1", +// @"ABKFlushIntervalOptionKey":@"2", +// @"ABKSessionTimeoutKey":@"3", +// @"ABKMinimumTriggerTimeIntervalKey":@"4", +// @"ABKCollectIDFA":@"true" +// }; +// +// [appBoy didFinishLaunchingWithConfiguration:kitConfiguration]; +// +// XCTAssertEqualObjects(@"https://original.com", [appBoy getApiEndpoint:@"https://original.com"]); +// XCTAssertEqualObjects(@"https://original.com/param1", [appBoy getApiEndpoint:@"https://original.com/param1"]); +// XCTAssertEqualObjects(@"https://original.com/param1/param2", [appBoy getApiEndpoint:@"https://original.com/param1/param2"]); +// +// +// NSString *testEndpoint; +// XCTAssertNil([appBoy getApiEndpoint:testEndpoint]); +// XCTAssertEqualObjects(@"moo.far.com", [appBoy getApiEndpoint:@"moo.far.com"]); +// XCTAssertEqualObjects(@"http://moo.far.com", [appBoy getApiEndpoint:@"http://moo.far.com"]); +//} + +- (void)testSetMessageDelegate { + id delegate = (id)[NSObject new]; + + XCTAssertNil([MPKitAppboy inAppMessageControllerDelegate]); + + [MPKitAppboy setInAppMessageControllerDelegate:delegate]; + + XCTAssertEqualObjects([MPKitAppboy inAppMessageControllerDelegate], delegate); + + XCTestExpectation *expectation = [self expectationWithDescription:@"async work"]; + + dispatch_async(dispatch_get_main_queue(), ^{ + XCTAssertEqualObjects([MPKitAppboy inAppMessageControllerDelegate], delegate); + [expectation fulfill]; + }); + + [self waitForExpectationsWithTimeout:1 handler:nil]; +} + +- (void)testStrongMessageDelegate { + id delegate = (id)[NSObject new]; + + [MPKitAppboy setInAppMessageControllerDelegate:delegate]; + + delegate = nil; + + XCTestExpectation *expectation = [self expectationWithDescription:@"async work"]; + + dispatch_async(dispatch_get_main_queue(), ^{ + XCTAssertNotNil([MPKitAppboy inAppMessageControllerDelegate]); + [expectation fulfill]; + }); + + [self waitForExpectationsWithTimeout:1 handler:nil]; +} + +- (void)testSetDisableNotificationHandling { + XCTAssertEqual([MPKitAppboy shouldDisableNotificationHandling], NO); + + [MPKitAppboy setShouldDisableNotificationHandling:YES]; + + XCTAssertEqual([MPKitAppboy shouldDisableNotificationHandling], YES); + + [MPKitAppboy setShouldDisableNotificationHandling:NO]; + + XCTAssertEqual([MPKitAppboy shouldDisableNotificationHandling], NO); +} + +- (void)testSetBrazeInstance { + BRZConfiguration *configuration = [[BRZConfiguration alloc] init]; + Braze *testClient = [[Braze alloc] initWithConfiguration:configuration]; + + XCTAssertEqualObjects([MPKitAppboy brazeInstance], nil); + + [MPKitAppboy setBrazeInstance:testClient]; + + MPKitAppboy *appBoy = [[MPKitAppboy alloc] init]; + + XCTAssertEqualObjects(appBoy.appboyInstance, nil); + XCTAssertEqualObjects(appBoy.providerKitInstance, nil); + XCTAssertEqualObjects([MPKitAppboy brazeInstance], testClient); + + NSDictionary *kitConfiguration = @{@"apiKey":@"BrazeID", + @"id":@42, + @"ABKCollectIDFA":@"true", + @"ABKRequestProcessingPolicyOptionKey": @"1", + @"ABKFlushIntervalOptionKey":@"2", + @"ABKSessionTimeoutKey":@"3", + @"ABKMinimumTriggerTimeIntervalKey":@"4", + @"userIdentificationType":@"CustomerId" + }; + + [appBoy didFinishLaunchingWithConfiguration:kitConfiguration]; + + XCTAssertEqualObjects(appBoy.appboyInstance, testClient); + XCTAssertEqualObjects(appBoy.providerKitInstance, testClient); + XCTAssertEqualObjects([MPKitAppboy brazeInstance], testClient); +} + +- (void)testUserIdCustomerId { + MPKitAppboy *appBoy = [[MPKitAppboy alloc] init]; + + NSDictionary *kitConfiguration = @{@"apiKey":@"BrazeID", + @"id":@42, + @"ABKCollectIDFA":@"true", + @"ABKRequestProcessingPolicyOptionKey": @"1", + @"ABKFlushIntervalOptionKey":@"2", + @"ABKSessionTimeoutKey":@"3", + @"ABKMinimumTriggerTimeIntervalKey":@"4", + @"userIdentificationType":@"CustomerId" + }; + + [appBoy didFinishLaunchingWithConfiguration:kitConfiguration]; + + XCTAssertEqual(appBoy.configuration[@"userIdentificationType"], @"CustomerId"); +} + +- (void)testUserIdMPID { + MPKitAppboy *appBoy = [[MPKitAppboy alloc] init]; + + NSDictionary *kitConfiguration = @{@"apiKey":@"BrazeID", + @"id":@42, + @"ABKCollectIDFA":@"true", + @"ABKRequestProcessingPolicyOptionKey": @"1", + @"ABKFlushIntervalOptionKey":@"2", + @"ABKSessionTimeoutKey":@"3", + @"ABKMinimumTriggerTimeIntervalKey":@"4", + @"userIdentificationType":@"MPID" + }; + + [appBoy didFinishLaunchingWithConfiguration:kitConfiguration]; + + XCTAssertEqual(appBoy.configuration[@"userIdentificationType"], @"MPID"); +} + +- (void)testlogCommerceEvent { + MPKitAppboy *kit = [[MPKitAppboy alloc] init]; + kit.configuration = @{@"bundleCommerceEventData" : @0}; + + BRZConfiguration *configuration = [[BRZConfiguration alloc] init]; + Braze *testClient = [[Braze alloc] initWithConfiguration:configuration]; + id mockClient = OCMPartialMock(testClient); + [kit setAppboyInstance:mockClient]; + + XCTAssertEqualObjects(mockClient, [kit appboyInstance]); + + MPProduct *product = [[MPProduct alloc] initWithName:@"product1" sku:@"1131331343" quantity:@1 price:@13]; + + MPCommerceEvent *event = [[MPCommerceEvent alloc] initWithAction:MPCommerceEventActionClick product:product]; + event.customAttributes = @{@"testKey" : @"testCustomAttValue"}; + + MPTransactionAttributes *attributes = [[MPTransactionAttributes alloc] init]; + attributes.transactionId = @"foo-transaction-id"; + attributes.revenue = @13.00; + attributes.tax = @3; + attributes.shipping = @3; + + event.transactionAttributes = attributes; + + [[mockClient expect] logCustomEvent:@"eCommerce - click - Item" + properties:@{@"Id" : @"1131331343", + @"Item Price" : @"13", + @"Name" : @"product1", + @"Quantity" : @"1", + @"Total Product Amount" : @"13", + @"testKey" : @"testCustomAttValue" + }]; + + MPKitExecStatus *execStatus = [kit logBaseEvent:event]; + + XCTAssertEqual(execStatus.returnCode, MPKitReturnCodeSuccess); + + [mockClient verify]; + + [mockClient stopMocking]; +} + +- (void)testlogCommerceEventWithBundledProducts { + MPKitAppboy *kit = [[MPKitAppboy alloc] init]; + kit.configuration = @{@"bundleCommerceEventData" : @1}; + + BRZConfiguration *configuration = [[BRZConfiguration alloc] init]; + Braze *testClient = [[Braze alloc] initWithConfiguration:configuration]; + id mockClient = OCMPartialMock(testClient); + [kit setAppboyInstance:mockClient]; + + XCTAssertEqualObjects(mockClient, [kit appboyInstance]); + + MPProduct *product = [[MPProduct alloc] initWithName:@"product1" sku:@"1131331343" quantity:@1 price:@13]; + + MPCommerceEvent *event = [[MPCommerceEvent alloc] initWithAction:MPCommerceEventActionClick product:product]; + event.customAttributes = @{@"testKey" : @"testCustomAttValue"}; + + MPTransactionAttributes *attributes = [[MPTransactionAttributes alloc] init]; + attributes.transactionId = @"foo-transaction-id"; + attributes.revenue = @13.00; + attributes.tax = @3; + attributes.shipping = @3; + + event.transactionAttributes = attributes; + + [[mockClient expect] logCustomEvent:@"eCommerce - click" + properties:@{@"Attributes" : @{@"testKey" : @"testCustomAttValue"}, + @"products" : @[@{ + @"Id" : @"1131331343", + @"Item Price" : @"13", + @"Name" : @"product1", + @"Quantity" : @"1", + @"Total Product Amount" : @"13" + } + ] + }]; + + MPKitExecStatus *execStatus = [kit logBaseEvent:event]; + + XCTAssertEqual(execStatus.returnCode, MPKitReturnCodeSuccess); + + [mockClient verify]; + + [mockClient stopMocking]; +} + +- (void)testlogPurchaseCommerceEvent { + MPKitAppboy *kit = [[MPKitAppboy alloc] init]; + kit.configuration = @{@"bundleCommerceEventData" : @0}; + + BRZConfiguration *configuration = [[BRZConfiguration alloc] init]; + Braze *testClient = [[Braze alloc] initWithConfiguration:configuration]; + id mockClient = OCMPartialMock(testClient); + [kit setAppboyInstance:mockClient]; + + XCTAssertEqualObjects(mockClient, [kit appboyInstance]); + + MPProduct *product = [[MPProduct alloc] initWithName:@"product1" sku:@"1131331343" quantity:@1 price:@13]; + product.category = @"category1"; + + MPCommerceEvent *event = [[MPCommerceEvent alloc] initWithAction:MPCommerceEventActionPurchase product:product]; + event.customAttributes = @{@"testKey" : @"testCustomAttValue"}; + + MPTransactionAttributes *attributes = [[MPTransactionAttributes alloc] init]; + attributes.transactionId = @"foo-transaction-id"; + attributes.revenue = @13.00; + attributes.tax = @3; + attributes.shipping = @3; + + event.transactionAttributes = attributes; + + [[mockClient expect] logPurchase:@"1131331343" + currency:@"USD" + price:[@"13" doubleValue] + quantity:1 + properties:@{@"Shipping Amount" : @3, + @"Total Amount" : @13.00, + @"Total Product Amount" : @"13", + @"Tax Amount" : @3, + @"Transaction Id" : @"foo-transaction-id", + @"Name" : @"product1", + @"Category" : @"category1", + @"testKey" : @"testCustomAttValue" + }]; + + MPKitExecStatus *execStatus = [kit logBaseEvent:event]; + + XCTAssertEqual(execStatus.returnCode, MPKitReturnCodeSuccess); + + [mockClient verify]; + + [mockClient stopMocking]; +} + +- (void)testlogPurchaseCommerceEventSendingProductName { + MPKitAppboy *kit = [[MPKitAppboy alloc] init]; + kit.configuration = @{@"bundleCommerceEventData" : @0, + @"replaceSkuWithProductName": @"True"}; + + BRZConfiguration *configuration = [[BRZConfiguration alloc] init]; + Braze *testClient = [[Braze alloc] initWithConfiguration:configuration]; + id mockClient = OCMPartialMock(testClient); + [kit setAppboyInstance:mockClient]; + + XCTAssertEqualObjects(mockClient, [kit appboyInstance]); + + MPProduct *product = [[MPProduct alloc] initWithName:@"product1" sku:@"1131331343" quantity:@1 price:@13]; + product.category = @"category1"; + + MPCommerceEvent *event = [[MPCommerceEvent alloc] initWithAction:MPCommerceEventActionPurchase product:product]; + event.customAttributes = @{@"testKey" : @"testCustomAttValue"}; + + MPTransactionAttributes *attributes = [[MPTransactionAttributes alloc] init]; + attributes.transactionId = @"foo-transaction-id"; + attributes.revenue = @13.00; + attributes.tax = @3; + attributes.shipping = @3; + + event.transactionAttributes = attributes; + + [[mockClient expect] logPurchase:@"product1" + currency:@"USD" + price:[@"13" doubleValue] + quantity:1 + properties:@{@"Shipping Amount" : @3, + @"Total Amount" : @13.00, + @"Total Product Amount" : @"13", + @"Tax Amount" : @3, + @"Transaction Id" : @"foo-transaction-id", + @"Name" : @"product1", + @"Category" : @"category1", + @"testKey" : @"testCustomAttValue" + }]; + + MPKitExecStatus *execStatus = [kit logBaseEvent:event]; + + XCTAssertEqual(execStatus.returnCode, MPKitReturnCodeSuccess); + + [mockClient verify]; + + [mockClient stopMocking]; +} + +- (void)testlogPurchaseCommerceEventWithBundledProducts { + MPKitAppboy *kit = [[MPKitAppboy alloc] init]; + kit.configuration = @{@"bundleCommerceEventData" : @1}; + + BRZConfiguration *configuration = [[BRZConfiguration alloc] init]; + Braze *testClient = [[Braze alloc] initWithConfiguration:configuration]; + id mockClient = OCMPartialMock(testClient); + [kit setAppboyInstance:mockClient]; + + XCTAssertEqualObjects(mockClient, [kit appboyInstance]); + + MPProduct *product = [[MPProduct alloc] initWithName:@"product1" sku:@"1131331343" quantity:@1 price:@13]; + + MPCommerceEvent *event = [[MPCommerceEvent alloc] initWithAction:MPCommerceEventActionPurchase product:product]; + event.customAttributes = @{@"testKey" : @"testCustomAttValue"}; + + MPTransactionAttributes *attributes = [[MPTransactionAttributes alloc] init]; + attributes.transactionId = @"foo-transaction-id"; + attributes.revenue = @13.00; + attributes.tax = @3; + attributes.shipping = @3; + + event.transactionAttributes = attributes; + + NSDictionary *testResultDict = @{@"Attributes" : @{@"testKey" : @"testCustomAttValue"}, + @"Shipping Amount" : @3, + @"Total Amount" : @13.00, + @"Tax Amount" : @3, + @"Transaction Id" : @"foo-transaction-id", + @"products" : @[@{ + @"Id" : @"1131331343", + @"Item Price" : @"13", + @"Name" : @"product1", + @"Quantity" : @"1", + @"Total Product Amount" : @"13" + } + ] + }; + BOOL (^testBlock)(id value) = ^BOOL(id value) { + if ([value isKindOfClass:[NSDictionary class]]) { + for (NSString *key in [(NSDictionary *)value allKeys]) { + if ([key isEqualToString: @"products"]) { + NSArray *productArray = (NSArray *)((NSDictionary *)value[key]); + for (int i = 0; i < productArray.count; i++) { + NSDictionary *productDict = productArray[i]; + for (NSString *productDictKey in [productDict allKeys]) { + if (![productDict[productDictKey] isEqual:testResultDict[key][i][productDictKey]]) { + NSLog(@"Invalid Object in Product: %@ Key: %@", productDict, productDictKey); + return false; + } + } + } + } + if (![(NSDictionary *)value[key] isEqual:testResultDict[key]]) { + NSLog(@"Invalid Object in Key: %@", key); + return false; + } + } + return true; + } + return false; + }; + + OCMExpect(([mockClient logPurchase:@"eCommerce - purchase" + currency:@"USD" + price:[@"13" doubleValue] + properties:[OCMArg checkWithBlock:testBlock] + ])); + + MPKitExecStatus *execStatus = [kit logBaseEvent:event]; + + XCTAssertEqual(execStatus.returnCode, MPKitReturnCodeSuccess); + + OCMVerifyAll(mockClient); + + [mockClient stopMocking]; +} + +- (void)testlogCommerceEventWithMultipleBundledProducts { + MPKitAppboy *kit = [[MPKitAppboy alloc] init]; + kit.configuration = @{@"bundleCommerceEventData" : @1}; + + BRZConfiguration *configuration = [[BRZConfiguration alloc] init]; + Braze *testClient = [[Braze alloc] initWithConfiguration:configuration]; + id mockClient = OCMPartialMock(testClient); + [kit setAppboyInstance:mockClient]; + + XCTAssertEqualObjects(mockClient, [kit appboyInstance]); + + MPProduct *product1 = [[MPProduct alloc] initWithName:@"product1" sku:@"1131331343" quantity:@1 price:@13]; + MPProduct *product2 = [[MPProduct alloc] initWithName:@"product2" sku:@"1131331888" quantity:@1 price:@13]; + product2.userDefinedAttributes[@"testKey"] = @"testCustomAttValue"; + + MPCommerceEvent *event = [[MPCommerceEvent alloc] initWithAction:MPCommerceEventActionPurchase]; + [event addProducts:@[product1, product2]]; + event.customAttributes = @{@"testKey" : @"testCustomAttValue"}; + + MPTransactionAttributes *attributes = [[MPTransactionAttributes alloc] init]; + attributes.transactionId = @"foo-transaction-id"; + attributes.revenue = @26.00; + attributes.tax = @3; + attributes.shipping = @3; + + event.transactionAttributes = attributes; + + NSDictionary *testResultDict = @{@"Attributes" : @{@"testKey" : @"testCustomAttValue"}, + @"Shipping Amount" : @3, + @"Total Amount" : @26.00, + @"Tax Amount" : @3, + @"Transaction Id" : @"foo-transaction-id", + @"products" : @[@{ + @"Id" : @"1131331343", + @"Item Price" : @"13", + @"Name" : @"product1", + @"Quantity" : @"1", + @"Total Product Amount" : @"13" + }, + @{ + @"Id" : @"1131331888", + @"Item Price" : @"13", + @"Name" : @"product2", + @"Quantity" : @"1", + @"Total Product Amount" : @"13", + @"Attributes" : @{@"testKey" : @"testCustomAttValue"} + } + ] + }; + BOOL (^testBlock)(id value) = ^BOOL(id value) { + if ([value isKindOfClass:[NSDictionary class]]) { + for (NSString *key in [(NSDictionary *)value allKeys]) { + if ([key isEqualToString: @"products"]) { + NSArray *productArray = (NSArray *)((NSDictionary *)value[key]); + for (int i = 0; i < productArray.count; i++) { + NSDictionary *productDict = productArray[i]; + for (NSString *productDictKey in [productDict allKeys]) { + if (![productDict[productDictKey] isEqual:testResultDict[key][i][productDictKey]]) { + NSLog(@"Invalid Object in Product: %@ Key: %@", productDict, productDictKey); + return false; + } + } + } + } + if (![(NSDictionary *)value[key] isEqual:testResultDict[key]]) { + NSLog(@"Invalid Object in Key: %@", key); + return false; + } + } + return true; + } + return false; + }; + + OCMExpect(([mockClient logPurchase:@"eCommerce - purchase" + currency:@"USD" + price:[@"26" doubleValue] + properties:[OCMArg checkWithBlock:testBlock] + ])); + + MPKitExecStatus *execStatus = [kit logBaseEvent:event]; + + XCTAssertEqual(execStatus.returnCode, MPKitReturnCodeSuccess); + + OCMVerifyAll(mockClient); + + [mockClient stopMocking]; +} + +- (void)testlogPromotionCommerceEventWithBundledProducts { + MPKitAppboy *kit = [[MPKitAppboy alloc] init]; + kit.configuration = @{@"bundleCommerceEventData" : @1}; + + BRZConfiguration *configuration = [[BRZConfiguration alloc] init]; + Braze *testClient = [[Braze alloc] initWithConfiguration:configuration]; + id mockClient = OCMPartialMock(testClient); + [kit setAppboyInstance:mockClient]; + + XCTAssertEqualObjects(mockClient, [kit appboyInstance]); + + MPPromotion *promotion = [[MPPromotion alloc] init]; + promotion.promotionId = @"my_promo_1"; + promotion.creative = @"sale_banner_1"; + promotion.name = @"App-wide 50% off sale"; + promotion.position = @"dashboard_bottom"; + + MPPromotionContainer *container = + [[MPPromotionContainer alloc] initWithAction:MPPromotionActionView + promotion:promotion]; + + MPCommerceEvent *event = [[MPCommerceEvent alloc] initWithPromotionContainer:container]; + event.customAttributes = @{@"testKey" : @"testCustomAttValue"}; + + [[mockClient expect] logCustomEvent:@"eCommerce - view" + properties:@{@"Attributes" : @{@"testKey" : @"testCustomAttValue"}, + @"promotions" : @[@{ + @"Creative" : @"sale_banner_1", + @"Name" : @"App-wide 50% off sale", + @"Position" : @"dashboard_bottom", + @"Id" : @"my_promo_1" + } + ] + }]; + + MPKitExecStatus *execStatus = [kit logBaseEvent:event]; + + XCTAssertEqual(execStatus.returnCode, MPKitReturnCodeSuccess); + + [mockClient verify]; + + [mockClient stopMocking]; +} + +- (void)testlogImpressionCommerceEventWithBundledProducts { + MPKitAppboy *kit = [[MPKitAppboy alloc] init]; + kit.configuration = @{@"bundleCommerceEventData" : @1}; + + BRZConfiguration *configuration = [[BRZConfiguration alloc] init]; + Braze *testClient = [[Braze alloc] initWithConfiguration:configuration]; + id mockClient = OCMPartialMock(testClient); + [kit setAppboyInstance:mockClient]; + + XCTAssertEqualObjects(mockClient, [kit appboyInstance]); + + MPProduct *product = [[MPProduct alloc] initWithName:@"product1" sku:@"1131331343" quantity:@1 price:@13]; + product.userDefinedAttributes = [@{@"productTestKey" : @"productTestCustomAttValue"} mutableCopy]; + + MPCommerceEvent *event = [[MPCommerceEvent alloc] initWithImpressionName:@"Suggested Products List" product:product]; + event.customAttributes = @{@"testKey" : @"testCustomAttValue"}; + + [[mockClient expect] logCustomEvent:@"eCommerce - impression" + properties:@{@"Attributes" : @{@"testKey" : @"testCustomAttValue"}, + @"impressions" : @[@{ + @"Product Impression List" : @"Suggested Products List", + @"products" : @[@{ + @"Id" : @"1131331343", + @"Item Price" : @"13", + @"Name" : @"product1", + @"Quantity" : @"1", + @"Total Product Amount" : @"13", + @"Attributes" : @{@"productTestKey" : @"productTestCustomAttValue"} + } + ] + } + ] + }]; + + MPKitExecStatus *execStatus = [kit logBaseEvent:event]; + + XCTAssertEqual(execStatus.returnCode, MPKitReturnCodeSuccess); + + [mockClient verify]; + + [mockClient stopMocking]; +} + +//- (void)testTypeDetection { +// MPKitAppboy *kit = [[MPKitAppboy alloc] init]; +// +// BRZConfiguration *configuration = [[BRZConfiguration alloc] init]; +// Braze *testClient = [[Braze alloc] initWithConfiguration:configuration]; +// id mockClient = OCMPartialMock(testClient); +// [kit setAppboyInstance:mockClient]; +// +// XCTAssertEqualObjects(mockClient, [kit appboyInstance]); +// +// +// MPEvent *event = [[MPEvent alloc] initWithName:@"test event" type:MPEventTypeNavigation]; +// event.customAttributes = @{@"foo":@"5.0", @"bar": @"true", @"baz": @"abc", @"qux": @"-3", @"quux": @"1970-01-01T00:00:00Z"}; +// +// [kit setEnableTypeDetection:YES]; +// [[mockClient expect] logCustomEvent:event.name withProperties:@{@"foo":@5.0, @"bar": @YES, @"baz":@"abc", @"qux": @-3, @"quux": [NSDate dateWithTimeIntervalSince1970:0]}]; +// +// MPKitExecStatus *execStatus = [kit logBaseEvent:event]; +// +// XCTAssertEqual(execStatus.returnCode, MPKitReturnCodeSuccess); +// +// [mockClient verify]; +// +// [mockClient stopMocking]; +//} +// +// +//- (void)testTypeDetectionDisable { +// MPKitAppboy *kit = [[MPKitAppboy alloc] init]; +// +// BRZConfiguration *configuration = [[BRZConfiguration alloc] init]; +// Braze *testClient = [[Braze alloc] initWithConfiguration:configuration]; +// id mockClient = OCMPartialMock(testClient); +// [kit setAppboyInstance:mockClient]; +// +// XCTAssertEqualObjects(mockClient, [kit appboyInstance]); +// +// +// MPEvent *event = [[MPEvent alloc] initWithName:@"test event" type:MPEventTypeNavigation]; +// event.customAttributes = @{@"foo":@"5.0", @"bar": @"true", @"baz": @"abc", @"quz": @"-3", @"qux": @"1970-01-01T00:00:00Z"}; +// +// [kit setEnableTypeDetection:NO]; +// [[mockClient expect] logCustomEvent:event.name withProperties:event.customAttributes]; +// +// MPKitExecStatus *execStatus = [kit logBaseEvent:event]; +// +// XCTAssertEqual(execStatus.returnCode, MPKitReturnCodeSuccess); +// +// [mockClient verify]; +// +// [mockClient stopMocking]; +//} + +- (void)testEventWithEmptyProperties { + MPKitAppboy *kit = [[MPKitAppboy alloc] init]; + + BRZConfiguration *configuration = [[BRZConfiguration alloc] init]; + Braze *testClient = [[Braze alloc] initWithConfiguration:configuration]; + id mockClient = OCMPartialMock(testClient); + [kit setAppboyInstance:mockClient]; + + XCTAssertEqualObjects(mockClient, [kit appboyInstance]); + + + MPEvent *event = [[MPEvent alloc] initWithName:@"test event" type:MPEventTypeNavigation]; + event.customAttributes = @{}; + + [kit setEnableTypeDetection:NO]; + [[mockClient expect] logCustomEvent:event.name]; + + MPKitExecStatus *execStatus = [kit logBaseEvent:event]; + + XCTAssertEqual(execStatus.returnCode, MPKitReturnCodeSuccess); + + [mockClient verify]; + + [mockClient stopMocking]; +} + +@end