Skip to content

Commit e466489

Browse files
Added support for (unsafe) custom HTTP clients
Fixes #39.
1 parent f94bc0e commit e466489

File tree

3 files changed

+57
-4
lines changed

3 files changed

+57
-4
lines changed

Sources/WebPush/WebPushManager.swift

+38-4
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ public actor WebPushManager: Sendable {
4848
/// An internal flag indicating if a manager was shutdown already.
4949
var didShutdown = false
5050

51+
/// An internal flag indicating if a manager should skipShutting down its client.
52+
var skipClientShutdown = false
53+
5154
/// An internal lookup of keys as provided by the VAPID configuration.
5255
let vapidKeyLookup: [VAPID.Key.ID : VAPID.Key]
5356

@@ -103,6 +106,34 @@ public actor WebPushManager: Sendable {
103106
)
104107
}
105108

109+
/// Initialize a manager with an unsafe HTTP Client.
110+
///
111+
/// - Note: You should generally not need to share an HTTP client — in fact, it is heavily discouraged, but provided as an override point should it be necessary. Instead, opt to customize a ``NetworkConfiguration-swift.struct`` and pass it to ``init(vapidConfiguration:networkConfiguration:backgroundActivityLogger:eventLoopGroupProvider:)``, or use `WebPushTesting`'s ``WebPushManager/makeMockedManager(vapidConfiguration:backgroundActivityLogger:messageHandlers:)`` if you intended to mock a ``WebPushManager`` in your tests. If these integration points are not enough, please [create an issue](https://github.com/mochidev/swift-webpush/issues) so we can support it directly.
112+
///
113+
/// - Important: You are responsible for shutting down the client, and there is no direct benefit to using a ``WebPushManager`` as a service if you opt for this initializer.
114+
///
115+
/// - Parameters:
116+
/// - vapidConfiguration: The VAPID configuration to use when identifying the application server.
117+
/// - networkConfiguration: The network configuration used when configuring the manager.
118+
/// - backgroundActivityLogger: The logger to use for misconfiguration and background activity. By default, a print logger will be used, and if set to `nil`, a no-op logger will be used in release builds. When running in a server environment, your shared logger should be used instead giving you full control of logging and metadata.
119+
/// - unsafeHTTPClient: A custom HTTP client to use.
120+
public init(
121+
vapidConfiguration: VAPID.Configuration,
122+
networkConfiguration: NetworkConfiguration = .default,
123+
backgroundActivityLogger: Logger? = .defaultWebPushPrintLogger,
124+
unsafeHTTPClient: HTTPClient
125+
) {
126+
let backgroundActivityLogger = backgroundActivityLogger ?? .defaultWebPushNoOpLogger
127+
128+
self.init(
129+
vapidConfiguration: vapidConfiguration,
130+
networkConfiguration: networkConfiguration,
131+
backgroundActivityLogger: backgroundActivityLogger,
132+
executor: .httpClient(unsafeHTTPClient),
133+
skipClientShutdown: true
134+
)
135+
}
136+
106137
/// Internal method to install a different executor for mocking.
107138
///
108139
/// Note that this must be called before ``run()`` is called or the client's syncShutdown won't be called.
@@ -111,11 +142,13 @@ public actor WebPushManager: Sendable {
111142
/// - networkConfiguration: The network configuration used when configuring the manager.
112143
/// - backgroundActivityLogger: The logger to use for misconfiguration and background activity.
113144
/// - executor: The executor to use when sending push messages.
145+
/// - skipClientShutdown: Whether to skip client shutdown or not.
114146
package init(
115147
vapidConfiguration: VAPID.Configuration,
116148
networkConfiguration: NetworkConfiguration = .default,
117149
backgroundActivityLogger: Logger,
118-
executor: Executor
150+
executor: Executor,
151+
skipClientShutdown: Bool = false
119152
) {
120153
var backgroundActivityLogger = backgroundActivityLogger
121154
backgroundActivityLogger[metadataKey: "vapidConfiguration"] = [
@@ -146,11 +179,12 @@ public actor WebPushManager: Sendable {
146179
)
147180
self.backgroundActivityLogger = backgroundActivityLogger
148181
self.executor = executor
182+
self.skipClientShutdown = skipClientShutdown
149183
}
150184

151185
/// Shutdown the client if it hasn't already been stopped.
152186
deinit {
153-
if !didShutdown, case let .httpClient(httpClient, _) = executor {
187+
if !didShutdown, !skipClientShutdown, case let .httpClient(httpClient, _) = executor {
154188
try? httpClient.syncShutdown()
155189
}
156190
}
@@ -729,10 +763,10 @@ extension WebPushManager: Service {
729763
}
730764
try await withTaskCancellationOrGracefulShutdownHandler {
731765
try await gracefulShutdown()
732-
} onCancelOrGracefulShutdown: { [backgroundActivityLogger, executor] in
766+
} onCancelOrGracefulShutdown: { [skipClientShutdown, backgroundActivityLogger, executor] in
733767
backgroundActivityLogger.debug("Shutting down WebPushManager")
734768
do {
735-
if case let .httpClient(httpClient, _) = executor {
769+
if !skipClientShutdown, case let .httpClient(httpClient, _) = executor {
736770
try httpClient.syncShutdown()
737771
}
738772
} catch {

Sources/WebPushTesting/WebPushManager+Testing.swift

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ extension WebPushManager {
2727
///
2828
/// The mocked manager will forward all messages as is to its message handler so that you may either verify that a push was sent, or inspect the contents of the message that was sent.
2929
///
30+
/// - SeeAlso: ``makeMockedManager(vapidConfiguration:backgroundActivityLogger:messageHandlers:_:)``
31+
///
3032
/// - Parameters:
3133
/// - vapidConfiguration: A VAPID configuration, though the mocked manager doesn't make use of it.
3234
/// - logger: An optional logger.

Tests/WebPushTests/WebPushManagerTests.swift

+17
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,23 @@ struct WebPushManagerTests {
7373
}
7474
}
7575

76+
@Test func managerCanHaveCustomHTTPClient() async throws {
77+
let sharedHTTPClient = HTTPClient()
78+
var manager: WebPushManager? = WebPushManager(vapidConfiguration: .mockedConfiguration, unsafeHTTPClient: sharedHTTPClient)
79+
80+
#expect(manager?.networkConfiguration.retryIntervals == [.milliseconds(500), .seconds(2), .seconds(10)])
81+
#expect(manager?.networkConfiguration.alwaysResolveTopics == false)
82+
83+
if case .httpClient(let httpClient, _) = await manager?.executor, let httpClient = httpClient as? HTTPClient {
84+
#expect(httpClient === sharedHTTPClient)
85+
} else {
86+
Issue.record("No HTTP client")
87+
}
88+
89+
manager = nil
90+
try await sharedHTTPClient.shutdown()
91+
}
92+
7693
@Test func mockedManagerUsesDefaultLogging() async throws {
7794
let manager = WebPushManager.makeMockedManager(messageHandler: { _, _, _, _, _ in })
7895
#expect(manager.backgroundActivityLogger.handler is PrintLogHandler)

0 commit comments

Comments
 (0)