Skip to content

Instantly share code, notes, and snippets.

@sisoje
Last active July 11, 2025 06:32
Show Gist options
  • Save sisoje/2e5e5f00b4f310d06245314b2b560376 to your computer and use it in GitHub Desktop.
Save sisoje/2e5e5f00b4f310d06245314b2b560376 to your computer and use it in GitHub Desktop.
The Composable Networking
import Foundation
import SwiftUI
// MARK: - errors
enum ComposableNetworkingError: Error {
case missingBearerToken
case missingRefreshToken
}
// MARK: - components
typealias NetworkingType = (URLRequest) async throws -> (Data, URLResponse)
struct AuthorizedNetworking {
@Binding var bearerToken: String?
let networking: NetworkingType
let authorizeRequest: ((URLRequest, String)) -> URLRequest // no default value
var composition: NetworkingType {
{ request in
guard let token = bearerToken else {
throw ComposableNetworkingError.missingBearerToken
}
return try await networking(authorizeRequest((request, token)))
}
}
}
struct ComposableNetworking {
@Binding var bearerToken: String?
let oauthNetworking: OAuthNetworking
let networking: NetworkingType
var composition: NetworkingType {
if bearerToken != nil {
oauthNetworking.composition
} else {
networking
}
}
}
struct OAuthNetworking {
@Binding var bearerToken: String?
let authorizedNetworking: AuthorizedNetworking
let localTokenRefresh: LocalTokenRefresh
let isNotAuthorized: (URLResponse) -> Bool
var composition: NetworkingType {
{ request in
let oldToken = bearerToken
let (data, response) = try await authorizedNetworking.composition(request)
guard isNotAuthorized(response) else {
return (data, response)
}
if oldToken == bearerToken {
try await localTokenRefresh.composition()
}
return try await authorizedNetworking.composition(request)
}
}
}
struct RemoteTokenRefresh {
let refreshTokenRequest: (String) -> URLRequest
let parseToken: (Data) throws -> String
let networking: NetworkingType
let isNotAuthorized: (URLResponse) -> Bool
var composition: (String) async throws -> String? {
{ refreshToken in
let req = refreshTokenRequest(refreshToken)
let (data, response) = try await networking(req)
if isNotAuthorized(response) {
return nil
}
return try parseToken(data)
}
}
}
struct LocalTokenRefresh {
@Binding var refreshToken: String?
@Binding var bearerToken: String?
@Binding var refreshTask: Task<Void, Error>?
let remoteTokenRefresh: RemoteTokenRefresh
var composition: () async throws -> Void {
{
if let task = refreshTask {
return try await task.value
}
let task = Task {
guard let refreshToken = refreshToken else {
throw ComposableNetworkingError.missingRefreshToken
}
let token = try await remoteTokenRefresh.composition(refreshToken)
if let token {
self.bearerToken = token
} else {
self.bearerToken = nil
self.refreshToken = nil
}
}
refreshTask = task
defer {
refreshTask = nil
}
return try await task.value
}
}
}
// MARK: - composer
struct NetworkingComposer {
@Binding var bearerToken: String?
@Binding var refreshToken: String?
@Binding var refreshTask: Task<Void, Error>?
let networking: NetworkingType
let refreshTokenRequest: (String) -> URLRequest
let parseToken: (Data) throws -> String
var authorizeRequest: ((URLRequest, String)) -> URLRequest = { arg in
var req = arg.0
req.setValue("Bearer \(arg.1)", forHTTPHeaderField: "Authorization")
return req
}
var isNotAuthorized: (URLResponse) -> Bool = { ($0 as? HTTPURLResponse)?.statusCode == 401 }
var remoteTokenRefresh: RemoteTokenRefresh {
RemoteTokenRefresh(refreshTokenRequest: refreshTokenRequest, parseToken: parseToken, networking: networking, isNotAuthorized: isNotAuthorized)
}
var authorizedNetworking: AuthorizedNetworking {
AuthorizedNetworking(bearerToken: $bearerToken, networking: networking, authorizeRequest: authorizeRequest)
}
var localTokenRefresh: LocalTokenRefresh {
LocalTokenRefresh(refreshToken: $refreshToken, bearerToken: $bearerToken, refreshTask: $refreshTask, remoteTokenRefresh: remoteTokenRefresh)
}
var oAuthNetworking: OAuthNetworking {
OAuthNetworking(bearerToken: $bearerToken, authorizedNetworking: authorizedNetworking, localTokenRefresh: localTokenRefresh, isNotAuthorized: isNotAuthorized)
}
var composableNetworking: ComposableNetworking {
ComposableNetworking(bearerToken: $bearerToken, oauthNetworking: oAuthNetworking, networking: networking)
}
var composition: NetworkingType {
composableNetworking.composition
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment