我尝试使用Siesta装饰器来启用当登录用户获得401时自动刷新我的authToken的流。对于身份验证,我使用Firebase。
在Siesta文档中,有一个关于如何链接Siesta请求的直接示例,但我找不到如何让异步Firebase getIDTokenForcingRefresh:完成:在这里工作的方法。问题是,Siesta总是期望返回一个请求或一个RequestChainAction,这在Firebase auth令牌刷新api中是不可能的。
据我所知,请求链接主要是针对只使用Siesta的用例进行的。但是,是否有一种方法可以使用异步第三方API(如FirebaseAuth ),而这些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()
}
}编辑:
基于禤浩焯建议的答案,我尝试了下面的解决方案。它仍然不像预期的那样起作用:
如何确保只在使用更新的jwt令牌发送原始请求之后才调用createUser的回调?这是我的不可行的解决方案--很高兴收到任何建议:
// 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
}
}
}RequestDelegate:
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"
}发布于 2020-03-28 21:41:50
首先,您应该重新表述问题的主旨,使其不特定于Firebase,类似于“如何使用任意异步代码而不是请求来进行请求链接?”这样对社会会有更大的帮助。然后,您可以提到Firebase auth是您的特定用例。我将相应地回答你的问题。
(编辑:回答了这个问题后,我现在看到保罗已经回答了这个问题:如何用异步任务装饰午觉请求)
午睡的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的。(这让我大吃一惊,我不确定我是否喜欢它,但西斯塔似乎总体上是充满了好的决定,所以我主要是相信这是一个很好的理由。)
这可能是值得注意的其他非请求类行为。
https://stackoverflow.com/questions/60692897
复制相似问题