diff --git a/ConsentViewController/Assets/javascript/SPJSReceiver.js b/ConsentViewController/Assets/javascript/SPJSReceiver.js index 7526e4add..1b7a1fdeb 100644 --- a/ConsentViewController/Assets/javascript/SPJSReceiver.js +++ b/ConsentViewController/Assets/javascript/SPJSReceiver.js @@ -85,7 +85,7 @@ var handleMessageEvent = function(SDK) { function isFromPM(event) { event.settings = event.settings || {}; - return event.fromPM || event.settings.vendorList; + return window.spLegislation == "preferences" || event.fromPM || event.settings.vendorList; } function isError(event) { diff --git a/ConsentViewController/Classes/Consents/SPPreferencesConsent.swift b/ConsentViewController/Classes/Consents/SPPreferencesConsent.swift new file mode 100644 index 000000000..d30195af0 --- /dev/null +++ b/ConsentViewController/Classes/Consents/SPPreferencesConsent.swift @@ -0,0 +1,78 @@ +// +// SPPreferencesConsent.swift +// Pods +// +// Created by Fedko Dmytro on 06.05.2025 +// + +import Foundation + +@objcMembers public class SPPreferencesConsent: NSObject, Codable, CampaignConsent, NSCopying { + var applies: Bool = true + public var uuid: String? + public var status, rejectedStatus: [PreferencesStatus]? + var dateCreated: SPDate + var messageId: String? + + init( + uuid: String? = nil, + dateCreated: SPDate, + status: [PreferencesStatus]?, + rejectedStatus: [PreferencesStatus]?, + messageId: String? = nil + ) { + self.uuid = uuid + self.dateCreated = dateCreated + self.status = status + self.rejectedStatus = rejectedStatus + self.messageId = messageId + } +} + +extension SPPreferencesConsent { + override open var description: String { + """ + SPPreferencesConsent( + - uuid: \(uuid ?? "") + - status: \(status ?? []) + - rejectedStatus: \(rejectedStatus ?? []) + - dateCreated: \(dateCreated) + ) + """ + } + + public static func empty() -> SPPreferencesConsent { SPPreferencesConsent( + dateCreated: .now(), + status: [], + rejectedStatus: [] + )} + + public func copy(with zone: NSZone? = nil) -> Any { + SPPreferencesConsent( + uuid: uuid, + dateCreated: dateCreated, + status: status, + rejectedStatus: rejectedStatus, + messageId: messageId + ) + } +} + +extension SPPreferencesConsent { + public struct PreferencesStatus: Codable { + var categoryId: Int + var channels: [PreferencesChannels]? + var changed: Bool? + var dateConsented: SPDate? + var subType: PreferencesSubType? = PreferencesSubType.Unknown + } + + struct PreferencesChannels: Codable { + var channelId: Int + var status: Bool + } + + public enum PreferencesSubType: Codable { + case AIPolicy, TermsAndConditions, PrivacyPolicy, LegalPolicy, TermsOfSale, Unknown + } +} diff --git a/ConsentViewController/Classes/Consents/SPUserData.swift b/ConsentViewController/Classes/Consents/SPUserData.swift index 6854b13a7..d3a7fa406 100644 --- a/ConsentViewController/Classes/Consents/SPUserData.swift +++ b/ConsentViewController/Classes/Consents/SPUserData.swift @@ -66,10 +66,14 @@ public class SPConsent: NSObject, /// - SeeAlso: `SPCCPAConsent` public let ccpa: SPConsent? - /// Consent data for USNat. This attribute will be nil if your setup doesn't include a CCPA campaign + /// Consent data for USNat. This attribute will be nil if your setup doesn't include a USNat campaign /// - SeeAlso: `SPUSNatConsent` public let usnat: SPConsent? + /// Consent data for Preferences. This attribute will be nil if your setup doesn't include a Preferences campaign + /// - SeeAlso: `SPPreferencesConsent` + public let preferences: SPConsent? + public var webConsents: SPWebConsents { SPWebConsents( gdpr: .init(uuid: gdpr?.consents?.uuid, webConsentPayload: gdpr?.consents?.webConsentPayload), ccpa: .init(uuid: ccpa?.consents?.uuid, webConsentPayload: ccpa?.consents?.webConsentPayload), @@ -80,28 +84,32 @@ public class SPConsent: NSObject, self != SPUserData() && gdpr?.consents != SPGDPRConsent.empty() && ccpa?.consents != SPCCPAConsent.empty() && - usnat?.consents != SPUSNatConsent.empty() + usnat?.consents != SPUSNatConsent.empty() && + preferences?.consents != SPPreferencesConsent.empty() } override public var description: String { - "gdpr: \(String(describing: gdpr)), ccpa: \(String(describing: ccpa)), usnat: \(String(describing: usnat))" + "gdpr: \(String(describing: gdpr)), ccpa: \(String(describing: ccpa)), usnat: \(String(describing: usnat)), preferences: \(String(describing: preferences))" } public init( gdpr: SPConsent? = nil, ccpa: SPConsent? = nil, - usnat: SPConsent? = nil + usnat: SPConsent? = nil, + preferences: SPConsent? = nil ) { self.gdpr = gdpr self.ccpa = ccpa self.usnat = usnat + self.preferences = preferences } public func copy(with zone: NSZone? = nil) -> Any { SPUserData( gdpr: gdpr?.copy() as? SPConsent, ccpa: ccpa?.copy() as? SPConsent, - usnat: usnat?.copy() as? SPConsent + usnat: usnat?.copy() as? SPConsent, + preferences: preferences?.copy() as? SPConsent ) } @@ -112,7 +120,8 @@ public class SPConsent: NSObject, ccpa?.applies == object.ccpa?.applies && ccpa?.consents == object.ccpa?.consents && usnat?.applies == object.usnat?.applies && - usnat?.consents == object.usnat?.consents + usnat?.consents == object.usnat?.consents && + preferences?.consents == object.preferences?.consents } else { return false } @@ -126,6 +135,7 @@ public protocol SPObjcUserData { func objcCCPAApplies() -> Bool func objcUSNatConsents() -> SPUSNatConsent? func objcUSNatApplies() -> Bool + func objcPreferencesConsents() -> SPPreferencesConsent? } @objc extension SPUserData: SPObjcUserData { @@ -161,4 +171,10 @@ public protocol SPObjcUserData { public func objcUSNatApplies() -> Bool { usnat?.applies ?? false } + + /// Returns Preferences consent data if any available. + /// - SeeAlso: `SPPreferencesConsent` + public func objcPreferencesConsents() -> SPPreferencesConsent? { + preferences?.consents + } } diff --git a/ConsentViewController/Classes/SPCampaignType.swift b/ConsentViewController/Classes/SPCampaignType.swift index d1932f78a..b81a50cde 100644 --- a/ConsentViewController/Classes/SPCampaignType.swift +++ b/ConsentViewController/Classes/SPCampaignType.swift @@ -8,7 +8,7 @@ import Foundation @objc public enum SPCampaignType: Int, Equatable { - case gdpr, ios14, ccpa, usnat, unknown + case gdpr, ios14, ccpa, usnat, preferences, unknown } extension SPCampaignType: Codable { @@ -20,6 +20,7 @@ extension SPCampaignType: Codable { case .ccpa: return "CCPA" case .ios14: return "ios14" case .usnat: return "usnat" + case .preferences: return "preferences" default: return "unknown" } } @@ -30,6 +31,7 @@ extension SPCampaignType: Codable { case "CCPA": self = .ccpa case "ios14": self = .ios14 case "usnat": self = .usnat + case "preferences": self = .preferences default: self = .unknown } } diff --git a/ConsentViewController/Classes/SPCampaigns.swift b/ConsentViewController/Classes/SPCampaigns.swift index 6b9520b0e..683fe8c4f 100644 --- a/ConsentViewController/Classes/SPCampaigns.swift +++ b/ConsentViewController/Classes/SPCampaigns.swift @@ -108,7 +108,7 @@ public typealias SPTargetingParams = [String: String] /// a active vendor list of that legislation. @objcMembers public class SPCampaigns: NSObject { public let environment: SPCampaignEnv - public let gdpr, ccpa, usnat, ios14: SPCampaign? + public let gdpr, ccpa, usnat, ios14, preferences: SPCampaign? override public var description: String { """ @@ -126,12 +126,14 @@ public typealias SPTargetingParams = [String: String] ccpa: SPCampaign? = nil, usnat: SPCampaign? = nil, ios14: SPCampaign? = nil, + preferences: SPCampaign? = nil, environment: SPCampaignEnv = .Public ) { self.gdpr = gdpr self.ccpa = ccpa self.usnat = usnat self.ios14 = ios14 + self.preferences = preferences self.environment = environment } } diff --git a/ConsentViewController/Classes/SPConsentManager.swift b/ConsentViewController/Classes/SPConsentManager.swift index 8d16a2b84..6d3ef14d5 100644 --- a/ConsentViewController/Classes/SPConsentManager.swift +++ b/ConsentViewController/Classes/SPConsentManager.swift @@ -197,7 +197,7 @@ import SPMobileCore func report(action: SPAction) { responsesToReceive += 1 switch action.campaignType { - case .ccpa, .gdpr, .usnat: + case .ccpa, .gdpr, .usnat, .preferences: spCoordinator.reportAction(action) { [weak self] result in self?.responsesToReceive -= 1 switch result { diff --git a/ConsentViewController/Classes/SourcePointClient/Adapters.swift b/ConsentViewController/Classes/SourcePointClient/Adapters.swift index 5c46e6f4d..3d8cf65b0 100644 --- a/ConsentViewController/Classes/SourcePointClient/Adapters.swift +++ b/ConsentViewController/Classes/SourcePointClient/Adapters.swift @@ -161,6 +161,8 @@ extension SPMobileCore.SPCampaignType { return .usnat case "IOS14": return .ios14 + case "Preferences": + return .preferences default: return .unknown @@ -255,6 +257,48 @@ extension SPMobileCore.USNatConsent { } } +extension SPMobileCore.PreferencesConsent.PreferencesSubType? { + func toNative() -> SPPreferencesConsent.PreferencesSubType { + switch self { + case .aipolicy: return .AIPolicy + case .legalpolicy: return .LegalPolicy + case .privacypolicy: return .PrivacyPolicy + case .termsandconditions: return .TermsAndConditions + case .termsofsale: return .TermsOfSale + case .unknown: return .Unknown + default: return .Unknown + } + } +} + +extension SPMobileCore.PreferencesConsent.PreferencesStatusPreferencesChannels { + func toNative() -> SPPreferencesConsent.PreferencesChannels { return .init(channelId: Int(self.channelId), status: self.status) } +} + +extension SPMobileCore.PreferencesConsent.PreferencesStatus { + func toNative() -> SPPreferencesConsent.PreferencesStatus { + return .init( + categoryId: Int(self.categoryId), + channels: self.channels?.map { $0.toNative() }, + changed: self.changed?.boolValue, + dateConsented: SPDate(string: self.dateConsented?.instantToString() ?? SPDate.now().originalDateString), + subType: self.subType.toNative() + ) + } +} + +extension SPMobileCore.PreferencesConsent { + func toNative() -> SPPreferencesConsent { + return .init( + uuid: self.uuid, + dateCreated: SPDate(string: self.dateCreated?.instantToString() ?? SPDate.now().originalDateString), + status: self.status?.map { $0.toNative() }, + rejectedStatus: self.rejectedStatus?.map { $0.toNative() }, + messageId: String(Int(truncating: self.messageId ?? 0)) + ) + } +} + extension SPUserDataSPConsent? { func toNative() -> SPConsent? { return SPConsent.init( @@ -282,12 +326,22 @@ extension SPUserDataSPConsent? { } } +extension SPUserDataSPConsent? { + func toNative() -> SPConsent? { + return SPConsent.init( + consents: self?.consents?.toNative(), + applies: true + ) + } +} + extension SPMobileCore.SPUserData { func toNative() -> SPUserData { return SPUserData( gdpr: self.gdpr.toNative(), ccpa: self.ccpa.toNative(), - usnat: self.usnat.toNative() + usnat: self.usnat.toNative(), + preferences: self.preferences.toNative() ) } } @@ -453,6 +507,7 @@ extension SPCampaignType { case .ccpa: return .ccpa case .usnat: return .usnat case .ios14: return .ios14 + case .preferences: return .preferences case .unknown: return .unknown } } @@ -501,6 +556,7 @@ extension SourcepointClientCoordinator.State { ccpa: self.ccpa.toCore(metaData: self.ccpaMetaData), usNat: self.usnat.toCore(metaData: self.usNatMetaData), ios14: self.ios14.toCore(), + preferences: emptyPreferencesCampaign(), authId: self.storedAuthId, propertyId: Int32(propertyId), accountId: Int32(accountId), @@ -593,6 +649,23 @@ extension SourcepointClientCoordinator.State.AttCampaign? { } } +func emptyPreferencesCampaign() -> SPMobileCore.State.PreferencesState { + return SPMobileCore.State.PreferencesState( + metaData: SPMobileCore.State.PreferencesStatePreferencesMetaData( + configurationId: "", + additionsChangeDate: SPDate(date: Date.distantPast).toCore(), + legalDocLiveDate: nil + ), + consents: SPMobileCore.PreferencesConsent( + dateCreated: nil, + messageId: nil, + status: nil, + rejectedStatus: nil, + uuid: nil + ) + ) +} + extension SourcepointClientCoordinator.State.GDPRMetaData? { func toCore() -> SPMobileCore.State.GDPRStateGDPRMetaData { return SPMobileCore.State.GDPRStateGDPRMetaData.init( @@ -662,7 +735,8 @@ extension SPCampaigns { gdpr: gdpr.toCore(), ccpa: ccpa.toCore(), usnat: usnat.toCore(), - ios14: ios14.toCore() + ios14: ios14.toCore(), + preferences: preferences.toCore() ) } } diff --git a/ConsentViewController/Classes/SourcePointClient/SourcepointClientCoordinator.swift b/ConsentViewController/Classes/SourcePointClient/SourcepointClientCoordinator.swift index fd4a28639..bab1ac150 100644 --- a/ConsentViewController/Classes/SourcePointClient/SourcepointClientCoordinator.swift +++ b/ConsentViewController/Classes/SourcePointClient/SourcepointClientCoordinator.swift @@ -4,7 +4,7 @@ // // Created by Andre Herculano on 14.09.22. // -// swiftlint:disable type_body_length file_length +// swiftlint:disable type_body_length import Foundation import SPMobileCore @@ -297,30 +297,6 @@ class SourcepointClientCoordinator: SPClientCoordinator { } } - func buildChoiceAllCampaigns(action: SPAction) -> ChoiceAllRequest.ChoiceAllCampaigns { - var gdprApplies: Bool? - var ccpaApplies: Bool? - var usnatApplies: Bool? - switch action.campaignType { - case .gdpr: - gdprApplies = state.gdpr?.applies - - case .ccpa: - ccpaApplies = state.ccpa?.applies - - case .usnat: - usnatApplies = state.usnat?.applies - - case .ios14, .unknown: - break - } - return .init( - gdpr: .init(applies: gdprApplies ?? false), - ccpa: .init(applies: ccpaApplies ?? false), - usnat: .init(applies: usnatApplies ?? false) - ) - } - func reportAction(_ action: SPAction, handler: @escaping ActionHandler) { coreCoordinator.reportAction( action: action.toCore() diff --git a/ConsentViewController/Classes/Views/iOS/SPWebMessageViewController.swift b/ConsentViewController/Classes/Views/iOS/SPWebMessageViewController.swift index d41306938..8a71020a0 100644 --- a/ConsentViewController/Classes/Views/iOS/SPWebMessageViewController.swift +++ b/ConsentViewController/Classes/Views/iOS/SPWebMessageViewController.swift @@ -242,6 +242,7 @@ import WebKit case .onMessageReady: timeoutWorkItem.cancel() + self.webview?.evaluateJavaScript("window.spLegislation=\"\(self.campaignType.rawValue)\"") messageUIDelegate?.loaded(self) case .onAction: