diff --git a/Package.swift b/Package.swift index 09d770a..38f6f43 100644 --- a/Package.swift +++ b/Package.swift @@ -18,8 +18,8 @@ let package = Package( targets: [ .binaryTarget( name: "Batch", - url: "https://download.batch.com/sdk/ios/spm/BatchSDK-ios_spm-xcframework-2.0.2.zip", - checksum: "2c8adaf4aec479d203263c02904ef7446f224d27b70df4164a5c7db5d5343f8c" + url: "https://download.batch.com/sdk/ios/spm/BatchSDK-ios_spm-xcframework-2.1.0.zip", + checksum: "7e91b40df3e2ce23ebf3b3589771770a9038042a8ccda6785be25e6b9c60307f" ) ] ) \ No newline at end of file diff --git a/Sources/Batch/BatchCore.m b/Sources/Batch/BatchCore.m index 83ede40..dd5edd0 100644 --- a/Sources/Batch/BatchCore.m +++ b/Sources/Batch/BatchCore.m @@ -23,11 +23,6 @@ + (void)startWithAPIKey:(NSString *)key { [BACenterMulticastDelegate startWithAPIKey:key]; } -// Set if Batch can try to use IDFA. Deprecated. -+ (void)setUseIDFA:(BOOL)use { - [BALogger publicForDomain:nil message:@"Ignoring 'setUseIDFA' API call: Batch has removed support for IDFA."]; -} - + (void)setLoggerDelegate:(id)loggerDelegate { [[[BACoreCenter instance] configuration] setLoggerDelegate:loggerDelegate]; } diff --git a/Sources/Batch/BatchProfileEditor.h b/Sources/Batch/BatchProfileEditor.h index 5f4b764..f5ed4a5 100644 --- a/Sources/Batch/BatchProfileEditor.h +++ b/Sources/Batch/BatchProfileEditor.h @@ -14,6 +14,12 @@ typedef NS_ENUM(NSUInteger, BatchEmailSubscriptionState) { BatchEmailSubscriptionStateUnsubscribed = 1, }; +/// Enum defining the state of an SMS subscription +typedef NS_ENUM(NSUInteger, BatchSMSSubscriptionState) { + BatchSMSSubscriptionStateSubscribed = 0, + BatchSMSSubscriptionStateUnsubscribed = 1, +}; + /// Provides profile attribute edition methods. /// /// Once save() has been called once (or implicitly when using the editor block), you will @@ -89,7 +95,7 @@ typedef NS_ENUM(NSUInteger, BatchEmailSubscriptionState) { /// Set the user email. /// /// - Important: This method requires to already have a registered identifier for the user -/// or to call ``BatchProfile.identify()`` method before this one. +/// or to call ``BatchProfile/identify`` method before this one. /// - Parameters: /// - email: User email. /// - error Pointer to an error describing. Note that the error is only about validation and doesn't @@ -99,10 +105,42 @@ typedef NS_ENUM(NSUInteger, BatchEmailSubscriptionState) { /// Set the user email subscription state. /// +/// Note that profile's subscription status is automatically set to unsubscribed when a user click an unsubscribe link. /// - Parameters: /// - state: Subscription state - (void)setEmailMarketingSubscriptionState:(BatchEmailSubscriptionState)state; +/// Set the profile phone number. +/// +/// - Important: This method requires to already have a registered identifier for the user +/// or to call ``BatchProfile/identify:`` method before this one. +/// - Parameters: +/// - phoneNumber: A valid [E.164](https://en.wikipedia.org/wiki/E.164) formatted string. Must start with a "+" and +/// not be longer than 15 digits without special characters (eg: "+33123456789"). nil to reset. +/// - error Pointer to an error describing. Note that the error is only about validation and doesn't mean the value +/// has been sent to the server yet. +/// - Returns: A boolean indicating whether the attribute passed validation or not. +/// +/// ## Examples: +/// ```swift +/// BatchProfile.identify("my_custom_user_id") +/// let editor = BatchProfile.editor() +/// try? editor.setPhoneNumber("+33123456789").save() +/// ``` +/// ```objc +/// [BatchProfile identify: @"my_custom_user_id"]; +/// BatchProfileEditor *editor = [BatchProfile editor]; +/// [editor setPhoneNumber:@"+33123456789" error:nil]; +/// ``` +- (BOOL)setPhoneNumber:(nullable NSString *)phoneNumber error:(NSError *_Nullable *_Nullable)error; + +/// Set the profile SMS marketing subscription state. +/// +/// Note that profile's subscription status is automatically set to unsubscribed when a user send a STOP message. +/// - Parameters: +/// - state: State of the subscription +- (void)setSMSMarketingSubscriptionState:(BatchSMSSubscriptionState)state; + /// Set a boolean profile attribute for a key. /// /// - Parameters: diff --git a/Sources/Batch/BatchProfileEditor.m b/Sources/Batch/BatchProfileEditor.m index eb32a48..cd00819 100644 --- a/Sources/Batch/BatchProfileEditor.m +++ b/Sources/Batch/BatchProfileEditor.m @@ -93,6 +93,24 @@ - (void)setEmailMarketingSubscriptionState:(BatchEmailSubscriptionState)state { [_backingImpl setEmailMarketingSubscriptionState:swiftState]; } +- (BOOL)setPhoneNumber:(nullable NSString *)phoneNumber error:(NSError **)error { + INIT_AND_BLANK_ERROR_IF_NEEDED(error) + ENSURE_ATTRIBUTE_VALUE_CLASS_NILABLE(phoneNumber, NSString.class) + return [_backingImpl setPhoneNumber:phoneNumber error:error]; +} + +- (void)setSMSMarketingSubscriptionState:(BatchSMSSubscriptionState)state { + BATProfileEditorSMSSubscriptionState swiftState; + switch (state) { + case BatchSMSSubscriptionStateSubscribed: + swiftState = BATProfileEditorSMSSubscriptionStateSubscribed; + break; + case BatchSMSSubscriptionStateUnsubscribed: + swiftState = BATProfileEditorSMSSubscriptionStateUnsubscribed; + } + [_backingImpl setSMSMarketingSubscriptionState:swiftState]; +} + - (BOOL)addItemToStringArrayAttribute:(NSString *)element forKey:(NSString *)key error:(NSError **)error { INIT_AND_BLANK_ERROR_IF_NEEDED(error) ENSURE_KEY_STRING(key) diff --git a/Sources/Batch/Modules/Opt Out/BAOptOut.m b/Sources/Batch/Modules/Opt Out/BAOptOut.m index 044ae60..de96cf8 100644 --- a/Sources/Batch/Modules/Opt Out/BAOptOut.m +++ b/Sources/Batch/Modules/Opt Out/BAOptOut.m @@ -149,9 +149,7 @@ - (void)applyOptOut:(BOOL)shouldOptOut wipeData:(BOOL)wipeData { - (NSMutableDictionary *)makeBaseEventData { NSMutableDictionary *data = [NSMutableDictionary new]; - data[@"di"] = [[BAPropertiesCenter valueForShortName:@"di"] uppercaseString]; - data[@"idfa"] = [BAPropertiesCenter valueForShortName:@"idfa"]; data[@"cus"] = [BAPropertiesCenter valueForShortName:@"cus"]; data[@"tok"] = [BAPropertiesCenter valueForShortName:@"tok"]; return data; diff --git a/Sources/Batch/Modules/Profile/BAProfileCenter.swift b/Sources/Batch/Modules/Profile/BAProfileCenter.swift index 18b4efb..a911db2 100644 --- a/Sources/Batch/Modules/Profile/BAProfileCenter.swift +++ b/Sources/Batch/Modules/Profile/BAProfileCenter.swift @@ -63,6 +63,11 @@ public class BAProfileCenter: NSObject, BAProfileCenterProtocol { BALogger.public(domain: loggerDomain, message: "Cannot identify, Custom ID is invalid: it cannot be only made of whitespace or contain a newline.") return } + + guard BATProfileDataValidators.isCustomIDBlocklisted(customID) == false else { + BALogger.public(domain: loggerDomain, message: "Cannot identify, Custom ID is blocklisted: `\(customID)`. Please ensure you have correctly implemented the API.") + return + } } // Compatibility @@ -200,7 +205,7 @@ public class BAProfileCenter: NSObject, BAProfileCenterProtocol { } func sendIdentifyEvent(customID: String?) { - guard let installID = BatchUser.installationID else { + guard let installID = BatchUser.installationID, !installID.isEmpty else { BALogger.error(domain: loggerDomain, message: "Could not track identify event: nil Installation ID") return } diff --git a/Sources/Batch/Modules/Profile/BATEventAttributesSerializer.swift b/Sources/Batch/Modules/Profile/BATEventAttributesSerializer.swift index 1383552..6e3e26a 100644 --- a/Sources/Batch/Modules/Profile/BATEventAttributesSerializer.swift +++ b/Sources/Batch/Modules/Profile/BATEventAttributesSerializer.swift @@ -38,12 +38,14 @@ public class BATEventAttributesSerializer: NSObject { switch attributeValue.type { case .date, .string, .double, .integer, .bool: jsonAttributes[jsonKey] = attributeValue.value + case .URL: if let urlValue = attributeValue.value as? URL { jsonAttributes[jsonKey] = urlValue.absoluteString } else { throw BATSDKError.sdkInternal(subcode: 1, reason: "attribute isn't an URL") } + case .stringArray: if let arrayValue = attributeValue.value as? [String] { jsonAttributes[jsonKey] = arrayValue diff --git a/Sources/Batch/Modules/Profile/BATProfileDataValidators.swift b/Sources/Batch/Modules/Profile/BATProfileDataValidators.swift index c6bb27e..a650794 100644 --- a/Sources/Batch/Modules/Profile/BATProfileDataValidators.swift +++ b/Sources/Batch/Modules/Profile/BATProfileDataValidators.swift @@ -10,22 +10,25 @@ import Foundation @objcMembers public class BATProfileDataValidators: NSObject { static let loggingDomain = "ProfileDataValidator" - // \r\n\t is \s but for some reason \S doesn't validate those in a negation so we explicitly use those - static let emailValidationRegexpPattern = "^[^@\\r\\n\\t]+@[A-z0-9\\-\\.]+\\.[A-z0-9]+$" + + static let emailAddressPattern = "^[^@\\s]+@[A-z0-9\\-\\.]+\\.[A-z0-9]+$" + static let phoneNumberPattern = "^\\+[0-9]{1,15}$" public static let emailMaxLength = 256 public static let customIDMaxLength = 1024 - public static func isValidEmail(_ email: String) -> Bool { - let regexp = BATRegularExpression(pattern: emailValidationRegexpPattern) - guard regexp.regexpFailedToInitialize == false else { - BALogger.debug(domain: loggingDomain, message: "Email regexp unavailable") - return false - } + static let blocklistedCustomIDs = ["undefined", "null", "nil", "(null)", "[object object]", "true", "false", "nan", "infinity", "-infinity"] + public static func isValidEmail(_ email: String) -> Bool { + let regexp = BATRegularExpression(pattern: emailAddressPattern) return regexp.matches(email) } + public static func isValidPhoneNumber(_ phoneNumber: String) -> Bool { + let regexp = BATRegularExpression(pattern: phoneNumberPattern) + return regexp.matches(phoneNumber) + } + public static func isEmailTooLong(_ email: String) -> Bool { return email.count > emailMaxLength } @@ -49,4 +52,8 @@ public class BATProfileDataValidators: NSObject { return false } + + public static func isCustomIDBlocklisted(_ customID: String) -> Bool { + return blocklistedCustomIDs.contains(customID.lowercased()) + } } diff --git a/Sources/Batch/Modules/Profile/BATProfileEditor.swift b/Sources/Batch/Modules/Profile/BATProfileEditor.swift index cb4c7c2..a9dba0e 100644 --- a/Sources/Batch/Modules/Profile/BATProfileEditor.swift +++ b/Sources/Batch/Modules/Profile/BATProfileEditor.swift @@ -9,13 +9,11 @@ import Foundation fileprivate enum Maximums { static let stringArrayItems = 25 static let stringLength = 64 - static let emailLength = 256 static let urlLength = 2048 } fileprivate enum Consts { static let attributeNamePattern = "^[a-zA-Z0-9_]{1,30}$" - static let emailAddressPattern = "^[^@\\s]+@[A-z0-9\\-\\.]+\\.[A-z0-9]+$" } /// Protocol that exposes BATProfileEditor's state, so that it can be serialized @@ -24,6 +22,10 @@ protocol BATSerializableProfileEditorProtocol { var emailMarketingSubscription: BATProfileEditorEmailSubscriptionState? { get } + var phoneNumber: (any BATProfileAttributeOperation)? { get } + + var smsMarketingSubscription: BATProfileEditorSMSSubscriptionState? { get } + var language: (any BATProfileAttributeOperation)? { get } var region: (any BATProfileAttributeOperation)? { get } @@ -79,12 +81,15 @@ public protocol BATInstallDataEditorCompatibilityProtocol { @objc public class BATProfileEditor: NSObject, BATSerializableProfileEditorProtocol, NSCopying { private let attributeNameRegexp: BATRegularExpression = .init(pattern: Consts.attributeNamePattern) - private let emailAddressRegexp: BATRegularExpression = .init(pattern: Consts.emailAddressPattern) private(set) var email: (any BATProfileAttributeOperation)? private(set) var emailMarketingSubscription: BATProfileEditorEmailSubscriptionState? + private(set) var phoneNumber: (any BATProfileAttributeOperation)? + + private(set) var smsMarketingSubscription: BATProfileEditorSMSSubscriptionState? + private(set) var language: (any BATProfileAttributeOperation)? private(set) var region: (any BATProfileAttributeOperation)? @@ -114,18 +119,18 @@ public class BATProfileEditor: NSObject, BATSerializableProfileEditorProtocol, N public func setEmail(_ value: String?) throws { try checkIfConsumed() + if !isProfileIdentified() { + throw BatchProfileError(code: .editorInvalidValue, reason: "Emails cannot be set on a profile if it has not been identified first. Please call 'BatchProfile.idenfity()' with a non nil value beforehand.") + } + if let value { let baseError = "Cannot set email address:" - if !canSetEmail() { - throw BatchProfileError(code: .editorInvalidValue, reason: "Emails cannot be set on a profile if it has not been identified first. Please call 'BatchProfile.idenfity()' with a non nil value beforehand.") - } - - if value.count > Maximums.emailLength { - throw BatchProfileError(code: .editorInvalidValue, reason: "\(baseError) address cannot be longer than \(Maximums.emailLength) characters") + if BATProfileDataValidators.isEmailTooLong(value) { + throw BatchProfileError(code: .editorInvalidValue, reason: "\(baseError) address cannot be longer than \(BATProfileDataValidators.emailMaxLength) characters") } - if !emailAddressRegexp.matches(value) { + if !BATProfileDataValidators.isValidEmail(value) { throw BatchProfileError(code: .editorInvalidValue, reason: "\(baseError) invalid address") } @@ -145,6 +150,34 @@ public class BATProfileEditor: NSObject, BATSerializableProfileEditorProtocol, N } } + @objc + public func setPhoneNumber(_ value: String?) throws { + try checkIfConsumed() + + if !isProfileIdentified() { + throw BatchProfileError(code: .editorInvalidValue, reason: "Phone number cannot be set on a profile if it has not been identified first. Please call 'BatchProfile.idenfity()' with a non nil value beforehand.") + } + + if let value { + if !BATProfileDataValidators.isValidPhoneNumber(value) { + throw BatchProfileError(code: .editorInvalidValue, reason: "Invalid phone number. Please make sure that the string starts with a `+` and is no longer than 15 digits.") + } + phoneNumber = BATProfileAttributeSetOperation(type: .string, value: value) + } else { + phoneNumber = BATProfileAttributeDeleteOperation() + } + } + + @objc + public func setSMSMarketingSubscriptionState(_ value: BATProfileEditorSMSSubscriptionState) { + do { + try checkIfConsumed() + smsMarketingSubscription = value + } catch { + // Do nothing + } + } + @objc public func setLanguage(_ value: String?) throws { try checkIfConsumed() @@ -393,14 +426,16 @@ public class BATProfileEditor: NSObject, BATSerializableProfileEditorProtocol, N let copy = BATProfileEditor() copy.email = self.email copy.emailMarketingSubscription = self.emailMarketingSubscription + copy.phoneNumber = self.phoneNumber + copy.smsMarketingSubscription = self.smsMarketingSubscription copy.language = self.language copy.region = self.region copy.customAttributes = self.customAttributes return copy } - func canSetEmail() -> Bool { - // We can only set an email if the user is logged in + func isProfileIdentified() -> Bool { + // We can only set an email or a phone number if the user is logged in // This method is exposed for testing purposes return BAUserProfile.default().customIdentifier != nil } @@ -478,3 +513,11 @@ public enum BATProfileEditorEmailSubscriptionState: UInt { case subscribed = 0 case unsubscribed = 1 } + +/// SMS subscription state. This is already defined in BatchProfile.h, but we cannot reexpose +/// an @objc method with a parameter from a public header, as this creates an import loop. +@objc +public enum BATProfileEditorSMSSubscriptionState: UInt { + case subscribed = 0 + case unsubscribed = 1 +} diff --git a/Sources/Batch/Modules/Profile/BATProfileOperationsSerializer.swift b/Sources/Batch/Modules/Profile/BATProfileOperationsSerializer.swift index 8452603..413a47f 100644 --- a/Sources/Batch/Modules/Profile/BATProfileOperationsSerializer.swift +++ b/Sources/Batch/Modules/Profile/BATProfileOperationsSerializer.swift @@ -28,6 +28,21 @@ class BATProfileOperationsSerializer: NSObject { jsonParameters["email_marketing"] = serializedValue } + if let phoneNumber = profileEditor.phoneNumber { + jsonParameters["phone_number"] = phoneNumber.value + } + + if let smsMarketingSubscription = profileEditor.smsMarketingSubscription { + let serializedValue: String + switch smsMarketingSubscription { + case .subscribed: + serializedValue = "subscribed" + case .unsubscribed: + serializedValue = "unsubscribed" + } + jsonParameters["sms_marketing"] = serializedValue + } + if let language = profileEditor.language { jsonParameters["language"] = language.value } diff --git a/Sources/Batch/Versions.h b/Sources/Batch/Versions.h index aed1102..afbe6d7 100644 --- a/Sources/Batch/Versions.h +++ b/Sources/Batch/Versions.h @@ -11,6 +11,6 @@ Comments should not use the // form, as the plist preprocessor will include them */ -#define BASDKVersion 2.0.2 -#define BAAPILevel 200 +#define BASDKVersion 2.1.0 +#define BAAPILevel 210 #define BAMessagingAPILevel 12 diff --git a/Sources/batchTests/Modules/Messaging/Webview/webviewBridgeLegacyTests.swift b/Sources/batchTests/Modules/Messaging/Webview/webviewBridgeLegacyTests.swift index 11d6c9d..02da8c9 100644 --- a/Sources/batchTests/Modules/Messaging/Webview/webviewBridgeLegacyTests.swift +++ b/Sources/batchTests/Modules/Messaging/Webview/webviewBridgeLegacyTests.swift @@ -194,7 +194,13 @@ fileprivate class MockWKWebView: WKWebView, MockDelegate { return mock } - override func evaluateJavaScript(_ javaScriptString: String, completionHandler: ((Any?, Error?) -> Void)? = nil) { - mock.call(javaScriptString, completionHandler) - } + #if compiler(>=6.0) + override func evaluateJavaScript(_ javaScriptString: String, completionHandler: (@MainActor @Sendable (Any?, (any Error)?) -> Void)? = nil) { + mock.call(javaScriptString, completionHandler) + } + #else + override func evaluateJavaScript(_ javaScriptString: String, completionHandler: ((Any?, Error?) -> Void)? = nil) { + mock.call(javaScriptString, completionHandler) + } + #endif } diff --git a/Sources/batchTests/Modules/Profile/TestProfileEditor.swift b/Sources/batchTests/Modules/Profile/TestProfileEditor.swift index 97f215d..7486dcd 100644 --- a/Sources/batchTests/Modules/Profile/TestProfileEditor.swift +++ b/Sources/batchTests/Modules/Profile/TestProfileEditor.swift @@ -10,9 +10,9 @@ import Foundation /// A test BATProfileEditor that has a controllable canSetEmail class TestProfileEditor: BATProfileEditor { - public var test_canSetEmail = true + public var test_isProfileIdentified = true - override func canSetEmail() -> Bool { - return test_canSetEmail + override func isProfileIdentified() -> Bool { + return test_isProfileIdentified } } diff --git a/Sources/batchTests/Modules/Profile/profileEditorValidationTests.swift b/Sources/batchTests/Modules/Profile/profileEditorValidationTests.swift index 1e07f89..184403e 100644 --- a/Sources/batchTests/Modules/Profile/profileEditorValidationTests.swift +++ b/Sources/batchTests/Modules/Profile/profileEditorValidationTests.swift @@ -28,6 +28,19 @@ final class profileEditorValidationTests: XCTestCase { XCTAssertFalse(BATProfileDataValidators.isValidEmail("inval\rid@invalid.gmail.com\n")) } + func testPhoneNumberPatterns() { + XCTAssertTrue(BATProfileDataValidators.isValidPhoneNumber("+2901234")) + XCTAssertTrue(BATProfileDataValidators.isValidPhoneNumber("+33612345678")) + XCTAssertTrue(BATProfileDataValidators.isValidPhoneNumber("+123456789123145")) + + XCTAssertFalse(BATProfileDataValidators.isValidEmail("+")) + XCTAssertFalse(BATProfileDataValidators.isValidEmail("+1234567891231456")) + XCTAssertFalse(BATProfileDataValidators.isValidEmail("33612345678")) + XCTAssertFalse(BATProfileDataValidators.isValidEmail("+33-6-12-34-56-78")) + XCTAssertFalse(BATProfileDataValidators.isValidEmail("+33 6 12 34 56 78")) + XCTAssertFalse(BATProfileDataValidators.isValidEmail("")) + } + func testEmailIsTooLong() { XCTAssertTrue(BATProfileDataValidators.isEmailTooLong("testastringtestastringtoolongtobeanemailtestastringtoolongtestastringtoolongtobeanemailtestastringtoolongtestastringtoolongtobeanemailtestastringtoolongtestastringtoolongtobeanemailtestastringtoolongtestastringtoolongtobeanemailtestastringtoolongtoolongtobeanemailtestastringtoolongtobeanemailtestastringtoolongtobeanemailtestastringtoolongtobeanemailtestastringtoo@batch.com")) XCTAssertFalse(BATProfileDataValidators.isEmailTooLong("bar@foo.batch.com")) @@ -35,7 +48,7 @@ final class profileEditorValidationTests: XCTestCase { func testEditorEmailErrors() { let editor = TestProfileEditor() - editor.test_canSetEmail = true + editor.test_isProfileIdentified = true XCTAssertNoThrow(try editor.setEmail("test@batch.com")) XCTAssertThrowsError(try editor.setEmail("invalid@inva lid.gmail.com")) let longEmailPart = String(repeating: "test_too_long", count: 100) @@ -46,4 +59,17 @@ final class profileEditorValidationTests: XCTestCase { XCTAssertFalse(BATProfileDataValidators.isCustomIDTooLong("customId")) XCTAssertTrue(BATProfileDataValidators.isCustomIDTooLong("my_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_idmy_test_id_1111")) } + + func testIsCustomIDBlocklisted() { + XCTAssertTrue(BATProfileDataValidators.isCustomIDBlocklisted("null")) + XCTAssertTrue(BATProfileDataValidators.isCustomIDBlocklisted("(null)")) + XCTAssertTrue(BATProfileDataValidators.isCustomIDBlocklisted("nil")) + XCTAssertTrue(BATProfileDataValidators.isCustomIDBlocklisted("[object Object]")) + XCTAssertTrue(BATProfileDataValidators.isCustomIDBlocklisted("undefined")) + XCTAssertTrue(BATProfileDataValidators.isCustomIDBlocklisted("Infinity")) + XCTAssertTrue(BATProfileDataValidators.isCustomIDBlocklisted("-Infinity")) + XCTAssertTrue(BATProfileDataValidators.isCustomIDBlocklisted("NaN")) + XCTAssertTrue(BATProfileDataValidators.isCustomIDBlocklisted("true")) + XCTAssertTrue(BATProfileDataValidators.isCustomIDBlocklisted("false")) + } } diff --git a/Sources/batchTests/Modules/Profile/profileOperationsSerializerTests.swift b/Sources/batchTests/Modules/Profile/profileOperationsSerializerTests.swift index 52576bb..c911acf 100644 --- a/Sources/batchTests/Modules/Profile/profileOperationsSerializerTests.swift +++ b/Sources/batchTests/Modules/Profile/profileOperationsSerializerTests.swift @@ -19,6 +19,8 @@ final class profileOperationsSerializerTests: XCTestCase { let serialized = try serializeEditor { editor in try editor.setEmail("test@batch.com") editor.setEmailMarketingSubscriptionState(.subscribed) + try editor.setPhoneNumber("+33123456789") + editor.setSMSMarketingSubscriptionState(.subscribed) try editor.setLanguage("fr_ch") try editor.setRegion("FR") try editor.setCustom(stringAttribute: "hello", forKey: "string_att") @@ -57,6 +59,8 @@ final class profileOperationsSerializerTests: XCTestCase { XCTAssertEqual(serialized["email"] as? String, "test@batch.com") XCTAssertEqual(serialized["email_marketing"] as? String, "subscribed") + XCTAssertEqual(serialized["phone_number"] as? String, "+33123456789") + XCTAssertEqual(serialized["sms_marketing"] as? String, "subscribed") XCTAssertEqual(serialized["language"] as? String, "fr_ch") XCTAssertEqual(serialized["region"] as? String, "FR") @@ -119,7 +123,7 @@ final class profileOperationsSerializerTests: XCTestCase { /// Test that an email cannot be set and is not serialized if not allowed func testCantSetEmail() throws { let editor = TestProfileEditor() - editor.test_canSetEmail = false + editor.test_isProfileIdentified = false XCTAssertThrowsError(try editor.setEmail("test@batch.com")) @@ -127,9 +131,20 @@ final class profileOperationsSerializerTests: XCTestCase { XCTAssertNil(serialized["email"]) } + /// Test that a phone number cannot be set and is not serialized if not allowed + func testCantSetPhoneNumber() throws { + let editor = TestProfileEditor() + editor.test_isProfileIdentified = false + + XCTAssertThrowsError(try editor.setPhoneNumber("+33123456789")) + + let serialized = BATProfileOperationsSerializer.serialize(profileEditor: editor) + XCTAssertNil(serialized["phone_number"]) + } + func serializeEditor(_ editClosure: (BATProfileEditor) throws -> Void) rethrows -> [AnyHashable: Any] { let editor = TestProfileEditor() - editor.test_canSetEmail = true + editor.test_isProfileIdentified = true try editClosure(editor) return BATProfileOperationsSerializer.serialize(profileEditor: editor) } diff --git a/Tools/Dockerfile.format b/Tools/Dockerfile.format index a686efc..2ede38b 100644 --- a/Tools/Dockerfile.format +++ b/Tools/Dockerfile.format @@ -1,6 +1,6 @@ -FROM ghcr.io/nicklockwood/swiftformat:0.53.1 as swiftformat +FROM ghcr.io/nicklockwood/swiftformat:0.54.5 as swiftformat -FROM swift:jammy +FROM swift:5.9.2-jammy ENV DEBIAN_FRONTEND=noninteractive