Im trying to refresh token using Combine in Swift. I put refresh part in the tryCatch part inside URLSession.shared.dataTaskPublisher but for some reason .sink returns as error even if this part in tryCatch is working and new token is fetched.
This is what I have in ViewModel:
private func testApi() {
useCase.getSomeData
.receive(on: DispatchQueue.main)
.sink { completion in
switch completion {
case .failure(_):
//This is where Im getting error code!
case .finished: break
}
} receiveValue: { [weak self] response in
print(response
}
.store(in: &cancellables)
}
And this func’s are in my Network class:
public func request<T>(_ req: NetworkRequest) -> AnyPublisher<T, NetworkError> where T: Decodable, T: Encodable {
guard let url = URL(string: req.url) else {
return AnyPublisher(
Fail<T, NetworkError>(error: NetworkError.badURL("Invalid Url"))
)
}
let urlRequest = req.buildURLRequest(with: url)
return URLSession
.shared
.dataTaskPublisher(for: urlRequest)
.tryMap { output in
let code = (output.response as? HTTPURLResponse)?.statusCode ?? 200
let httpStatusCode = HTTPStatusCode(rawValue: code) ?? .noResponse
let outputData = output.data
let message = self.responseErrorMessage(outputData)
guard output.response is HTTPURLResponse else {
throw NetworkError.serverError(code: code, error: message)
}
switch httpStatusCode {
case .ok, .created, .accepted:
return output.data
case .badRequest, .notFound:
throw NetworkError.badRequest(code: code, error: message)
case .conflict:
throw NetworkError.unknown(code: code, error: message)
case .tokenExpired:
throw NetworkError.unknown(code: code, error: message)
case .unauthorised:
throw NetworkError.unauthorised(code: code, error: message)
case .paymentRequired, .forbidden, .methodNotAllowed, .requestTimeout, .unsupportedMediaType, .tooManyRequests:
throw NetworkError.unknown(code: code, error: message)
case .noResponse:
throw NetworkError.noResponse(message)
case .internalServerError, .serviceUnavailable:
throw NetworkError.serverError(code: code, error: message)
}
}
.tryCatch { [weak self] error -> AnyPublisher<Data, NetworkError> in
guard let self = self else { throw error }
if error as? NetworkError == .unauthorised(code: 401, error: "Unauthorized") {
return refreshToken
.flatMap { _ -> AnyPublisher<Data, NetworkError> in
self.request(self.updateRequest(req))
}
.eraseToAnyPublisher()
}
throw error
}
.decode(type: T.self, decoder: jsonDecoder)
.mapError { error in
if let error = error as? NetworkError {
return error
}
return NetworkError.invalidJSON(String(describing: error))
}
.eraseToAnyPublisher()
}
This is refresh part:
private var refreshToken: AnyPublisher<Void, NetworkError> {
URLSession
.shared
.dataTaskPublisher(for: buildRefreshRequest())
.map(\.data)
.decode(type: LoginResponseData.self, decoder: jsonDecoder)
.map { response in
let token = response.accessToken
UserDefaults.token = token
}
.mapError { error in
if let error = error as? NetworkError {
return error
}
return NetworkError.invalidJSON(String(describing: error))
}
.eraseToAnyPublisher()
}
So in short, when I call useCase.getSomeData, I simulate token expire in debug mode by deleting token from headers, after that Im getting 401 in tryMap and in tryCatch catches that error and calling refreshToken and ‘self.request(self.updateRequest(req))’ is called after new token is fetched but Im not getting code in ‘receiveValue: { [weak self] response ‘ in viewModel, instead it completes in ‘.sink ‘ as a failure.
Im fresh in Combine and trying to implement this 401/refreshToken part but I could not figure out what Im missing here.