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

Ported TraktKit to tvOS #36

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
9 changes: 8 additions & 1 deletion Common/MLKeychain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public class MLKeychain {
let keychainQuery: [String: Any] = [
kSecClassValue: kSecClassGenericPasswordValue,
kSecAttrAccountValue: key,
kSecReturnDataValue: kCFBooleanTrue,
kSecReturnDataValue: kCFBooleanTrue!,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why I have been told by Xcode to fix this one. It was working a year ago I suppose?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll have to look into this some. I'd love to avoid any force unwraps.

kSecMatchLimitValue: kSecMatchLimitOneValue
]

Expand All @@ -55,6 +55,13 @@ public class MLKeychain {
if status == noErr {
return dataTypeRef as? Data
} else {
if #available(iOSApplicationExtension 11.3, *) {
#if DEBUG
print("[\(#function)] Security result error code is: \(String(SecCopyErrorMessageString(status, nil) ?? "UNKNOWN"))")
#endif
} else {
// Fallback on earlier versions
}
Comment on lines +58 to +64
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this!

return nil
}
}
Expand Down
23 changes: 23 additions & 0 deletions Common/Models/Users/TraktUser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ public struct User: Codable {
public let name: String?
public let isVIP: Bool?
public let isVIPEP: Bool?
// public let ids: ID
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes should be reverted, I just merged a PR that added support for this.


// Full
public let joinedAt: Date?
public let location: String?
public let about: String?
public let gender: String?
public let age: Int?
public let images: ImagesType?

// VIP
public let vipOG: Bool?
Expand All @@ -34,12 +36,33 @@ public struct User: Codable {
case name
case isVIP = "vip"
case isVIPEP = "vip_ep"
// case ids

case joinedAt = "joined_at"
case location
case about
case gender
case age
case images = "images"
case vipOG = "vip_og"
case vipYears = "vip_years"
}
}

// MARK: URL for user avatar is quite nested
public struct ImagesType: Codable {
public let avatar: AvatarType?

enum CodingKeys: String, CodingKey {
case avatar
}
}

public struct AvatarType: Codable {
public let urlString: String?

enum CodingKeys: String, CodingKey {
case urlString = "full"
}
}

