Last active
February 17, 2024 02:19
-
-
Save saroar/ca78de9dc798cdbaaa47791380062596 to your computer and use it in GitHub Desktop.
RefreshToken URLSession + Combine swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, macCatalyst 13.0, *) | |
extension JSONDecoder { | |
public static let ISO8601JSONDecoder: JSONDecoder = { | |
let decoder = JSONDecoder() | |
decoder.keyDecodingStrategy = .convertFromSnakeCase | |
decoder.dateDecodingStrategy = .iso8601 | |
return decoder | |
}() | |
} | |
var rToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmNDUyY2RjZDUyMTE1ZDE0Yzk2NDUwYiIsImlhdCI6MTU5ODk4NTQyMCwiZXhwIjoxNjMwNTIxNDIwfQ.of5jT0LlqrlVoPZ7N6zXXWjvgsmZtkQKfhj0sWKB3rinhxFe1QeY-wueaWBrbxHYdI9cI7Kmj6dSPfK8b9Oc4yOGIOzQ4yONHHAShXKp6HszArjVe8wdRcZN02rxilHDCJoqXAMnjQXi7tMsMDtuX2e7iHsuNgSDNU9WMAtMJ6iMHj95IE-W5jOPyNXodQmqfSP5XJcyRI1LPq-nuMWa86L60BVhH2oySUNzFYLYGG3mrF2wAf6pC9XtWUMgQJ1zWptS08_O3DKzWwbDTzjbdnnx5aBH5SwOBxvgci-Ngl4Ix7EyTfHX1Akuxy8gwBu5qTP3erPeyH6uK0vrbkyJeQ" | |
var invalidToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmNDUyY2RjZDUyMTE1ZDE0Yzk2NDUwYiIsImlhdCI6MTU5OTAyOTczNCwiZXhwIjoxNjMwNTY1NzM0fQ.T1Emc8J3QUh49nUO9GLwiWkGIKA9EoQBej0P6_-uNo0BenkcLpJWOq_DSKAexhT06S4_CqlGiJ1kn8q7gDJOZR4tX7xDXfObQeZisbbsgo_UIWlaSZTu3l3Ey_93vlt8c0W4-pOj99-voSwQ_Q4RvRi6r3r3P1aGb5JZ48vCZ_ulT13SGSV1xQL08VuV87KwsosoXLa56hJTBqpKyohkbvTr6Nb0rLwS48FEn-T2mKkyZmARvQlpEO3j2IGroskNYelMt2qU80h7k6GyzTOJh1mB1ZTBHXQSaE5z3VHNpFN5M9sRvegkkVucU9zrfQ85OM4Rs4vx9RJMmDfAIbSuyw" | |
var aToken: String = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdGF0dXMiOjAsImV4cCI6MTU5OTAzMzEyOCwiaWF0IjoxNTk5MDMzMDY4LCJ1c2VySWQiOiI1ZjQ1MmNkY2Q1MjExNWQxNGM5NjQ1MGIiLCJwaG9uZU51bWJlciI6Iis3OTIxODgyMTIxNyJ9.UUZ5TUPGPRreS3AKko7NZ_gcoJYvKuJCipUHJPxS1ormw1yQXolQzrCCf34EK58peZm5WngLwo3nAuPplL5rmInvjcYQotI0N0grpKkMf_ITPRMv80iqObpBr1r2zsvJVqwMysmRM4wP-mipvvwvlb0lkKXPoqn2M5Eckkk97hsrQ5pAFsaMJQytpm6YW-IC3NXinYAeTlWQbm_7_9naxSobLQyVJ2VXc4lArddzDLpEMoZEkC28fXLoQKL36W93MBcoNFxjdHCYyuxBFHQrtu7drCYJ1EyGOA-lCACf5twslGoZKWJjvqa8IjWcaJVfGveMVakaBfR90Do6f-f6ZA" | |
var headers = [ | |
"authorization": "Bearer \(aToken)", | |
"Content-Type": "application/json" | |
] | |
public struct RefreshTokenResponse: Codable { | |
public var access_token: String | |
public var refresh_token: String | |
// enum CodingKeys: String, CodingKey { | |
// case accessToken = "access_token" | |
// case refreshToken = "refresh_token" | |
// } | |
} | |
public struct RefreshTokenInput: Codable { | |
public var refresh_token: String | |
// enum CodingKeys: String, CodingKey { | |
// case refreshToken = "refresh_token" | |
// } | |
} | |
class Authenticator { | |
private var currentToken = RefreshTokenResponse( | |
access_token: aToken, | |
refresh_token: rToken | |
) | |
private var cancellationToken: AnyCancellable? | |
func refreshToken<S: Subject>(using subject: S) where S.Output == RefreshTokenResponse { | |
//self.currentToken = Token(isValid: true) | |
let parameters: [String: Any] = [ | |
"refresh_token": rToken, | |
] | |
let jsonDecoder: JSONDecoder = .ISO8601JSONDecoder | |
let url: URL = URL(string: "http://10.0.1.3:8080/v1/auth/refreshToken")! | |
var request = URLRequest(url: url) | |
request.httpMethod = "POST" | |
request.allHTTPHeaderFields = headers | |
do { | |
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted) | |
} catch let error { | |
print(error.localizedDescription) | |
} | |
URLSession.shared.dataTaskPublisher(for: request) | |
.retry(3) | |
.sink { com in | |
print(com) | |
subject.send(self.currentToken) | |
} receiveValue: { data in | |
print(#line, data) | |
do { | |
let jsonDecoder2 = JSONDecoder() | |
let rtResponse = try jsonDecoder2.decode(RefreshTokenResponse.self, from: data.data) | |
aToken = rtResponse.access_token | |
rToken = rtResponse.refresh_token | |
headers = [ | |
"authorization": "Bearer \(rtResponse.access_token)", | |
"Content-Type": "application/json" | |
] | |
} catch { | |
print(#line, error) | |
} | |
}.store(in: &cancellables) | |
} | |
func tokenSubject() -> CurrentValueSubject<RefreshTokenResponse, Never> { | |
print(#line, currentToken) | |
return CurrentValueSubject(currentToken) | |
} | |
} | |
struct UserApi { | |
let authenticator: Authenticator | |
@available(iOS 14.0, *) | |
func getProfile() -> AnyPublisher<Data, URLError> { | |
let tokenSubject = authenticator.tokenSubject() | |
return tokenSubject | |
.flatMap({ token -> AnyPublisher<Data, URLError> in | |
let url: URL = URL(string: "http://10.0.1.3:8080/v1/events")! | |
var request = URLRequest(url: url) | |
request.allHTTPHeaderFields = headers | |
print(#line, headers) | |
return URLSession.shared.dataTaskPublisher(for: request) | |
.flatMap({ result -> AnyPublisher<Data, URLError> in | |
if let httpResponse = result.response as? HTTPURLResponse, | |
httpResponse.statusCode == 401 { | |
self.authenticator.refreshToken(using: tokenSubject) | |
print(#line, tokenSubject) | |
return Empty().eraseToAnyPublisher() | |
} | |
return Just(result.data) | |
.setFailureType(to: URLError.self) | |
.eraseToAnyPublisher() | |
}) | |
.eraseToAnyPublisher() | |
}) | |
.handleEvents(receiveOutput: { _ in | |
tokenSubject.send(completion: .finished) | |
}) | |
.eraseToAnyPublisher() | |
} | |
} | |
let authenticator = Authenticator() | |
let get = UserApi(authenticator: authenticator).getProfile() | |
var cancellationToken: AnyCancellable? | |
cancellationToken = get | |
.retry(3) | |
.sink { com in | |
print(com) | |
} receiveValue: { data in | |
print(#line, data) | |
} | |
233 RefreshTokenResponse(access_token: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmNDUyY2RjZDUyMTE1ZDE0Yzk2NDUwYiIsImlhdCI6MTU5OTAyOTczNCwiZXhwIjoxNjMwNTY1NzM0fQ.T1Emc8J3QUh49nUO9GLwiWkGIKA9EoQBej0P6_-uNo0BenkcLpJWOq_DSKAexhT06S4_CqlGiJ1kn8q7gDJOZR4tX7xDXfObQeZisbbsgo_UIWlaSZTu3l3Ey_93vlt8c0W4-pOj99-voSwQ_Q4RvRi6r3r3P1aGb5JZ48vCZ_ulT13SGSV1xQL08VuV87KwsosoXLa56hJTBqpKyohkbvTr6Nb0rLwS48FEn-T2mKkyZmARvQlpEO3j2IGroskNYelMt2qU80h7k6GyzTOJh1mB1ZTBHXQSaE5z3VHNpFN5M9sRvegkkVucU9zrfQ85OM4Rs4vx9RJMmDfAIbSuyw", refresh_token: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmNDUyY2RjZDUyMTE1ZDE0Yzk2NDUwYiIsImlhdCI6MTU5ODk4NTQyMCwiZXhwIjoxNjMwNTIxNDIwfQ.of5jT0LlqrlVoPZ7N6zXXWjvgsmZtkQKfhj0sWKB3rinhxFe1QeY-wueaWBrbxHYdI9cI7Kmj6dSPfK8b9Oc4yOGIOzQ4yONHHAShXKp6HszArjVe8wdRcZN02rxilHDCJoqXAMnjQXi7tMsMDtuX2e7iHsuNgSDNU9WMAtMJ6iMHj95IE-W5jOPyNXodQmqfSP5XJcyRI1LPq-nuMWa86L60BVhH2oySUNzFYLYGG3mrF2wAf6pC9XtWUMgQJ1zWptS08_O3DKzWwbDTzjbdnnx5aBH5SwOBxvgci-Ngl4Ix7EyTfHX1Akuxy8gwBu5qTP3erPeyH6uK0vrbkyJeQ") | |
251 ["authorization": "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmNDUyY2RjZDUyMTE1ZDE0Yzk2NDUwYiIsImlhdCI6MTU5OTAyOTczNCwiZXhwIjoxNjMwNTY1NzM0fQ.T1Emc8J3QUh49nUO9GLwiWkGIKA9EoQBej0P6_-uNo0BenkcLpJWOq_DSKAexhT06S4_CqlGiJ1kn8q7gDJOZR4tX7xDXfObQeZisbbsgo_UIWlaSZTu3l3Ey_93vlt8c0W4-pOj99-voSwQ_Q4RvRi6r3r3P1aGb5JZ48vCZ_ulT13SGSV1xQL08VuV87KwsosoXLa56hJTBqpKyohkbvTr6Nb0rLwS48FEn-T2mKkyZmARvQlpEO3j2IGroskNYelMt2qU80h7k6GyzTOJh1mB1ZTBHXQSaE5z3VHNpFN5M9sRvegkkVucU9zrfQ85OM4Rs4vx9RJMmDfAIbSuyw", "Content-Type": "application/json"] | |
259 Combine.CurrentValueSubject<__lldb_expr_28.RefreshTokenResponse, Swift.Never> | |
212 (data: 1036 bytes, response: <NSHTTPURLResponse: 0x60000219ea60> { URL: http://10.0.1.3:8080/v1/auth/refreshToken } { Status Code: 200, Headers { | |
Connection = ( | |
"keep-alive", | |
"keep-alive" | |
); | |
"Content-Length" = ( | |
1036 | |
); | |
"Content-Type" = ( | |
"application/json; charset=utf-8" | |
); | |
Date = ( | |
"Wed, 02 Sep 2020 07:51:08 GMT", | |
"Wed, 02 Sep 2020 07:51:08 GMT" | |
); | |
} }) | |
finished | |
251 ["Content-Type": "application/json", "authorization": "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdGF0dXMiOjAsImV4cCI6MTU5OTAzMzEyOCwiaWF0IjoxNTk5MDMzMDY4LCJ1c2VySWQiOiI1ZjQ1MmNkY2Q1MjExNWQxNGM5NjQ1MGIiLCJwaG9uZU51bWJlciI6Iis3OTIxODgyMTIxNyJ9.UUZ5TUPGPRreS3AKko7NZ_gcoJYvKuJCipUHJPxS1ormw1yQXolQzrCCf34EK58peZm5WngLwo3nAuPplL5rmInvjcYQotI0N0grpKkMf_ITPRMv80iqObpBr1r2zsvJVqwMysmRM4wP-mipvvwvlb0lkKXPoqn2M5Eckkk97hsrQ5pAFsaMJQytpm6YW-IC3NXinYAeTlWQbm_7_9naxSobLQyVJ2VXc4lArddzDLpEMoZEkC28fXLoQKL36W93MBcoNFxjdHCYyuxBFHQrtu7drCYJ1EyGOA-lCACf5twslGoZKWJjvqa8IjWcaJVfGveMVakaBfR90Do6f-f6ZA"] | |
286 1940 bytes | |
finished |
sorry don't have time now but when will get time i will update with you real-world app where i will use it thanks
sure, hope to see it soon.
Thanks.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi Saroar,
Did you get a chance to look at it?