diff --git a/Sources/WebPush/Helpers/DataProtocol+Base64URLCoding.swift b/Sources/WebPush/Helpers/DataProtocol+Base64URLCoding.swift new file mode 100644 index 0000000..ef615ad --- /dev/null +++ b/Sources/WebPush/Helpers/DataProtocol+Base64URLCoding.swift @@ -0,0 +1,33 @@ +// +// DataProtocol+Base64URLCoding.swift +// swift-webpush +// +// Created by Dimitri Bouniol on 2024-12-06. +// Copyright © 2024 Mochi Development, Inc. All rights reserved. +// + +import Foundation + +extension DataProtocol { + func base64URLEncodedString() -> String { + Data(self) + .base64EncodedString() + .replacingOccurrences(of: "+", with: "-") + .replacingOccurrences(of: "/", with: "_") + .replacingOccurrences(of: "=", with: "") + } +} + +extension DataProtocol where Self: RangeReplaceableCollection { + init?(base64URLEncoded string: String) { + var base64String = string.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/") + while base64String.count % 4 != 0 { + base64String = base64String.appending("=") + } + + guard let decodedData = Data(base64Encoded: base64String) + else { return nil } + + self = Self(decodedData) + } +} diff --git a/Sources/WebPush/VAPID/VAPIDKey.swift b/Sources/WebPush/VAPID/VAPIDKey.swift index 40e8d38..4763c25 100644 --- a/Sources/WebPush/VAPID/VAPIDKey.swift +++ b/Sources/WebPush/VAPID/VAPIDKey.swift @@ -69,6 +69,6 @@ extension VAPID.Key: Identifiable { } public var id: ID { - ID(privateKey.publicKey.x963Representation.base64EncodedString()) // TODO: make url-safe + ID(privateKey.publicKey.x963Representation.base64URLEncodedString()) } } diff --git a/Tests/WebPushTests/Base64URLCodingTests.swift b/Tests/WebPushTests/Base64URLCodingTests.swift new file mode 100644 index 0000000..759ceec --- /dev/null +++ b/Tests/WebPushTests/Base64URLCodingTests.swift @@ -0,0 +1,25 @@ +// +// Base64URLCodingTests.swift +// swift-webpush +// +// Created by Dimitri Bouniol on 2024-12-06. +// Copyright © 2024 Mochi Development, Inc. All rights reserved. +// + +import Foundation +import Testing +@testable import WebPush + +@Test func base64URLDecoding() async throws { + let string = ">>> Hello, swift-webpush world??? 🎉" + let base64Encoded = "Pj4+IEhlbGxvLCBzd2lmdC13ZWJwdXNoIHdvcmxkPz8/IPCfjok=" + let base64URLEncoded = "Pj4-IEhlbGxvLCBzd2lmdC13ZWJwdXNoIHdvcmxkPz8_IPCfjok" + #expect(String(decoding: Data(base64URLEncoded: base64Encoded)!, as: UTF8.self) == string) + #expect(String(decoding: Data(base64URLEncoded: base64URLEncoded)!, as: UTF8.self) == string) +} + +@Test func base64URLEncoding() async throws { + let string = ">>> Hello, swift-webpush world??? 🎉" + let base64URLEncoded = "Pj4-IEhlbGxvLCBzd2lmdC13ZWJwdXNoIHdvcmxkPz8_IPCfjok" + #expect(Array(string.utf8).base64URLEncodedString() == base64URLEncoded) +}