Skip to content

Commit 12878da

Browse files
authored
Fix payload encoding for VoIP, PushToTalk, Complication, FileProvider, and Location notifications (#245)
1 parent 81eb92b commit 12878da

10 files changed

Lines changed: 314 additions & 4 deletions

Sources/APNSCore/Complication/APNSComplicationNotification.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ public struct APNSComplicationNotification<Payload: Encodable & Sendable>: APNSM
9494
self.payload = payload
9595
self.apnsID = apnsID
9696
}
97+
98+
@inlinable
99+
public func encode(to encoder: Encoder) throws {
100+
try self.payload.encode(to: encoder)
101+
}
97102
}
98103

99104
extension APNSComplicationNotification where Payload == EmptyPayload {

Sources/APNSCore/FileProvider/APNSFileProviderNotification.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ public struct APNSFileProviderNotification<Payload: Encodable & Sendable>: APNSM
8585
self.payload = payload
8686
self.apnsID = apnsID
8787
}
88+
89+
@inlinable
90+
public func encode(to encoder: Encoder) throws {
91+
try self.payload.encode(to: encoder)
92+
}
8893
}
8994

9095
extension APNSFileProviderNotification where Payload == EmptyPayload {

Sources/APNSCore/Location/APNSLocationNotification.swift

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ public struct APNSLocationNotification: APNSMessage {
3636

3737
/// Initializes a new ``APNSLocationNotification``.
3838
///
39-
/// - Important: Your dynamic payload will get encoded to the root of the JSON payload that is send to APNs.
40-
/// It is **important** that you do not encode anything with the key `aps`
39+
/// - Important: This notification sends a fixed, minimal body containing only an empty `aps` object;
40+
/// it has no dynamic payload.
4141
///
4242
/// - Parameters:
4343
/// - priority: The priority of the notification.
@@ -58,8 +58,8 @@ public struct APNSLocationNotification: APNSMessage {
5858

5959
/// Initializes a new ``APNSLocationNotification``.
6060
///
61-
/// - Important: Your dynamic payload will get encoded to the root of the JSON payload that is send to APNs.
62-
/// It is **important** that you do not encode anything with the key `aps`
61+
/// - Important: This notification sends a fixed, minimal body containing only an empty `aps` object;
62+
/// it has no dynamic payload.
6363
///
6464
/// - Parameters:
6565
/// - priority: The priority of the notification.
@@ -75,4 +75,15 @@ public struct APNSLocationNotification: APNSMessage {
7575
self.topic = topic
7676
self.apnsID = apnsID
7777
}
78+
79+
internal enum CodingKeys: String, CodingKey {
80+
case aps
81+
}
82+
83+
internal struct EmptyAPS: Encodable {}
84+
85+
public func encode(to encoder: Encoder) throws {
86+
var container = encoder.container(keyedBy: CodingKeys.self)
87+
try container.encode(EmptyAPS(), forKey: .aps)
88+
}
7889
}

Sources/APNSCore/PushToTalk/APNSPushToTalkNotification.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ public struct APNSPushToTalkNotification<Payload: Encodable & Sendable>: APNSMes
9797
self.payload = payload
9898
self.apnsID = apnsID
9999
}
100+
101+
@inlinable
102+
public func encode(to encoder: Encoder) throws {
103+
try self.payload.encode(to: encoder)
104+
}
100105
}
101106

102107
extension APNSPushToTalkNotification where Payload == EmptyPayload {

Sources/APNSCore/VoIP/APNSVoIPNotification.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ public struct APNSVoIPNotification<Payload: Encodable & Sendable>: APNSMessage {
9797
self.payload = payload
9898
self.apnsID = apnsID
9999
}
100+
101+
@inlinable
102+
public func encode(to encoder: Encoder) throws {
103+
try self.payload.encode(to: encoder)
104+
}
100105
}
101106

