Skip to content

Commit f76f607

Browse files
authored
Add support for widget update push notifications (#220)
1 parent a977df5 commit f76f607

File tree

4 files changed

+196
-0
lines changed

4 files changed

+196
-0
lines changed

Sources/APNSCore/APNSPushType.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public struct APNSPushType: Hashable, Sendable, CustomStringConvertible {
2525
case mdm
2626
case liveactivity
2727
case pushtotalk
28+
case widgets
2829
}
2930

3031
public var description: String {
@@ -108,4 +109,10 @@ public struct APNSPushType: Hashable, Sendable, CustomStringConvertible {
108109
/// - Important: If you set this push type, the topic must use your app’s bundle ID with `.voip-ptt` appended to the end.
109110
///
110111
public static let pushtotalk = Self(configuration: .pushtotalk)
112+
113+
/// Use the widgets push type for notifications that trigger widget updates.
114+
///
115+
/// - Important: if you set this push type, the topic must use your app’s bundle ID with `.push-type.widgets` appended to the end.
116+
///
117+
public static let widgets = Self(configuration: .widgets)
111118
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the APNSwift open source project
4+
//
5+
// Copyright (c) 2025 the APNSwift project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of APNSwift project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
16+
extension APNSClientProtocol {
17+
/// Sends a widgets update notification to APNs.
18+
///
19+
/// - Parameters:
20+
/// - notification: The notification to send.
21+
///
22+
/// - deviceToken: The hexadecimal bytes that identify the user’s device. Your app receives the bytes for this device token
23+
/// when registering for remote notifications.
24+
///
25+
@discardableResult
26+
@inlinable
27+
public func sendWidgetsNotification(
28+
notification: APNSWidgetsNotification,
29+
deviceToken: String
30+
) async throws -> APNSResponse {
31+
let request = APNSRequest(
32+
message: notification,
33+
deviceToken: deviceToken,
34+
pushType: .widgets,
35+
expiration: notification.expiration,
36+
priority: notification.priority,
37+
apnsID: notification.apnsID,
38+
topic: notification.topic,
39+
collapseID: nil
40+
)
41+
return try await send(request)
42+
}
43+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the APNSwift open source project
4+
//
5+
// Copyright (c) 2022 the APNSwift project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of APNSwift project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import struct Foundation.UUID
16+
17+
/// A widget update notification.
18+
public struct APNSWidgetsNotification: APNSMessage {
19+
@usableFromInline
20+
struct APS: Encodable, Sendable {
21+
enum CodingKeys: String, CodingKey {
22+
case contentAvailable = "content-changed"
23+
}
24+
25+
let contentAvailable: Bool = true
26+
}
27+
28+
@usableFromInline
29+
enum CodingKeys: CodingKey {
30+
case aps
31+
}
32+
33+
/// The fixed content to indicate that this is a background notification.
34+
@usableFromInline
35+
internal let aps = APS()
36+
37+
/// A canonical UUID that identifies the notification. If there is an error sending the notification,
38+
/// APNs uses this value to identify the notification to your server. The canonical form is 32 lowercase hexadecimal digits,
39+
/// displayed in five groups separated by hyphens in the form 8-4-4-4-12. An example UUID is as follows:
40+
/// `123e4567-e89b-12d3-a456-42665544000`.
41+
///
42+
/// If you omit this, a new UUID is created by APNs and returned in the response.
43+
public var apnsID: UUID?
44+
45+
/// The topic for the notification. In general, the topic is your app’s bundle ID/app ID suffixed with `.push-type.widgets`.
46+
public var topic: String
47+
48+
/// The date when the notification is no longer valid and can be discarded. If this value is not `none`,
49+
/// APNs stores the notification and tries to deliver it at least once,
50+
/// repeating the attempt as needed if it is unable to deliver the notification the first time.
51+
/// If the value is `immediately`, APNs treats the notification as if it expires immediately
52+
/// and does not store the notification or attempt to redeliver it.
53+
public var expiration: APNSNotificationExpiration
54+
55+
/// The priority of the notification.
56+
public var priority: APNSPriority
57+
58+
/// Initializes a new ``APNSWidgetsNotification``.
59+
///
60+
/// - Parameters:
61+
/// - expiration: The date when the notification is no longer valid and can be discarded.
62+
/// - priority: The priority of the notification.
63+
/// - appID: Your app’s bundle ID/app ID. This will be suffixed with `.push-type.widgets`.
64+
/// - apnsID: A canonical UUID that identifies the notification.
65+
@inlinable
66+
public init(
67+
expiration: APNSNotificationExpiration,
68+
priority: APNSPriority,
69+
appID: String,
70+
apnsID: UUID? = nil
71+
) {
72+
self.init(
73+
expiration: expiration,
74+
priority: priority,
75+
topic: appID + ".push-type.widgets",
76+
apnsID: apnsID
77+
)
78+
}
79+
80+
/// Initializes a new ``APNSWidgetsNotification``.
81+
///
82+
/// - Parameters:
83+
/// - expiration: The date when the notification is no longer valid and can be discarded.
84+
/// - priority: The priority of the notification.
85+
/// - topic: The topic for the notification. In general, the topic is your app’s bundle ID/app ID suffixed with `.push-type.widgets`.
86+
/// - apnsID: A canonical UUID that identifies the notification.
87+
@inlinable
88+
public init(
89+
expiration: APNSNotificationExpiration,
90+
priority: APNSPriority,
91+
topic: String,
92+
apnsID: UUID? = nil
93+
) {
94+
self.expiration = expiration
95+
self.priority = priority
96+
self.topic = topic
97+
self.apnsID = apnsID
98+
}
99+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the APNSwift open source project
4+
//
5+
// Copyright (c) 2022 the APNSwift project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of APNSwift project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import APNSCore
16+
import XCTest
17+
18+
final class APNSWidgetsNotificationTests: XCTestCase {
19+
func testAppID() {
20+
let widgetsNotification = APNSWidgetsNotification(
21+
expiration: .none,
22+
priority: .immediately,
23+
appID: "com.example.app"
24+
)
25+
26+
XCTAssertEqual(widgetsNotification.topic, "com.example.app.push-type.widgets")
27+
}
28+
29+
func testEncode() throws {
30+
let widgetsNotification = APNSWidgetsNotification(
31+
expiration: .none,
32+
priority: .immediately,
33+
appID: "com.example.app"
34+
)
35+
36+
let encoder = JSONEncoder()
37+
let data = try encoder.encode(widgetsNotification)
38+
39+
let expectedJSONString = """
40+
{"aps":{"content-changed":true}}
41+
"""
42+
let jsonObject1 = try JSONSerialization.jsonObject(with: data) as! NSDictionary
43+
let jsonObject2 = try JSONSerialization.jsonObject(with: expectedJSONString.data(using: .utf8)!) as! NSDictionary
44+
XCTAssertEqual(jsonObject1, jsonObject2)
45+
}
46+
47+
}

0 commit comments

Comments
 (0)