174 changes: 173 additions & 1 deletion Common/Wrapper/TraktManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ public class TraktManager {

let session: URLSessionProtocol

// MARK: - Device codes

// Would it be better to define a dictionary or a struct?
public var deviceCode: String?
public var userCode: String?
public var verificationURL: String?
public var timeInterval: TimeInterval?
public var expiresIn: TimeInterval?
Comment on lines +46 to +50
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll have to look into the API some more before I know if a struct would be better than the variables kept in this class. I would recommend adding more documentation from the Trakt API to define all of these.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I had looked into that. I wasn't sure either. But I needed them to make the authorization functions working on tvOS.


// MARK: Public
public static let sharedManager = TraktManager()

Expand Down Expand Up @@ -107,8 +116,21 @@ public class TraktManager {
self.session = session
}

// MARK: - Setup
// MARK: - Setup Clients
public func setOauth2RedirectURL(withClientID: String, clientSecret secret: String, redirectURI: String, staging: Bool = false) {
self.clientID = withClientID
self.clientSecret = secret
self.redirectURI = redirectURI

self.staging = staging

self.baseURL = !staging ? "trakt.tv" : "staging.trakt.tv"
self.APIBaseURL = !staging ? "api.trakt.tv" : "api-staging.trakt.tv"
self.oauthURL = URL(string: "https://\(baseURL!)/oauth/authorize?response_type=code&client_id=\(withClientID)&redirect_uri=\(redirectURI)")
}
Comment on lines +120 to +130
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be reverted. set could use a better signature but I'm iffy on setOauth2RedirectURL, this can be a different PR / change to the tvOS support.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, no problem. I just wanted to give it a different name because "set" was a bit too generic.


// MARK: Deprecated
@available(*, deprecated, renamed: "setOauth2RedirectURL(withClientID:clientSecret:redirectURI:)")
public func set(clientID: String, clientSecret secret: String, redirectURI: String, staging: Bool = false) {
self.clientID = clientID
self.clientSecret = secret
Expand Down Expand Up @@ -216,6 +238,156 @@ public class TraktManager {
return try JSONSerialization.data(withJSONObject: json, options: [])
}

// MARK: - Authenticate Devices

public func getDeviceCode(completionHandler: @escaping DataResultCompletionHandler) throws {
guard let clientID = clientID
else {
completionHandler(.error(error: nil))
return
}


let urlString = "https://\(baseURL!)/oauth/device/code"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid force unwrapping.

let url = URL(string: urlString)
guard var request = mutableRequestForURL(url, authorization: false, HTTPMethod: .POST) else {
completionHandler(.error(error: nil))
return
}

let json = [
"client_id": clientID,
]
request.httpBody = try JSONSerialization.data(withJSONObject: json, options: [])
session._dataTask(with: request) { [weak self] (data, response, error) -> Void in
guard let welf = self else { return }
guard error == nil else {
completionHandler(.error(error: error))
return
}

// Check response
guard let HTTPResponse = response as? HTTPURLResponse,
HTTPResponse.statusCode == StatusCodes.Success
else {
if let HTTPResponse = response as? HTTPURLResponse {
completionHandler(.error(error: welf.createErrorWithStatusCode(HTTPResponse.statusCode)))
} else {
completionHandler(.error(error: nil))
}
return
}
// Check data
guard
let data = data else {
completionHandler(.error(error: nil))
return
}

do {
if let deviceCodeDict = try JSONSerialization.jsonObject(with: data, options: []) as? [String: AnyObject] {

welf.deviceCode = deviceCodeDict["device_code"] as? String
welf.userCode = deviceCodeDict["user_code"] as? String
welf.verificationURL = deviceCodeDict["verification_url"] as? String
welf.timeInterval = deviceCodeDict["interval"] as? TimeInterval
welf.expiresIn = deviceCodeDict["expires_in"] as? TimeInterval
Comment on lines +288 to +294
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think decoding into an object would be preferable to this old way of decoding JSON



#if DEBUG
print("[\(#function)] Device Code is \(String(describing: welf.deviceCode))")
print("[\(#function)] User Code is \(String(describing: welf.userCode))")
print("[\(#function)] Verification URL is \(String(describing: welf.verificationURL))")
print("[\(#function)] Time Interval is \(String(describing: welf.timeInterval)) sec.")
print("[\(#function)] Expires in \(String(describing: welf.expiresIn)) sec.")
#endif

completionHandler(.success(data: data))
}
}
catch {
welf.deviceCode = nil
welf.userCode = nil
welf.verificationURL = nil
completionHandler(.error(error: nil))
}
}.resume()
}

public func getTokenFromDeviceCode(completionHandler: SuccessCompletionHandler?) throws {
guard
let clientID = clientID,
let clientSecret = clientSecret,
let deviceCode = deviceCode else {
completionHandler?(.fail)
return
}

let urlString = "https://\(baseURL!)/oauth/device/token"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

force unwrap

let url = URL(string: urlString)
guard var request = mutableRequestForURL(url, authorization: false, HTTPMethod: .POST) else {
completionHandler?(.fail)
return
}

let json = [
"code": deviceCode,
"client_id": clientID,
"client_secret": clientSecret,
]
request.httpBody = try JSONSerialization.data(withJSONObject: json, options: [])

session._dataTask(with: request) { [weak self] (data, response, error) -> Void in
guard let welf = self else { return }
guard error == nil else {
completionHandler?(.fail)
return
}

// Check response
guard let HTTPResponse = response as? HTTPURLResponse,
HTTPResponse.statusCode == StatusCodes.Success else {
completionHandler?(.fail)
return
}

// Check data
guard let data = data else {
completionHandler?(.fail)
return
}

do {
if let accessTokenDict = try JSONSerialization.jsonObject(with: data, options: []) as? [String: AnyObject] {

welf.accessToken = accessTokenDict["access_token"] as? String
welf.refreshToken = accessTokenDict["refresh_token"] as? String

#if DEBUG
print("[\(#function)] Access token is \(String(describing: welf.accessToken))")
print("[\(#function)] Refresh token is \(String(describing: welf.refreshToken))")
#endif

// Save expiration date
let timeInterval = accessTokenDict["expires_in"] as! NSNumber
let expiresDate = Date(timeIntervalSinceNow: timeInterval.doubleValue)

UserDefaults.standard.set(expiresDate, forKey: "accessTokenExpirationDate")
UserDefaults.standard.synchronize()

// Post notification
DispatchQueue.main.async {
NotificationCenter.default.post(name: .TraktAccountStatusDidChange, object: nil)
}

completionHandler?(.success)
}
}
catch {
completionHandler?(.fail)
}
}.resume()
}
// MARK: - Authentication

public func getTokenFromAuthorizationCode(code: String, completionHandler: SuccessCompletionHandler?) throws {
Expand Down
Loading