Swift Siesta-如何将异步代码包含在请求链中



我尝试使用Siesta装饰器来启用一个流,当登录用户获得401时,我的authToken会自动刷新。对于身份验证,我使用Firebase。

在Siesta文档中,有一个关于如何链接Siesta请求的直接示例,但我找不到如何获得异步Firebase getIDTokenForcingRefresh:completion:在这里工作。问题是Siesta总是期望返回一个Request或RequestChainAction,这在Firebase身份验证令牌刷新api中是不可能的。

据我所知,请求链接主要是针对Siesta专用用例完成的。但是,有没有一种方法可以使用像FirebaseAuth这样的异步第三方API,而这些API并不完全符合要求?

这是代码:

init() {
configure("**") {
$0.headers["jwt"] = self.authToken

$0.decorateRequests {
self.refreshTokenOnAuthFailure(request: $1)
}  
}
func refreshTokenOnAuthFailure(request: Request) -> Request {
return request.chained {
guard case .failure(let error) = $0.response,  // Did request fail…
error.httpStatusCode == 401 else {           // …because of expired token?
return .useThisResponse                    // If not, use the response we got.
}
return .passTo(
self.createAuthToken().chained {             // If so, first request a new token, then:
if case .failure = $0.response {           // If token request failed…
return .useThisResponse                  // …report that error.
} else {
return .passTo(request.repeated())       // We have a new token! Repeat the original request.
}
}
)
}
}
//What to do here? This should actually return a Siesta request
func createAuthToken() -> Void {
let currentUser = Auth.auth().currentUser
currentUser?.getIDTokenForcingRefresh(true) { idToken, error in
if let error = error {
// Error
return;
}
self.authToken = idToken
self.invalidateConfiguration()
}
}

编辑:

根据阿德里安的建议答案,我尝试了以下解决方案。它仍然没有按预期工作:

  • 我使用request((.post发送请求
  • 用这个解决方案,我得到了一个失败的";请求被取消";在回调中
  • 调用createUser的回调后,将使用更新的jwt令牌发送原始请求
  • 这个带有正确jwt令牌的新请求将丢失,因为没有为响应调用createUser的回调->因此,在这种情况下,永远无法实现onSuccess

如何确保createUser的回调仅在原始请求与更新的jwt令牌一起发送后调用?这是我不工作的解决方案-很高兴有任何建议:

// This ends up with a requestError "Request Cancelled" before the original request is triggered a second time with the refreshed jwt token.
func createUser(user: UserModel, completion: @escaping CompletionHandler) {
do {
let userAsDict = try user.asDictionary()
Api.sharedInstance.users.request(.post, json: userAsDict)
.onSuccess {
data in
if let user: UserModel = data.content as? UserModel {
completion(user, nil)
} else {
completion(nil, "Deserialization Error")
}
}.onFailure {
requestError in
completion(nil, requestError)
}
} catch let error {
completion(nil, nil, "Serialization Error")
}
}

Api类:

class Api: Service {

static let sharedInstance = Api()
var jsonDecoder = JSONDecoder()
var authToken: String? {
didSet {
// Rerun existing configuration closure using new value
invalidateConfiguration()
// Wipe any cached state if auth token changes
wipeResources()
}
}

init() {
configureJSONDecoder(decoder: jsonDecoder)
super.init(baseURL: Urls.baseUrl.rawValue, standardTransformers:[.text, .image])
SiestaLog.Category.enabled = SiestaLog.Category.all

configure("**") {
$0.expirationTime = 1
$0.headers["bearer-token"] = self.authToken
$0.decorateRequests {
self.refreshTokenOnAuthFailure(request: $1)
}
}

self.configureTransformer("/users") {
try self.jsonDecoder.decode(UserModel.self, from: $0.content)
}

}

var users: Resource { return resource("/users") }

func refreshTokenOnAuthFailure(request: Request) -> Request {
return request.chained {
guard case .failure(let error) = $0.response,  // Did request fail…
error.httpStatusCode == 401 else {           // …because of expired token?
return .useThisResponse                    // If not, use the response we got.
}
return .passTo(
self.refreshAuthToken(request: request).chained {          // If so, first request a new token, then:
if case .failure = $0.response {
return .useThisResponse                  // …report that error.
} else {
return .passTo(request.repeated())       // We have a new token! Repeat the original request.
}
}
)
}
}

func refreshAuthToken(request: Request) -> Request {
return Resource.prepareRequest(using: RefreshJwtRequest())
.onSuccess {
self.authToken = $0.text                  // …make future requests use it
}
}
}

请求代表:

class RefreshJwtRequest: RequestDelegate {
func startUnderlyingOperation(passingResponseTo completionHandler: RequestCompletionHandler) {
if let currentUser = Auth.auth().currentUser {
currentUser.getIDTokenForcingRefresh(true) { idToken, error in
if let error = error {
let reqError = RequestError(response: nil, content: nil, cause: error, userMessage: nil)
completionHandler.broadcastResponse(ResponseInfo(response: .failure(reqError)))
return;
}
let entity = Entity<Any>(content: idToken ?? "no token", contentType: "text/plain")
completionHandler.broadcastResponse(ResponseInfo(response: .success(entity)))            }
} else {
let authError = RequestError(response: nil, content: nil, cause: AuthError.NOT_LOGGED_IN_ERROR, userMessage: "You are not logged in. Please login and try again.".localized())
completionHandler.broadcastResponse(ResponseInfo(response: .failure(authError)))
}
}

func cancelUnderlyingOperation() {}
func repeated() -> RequestDelegate { RefreshJwtRequest() }
private(set) var requestDescription: String = "CustomSiestaRequest"
}

首先,您应该重新表述问题的主旨,使其不特定于Firebase,就像"如何使用一些任意异步代码而不是请求进行请求链接?"。这样对社会会更有用。然后您可以提到Firebaseauth是您的特定用例。我将相应地回答你的问题

(编辑:在回答了这个问题后,我现在看到Paul已经在这里回答了:如何用异步任务装饰Siesta请求(

Siesta的RequestDelegate可以满足您的需求。引用文档的话:"这对于处理非标准网络请求的内容,并将它们包装起来,使它们看起来像Siesta一样非常有用。要创建自定义请求,请将您的代理传递给Resource.prepareRequest(using:)。">

您可以使用类似的东西作为粗略的起点——它运行一个闭包(在您的情况下是auth调用(,该闭包要么成功,没有输出,要么返回错误。根据使用情况,您可能会对其进行调整,以使用实际内容填充实体。

// todo better name
class SiestaPseudoRequest: RequestDelegate {
private let op: (@escaping (Error?) -> Void) -> Void
init(op: @escaping (@escaping (Error?) -> Void) -> Void) {
self.op = op
}
func startUnderlyingOperation(passingResponseTo completionHandler: RequestCompletionHandler) {
op {
if let error = $0 {
// todo better
let reqError = RequestError(response: nil, content: nil, cause: error, userMessage: nil)
completionHandler.broadcastResponse(ResponseInfo(response: .failure(reqError)))
}
else {
// todo you might well produce output at this point
let ent = Entity<Any>(content: "", contentType: "text/plain")
completionHandler.broadcastResponse(ResponseInfo(response: .success(ent)))
}
}
}
func cancelUnderlyingOperation() {}
func repeated() -> RequestDelegate { SiestaPseudoRequest(op: op) }
// todo better
private(set) var requestDescription: String = "SiestaPseudoRequest"
}

我发现的一个问题是,响应转换器并不是为这样的"请求"运行的——转换器管道是Siesta的NetworkRequest特有的。

可能值得注意的是其他非请求式的行为。

相关内容

  • 没有找到相关文章

最新更新