Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve countImpression functionality in native ios #6

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ContentPassExample/ContentPassExample/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,6 @@ struct ContentView: View {
viewModel.countImpression()
}
.buttonStyle(.borderedProminent)
.opacity(viewModel.isError || !viewModel.isAuthenticated ? 0 : 1)
.opacity(viewModel.isError ? 0 : 1)
}
}
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,12 @@ Since we don't monitor the device's connection state you need to tell the SDK th
contentPass.recoverFromError()
```

### Couting an impression

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.
### Counting an impression
`countImpression` method counts impressions for billing purposes. This method must be invoked whenever a user views a piece
of content, independently of authentication state. If the current user is authenticated the impression will automatically
be logged as paid ad-free impression to calculate the publisher compensation. As the total amount of impressions is required
for billing as well, this method also counts sampled impressions of non-subscribers. Counting an impression is as easy as
calling the function `countImpression(completionHandler:)`

```swift
contentPass.countImpression { result in
Expand Down
108 changes: 92 additions & 16 deletions Sources/ContentPass/ContentPass.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import AppAuth
import AuthenticationServices
import UIKit

let samplingRate: Double = 0.05
slawomirzaba marked this conversation as resolved.
Show resolved Hide resolved

/// Functions that enable you to react to changes in the contentpass sdk's state.
public protocol ContentPassDelegate: AnyObject {
/// A function that enables you to react to a change in contentpass state.
Expand Down Expand Up @@ -43,7 +45,7 @@ public class ContentPass: NSObject {
/// 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`.
///
/// For the possible values and their meaning see `ContentPass.State`.
internal (set) public var state = State.initializing { didSet { didSetState(state) } }
internal(set) public var state = State.initializing { didSet { didSetState(state) } }

/// The object that acts as the delegate of the contentpass sdk.
///
Expand Down Expand Up @@ -134,26 +136,39 @@ public class ContentPass: NSObject {
validateAuthState()
}

/// Count an impression for the logged in user.
/// Count an impression for user.
///
/// A user needs to be authenticated and have a subscription applicable to your service.
/// - 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.
/// If user has a valid subscription, a paid impression will be counted. Additionally a sampled impression will be
/// counted for all users, no matter if they have a valid subscription or not.
/// - 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.
public func countImpression(completionHandler: @escaping (Result<Void, Error>) -> Void) {
let impressionID = UUID()
let propertyId = propertyId.split(separator: "-").first!
let request = URLRequest(url: URL(string: "\(configuration.apiUrl)/pass/hit?pid=\(propertyId)&iid=\(impressionID)&t=pageview")!)
let dispatchGroup = DispatchGroup()
var errors: [Error] = []

if state == .authenticated(hasValidSubscription: true) {
dispatchGroup.enter()
countPaidImpression { result in
if case .failure(let error) = result {
errors.append(error)
}
dispatchGroup.leave()
}
}

oidAuthState?.fireRequest(urlRequest: request) { _, response, error in
if let error = error {
dispatchGroup.enter()
countSampledImpression { result in
if case .failure(let error) = result {
errors.append(error)
}
dispatchGroup.leave()
}

dispatchGroup.notify(queue: .main) {
if let error = errors.first {
completionHandler(.failure(error))
} else if let httpResponse = response as? HTTPURLResponse {
if httpResponse.statusCode == 200 {
completionHandler(.success(()))
} else {
completionHandler(.failure(ContentPassError.badHTTPStatusCode(httpResponse.statusCode)))
}
} else {
completionHandler(.failure(ContentPassError.corruptedResponseFromWeb))
completionHandler(.success(()))
}
}
}
Expand Down Expand Up @@ -190,6 +205,67 @@ public class ContentPass: NSObject {
super.init()
}

private func countPaidImpression(completionHandler: @escaping (Result<Void, Error>) -> Void) {
let impressionID = UUID()
let propertyId = propertyId.split(separator: "-").first!
let request = URLRequest(url: URL(string: "\(configuration.apiUrl)/pass/hit?pid=\(propertyId)&iid=\(impressionID)&t=pageview")!)

oidAuthState?.fireRequest(urlRequest: request) { _, response, error in
if let error = error {
completionHandler(.failure(error))
} else if let httpResponse = response as? HTTPURLResponse {
if httpResponse.statusCode == 200 {
completionHandler(.success(()))
} else {
completionHandler(.failure(ContentPassError.badHTTPStatusCode(httpResponse.statusCode)))
}
} else {
completionHandler(.failure(ContentPassError.corruptedResponseFromWeb))
}
}
}

private func countSampledImpression(completionHandler: @escaping (Result<Void, Error>) -> Void) {
let generatedSample = Double.random(in: 0...1)
if generatedSample >= samplingRate {
completionHandler(.success(()))
return
}

let instanceId = UUID().uuidString
let publicId = propertyId.prefix(8)
var request = URLRequest(url: URL(string: "\(configuration.apiUrl)/stats")!)
request.httpMethod="POST"
request.setValue("application/json; charset=UTF-8", forHTTPHeaderField: "Content-Type")
let body: [String: Any] = [
"ea": "load",
"ec": "tcf-sampled",
"cpabid": instanceId,
"cppid": publicId,
"cpsr": samplingRate
]
do {
request.httpBody = try JSONSerialization.data(withJSONObject: body)
} catch {
completionHandler(.failure(error))
return
}

URLSession.shared.dataTask(with: request) {_, response, error in
if let error = error {
completionHandler(.failure(error))
} else if let httpResponse = response as? HTTPURLResponse {
if httpResponse.statusCode >= 200 && httpResponse.statusCode < 300 {
completionHandler(.success(()))
} else {
completionHandler(.failure(ContentPassError.badHTTPStatusCode(httpResponse.statusCode)))
}
} else {
completionHandler(.failure(ContentPassError.corruptedResponseFromWeb))
}
}.resume()
}

private func validateAuthState() {
guard
let authState = oidAuthState,
Expand Down
Loading