This is the iOS / Mac OS X / watchOS / tvOS SDK that can be used to communicate with the StreamOne Platform by using the StreamOne API version 3.
Note that this SDK is Swift 2.0 only. It can not be used with earlier versions of Swift or with Objective-C, as it highly depends on newer Swift functionality. We provide a separate SDK for use with Objective-C.
- Requirements
- Installation
- Usage
- Configuration
- Platform
- Request and response
- Session
- Actor
- Other useful classes
- Complete example
- Acknowledgements
- License and copyright
The SDK requires Swift 2.0 or higher and XCode 7 or higher. Furthermore, it supports the following platforms:
- iOS 8.0+
- Mac OS X 10.9+
- watchOS 2.0+
- tvOS 9.0+
The recommended way to install the SDK is to use Carthage.
To install, add the following to your Cartfile file:
github "StreamOneNL/iOS-SDK"
Afterwards, you should update the package by running Carthage in the directory where the Cartfile file is located:
carthage update --no-use-binaries --platform iosThis will fetch dependencies into a "Carthage/Checkouts" folder, then build each one. Replace --platform ios with your platform or leave it out if you want to build for all platforms.
Note that there is a problem when buidling for tvOS, which require you to add --no-use-binaries with Carthage.
Next, on your application targets’ "General" settings tab, in the "Linked Frameworks and Libraries" section, drag and drop the following frameworks from the "Carthage/Build" folder on disk (use the correct subfolder for your target):
Alamofire.frameworkArgo.frameworkCommonCrypto.frameworkCrypto.frameworkCurry.frameworkJFCommon.frameworkStreamOneSDK.framework
Finally, for iOS, on your application targets’ "Build Phases" settings tab, click the "+" icon and choose "New Run Script Phase". Create a Run Script with the following contents:
/usr/local/bin/carthage copy-frameworksand add the paths to the frameworks "Input Files", e.g.:
$(SRCROOT)/Carthage/Build/iOS/Alamofire.framework
$(SRCROOT)/Carthage/Build/iOS/Argo.framework
$(SRCROOT)/Carthage/Build/iOS/CommonCrypto.framework
$(SRCROOT)/Carthage/Build/iOS/Crypto.framework
$(SRCROOT)/Carthage/Build/iOS/Curry.framework
$(SRCROOT)/Carthage/Build/iOS/JFCommon.framework
$(SRCROOT)/Carthage/Build/iOS/StreamOneSDK.framework
This script works around an App Store submission bug triggered by universal binaries.
For Mac OS X, watchOS and tvOS you can just add the above frameworks as embedded binaries of your target.
For more information or for custom setups please see the Carthage website.
To use the StreamOne SDK, you should first set up a configuration and afterwards you can start communicating with the StreamOne API.
To set up a configuration, you should initialize the Config struct with the desired authentication type:
import StreamOneSDK
let config = Config(authenticationType: .Application(id: "application", psk: "mypsk"))The following configuration properties are available (note that if you want to change any of them, you should replace let by var in the above statement, as Config is a struct):
- apiUrl (defaults to
https://api.streamonecloud.net): this should be the base URL of the API to use. - defaultAccountId (optional): this can be set to the ID of an account and if set, this will be the account to use by default for all API actions.
- requestFactory (optional, defaults to
StandardRequestFactory()): factory to use for creating requests. If you want to overwrite it you can pass something that adheres to the theRequestFactoryprotocol here. - requestCache (optional, defaults to
NoopCache()): cache to use for requests. Should be something that adheres to theCacheprotocol. - tokenCache (optional, defaults to
NoopCache()): cache to use for tokens. Should be something that adheres to theCacheprotocol. - useSessionForTokenCache (optional, defaults to
true): iftrue, the session will be used to store token information if using a session. Otherwise the tokenCache will always be used. - sessionStore (optional, defaults to
MemorySessionStore()): the session store to use to store session information and optionally token information (if useSessionForTokenCache is set totrue). - There is a convenience method
setCachethat sets both therequestCacheandtokenCache.
The Platform class is the main entry point for performing requests. You pass it the Config during creation and it allows you to perform requests, start a new session or create an actor.
Example:
import StreamOneSDK
let config = Config(...) // as above
let platform = Platform(config: config)
// Start a new request
let request = platform.newRequest(command: "api", action: "info")
// Or use a session
let session = platform.newSession() // You can optionally pass a different session store here
// Or create an actor
let actor = platform.newActor() // You can pass a session here to use that session for this actorA Request can be used to perform an actual request to the StreamOne API. It extends RequestBase which contains code that can be used by other request classes.
The following actions can be done using a request:
- Get or set a account: use the
accountproperty to get or set the account used for the request. By default the defaultAccountId from the Config will be used, if set. To clear the account, set the value tonil. - Get or set multiple accounts: use the
accountsproperty to get or set multiple accounts for this request. Some API actions allow you to provide more than one account. - Get or set a customer: use the
customerproperty to get or set a customer instead of an account for this request. API actions supporting multiple accounts or a customer can use this. - Get or set the timezone using the
timezoneproperty. If not set the default timezone of the current actor will be used, but one might want to overwrite this. - Set an argument by using
setArgument(argument: String, value: Argument): most API actions allow and / or require arguments to be set. Use this function to provide them. An argument can either be aString,Int,Double,FloatorBool; an optional of one of these types or an array of these types. All arguments will be converted to strings. Use the read-onlyargumentsproperty to read the arguments again as a[String: String]dictionary.
After setting up a request you should call execute() to actually connect to the API and perform the request.
This function takes a callback that gets passed a response. Not that this callback will be called on the same thread as the caller of the function.
The callback will receive a Response struct, which will contain the response from the API. The following can be done with the response:
valid: true if and only if the API request connected to the API successfully and contains valid data.header: ifvalidis true this will return the header for this response. This header will contain:status: the status code of the API response..OKmeans that everything was OK.statusMessage: the (textual) status message of the API response.allFields: the header might contain more fields. Use this to get the value of these fields.success: true if and only ifvalidreturnstrueandheader.statusreturns.OK.fromCache: true if and only if the data came from the cache.cacheAge: iffromCachereturnstrue, this contains the age of the cache-item.cacheable: whether this request can be cached.body: the complete body of the API response as anAnyObject.nilifvalidreturnsfalse.error: an error if and only ifvalidisfalse. This error will contain information about why the response is not valid.typedBody(): a function that will try to convert the response to a typed value. See below for usage.
Because Swift is a strongly typed language, it would normally be really cumbersome to use an AnyObject as a body if one wants to parse all kinds of data from it.
Therefore we use Argo to make life a lot easier. Argo is a "Functional JSON parsing library for Swift".
It can be used to easily convert a JSON return value into an arbitrary object. To do this, create a class, enum or struct that conforms to Argo.Decodable.
Any data structure that conforms to Argo.Decodable can be used as a return type of Response.typedBody().
Note that an array of Decodable entries an a dictionary where the keys are String and the values conform to Argo.Decodable also conform to Argo.Decodable.
Most built-in Swift types also already conform to the protocol.
As an example, here is the code that defines an Item struct that conforms to the Argo.Decodable protocol:
import Foundation
import Argo
import Curry
struct Item : Decodable {
let id: String
let title: String
let description: String?
let type: String
let duration: String
let dateCreated: String
let keywords: [String]
let selectedThumbnailUrl: String?
let progressiveLink: String?
let hlsLink: String?
static func decode(json: JSON) -> Decoded<Item> {
return curry(Item.init)
<^> json <| "id"
<*> json <| "title"
<*> json <|? "description"
<*> json <| "type"
<*> json <| "duration"
<*> json <| "datecreated"
<*> json <|| "keywords"
<*> json <|? "selectedthumbnailurl"
<*> json <|? ["medialink", "progressive"]
<*> json <|? ["medialink", "hls"]
}
}Trhee notes about this example:
- It does not contain all fields from the
itemAPI - We parse
datecreatedas string, because the actually returned value will depend on the timezone used. You can use the timezone from the header to convert this to the correctNSDatevalue. - It might be that the Swift compiler will complain that a too complex expression has been created. If so,
use an intermediate variable in the
decodefunction to split up the expression.
See the Argo website for more information and examples.
The SDK uses some types internally and they are also exposed, so you can use them:
SessionInitializeis the response to thesession/initializeAPI action.SessionCreateis the response to thesession/createAPI action.RoleInActoris an object in the response of both theuser/getrolesand theapplication/getrolesAPI action.Roleis the subobjectroleas used inRoleInActor. It is also the response of therole/viewAPI action.BasicAccountandBasicCustomerare both also used inRoleInActorand correspond to theaccountandcustomersubobjects.
An example API request:
let platform: Platform = ... // As above
let request = platform.newRequest(command: "item", action: "view")
request.setArgument("itemtype", value: "video")
request.execute { response in
if response.success {
// Assume Item is a struct conforming to Decodable as described above
let items: [Item]? = response.typedBody()
// Do something with the items
} else {
// Do something with response.error and/or response.header
}
}A Session can be used in the StreamOne platform by an application to perform API actions on behalf of a user.
To use a session, you need to authenticate as an application (by setting authenticationType to application).
Then you can use the Session struct to start a session and to perform actions using that session.
The Session class provides the following useful actions:
isActive: returnstrueif and only if a session is active, i.e. the user is currently logged in.start(useername username: String, password: String, ip: String, callback: (success: Bool, lastResponse: Response) -> Void): start a new session for the user with the given username and password. For theipparameter you should use something that is unique to the current device, i.e.UIDevice.currentDevice().identifierForVendor!.UUIDString. This makes sure the API can perform rate limiting when someone fails to log in too many times without succeeding.end()can be used to end the currently active session.newRequest(command command: String, action: String)can be used to perform a request on behalf of the user for this session.
An example of using a session:
let platform: Platform = ... // As above
let session = platform.newSession()
let afterSession = {
let request = session.newRequest(command: "item", action: "view")
// etc
}
let deviceId = UIDevice.currentDevice().identifierForVendor!.UUIDString
if !session.isActive {
session.start(username: "username", password: "password", ip: deviceId) { success, lastResponse in
if success {
afterSession()
} else {
// Handle error. lastResponse contains the last response sent to the API for session creation
// This is either a session/initialize or a session/create request
}
}
} else {
afterSession()
}An Actor corresponds to a user or an application. It can be used to perform multiple requests with the same settings, like accounts and / or customer.
Actors can also be used to check if the required tokens for an API action are available for the given actor. Furthermore, it is possible to get a list of all the roles of an actor. When fetching tokens or roles, the system will request these roles and tokens from the API when required and it will cache this information so this is not done for every request. The tokenCache from the Config will be used to store this information.
An example of using an actor:
let platform: Platform = ... // As above
let actor = platform.newActor()
// or
let session: Session = ... // As above
let actor = platform.newActor(session: session)
actor.accounts = ["abcdef"]
actor.hasToken("item-view") { hasToken in
if let hasToken = hasToken.value where hasToken {
let request = actor.newRequest(command: "item", action: "view")
// etc
} else {
// Check if hasToken.error is non-nil. If so, something was wrong.
// Otherwise the actor does not have the token
}
}There are more classes available in the StreamOne SDK:
FileCache,MemoryCache,NoopCacheandSessionCache: different cache classes storing the cache in a file, the memory, nowhere and in the current session respectively.MemorySessionStore: a session stores that stores session information in memory.Passwordis used when logging in using a session and can also be used when changing the password of a user.Statusis an enum with all statuses that the API can report.
var config = Config(authenticationType: .User(id: "abcdefghijkl", psk: "abcdefghijklmnopqrstuvwxyzABCDEF"))
config.defaultAccountId = "mnopqrstuvwx"
let platform = Platform(config: config)
let request = platform.newRequest(command: "api", action: "info")
request.execute { response in
if response.success {
print(response.body)
} else {
// Handle response error
}
}- Sam Soffes for providing Crypto, an easy to use cryptographic library and a framework wrapper around CommonCrypto.
- Thoughtbot, inc. for providing both Argo, a great functional JSON parsing library in Swift and Curry, a Swift implementation for function currying.
- The Alamofire Software Foundation for providing Alamofire, an elegant HTTP Networking library in Swift.
- Jay Fuerstenberg for creating JFCommon, a collection of common classes. The SDK uses
JFBCryptto encrypt password hashes. - Realm for creating Jazzy, a documentation generation tool for Swift code.
All source code is licensed under the MIT License.
Copyright (c) 2015 StreamOne B.V.