Skip to content
This repository was archived by the owner on Apr 16, 2024. It is now read-only.

Commit 758fa46

Browse files
Merge pull request #40 from SourcePointUSA/SP-2551_add_usp_string
SP-2551 add US Privacy String
2 parents 396103d + 12c808f commit 758fa46

File tree

8 files changed

+107
-34
lines changed

8 files changed

+107
-34
lines changed

CCPAConsentViewController/Classes/CCPAConsentViewController.swift

+25-7
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@ public typealias TargetingParams = [String: String]
1515
static public let CONSENT_UUID_KEY: String = "sp_ccpa_consentUUID"
1616
static let CCPA_AUTH_ID_KEY = "sp_ccpa_authId"
1717
static public let META_KEY: String = "sp_ccpa_meta"
18+
public static let IAB_PRIVACY_STRING_KEY = "IABUSPrivacy_String"
19+
public static let CCPA_APPLIES_KEY = "sp_ccpa_applies"
20+
21+
static func setUSPrivacyString(_ usps: SPUsPrivacyString) {
22+
UserDefaults.standard.set(usps, forKey: IAB_PRIVACY_STRING_KEY)
23+
}
24+
25+
static func setCCPAApplies(_ applies: Bool) {
26+
UserDefaults.standard.set(applies, forKey: CCPA_APPLIES_KEY)
27+
}
1828

