contentpass supports iOS 10.0 and above
Since we use external dependencies for solved problems such as OAuth 2.0, we strongly recommend using a dependency manager. We currently support SPM as well as CocoaPods.
With Swift Package Manager, either
- In Xcode simply select File > Swift Packages > Add Package Dependency from menu and paste
https://github.com/contentpass/contentpass-ios
or
- Add the following
dependency
to yourPackage.swift
:
dependencies: [
.package(url: "https://github.com/contentpass/contentpass-ios", .upToNextMajor(from: "2.2.1"))
]
In both cases don't forget to add the sdk to your targets.
With CocoaPods, add the following line to your Podfile
:
pod 'ContentPass', '~> 2.2.1'
Then, run pod install
via terminal.
All of our public facing functions and properties are documented in XCode.
We also provide an example application in the /ContentPassExample
directory. If you're unclear about the usage of some of our features, have a look and tinker around with the code.
You will be provided a json file with all values necessary to configure the contentpass object. The json file will need to be added to your bundle and target. If unsure what that means: Drag the file into your Xcode project root and when prompted, select to add the file to all targets.
You should instantiate and hold one instance of the ContentPass
class in one of your top level state holding objects. This can be your implementation of UIAppDelegate
, UISceneDelegate
or any state container. You should only ever instantiate one instance of ContentPass
at any one time to rule out state inconsistency shenanigans.
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
let contentPass = ContentPass()
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// ...
contentPass.delegate = self
}
}
Don't forget to set one of your objects as the delegate of the ContentPass
instance. Otherwise you will not be notified of any changes in the authentication state. For that you need to implement the ContentPassDelegate
protocol.
extension SceneDelegate: ContentPassDelegate {
func onStateChanged(contentPass: ContentPass, newState: ContentPass.State) {
// handle the new ContentPass.State, e.g.
switch newState {
case .authenticated(let hasValidSubscription):
print("This was a triumph!")
default:
break
}
}
}
We use AppAuth for the OAuth 2.0 process. AppAuth uses Apple's ASWebAuthenticationSession
or if below iOS 12 SFAuthenticationSession
.
That means, that the user will be presented with a modal SFSafariViewController
like view in which they can authenticate themselves with our servers. We then use the OAuth result to validate whether the user has any active contentpass subscriptions that are applicable.
To authenticate and authorize the user, simply call authenticate
on the ContentPass
object. You will need to pass a UIViewController
that is able to present the OAuth session as well as a completionHandler
that takes a Result<Void, Error>
.
contentPass.authenticate(presentingViewController: viewController) { result in
switch result {
case .success:
// this only means that authentication was successful,
// it doesn't tell you anything about the subscription status
break
case .failure(let error):
// handle errors accordingly - refer to "Error handling" in this document
}
}
If the authentication was a success, we will poll our servers for subscription plans in the background.
The delegate
will be called with the final authentication and subscription state.
Be aware that a successfully authenticated user may have no active subscription plans and act accordingly!
- We store tokens that anonymously identify the logged in user's session to our servers in the device's keychain.
- We refresh these tokens automatically in the background before they're invalidated.
- The subscription information gets validated as well on every token refresh.
Since we persist the user's session, you need a way to log the user out. Simply call logout
and we remove all stored token data.
contentPass.logout()
The user will of course have to log in again afterwards.
You can also call authenticate
again and all previous user information will get overwritten.
We only store one user session at any one time.
An error can occur during the authenticate
function's lifetime or you can get an .error(Error)
state in the delegate's onStateChanged
function.
We have our own ContentPassError
enum for the following cases:
- The user has canceled or dismissed the OAuth flow:
userCanceledAuthentication
You should handle this accordingly. - Something went wrong while communicating with the backend:
subscriptionDataCorrupted
,corruptedResponseFromWeb
,badHTTPStatusCode
oroidAuthenticatedButMissingIdToken
If you're sure that you have configured and set up everything correctly and one of these problems persists, contact us via GitHub issues or by mail. - Something very unexpected happened:
unexpectedState
This should never occur and is basically ourthrows
replacement since we're async. If you encounter one of these, feel very free to open a GitHub issue.
We also bubble up underlying errors that may occur because of connectivity problems or other issues regarding the OAuth flow.
With these errors it's best practice to cast them to NSError
and look up the error's domain
and code
on your favorite search engine.
Sometimes we encounter an error state while refreshing the tokens in the background due to bad or no internet connection. Since we don't monitor the device's connection state you need to tell the SDK that the network connection has been reestablished / improved. We will then refresh and revalidate the user's authentication tokens.
contentPass.recoverFromError()
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:)
contentPass.countImpression { result in
switch result {
case .success:
// continue with your life
case .error(let error):
// handle the error.
}
}
We use the following open source packages in this SDK: