Cobalt

Cobalt

Cobalt 是 E-sites iOS Suite 的一部分。


E-sites Swift iOS API 客户端用于标准的 RESTful API,默认支持 OAuth2。

forthebadge forthebadge

Travis-ci

安装

Swift PM

package.swift 依赖项

.package(url: "https://github.com/e-sites/cobalt.git", from: "7.0.0"),

"Cobalt" 添加到您的应用程序/库目标的 dependencies 中,例如这样

.target(name: "BestExampleApp", dependencies: ["Cobalt"]),

实现

扩展 Cobalt 类以便在您自己的 API 客户端中使用它。

初始化

import Cobalt

class APIClient: CobaltClient {
   static let `default` = APIClient()
    
   private init() {
      let config = CobaltConfig {
         $0.authentication.path = "/oauth/v2/token"
         $0.authentication.authorizationPath = "/oauth/v2/connect"
         $0.authentication.clientID = "my_oauth_client_id"
         $0.authentication.clientSecret = "my_oauth_client_secret"
         $0.authentication.pkceEnabled = false // Disabled by default
         $0.host = "https://api.domain.com"
      }
      super.init(config: config)
   }
}

发起请求

APIClient 内部使用 Swift 的 Combine 框架来处理请求的响应。

Combine

class APIClient: CobaltClient {
   // ...
   
   func users() -> AnyPublisher<[User], CobaltError> {
      let request = CobaltRequest {
         $0.path = "/users"
         $0.parameters = [
            "per_page": 10
         ]
      }
		
      return self.request(request).tryMap { response in
         return try response.map(to: [User].self)
      }.eraseToAnyPublisher()
   }
}

缓存

要直接使用磁盘缓存,请将以下行添加到您的 Podfile

pod 'Cobalt/Cache'

并像这样实现它

class APIClient: CobaltClient {
   // ...
   
   func users() -> AnyPublisher<[User], CobaltError> {
      let request = CobaltRequest {
         $0.path = "/users"
         $0.parameters = [
            "per_page": 10
         ]
         $0.diskCachePolicy = .expires(seconds: 60 * 60 * 24) // expires after 1 day
      }
		
      return self.request(request).tryMap { response in
         return try response.map(to: [User].self)
      }.eraseToAnyPublisher()
   }
}

要清除整个缓存

APIClientInstance.cache.clearAll()

OAuth2

如果您想使用 OAuth2 协议登录用户,请使用 Cobalt 类中的 login() 函数。 在内部,它将处理提供的 access_token 的检索和刷新

func login(email: String, password: String) -> AnyPublisher<Void, CobaltError>

您还可以使用其他身份验证选项

password(密码)

如果您想检索用户资料,您需要 .oauth2(.password) 身份验证,这样只有在用户通过 login() 函数请求了 access_token 时,请求才会成功。
如果 access_token 过期,Cobalt 将使用 refresh_token 自动刷新它

class APIClient: CobaltClient {
   // ...
   
   func profile() -> AnyPublisher<User, CobaltError> {
        let request = CobaltRequest {
            $0.authentication = .oauth2(.password)
            $0.path = "/user/profile"
        }

        return request(request).tryCompactMap { response in
            if let dictionary = response as? [String: Any], let data = dictionary["data"] as? CobaltResponse {
                return try data.map(to: User.self)
            }
        }.eraseToAnyPublisher()
    }
}

authorization_code(授权码)

此授权类型需要用户在 webview 或浏览器中登录。 要启用这种类型的身份验证,请将 .oauth2(.authorizationCode) 添加到 Cobalt.Request。 如果 access_token 过期,Cobalt 将使用 refresh_token 自动刷新它。

class APIClient: CobaltClient {
    // ...

    func profile() -> Promise<User> {
        let request = Cobalt.Request({
            $0.authentication = .oauth2(.authorizationCode)
            $0.path = "/user/profile"
        })

        return request(request).then { json -> Promise<User> in
            let user = try json["data"].map(to: User.self)
            return Promise(user)
        }
    }
}

在请求个人资料之前,用户需要登录。 为了简化,Cobalt 可以为您创建一个 AuthorizationCodeRequest,其中包含您需要将用户重定向到的 URL

public struct AuthorizationCodeRequest {
    public var url: URL
    public var redirectUri: String
    public var state: String?
    public var codeVerifier: String?
}

class OAuthAuthenticator {
    // ...
    
    private var presentedViewController: UIViewController?
    
    func login() {
        // Cobalt uses the credentials you provided in the config
        // When you enabled PKCE, Cobalt will also create the code challenge and verifier for you
        // The code verifier is returned to you in the AuthorizationCodeRequest
        client.startAuthorizationFlow(
            scope: ["openid", "profile", "email", "offline_access"],
            redirectUri: "app://oauth/authorized"
        ).subscribe(onSuccess: { [weak self] request in
            self?.request = request
            
            let safariController = SFSafariViewController(url: request.url)
            self?.presentedViewController = UINavigationController(rootViewController: safariController)
            self?.presentedViewController!.setNavigationBarHidden(true, animated: false)
            
            viewController.present(
                (self?.presentedViewController)!,
                animated: true,
                completion: nil
            )
        }, onError: { error in
            print("error: \(error)")
        }).disposed(by: disposeBag)
    }
    
    // You execute this when receiving the callback from: "app://oauth/authorized?code=code&scope=scope&state=state"
    func getAccessToken(from code: String, scope: String? = nil, state: String? = nil) -> Single<Void> {
        defer {
            presentedViewController = nil
        }
        
        if let presentedViewController = presentedViewController {
            presentedViewController.dismiss(animated: true, completion: nil)
        }
        
        // Validate that the state of the callback equals the state created by Cobalt
        // Perform some extra validation by your needs
        if request.state != state {
            return Single<Void>.error(Error.invalidUrl)
        }
        
        client.requestTokenFromAuthorizationCode(initialRequest: request, code: code).subscribe(onSuccess: {
            // The user is signed in successfully 
        }, onError: { error in
            // Something went wrong, notify the user
        })
    }
}

client_credentials(客户端凭据)

您必须为 Cobalt.Request 提供 .oauth2(.clientCredentials) 身份验证

class APIClient: CobaltClient {
   // ...
   
   func register(email: String, password: String) -> AnyPublisher<Void, CobaltError> {
      let request = CobaltRequest {
            $0.httpMethod = .post
            $0.path = "/register"
            $0.authentication = .oauth2(.clientCredentials)
            $0.parameters = [
                "email": email,
                "password": password
            ]
        }

        return request(request).map { _ in }
            .eraseToAnyPublisher()
    }

这样 Cobalt 就会知道该请求需要具有 access-token 的 client_credentials grant_type。
如果用户已经具有带有该 grant_type 的 access_token,Cobalt 将使用它。 否则,它将为您请求一个新的 access_token

清除 access_token

要从其内存和钥匙串中删除 access_token,请使用

func clearAccessToken()

开发

只需打开 Cobalt.xcodeproj