Skip to content

Commit 7e934b9

Browse files
authored
Merge pull request #6 from contentpass/CP-2522-Improve-countImpression-functionality-in-native-ios
Improve countImpression functionality in native ios
2 parents 5cd61c5 + b6ba77d commit 7e934b9

File tree

3 files changed

+99
-20
lines changed

3 files changed

+99
-20
lines changed

ContentPassExample/ContentPassExample/ContentView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,6 @@ struct ContentView: View {
6060
viewModel.countImpression()
6161
}
6262
.buttonStyle(.borderedProminent)
63-
.opacity(viewModel.isError || !viewModel.isAuthenticated ? 0 : 1)
63+
.opacity(viewModel.isError ? 0 : 1)
6464
}
6565
}

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,12 @@ Since we don't monitor the device's connection state you need to tell the SDK th
157157
contentPass.recoverFromError()
158158
```
159159

160-
### Couting an impression
161-
162-
Counting an impression is as easy as calling the function `countImpression(completionHandler:)`. A user has to be authenticated and have an active subscription applicable to your scope for this to work.
160+
### Counting an impression
161+
`countImpression` method counts impressions for billing purposes. This method must be invoked whenever a user views a piece
162+
of content, independently of authentication state. If the current user is authenticated the impression will automatically
163+
be logged as paid ad-free impression to calculate the publisher compensation. As the total amount of impressions is required
164+
for billing as well, this method also counts sampled impressions of non-subscribers. Counting an impression is as easy as
165+
calling the function `countImpression(completionHandler:)`
163166

164167
```swift
165168
contentPass.countImpression { result in

Sources/ContentPass/ContentPass.swift

Lines changed: 92 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import AppAuth
22
import AuthenticationServices
33
import UIKit
44

5+
let samplingRate: Double = 0.05
6+
57
/// Functions that enable you to react to changes in the contentpass sdk's state.
68
public protocol ContentPassDelegate: AnyObject {
79
/// A function that enables you to react to a change in contentpass state.
@@ -43,7 +45,7 @@ public class ContentPass: NSObject {
4345
/// This is always up to date but to be notified of changes in state, be sure to register a `ContentPassDelegate` as the parent object's `delegate`.
4446
///
4547
/// For the possible values and their meaning see `ContentPass.State`.
46-
internal (set) public var state = State.initializing { didSet { didSetState(state) } }
48+
internal(set) public var state = State.initializing { didSet { didSetState(state) } }
4749

4850
/// The object that acts as the delegate of the contentpass sdk.
4951
///
@@ -134,26 +136,39 @@ public class ContentPass: NSObject {
134136
validateAuthState()
135137
}
136138

137-
/// Count an impression for the logged in user.
139+
/// Count an impression for user.
138140
///
139-
/// A user needs to be authenticated and have a subscription applicable to your service.
140-
/// - Parameter completionHandler: On a successful counting of the impression, the Result is a `success`. If something went wrong, you'll be supplied with an appropriate error case. The error `ContentPassError.badHTTPStatusCode(404)` most probably means that your user has no applicable subscription.
141+
/// If user has a valid subscription, a paid impression will be counted. Additionally a sampled impression will be
142+
/// counted for all users, no matter if they have a valid subscription or not.
143+
/// - Parameter completionHandler: On a successful counting of the impression, the Result is a `success`. If something went wrong,
144+
/// you'll be supplied with an appropriate error case.
141145
public func countImpression(completionHandler: @escaping (Result<Void, Error>) -> Void) {
142-
let impressionID = UUID()
143-
let propertyId = propertyId.split(separator: "-").first!
144-
let request = URLRequest(url: URL(string: "\(configuration.apiUrl)/pass/hit?pid=\(propertyId)&iid=\(impressionID)&t=pageview")!)
146+
let dispatchGroup = DispatchGroup()
147+
var errors: [Error] = []
148+
149+
if state == .authenticated(hasValidSubscription: true) {
150+
dispatchGroup.enter()
151+
countPaidImpression { result in
152+
if case .failure(let error) = result {
153+
errors.append(error)
154+
}
155+
dispatchGroup.leave()
156+
}
157+
}
145158

146-
oidAuthState?.fireRequest(urlRequest: request) { _, response, error in
147-
if let error = error {
159+
dispatchGroup.enter()
160+
countSampledImpression { result in
161+
if case .failure(let error) = result {
162+
errors.append(error)
163+
}
164+
dispatchGroup.leave()
165+
}
166+
167+
dispatchGroup.notify(queue: .main) {
168+
if let error = errors.first {
148169
completionHandler(.failure(error))
149-
} else if let httpResponse = response as? HTTPURLResponse {
150-
if httpResponse.statusCode == 200 {
151-
completionHandler(.success(()))
152-
} else {
153-
completionHandler(.failure(ContentPassError.badHTTPStatusCode(httpResponse.statusCode)))
154-
}
155170
} else {
156-
completionHandler(.failure(ContentPassError.corruptedResponseFromWeb))
171+
completionHandler(.success(()))
157172
}
158173
}
159174
}
@@ -190,6 +205,67 @@ public class ContentPass: NSObject {
190205
super.init()
191206
}
192207

208+
private func countPaidImpression(completionHandler: @escaping (Result<Void, Error>) -> Void) {
209+
let impressionID = UUID()
210+
let propertyId = propertyId.split(separator: "-").first!
211+
let request = URLRequest(url: URL(string: "\(configuration.apiUrl)/pass/hit?pid=\(propertyId)&iid=\(impressionID)&t=pageview")!)
212+
213+
oidAuthState?.fireRequest(urlRequest: request) { _, response, error in
214+
if let error = error {
215+
completionHandler(.failure(error))
216+
} else if let httpResponse = response as? HTTPURLResponse {
217+
if httpResponse.statusCode == 200 {
218+
completionHandler(.success(()))
219+
} else {
220+
completionHandler(.failure(ContentPassError.badHTTPStatusCode(httpResponse.statusCode)))
221+
}
222+
} else {
223+
completionHandler(.failure(ContentPassError.corruptedResponseFromWeb))
224+
}
225+
}
226+
}
227+
228+
private func countSampledImpression(completionHandler: @escaping (Result<Void, Error>) -> Void) {
229+
let generatedSample = Double.random(in: 0...1)
230+
if generatedSample >= samplingRate {
231+
completionHandler(.success(()))
232+
return
233+
}
234+
235+
let instanceId = UUID().uuidString
236+
let publicId = propertyId.prefix(8)
237+
var request = URLRequest(url: URL(string: "\(configuration.apiUrl)/stats")!)
238+
request.httpMethod="POST"
239+
request.setValue("application/json; charset=UTF-8", forHTTPHeaderField: "Content-Type")
240+
let body: [String: Any] = [
241+
"ea": "load",
242+
"ec": "tcf-sampled",
243+
"cpabid": instanceId,
244+
"cppid": publicId,
245+
"cpsr": samplingRate
246+
]
247+
do {
248+
request.httpBody = try JSONSerialization.data(withJSONObject: body)
249+
} catch {
250+
completionHandler(.failure(error))
251+
return
252+
}
253+
254+
URLSession.shared.dataTask(with: request) {_, response, error in
255+
if let error = error {
256+
completionHandler(.failure(error))
257+
} else if let httpResponse = response as? HTTPURLResponse {
258+
if httpResponse.statusCode >= 200 && httpResponse.statusCode < 300 {
259+
completionHandler(.success(()))
260+
} else {
261+
completionHandler(.failure(ContentPassError.badHTTPStatusCode(httpResponse.statusCode)))
262+
}
263+
} else {
264+
completionHandler(.failure(ContentPassError.corruptedResponseFromWeb))
265+
}
266+
}.resume()
267+
}
268+
193269
private func validateAuthState() {
194270
guard
195271
let authState = oidAuthState,

0 commit comments

Comments
 (0)