-
Notifications
You must be signed in to change notification settings - Fork 37
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,7 +40,7 @@ public class MLKeychain { | |
let keychainQuery: [String: Any] = [ | ||
kSecClassValue: kSecClassGenericPasswordValue, | ||
kSecAttrAccountValue: key, | ||
kSecReturnDataValue: kCFBooleanTrue, | ||
kSecReturnDataValue: kCFBooleanTrue!, | ||
kSecMatchLimitValue: kSecMatchLimitOneValue | ||
] | ||
|
||
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for this! |
||
return nil | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,13 +16,15 @@ public struct User: Codable { | |
public let name: String? | ||
public let isVIP: Bool? | ||
public let isVIPEP: Bool? | ||
// public let ids: ID | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? | ||
|
@@ -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" | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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() | ||
|
||
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this should be reverted. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
@@ -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" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
|
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.