summaryrefslogtreecommitdiffhomepage
path: root/ios/MullvadREST/ApiHandlers/RESTProxy.swift
blob: cabfd4bfdf049d10336f6ad6885bbe0d83292012 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
//
//  RESTProxy.swift
//  MullvadREST
//
//  Created by pronebird on 20/04/2022.
//  Copyright © 2025 Mullvad VPN AB. All rights reserved.
//

import Foundation
import MullvadRustRuntime
import MullvadTypes
import Operations

public typealias ProxyCompletionHandler<Success: Sendable> = @Sendable (Result<Success, Swift.Error>) -> Void

extension REST {
    public class Proxy<ConfigurationType: ProxyConfiguration>: @unchecked Sendable {
        /// Synchronization queue used by network operations.
        let dispatchQueue: DispatchQueue

        /// Operation queue used for running network operations.
        let operationQueue = AsyncOperationQueue()

        /// Proxy configuration.
        let configuration: ConfigurationType

        /// URL request factory.
        let requestFactory: REST.RequestFactory

        /// URL response decoder.
        let responseDecoder: JSONDecoder

        init(
            name: String,
            configuration: ConfigurationType,
            requestFactory: REST.RequestFactory,
            responseDecoder: JSONDecoder
        ) {
            dispatchQueue = DispatchQueue(label: "REST.\(name).dispatchQueue")
            operationQueue.name = "REST.\(name).operationQueue"

            self.configuration = configuration
            self.requestFactory = requestFactory
            self.responseDecoder = responseDecoder
        }

        func makeRequestExecutor<Success: Sendable>(
            name: String,
            requestHandler: RESTRequestHandler,
            responseHandler: some RESTResponseHandler<Success>
        ) -> any RESTRequestExecutor<Success> {
            let operationFactory = NetworkOperationFactory(
                dispatchQueue: dispatchQueue,
                configuration: configuration,
                name: name,
                requestHandler: requestHandler,
                responseHandler: responseHandler
            )

            return RequestExecutor(operationFactory: operationFactory, operationQueue: operationQueue)
        }
    }

    /// Factory object producing instances of `NetworkOperation`.
    private struct NetworkOperationFactory<Success: Sendable, ConfigurationType: ProxyConfiguration> {
        let dispatchQueue: DispatchQueue
        let configuration: ConfigurationType

        let name: String
        let requestHandler: RESTRequestHandler
        let responseHandler: any RESTResponseHandler<Success>

        /// Creates new network operation but does not schedule it for execution.
        func makeOperation(
            retryStrategy: REST.RetryStrategy,
            completionHandler: ProxyCompletionHandler<Success>? = nil
        ) -> NetworkOperation<Success> {
            return NetworkOperation(
                name: getTaskIdentifier(name: name),
                dispatchQueue: dispatchQueue,
                configuration: configuration,
                retryStrategy: retryStrategy,
                requestHandler: requestHandler,
                responseHandler: responseHandler,
                completionHandler: completionHandler
            )
        }
    }

    /// Network request executor that supports block-based and async execution flows.
    private struct RequestExecutor<Success: Sendable, ConfigurationType: ProxyConfiguration>: RESTRequestExecutor {
        let operationFactory: NetworkOperationFactory<Success, ConfigurationType>
        let operationQueue: AsyncOperationQueue

        func execute(
            retryStrategy: REST.RetryStrategy,
            completionHandler: @escaping ProxyCompletionHandler<Success>
        ) -> Cancellable {
            let operation = operationFactory.makeOperation(
                retryStrategy: retryStrategy,
                completionHandler: completionHandler
            )

            operationQueue.addOperation(operation)

            return operation
        }

        func execute(retryStrategy: REST.RetryStrategy) async throws -> Success {
            let operation = operationFactory.makeOperation(retryStrategy: retryStrategy)

            return try await withTaskCancellationHandler {
                return try await withCheckedThrowingContinuation { continuation in
                    operation.completionHandler = { result in
                        continuation.resume(with: result)
                    }
                    operationQueue.addOperation(operation)
                }
            } onCancel: {
                operation.cancel()
            }
        }

        func execute(completionHandler: @escaping @Sendable ProxyCompletionHandler<Success>) -> Cancellable {
            return execute(retryStrategy: .noRetry, completionHandler: completionHandler)
        }

        func execute() async throws -> Success {
            return try await execute(retryStrategy: .noRetry)
        }
    }

    public class ProxyConfiguration: @unchecked Sendable {
        public let transportProvider: RESTTransportProvider
        public let apiTransportProvider: APITransportProviderProtocol
        public let addressCacheStore: AddressCache

        public init(
            transportProvider: RESTTransportProvider,
            apiTransportProvider: APITransportProviderProtocol,
            addressCacheStore: AddressCache
        ) {
            self.transportProvider = transportProvider
            self.apiTransportProvider = apiTransportProvider
            self.addressCacheStore = addressCacheStore
        }
    }

    public class AuthProxyConfiguration: ProxyConfiguration, @unchecked Sendable {
        public let accessTokenManager: RESTAccessTokenManagement

        public init(
            proxyConfiguration: ProxyConfiguration,
            accessTokenManager: RESTAccessTokenManagement
        ) {
            self.accessTokenManager = accessTokenManager

            super.init(
                transportProvider: proxyConfiguration.transportProvider,
                apiTransportProvider: proxyConfiguration.apiTransportProvider,
                addressCacheStore: proxyConfiguration.addressCacheStore
            )
        }
    }
}