1929
private let accountId, propertyId: Int
2030
private let propertyName: PropertyName
@@ -171,7 +181,9 @@ public typealias TargetingParams = [String: String]
171181
sourcePoint.getMessage(consentUUID: consentUUID, authId: authId) { [weak self] messageResponse, error in
172182
self?.loading = .Ready
173183
if let message = messageResponse {
184+
self?.setLocalStorageData(uuid: message.uuid, userConsent: message.userConsent, ccpaApplies: message.ccpaApplies)
174185
self?.consentUUID = message.uuid
186+
self?.userConsent = message.userConsent
175187
if let url = message.url {
176188
self?.loadMessage(fromUrl: url)
177189
} else {
@@ -184,6 +196,16 @@ public typealias TargetingParams = [String: String]
184196
}
185197
}
186198

199+
func setLocalStorageData(uuid: ConsentUUID, userConsent: UserConsent, ccpaApplies: Bool) {
200+
UserDefaults.standard.set(uuid, forKey: CCPAConsentViewController.CONSENT_UUID_KEY)
201+
CCPAConsentViewController.setUSPrivacyString(userConsent.uspstring)
202+
CCPAConsentViewController.setCCPAApplies(ccpaApplies)
203+
if let encodedConsents = try? JSONEncoder().encode(userConsent) {
204+
UserDefaults.standard.set(String(data: encodedConsents, encoding: .utf8), forKey: CCPAConsentViewController.CCPA_USER_CONSENTS)
205+
}
206+
UserDefaults.standard.synchronize()
207+
}
208+
187209
func didAuthIdChange(newAuthId: String?) -> Bool {
188210
let storedAuthId = UserDefaults.standard.string(forKey: CCPAConsentViewController.CCPA_AUTH_ID_KEY)
189211
return newAuthId != nil && storedAuthId != nil && storedAuthId != newAuthId
@@ -247,6 +269,9 @@ extension CCPAConsentViewController: ConsentDelegate {
247269
if action == .AcceptAll || action == .RejectAll || action == .SaveAndExit {
248270
sourcePoint.postAction(action: action, consentUUID: consentUUID, consents: consents) { [weak self] actionResponse, error in
249271
if let response = actionResponse {
272+
self?.consentUUID = response.uuid
273+
self?.userConsent = response.userConsent
274+
self?.setLocalStorageData(uuid: response.uuid, userConsent: response.userConsent, ccpaApplies: response.ccpaApplies)
250275
self?.onConsentReady(consentUUID: response.uuid, userConsent: response.userConsent)
251276
} else {
252277
self?.onError(error: error)
@@ -256,13 +281,6 @@ extension CCPAConsentViewController: ConsentDelegate {
256281
}
257282

258283
public func onConsentReady(consentUUID: ConsentUUID, userConsent: UserConsent) {
259-
self.consentUUID = consentUUID
260-
self.userConsent = userConsent
261-
if let encodedConsents = try? JSONEncoder().encode(userConsent) {
262-
UserDefaults.standard.set(String(data: encodedConsents, encoding: .utf8), forKey: CCPAConsentViewController.CCPA_USER_CONSENTS)
263-
}
264-
UserDefaults.standard.set(consentUUID, forKey: CCPAConsentViewController.CONSENT_UUID_KEY)
265-
UserDefaults.standard.synchronize()
266284
consentDelegate?.onConsentReady?(consentUUID: consentUUID, userConsent: userConsent)
267285
}
268286

CCPAConsentViewController/Classes/SourcePointRequestsResponses.swift

+2
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@ protocol WrapperApiRequest: Codable, Equatable {
1919
struct MessageResponse: Codable, Equatable {
2020
let url: URL?
2121
let uuid: ConsentUUID
22+
let ccpaApplies: Bool
2223
let userConsent: UserConsent
2324
var meta: Meta
2425
}
2526

2627
struct ActionResponse: Codable, Equatable {
2728
let uuid: ConsentUUID
29+
let ccpaApplies: Bool
2830
let userConsent: UserConsent
2931
var meta: Meta
3032
}

CCPAConsentViewController/Classes/UserConsent.swift

+20-4
Original file line numberDiff line numberDiff line change
@@ -55,37 +55,53 @@ import Foundation
5555
}
5656
}
5757

58+
public typealias SPUsPrivacyString = String
59+
5860
/**
5961
The UserConsent class encapsulates the consent status, rejected vendor ids and rejected categories (purposes) ids.
6062
- Important: The `rejectedVendors` and `rejectedCategories` arrays will only be populated if the `status` is `.Some`.
6163
That is, if the user has rejected `.All` or `.None` vendors/categories, those arrays will be empty.
6264
*/
6365
@objcMembers public class UserConsent: NSObject, Codable {
66+
/// represents the default state of the consumer prior to seeing the consent message
67+
/// - seealso: https://github.com/InteractiveAdvertisingBureau/USPrivacy/blob/master/CCPA/US%20Privacy%20String.md#us-privacy-string-format
68+
public static let defaultUsPrivacyString = "1---"
69+
70+
public static func empty() -> UserConsent {
71+
return UserConsent(status: .RejectedNone, rejectedVendors: [], rejectedCategories: [], uspstring: defaultUsPrivacyString)
72+
}
73+
6474
/// Indicates if the user has rejected `.All`, `.Some` or `.None` of the vendors **and** categories.
6575
public let status: ConsentStatus
6676
/// The ids of the rejected vendors and categories. These can be found in SourcePoint's dashboard
6777
public let rejectedVendors, rejectedCategories: [String]
78+
/// the US Privacy String as described by the IAB
79+
public let uspstring: SPUsPrivacyString
6880

6981
public static func rejectedNone () -> UserConsent {
70-
return UserConsent(status: ConsentStatus.RejectedNone, rejectedVendors: [], rejectedCategories: [])
82+
return UserConsent(status: ConsentStatus.RejectedNone, rejectedVendors: [], rejectedCategories: [], uspstring: "")
7183
}
7284

73-
public init(status: ConsentStatus, rejectedVendors: [String], rejectedCategories: [String]) {
85+
public init(status: ConsentStatus, rejectedVendors: [String], rejectedCategories: [String], uspstring: SPUsPrivacyString) {
7486
self.status = status
7587
self.rejectedVendors = rejectedVendors
7688
self.rejectedCategories = rejectedCategories
89+
self.uspstring = uspstring
7790
}
7891

79-
open override var description: String { return "Status: \(status.rawValue), rejectedVendors: \(rejectedVendors), rejectedCategories: \(rejectedCategories)" }
92+
open override var description: String {
93+
return "UserConsent(status: \(status.rawValue), rejectedVendors: \(rejectedVendors), rejectedCategories: \(rejectedCategories), uspstring: \(uspstring))"
94+
}
8095

8196
enum CodingKeys: CodingKey {
82-
case status, rejectedVendors, rejectedCategories
97+
case status, rejectedVendors, rejectedCategories, uspstring
8398
}
8499

85100
required public init(from decoder: Decoder) throws {
86101
let values = try decoder.container(keyedBy: CodingKeys.self)
87102
rejectedVendors = try values.decode([String].self, forKey: .rejectedVendors)
88103
rejectedCategories = try values.decode([String].self, forKey: .rejectedCategories)
104+
uspstring = try values.decode(SPUsPrivacyString.self, forKey: .uspstring)
89105
let statusString = try values.decode(String.self, forKey: .status)
90106
switch statusString {
91107
case "rejectedNone": status = .RejectedNone

Example/CCPAConsentViewController.xcodeproj/xcuserdata/andreherculano.xcuserdatad/xcschemes/xcschememanagement.plist

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<key>CCPAConsentViewController_ExampleTests.xcscheme_^#shared#^_</key>
1818
<dict>
1919
<key>orderHint</key>
20-
<integer>15</integer>
20+
<integer>3</integer>
2121
</dict>
2222
<key>ConsentViewController-Example.xcscheme_^#shared#^_</key>
2323
<dict>
@@ -27,7 +27,7 @@
2727
<key>SourcePointMetaApp.xcscheme_^#shared#^_</key>
2828
<dict>
2929
<key>orderHint</key>
30-
<integer>14</integer>
30+
<integer>2</integer>
3131
</dict>
3232
</dict>
3333
</dict>

Example/CCPAConsentViewController/ViewController.swift

+5
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ extension ViewController: ConsentDelegate {
4444
func onConsentReady(consentUUID: ConsentUUID, userConsent: UserConsent) {
4545
print("consentUUID: \(consentUUID)")
4646
print("userConsents: \(userConsent)")
47+
48+
print("CCPA applies:", UserDefaults.standard.bool(forKey: CCPAConsentViewController.CCPA_APPLIES_KEY))
49+
50+
// the us privacy string can also be accessed via userConsent.uspstring
51+
print("US Privacy String:", UserDefaults.standard.string(forKey: CCPAConsentViewController.IAB_PRIVACY_STRING_KEY)!)
4752
}
4853

4954
func onError(error: CCPAConsentViewControllerError?) {

Example/CCPAConsentViewController_ExampleTests/CCPAConsentViewControllerSpec.swift

+35-21
Original file line numberDiff line numberDiff line change
@@ -82,19 +82,17 @@ class CCPAConsentViewControllerSpec: QuickSpec, ConsentDelegate {
8282
let mockConsentDelegate = MockConsentDelegate()
8383
let consentUUID = UUID().uuidString
8484
let messageViewController = MessageViewController()
85-
let rejectedVendors = ["321", "654", "987"]
86-
let rejectedCategories = ["4321", "7654", "0987"]
87-
let userConsents = UserConsent(
88-
status: .RejectedSome,
89-
rejectedVendors: rejectedVendors,
90-
rejectedCategories: rejectedCategories
91-
)
85+
let userConsents = UserConsent.empty()
9286

9387
describe("load Message in webview") {
9488
beforeEach {
9589
consentViewController = self.getCCPAConsentViewController()
9690
}
9791

92+
afterEach {
93+
consentViewController.clearAllConsentData()
94+
}
95+
9896
it("Load message in webview without authId") {
9997
consentViewController.loadMessage()
10098
expect(consentViewController.loading).to(equal(.Loading), description: "loadMessage method works as expected")
@@ -139,9 +137,23 @@ class CCPAConsentViewControllerSpec: QuickSpec, ConsentDelegate {
139137
consentViewController = self.getCCPAConsentViewController()
140138
}
141139

142-
it("Test did AuthId Change") {
143-
let authIdChangeStatus = consentViewController.didAuthIdChange(newAuthId: Date().description)
144-
expect(authIdChangeStatus).to(equal(true), description: "Auth Id changed successfully")
140+
context("when the new authId is nil") {
141+
it("returns false") {
142+
expect(consentViewController.didAuthIdChange(newAuthId: nil)).to(equal(false))
143+
}
144+
}
145+
146+
context("when there's no stored id") {
147+
it("returns false") {
148+
expect(consentViewController.didAuthIdChange(newAuthId: "different")).to(equal(false))
149+
}
150+
}
151+
152+
context("when there is stored id") {
153+
it("returns true") {
154+
UserDefaults.standard.set("authId", forKey: CCPAConsentViewController.CCPA_AUTH_ID_KEY)
155+
expect(consentViewController.didAuthIdChange(newAuthId: "different")).to(equal(true))
156+
}
145157
}
146158
}
147159

@@ -172,6 +184,19 @@ class CCPAConsentViewControllerSpec: QuickSpec, ConsentDelegate {
172184
consentViewController = self.getCCPAConsentViewController()
173185
consentViewController.consentDelegate = mockConsentDelegate
174186
}
187+
188+
describe("onAction") {
189+
it("should set the consentUUID in the UserDefaults") {
190+
consentViewController.onAction(.AcceptAll, consents: nil)
191+
expect(UserDefaults.standard.string(forKey: CCPAConsentViewController.CONSENT_UUID_KEY)).toEventuallyNot(beEmpty())
192+
}
193+
194+
it("should set the consentUUID as attribute of CCPAConsentViewController") {
195+
consentViewController.onAction(.AcceptAll, consents: nil)
196+
expect(consentViewController.consentUUID).toEventuallyNot(beEmpty())
197+
}
198+
}
199+
175200
context("Test consentUIWillShow delegate method") {
176201
it("Test CCPAConsentViewController calls consentUIWillShow delegate method") {
177202
consentViewController.ccpaConsentUIWillShow()
@@ -199,21 +224,10 @@ class CCPAConsentViewControllerSpec: QuickSpec, ConsentDelegate {
199224
}
200225

201226
context("onConsentReady delegate method") {
202-
203227
it("calls onConsentReady delegate method") {
204228
consentViewController.onConsentReady(consentUUID: consentUUID, userConsent: userConsents)
205229
expect(mockConsentDelegate.isOnConsentReadyCalled).to(equal(true), description: "onConsentReady delegate method calls successfully")
206230
}
207-
208-
it("should set the consentUUID in the UserDefaults") {
209-
consentViewController.onConsentReady(consentUUID: "sp_ccpa_consentUUID", userConsent: userConsents)
210-
expect(UserDefaults.standard.string(forKey: CCPAConsentViewController.CONSENT_UUID_KEY)).to(equal("sp_ccpa_consentUUID"))
211-
}
212-
213-
it("should set the consentUUID as attribute of CCPAConsentViewController") {
214-
consentViewController.onConsentReady(consentUUID: "sp_ccpa_consentUUID", userConsent: userConsents)
215-
expect(consentViewController.consentUUID).to(equal("sp_ccpa_consentUUID"))
216-
}
217231
}
218232

219233
context("Test messageWillShow delegate method") {

Example/Pods/Target Support Files/SwiftLint/SwiftLint.debug.xcconfig

+9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Example/Pods/Target Support Files/SwiftLint/SwiftLint.release.xcconfig

+9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)