102107
extension APNSVoIPNotification where Payload == EmptyPayload {
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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 APNSComplicationNotificationEncodingTests: XCTestCase {
19+
func testEncode() throws {
20+
struct Payload: Encodable {
21+
let foo = "bar"
22+
}
23+
let notification = APNSComplicationNotification(
24+
expiration: .immediately,
25+
priority: .immediately,
26+
topic: "com.example.app.complication",
27+
payload: Payload(),
28+
apnsID: nil
29+
)
30+
31+
let encoder = JSONEncoder()
32+
let data = try encoder.encode(notification)
33+
34+
let expectedJSONString = """
35+
{"foo":"bar"}
36+
"""
37+
let jsonObject1 = try JSONSerialization.jsonObject(with: data) as! NSDictionary
38+
let jsonObject2 = try JSONSerialization.jsonObject(with: expectedJSONString.data(using: .utf8)!) as! NSDictionary
39+
XCTAssertEqual(jsonObject1, jsonObject2)
40+
}
41+
42+
func testEncode_whenEmptyPayload() throws {
43+
let notification = APNSComplicationNotification(
44+
expiration: .immediately,
45+
priority: .immediately,
46+
appID: "com.example.app"
47+
)
48+
49+
let encoder = JSONEncoder()
50+
let data = try encoder.encode(notification)
51+
52+
let expectedJSONString = """
53+
{}
54+
"""
55+
let jsonObject1 = try JSONSerialization.jsonObject(with: data) as! NSDictionary
56+
let jsonObject2 = try JSONSerialization.jsonObject(with: expectedJSONString.data(using: .utf8)!) as! NSDictionary
57+
XCTAssertEqual(jsonObject1, jsonObject2)
58+
}
59+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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 APNSFileProviderNotificationEncodingTests: XCTestCase {
19+
func testEncode() throws {
20+
struct Payload: Encodable {
21+
let foo = "bar"
22+
}
23+
let notification = APNSFileProviderNotification(
24+
expiration: .immediately,
25+
topic: "com.example.app.pushkit.fileprovider",
26+
payload: Payload(),
27+
apnsID: nil
28+
)
29+
30+
let encoder = JSONEncoder()
31+
let data = try encoder.encode(notification)
32+
33+
let expectedJSONString = """
34+
{"foo":"bar"}
35+
"""
36+
let jsonObject1 = try JSONSerialization.jsonObject(with: data) as! NSDictionary
37+
let jsonObject2 = try JSONSerialization.jsonObject(with: expectedJSONString.data(using: .utf8)!) as! NSDictionary
38+
XCTAssertEqual(jsonObject1, jsonObject2)
39+
}
40+
41+
func testEncode_whenEmptyPayload() throws {
42+
let notification = APNSFileProviderNotification(
43+
expiration: .immediately,
44+
appID: "com.example.app"
45+
)
46+
47+
let encoder = JSONEncoder()
48+
let data = try encoder.encode(notification)
49+
50+
let expectedJSONString = """
51+
{}
52+
"""
53+
let jsonObject1 = try JSONSerialization.jsonObject(with: data) as! NSDictionary
54+
let jsonObject2 = try JSONSerialization.jsonObject(with: expectedJSONString.data(using: .utf8)!) as! NSDictionary
55+
XCTAssertEqual(jsonObject1, jsonObject2)
56+
}
57+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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 APNSLocationNotificationTests: XCTestCase {
19+
func testAppID() {
20+
let locationNotification = APNSLocationNotification(
21+
priority: .immediately,
22+
appID: "com.example.app"
23+
)
24+
25+
XCTAssertEqual(locationNotification.topic, "com.example.app.location-query")
26+
}
27+
28+
func testEncode() throws {
29+
let notification = APNSLocationNotification(
30+
priority: .immediately,
31+
topic: "com.example.app.location-query",
32+
apnsID: nil
33+
)
34+
35+
let encoder = JSONEncoder()
36+
let data = try encoder.encode(notification)
37+
38+
let expectedJSONString = """
39+
{"aps":{}}
40+
"""
41+
let jsonObject1 = try JSONSerialization.jsonObject(with: data) as! NSDictionary
42+
let jsonObject2 = try JSONSerialization.jsonObject(with: expectedJSONString.data(using: .utf8)!) as! NSDictionary
43+
XCTAssertEqual(jsonObject1, jsonObject2)
44+
}
45+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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 APNSPushToTalkNotificationEncodingTests: XCTestCase {
19+
func testEncode() throws {
20+
struct Payload: Encodable {
21+
let foo = "bar"
22+
}
23+
let notification = APNSPushToTalkNotification(
24+
expiration: .immediately,
25+
priority: .immediately,
26+
topic: "com.example.app.voip-ptt",
27+
payload: Payload(),
28+
apnsID: nil
29+
)
30+
31+
let encoder = JSONEncoder()
32+
let data = try encoder.encode(notification)
33+
34+
let expectedJSONString = """
35+
{"foo":"bar"}
36+
"""
37+
let jsonObject1 = try JSONSerialization.jsonObject(with: data) as! NSDictionary
38+
let jsonObject2 = try JSONSerialization.jsonObject(with: expectedJSONString.data(using: .utf8)!) as! NSDictionary
39+
XCTAssertEqual(jsonObject1, jsonObject2)
40+
}
41+
42+
func testEncode_whenEmptyPayload() throws {
43+
let notification = APNSPushToTalkNotification(
44+
expiration: .immediately,
45+
priority: .immediately,
46+
appID: "com.example.app"
47+
)
48+
49+
let encoder = JSONEncoder()
50+
let data = try encoder.encode(notification)
51+
52+
let expectedJSONString = """
53+
{}
54+
"""
55+
let jsonObject1 = try JSONSerialization.jsonObject(with: data) as! NSDictionary
56+
let jsonObject2 = try JSONSerialization.jsonObject(with: expectedJSONString.data(using: .utf8)!) as! NSDictionary
57+
XCTAssertEqual(jsonObject1, jsonObject2)
58+
}
59+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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 APNSVoIPNotificationEncodingTests: XCTestCase {
19+
func testEncode() throws {
20+
struct Payload: Encodable {
21+
let foo = "bar"
22+
}
23+
let notification = APNSVoIPNotification(
24+
expiration: .immediately,
25+
priority: .immediately,
26+
topic: "com.example.app.voip",
27+
payload: Payload(),
28+
apnsID: nil
29+
)
30+
31+
let encoder = JSONEncoder()
32+
let data = try encoder.encode(notification)
33+
34+
let expectedJSONString = """
35+
{"foo":"bar"}
36+
"""
37+
let jsonObject1 = try JSONSerialization.jsonObject(with: data) as! NSDictionary
38+
let jsonObject2 = try JSONSerialization.jsonObject(with: expectedJSONString.data(using: .utf8)!) as! NSDictionary
39+
XCTAssertEqual(jsonObject1, jsonObject2)
40+
}
41+
42+
func testEncode_whenEmptyPayload() throws {
43+
let notification = APNSVoIPNotification(
44+
expiration: .immediately,
45+
priority: .immediately,
46+
appID: "com.example.app"
47+
)
48+
49+
let encoder = JSONEncoder()
50+
let data = try encoder.encode(notification)
51+
52+
let expectedJSONString = """
53+
{}
54+
"""
55+
let jsonObject1 = try JSONSerialization.jsonObject(with: data) as! NSDictionary
56+
let jsonObject2 = try JSONSerialization.jsonObject(with: expectedJSONString.data(using: .utf8)!) as! NSDictionary
57+
XCTAssertEqual(jsonObject1, jsonObject2)
58+
}
59+
}

0 commit comments

Comments
 (0)