Skip to content

Commit 7c6f049

Browse files
Added a subscriber model with associated documentation
1 parent f52da0b commit 7c6f049

File tree

2 files changed

+140
-0
lines changed

2 files changed

+140
-0
lines changed

Sources/WebPush/Subscriber.swift

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
//
2+
// Subscriber.swift
3+
// swift-webpush
4+
//
5+
// Created by Dimitri Bouniol on 2024-12-10.
6+
// Copyright © 2024 Mochi Development, Inc. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
/// Represents a subscriber registration from the browser.
12+
///
13+
/// Prefer to use ``Subscriber`` directly when possible.
14+
///
15+
/// - SeeAlso: [Push API Working Draft §8. `PushSubscription` interface](https://www.w3.org/TR/push-api/#pushsubscription-interface). Note that the VAPID Key ID must be manually added to the structure supplied by the spec.
16+
public protocol SubscriberProtocol: Sendable {
17+
/// The endpoint representing the subscriber on their push registration service of choice.
18+
var endpoint: URL { get }
19+
20+
/// The key material supplied by the user agent.
21+
var userAgentKeyMaterial: UserAgentKeyMaterial { get }
22+
23+
/// The preferred VAPID Key ID to use, if available.
24+
///
25+
/// If unknown, use the key set to ``VoluntaryApplicationServerIdentification/Configuration/primaryKey``, but be aware that this may be different from the key originally used at time of subscription, and if it is, push messages will be rejected.
26+
///
27+
/// - Important: It is highly recommended to store the VAPID Key ID used at time of registration with the subscriber, and always supply the key itself to the manager. If you are phasing out the key and don't want new subscribers registered against it, store the key in ``VoluntaryApplicationServerIdentification/Configuration/deprecatedKeys``, otherwise store it in ``VoluntaryApplicationServerIdentification/Configuration/keys``.
28+
var vapidKeyID: VAPID.Key.ID { get }
29+
}
30+
31+
/// The set of cryptographic secrets shared by the browser (is. user agent) along with a subscription.
32+
///
33+
/// - SeeAlso: [RFC8291 Message Encryption for Web Push §2.1. Key and Secret Distribution](https://datatracker.ietf.org/doc/html/rfc8291#section-2.1)
34+
public struct UserAgentKeyMaterial: Codable, Hashable, Sendable {
35+
/// The encoded representation of a subscriber's key material.
36+
///
37+
/// - SeeAlso: [Push API Working Draft §8.1. `PushEncryptionKeyName` enumeration](https://www.w3.org/TR/push-api/#pushencryptionkeyname-enumeration)
38+
enum CodingKeys: String, CodingKey {
39+
case publicKey = "p256dh"
40+
case authenticationSecret = "auth"
41+
}
42+
43+
/// The public key a shared secret can be derived from for message encryption.
44+
///
45+
/// - SeeAlso: [Push API Working Draft §8.1. `PushEncryptionKeyName` enumeration — `p256dh`](https://www.w3.org/TR/push-api/#dom-pushencryptionkeyname-p256dh)
46+
public var publicKey: String
47+
48+
/// The authentication secret to validate our ability to send a subscriber push messages.
49+
///
50+
/// - SeeAlso: [Push API Working Draft §8.1. `PushEncryptionKeyName` enumeration — `auth`](https://www.w3.org/TR/push-api/#dom-pushencryptionkeyname-auth)
51+
public var authenticationSecret: String
52+
53+
/// Initialize key material with a public key and authentication secret from a user agent.
54+
///
55+
/// - Parameters:
56+
/// - publicKey: The public key a shared secret can be derived from for message encryption.
57+
/// - authenticationSecret: The authentication secret to validate our ability to send a subscriber push messages.
58+
public init(
59+
publicKey: String,
60+
authenticationSecret: String
61+
) {
62+
self.publicKey = publicKey
63+
self.authenticationSecret = authenticationSecret
64+
}
65+
}
66+
67+
/// A default subscriber implementation that can be used to decode subscriptions encoded by client-side JavaScript directly.
68+
///
69+
/// Note that this object requires the VAPID key (`applicationServerKey` in JavaScript) that was supplied during registration, which is not provided by default by [`PushSubscription.toJSON()`](https://www.w3.org/TR/push-api/#dom-pushsubscription-tojson):
70+
/// ```js
71+
/// const subscriptionStatusResponse = await fetch(`/registerSubscription`, {
72+
/// method: "POST",
73+
/// body: {
74+
/// ...subscription.toJSON(),
75+
/// applicationServerKey: subscription.options.applicationServerKey,
76+
/// }
77+
/// });
78+
/// ```
79+
///
80+
/// If you cannot provide this for whatever reason, opt to decode the object using your own type, and conform to ``SubscriberProtocol`` instead.
81+
public struct Subscriber: SubscriberProtocol, Codable, Hashable, Sendable {
82+
/// The encoded representation of a subscriber.
83+
///
84+
/// - Note: The VAPID Key ID must be manually added to the structure supplied by the spec.
85+
/// - SeeAlso: [Push API Working Draft §8. `PushSubscription` interface](https://www.w3.org/TR/push-api/#pushsubscription-interface).
86+
enum CodingKeys: String, CodingKey {
87+
case endpoint = "endpoint"
88+
case userAgentKeyMaterial = "keys"
89+
case vapidKeyID = "applicationServerKey"
90+
}
91+
92+
/// The push endpoint associated with the push subscription.
93+
///
94+
/// - SeeAlso: [Push API Working Draft §8. `PushSubscription` interface — `endpoint`](https://www.w3.org/TR/push-api/#dfn-getting-the-endpoint-attribute)
95+
public var endpoint: URL
96+
97+
/// The key material provided by the user agent to encrupt push data with.
98+
///
99+
/// - SeeAlso: [Push API Working Draft §8. `PushSubscription` interface — `getKey`](https://www.w3.org/TR/push-api/#dom-pushsubscription-getkey)
100+
public var userAgentKeyMaterial: UserAgentKeyMaterial
101+
102+
/// The VAPID Key ID used to register the subscription, that identifies the application server with the push service.
103+
///
104+
/// - SeeAlso: [Push API Working Draft §8. `PushSubscription` interface — `options`](https://www.w3.org/TR/push-api/#dom-pushsubscription-options)
105+
public var vapidKeyID: VAPID.Key.ID
106+
107+
/// Initialize a new subscriber manually.
108+
///
109+
/// Prefer decoding a subscription directly with the results of the subscription directly:
110+
/// ```js
111+
/// const subscriptionStatusResponse = await fetch(`/registerSubscription`, {
112+
/// method: "POST",
113+
/// body: {
114+
/// ...subscription.toJSON(),
115+
/// applicationServerKey: subscription.options.applicationServerKey,
116+
/// }
117+
/// });
118+
/// ```
119+
public init(
120+
endpoint: URL,
121+
userAgentKeyMaterial: UserAgentKeyMaterial,
122+
vapidKeyID: VAPID.Key.ID
123+
) {
124+
self.endpoint = endpoint
125+
self.userAgentKeyMaterial = userAgentKeyMaterial
126+
self.vapidKeyID = vapidKeyID
127+
}
128+
}
129+
130+
extension Subscriber: Identifiable {
131+
public var id: String { endpoint.absoluteString }
132+
}

Sources/WebPush/VAPID/VAPIDKey.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
import Foundation
1111

1212
extension VoluntaryApplicationServerIdentification {
13+
/// Represents the application server's identification key that is used to confirm to a push service that the server connecting to it is the same one that was subscribed to.
14+
///
15+
/// When sharing with the browser, ``VoluntaryApplicationServerIdentification/Key/ID`` can be used.
1316
public struct Key: Sendable {
1417
private var privateKey: P256.Signing.PrivateKey
1518

@@ -46,6 +49,11 @@ extension VAPID.Key: Codable {
4649
}
4750

4851
extension VAPID.Key: Identifiable {
52+
/// The identifier for a private ``VoluntaryApplicationServerIdentification/Key``'s public key.
53+
///
54+
/// This value can be shared as is with a subscription registration as the `applicationServerKey` key in JavaScript.
55+
///
56+
/// - SeeAlso: [Push API Working Draft §7.2. PushSubscriptionOptions Interface](https://www.w3.org/TR/push-api/#pushsubscriptionoptions-interface)
4957
public struct ID: Hashable, Comparable, Codable, Sendable, CustomStringConvertible {
5058
private var rawValue: String
5159

0 commit comments

Comments
 (0)