diff --git a/Sources/WebPush/Errors/MessageTooLargeError.swift b/Sources/WebPush/Errors/MessageTooLargeError.swift new file mode 100644 index 0000000..8569f13 --- /dev/null +++ b/Sources/WebPush/Errors/MessageTooLargeError.swift @@ -0,0 +1,20 @@ +// +// MessageTooLargeError.swift +// swift-webpush +// +// Created by Dimitri Bouniol on 2024-12-13. +// Copyright © 2024 Mochi Development, Inc. All rights reserved. +// + +import Foundation + +/// The message was too large, and could not be delivered to the push service. +/// +/// - SeeAlso: ``WebPushManager/maximumMessageSize`` +public struct MessageTooLargeError: LocalizedError, Hashable { + public init() {} + + public var errorDescription: String? { + "The message was too large, and could not be delivered to the push service." + } +} diff --git a/Sources/WebPush/WebPushManager.swift b/Sources/WebPush/WebPushManager.swift index 55396df..37c6cbb 100644 --- a/Sources/WebPush/WebPushManager.swift +++ b/Sources/WebPush/WebPushManager.swift @@ -494,7 +494,9 @@ public actor WebPushManager: Sendable { switch response.status { case .created: break case .notFound, .gone: throw BadSubscriberError() - // TODO: 413 payload too large - log.error and throw error + case .payloadTooLarge: + logger.error("The encrypted payload was too large and was rejected by the push service.") + throw MessageTooLargeError() // TODO: 429 too many requests, 500 internal server error, 503 server shutting down - check config and perform a retry after a delay? default: throw HTTPError(response: response) } diff --git a/Tests/WebPushTests/WebPushManagerTests.swift b/Tests/WebPushTests/WebPushManagerTests.swift index 14e0170..3630eb5 100644 --- a/Tests/WebPushTests/WebPushManagerTests.swift +++ b/Tests/WebPushTests/WebPushManagerTests.swift @@ -508,6 +508,35 @@ struct WebPushManagerTests { } } + @Test func sendExtraLargeMessageFails() async throws { + await confirmation { requestWasMade in + let vapidConfiguration = VAPID.Configuration.makeTesting() + + let subscriberPrivateKey = P256.KeyAgreement.PrivateKey(compactRepresentable: false) + var authenticationSecret: [UInt8] = Array(repeating: 0, count: 16) + for index in authenticationSecret.indices { authenticationSecret[index] = .random(in: .min ... .max) } + + let subscriber = Subscriber( + endpoint: URL(string: "https://example.com/subscriber")!, + userAgentKeyMaterial: UserAgentKeyMaterial(publicKey: subscriberPrivateKey.publicKey, authenticationSecret: Data(authenticationSecret)), + vapidKeyID: vapidConfiguration.primaryKey!.id + ) + + let manager = WebPushManager( + vapidConfiguration: vapidConfiguration, + backgroundActivityLogger: Logger(label: "WebPushManagerTests", factory: { PrintLogHandler(label: $0, metadataProvider: $1) }), + executor: .httpClient(MockHTTPClient({ request in + requestWasMade() + return HTTPClientResponse(status: .payloadTooLarge) + })) + ) + + await #expect(throws: MessageTooLargeError()) { + try await manager.send(data: Array(repeating: 0, count: 3994), to: subscriber) + } + } + } + @Test func sendMessageToNotFoundPushServerError() async throws { await confirmation { requestWasMade in let vapidConfiguration = VAPID.Configuration.mockedConfiguration