From 3540be5eb981327ce026aab74193d7a50318f6b1 Mon Sep 17 00:00:00 2001 From: Pat Nakajima Date: Wed, 3 Apr 2024 12:35:07 -0700 Subject: [PATCH 01/16] Add start case --- .../LiveActivity/APNSLiveActivityNotificationEvent.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift index a64b329a..612ea3f1 100644 --- a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift +++ b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift @@ -13,7 +13,6 @@ //===----------------------------------------------------------------------===// public struct APNSLiveActivityNotificationEvent: Hashable { - /// The underlying raw value that is send to APNs. @usableFromInline internal let rawValue: String @@ -23,4 +22,6 @@ public struct APNSLiveActivityNotificationEvent: Hashable { /// Specifies that live activity should be ended public static let end = Self(rawValue: "end") + + public static let start = Self(rawValue: "start") } From 0c2bb05b3cd78b0fb83fe7540760236536f50296 Mon Sep 17 00:00:00 2001 From: Pat Nakajima Date: Wed, 3 Apr 2024 14:20:11 -0700 Subject: [PATCH 02/16] Add ability to start live activities --- .../APNSLiveActivityNotification.swift | 33 ++++++++--- ...NSLiveActivityNotificationAPSStorage.swift | 10 +++- .../APNSLiveActivityNotificationEvent.swift | 59 +++++++++++++++---- .../APNSLiveActivityNotificationTests.swift | 24 +++++++- 4 files changed, 105 insertions(+), 21 deletions(-) diff --git a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift index 2f08a20a..b80b2cec 100644 --- a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift +++ b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift @@ -13,11 +13,12 @@ //===----------------------------------------------------------------------===// import struct Foundation.UUID +import struct Foundation.Data /// A live activity notification. /// /// It is **important** that you do not encode anything with the key `aps`. -public struct APNSLiveActivityNotification: APNSMessage { +public struct APNSLiveActivityNotification: APNSMessage { enum CodingKeys: CodingKey { case aps } @@ -37,16 +38,28 @@ public struct APNSLiveActivityNotification: APNSMessage } /// Event type e.g. update - public var event: APNSLiveActivityNotificationEvent { + public var event: any APNSLiveActivityNotificationEvent { get { - return APNSLiveActivityNotificationEvent(rawValue: self.aps.event) + switch self.aps.event { + case "end": + return APNSLiveActivityNotificationEventEnd() + case "update": + return APNSLiveActivityNotificationEventUpdate() + default: + guard let attributesType = self.aps.attributesType, let state = self.aps.attributesContent else { + // Default to update + return APNSLiveActivityNotificationEventUpdate() + } + + return APNSLiveActivityNotificationEventStart(attributes: .init(type: attributesType, state: state)) + } } set { self.aps.event = newValue.rawValue } } - + /// The dynamic content of a Live Activity. public var contentState: ContentState { get { @@ -57,7 +70,7 @@ public struct APNSLiveActivityNotification: APNSMessage self.aps.contentState = newValue } } - + public var dismissalDate: APNSLiveActivityDismissalDate? { get { return .init(dismissal: self.aps.dismissalDate) @@ -108,7 +121,7 @@ public struct APNSLiveActivityNotification: APNSMessage priority: APNSPriority, appID: String, contentState: ContentState, - event: APNSLiveActivityNotificationEvent, + event: any APNSLiveActivityNotificationEvent, timestamp: Int, dismissalDate: APNSLiveActivityDismissalDate = .none, apnsID: UUID? = nil @@ -146,13 +159,19 @@ public struct APNSLiveActivityNotification: APNSMessage topic: String, apnsID: UUID? = nil, contentState: ContentState, - event: APNSLiveActivityNotificationEvent, + event: any APNSLiveActivityNotificationEvent, timestamp: Int, dismissalDate: APNSLiveActivityDismissalDate = .none ) { + var attributes: APNSLiveActivityNotificationEventStart.Attributes? + if let event = event as? APNSLiveActivityNotificationEventStart { + attributes = event.attributes + } + self.aps = APNSLiveActivityNotificationAPSStorage( timestamp: timestamp, event: event.rawValue, + attributes: attributes, contentState: contentState, dismissalDate: dismissalDate.dismissal ) diff --git a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationAPSStorage.swift b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationAPSStorage.swift index 8a975e96..188ac4be 100644 --- a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationAPSStorage.swift +++ b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationAPSStorage.swift @@ -11,28 +11,36 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// +import struct Foundation.Data -struct APNSLiveActivityNotificationAPSStorage: Encodable { +struct APNSLiveActivityNotificationAPSStorage: Encodable, Sendable, Hashable { enum CodingKeys: String, CodingKey { case timestamp = "timestamp" case event = "event" case contentState = "content-state" case dismissalDate = "dismissal-date" + case attributesType = "attributes-type" + case attributesContent = "attributes" } var timestamp: Int var event: String + var attributesType: String? + var attributesContent: ContentState? var contentState: ContentState var dismissalDate: Int? init( timestamp: Int, event: String, + attributes: APNSLiveActivityNotificationEventStart.Attributes?, contentState: ContentState, dismissalDate: Int? ) { self.timestamp = timestamp self.event = event + self.attributesType = attributes?.type + self.attributesContent = attributes?.state self.contentState = contentState self.dismissalDate = dismissalDate } diff --git a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift index 612ea3f1..0551783c 100644 --- a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift +++ b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift @@ -12,16 +12,51 @@ // //===----------------------------------------------------------------------===// -public struct APNSLiveActivityNotificationEvent: Hashable { - /// The underlying raw value that is send to APNs. - @usableFromInline - internal let rawValue: String - - /// Specifies that live activity should be updated - public static let update = Self(rawValue: "update") - - /// Specifies that live activity should be ended - public static let end = Self(rawValue: "end") - - public static let start = Self(rawValue: "start") +public protocol APNSLiveActivityNotificationEvent: Hashable, Encodable { + var rawValue: String { get } +} + +public struct APNSLiveActivityNotificationEventUpdate: APNSLiveActivityNotificationEvent { + public let rawValue = "update" +} + +public struct APNSLiveActivityNotificationEventEnd: APNSLiveActivityNotificationEvent { + public let rawValue = "end" +} + +public protocol APNSLiveActivityNotificationEventStartStateProtocol: Encodable & Hashable & Sendable { + associatedtype State: Encodable & Hashable & Sendable +} + +public struct APNSLiveActivityNotificationEventStart: APNSLiveActivityNotificationEvent, APNSLiveActivityNotificationEventStartStateProtocol { + public struct Attributes: Encodable, Hashable, Sendable { + public let type: String + public let state: State + + public init(type: String, state: State) { + self.type = type + self.state = state + } + } + + public let rawValue = "start" + public let attributes: Attributes + + public init(attributes: Attributes) { + self.attributes = attributes + } +} + +public extension APNSLiveActivityNotificationEvent where Self == APNSLiveActivityNotificationEventUpdate { + static var update: APNSLiveActivityNotificationEventUpdate { APNSLiveActivityNotificationEventUpdate() } +} + +public extension APNSLiveActivityNotificationEvent where Self == APNSLiveActivityNotificationEventEnd { + static var end: APNSLiveActivityNotificationEventEnd { APNSLiveActivityNotificationEventEnd() } +} + +public extension APNSLiveActivityNotificationEvent where Self: APNSLiveActivityNotificationEventStartStateProtocol { + static func start(type: String, state: State) -> APNSLiveActivityNotificationEventStart { + .init(attributes: .init(type: type, state: state)) + } } diff --git a/Tests/APNSTests/LiveActivity/APNSLiveActivityNotificationTests.swift b/Tests/APNSTests/LiveActivity/APNSLiveActivityNotificationTests.swift index 01b5ac82..f617597a 100644 --- a/Tests/APNSTests/LiveActivity/APNSLiveActivityNotificationTests.swift +++ b/Tests/APNSTests/LiveActivity/APNSLiveActivityNotificationTests.swift @@ -42,7 +42,29 @@ final class APNSLiveActivityNotificationTests: XCTestCase { let jsonObject2 = try JSONSerialization.jsonObject(with: expectedJSONString.data(using: .utf8)!) as! NSDictionary XCTAssertEqual(jsonObject1, jsonObject2) } - + + func testEncodeStart() throws { + let notification = APNSLiveActivityNotification( + expiration: .immediately, + priority: .immediately, + appID: "test.app.id", + contentState: State(), + // Need the fully qualified name here + event: APNSLiveActivityNotificationEventStart(attributes: .init(type: "State", state: State())), + timestamp: 1672680658) + + let encoder = JSONEncoder() + let data = try encoder.encode(notification) + + let expectedJSONString = """ + {"aps":{"event":"start", "attributes-type": "State", "attributes": {"string":"Test","number":123},"content-state":{"string":"Test","number":123},"timestamp":1672680658}} + """ + + let jsonObject1 = try JSONSerialization.jsonObject(with: data) as! NSDictionary + let jsonObject2 = try JSONSerialization.jsonObject(with: expectedJSONString.data(using: .utf8)!) as! NSDictionary + XCTAssertEqual(jsonObject1, jsonObject2) + } + func testEncodeEndNoDismiss() throws { let notification = APNSLiveActivityNotification( expiration: .immediately, From d39bd0b994d27f31ff12876cfb15018b2217cc31 Mon Sep 17 00:00:00 2001 From: Pat Nakajima Date: Wed, 3 Apr 2024 14:22:35 -0700 Subject: [PATCH 03/16] Run swift-format --- .../APNSLiveActivityNotification.swift | 56 +++++++------ ...NSLiveActivityNotificationAPSStorage.swift | 20 ++--- .../APNSLiveActivityNotificationEvent.swift | 64 ++++++++------- .../APNSLiveActivityNotificationTests.swift | 81 ++++++++++--------- 4 files changed, 123 insertions(+), 98 deletions(-) diff --git a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift index b80b2cec..5a232700 100644 --- a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift +++ b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift @@ -12,13 +12,15 @@ // //===----------------------------------------------------------------------===// -import struct Foundation.UUID import struct Foundation.Data +import struct Foundation.UUID /// A live activity notification. /// /// It is **important** that you do not encode anything with the key `aps`. -public struct APNSLiveActivityNotification: APNSMessage { +public struct APNSLiveActivityNotification: + APNSMessage +{ enum CodingKeys: CodingKey { case aps } @@ -31,30 +33,33 @@ public struct APNSLiveActivityNotification.Attributes? - if let event = event as? APNSLiveActivityNotificationEventStart { - attributes = event.attributes - } + var attributes: APNSLiveActivityNotificationEventStart.Attributes? + if let event = event as? APNSLiveActivityNotificationEventStart { + attributes = event.attributes + } self.aps = APNSLiveActivityNotificationAPSStorage( timestamp: timestamp, event: event.rawValue, - attributes: attributes, + attributes: attributes, contentState: contentState, dismissalDate: dismissalDate.dismissal ) diff --git a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationAPSStorage.swift b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationAPSStorage.swift index 188ac4be..15c8a93a 100644 --- a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationAPSStorage.swift +++ b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationAPSStorage.swift @@ -13,34 +13,36 @@ //===----------------------------------------------------------------------===// import struct Foundation.Data -struct APNSLiveActivityNotificationAPSStorage: Encodable, Sendable, Hashable { +struct APNSLiveActivityNotificationAPSStorage: + Encodable, Sendable, Hashable +{ enum CodingKeys: String, CodingKey { case timestamp = "timestamp" case event = "event" case contentState = "content-state" case dismissalDate = "dismissal-date" - case attributesType = "attributes-type" - case attributesContent = "attributes" + case attributesType = "attributes-type" + case attributesContent = "attributes" } var timestamp: Int var event: String - var attributesType: String? - var attributesContent: ContentState? + var attributesType: String? + var attributesContent: ContentState? var contentState: ContentState var dismissalDate: Int? - + init( timestamp: Int, event: String, - attributes: APNSLiveActivityNotificationEventStart.Attributes?, + attributes: APNSLiveActivityNotificationEventStart.Attributes?, contentState: ContentState, dismissalDate: Int? ) { self.timestamp = timestamp self.event = event - self.attributesType = attributes?.type - self.attributesContent = attributes?.state + self.attributesType = attributes?.type + self.attributesContent = attributes?.state self.contentState = contentState self.dismissalDate = dismissalDate } diff --git a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift index 0551783c..b0331b4b 100644 --- a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift +++ b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift @@ -13,50 +13,60 @@ //===----------------------------------------------------------------------===// public protocol APNSLiveActivityNotificationEvent: Hashable, Encodable { - var rawValue: String { get } + var rawValue: String { get } } public struct APNSLiveActivityNotificationEventUpdate: APNSLiveActivityNotificationEvent { - public let rawValue = "update" + public let rawValue = "update" } public struct APNSLiveActivityNotificationEventEnd: APNSLiveActivityNotificationEvent { - public let rawValue = "end" + public let rawValue = "end" } -public protocol APNSLiveActivityNotificationEventStartStateProtocol: Encodable & Hashable & Sendable { - associatedtype State: Encodable & Hashable & Sendable +public protocol APNSLiveActivityNotificationEventStartStateProtocol: Encodable & Hashable & Sendable +{ + associatedtype State: Encodable & Hashable & Sendable } -public struct APNSLiveActivityNotificationEventStart: APNSLiveActivityNotificationEvent, APNSLiveActivityNotificationEventStartStateProtocol { - public struct Attributes: Encodable, Hashable, Sendable { - public let type: String - public let state: State +public struct APNSLiveActivityNotificationEventStart: + APNSLiveActivityNotificationEvent, APNSLiveActivityNotificationEventStartStateProtocol +{ + public struct Attributes: Encodable, Hashable, Sendable { + public let type: String + public let state: State - public init(type: String, state: State) { - self.type = type - self.state = state - } - } + public init(type: String, state: State) { + self.type = type + self.state = state + } + } - public let rawValue = "start" - public let attributes: Attributes + public let rawValue = "start" + public let attributes: Attributes - public init(attributes: Attributes) { - self.attributes = attributes - } + public init(attributes: Attributes) { + self.attributes = attributes + } } -public extension APNSLiveActivityNotificationEvent where Self == APNSLiveActivityNotificationEventUpdate { - static var update: APNSLiveActivityNotificationEventUpdate { APNSLiveActivityNotificationEventUpdate() } +extension APNSLiveActivityNotificationEvent where Self == APNSLiveActivityNotificationEventUpdate { + public static var update: APNSLiveActivityNotificationEventUpdate { + APNSLiveActivityNotificationEventUpdate() + } } -public extension APNSLiveActivityNotificationEvent where Self == APNSLiveActivityNotificationEventEnd { - static var end: APNSLiveActivityNotificationEventEnd { APNSLiveActivityNotificationEventEnd() } +extension APNSLiveActivityNotificationEvent where Self == APNSLiveActivityNotificationEventEnd { + public static var end: APNSLiveActivityNotificationEventEnd { + APNSLiveActivityNotificationEventEnd() + } } -public extension APNSLiveActivityNotificationEvent where Self: APNSLiveActivityNotificationEventStartStateProtocol { - static func start(type: String, state: State) -> APNSLiveActivityNotificationEventStart { - .init(attributes: .init(type: type, state: state)) - } +extension APNSLiveActivityNotificationEvent +where Self: APNSLiveActivityNotificationEventStartStateProtocol { + public static func start(type: String, state: State) -> APNSLiveActivityNotificationEventStart< + State + > { + .init(attributes: .init(type: type, state: state)) + } } diff --git a/Tests/APNSTests/LiveActivity/APNSLiveActivityNotificationTests.swift b/Tests/APNSTests/LiveActivity/APNSLiveActivityNotificationTests.swift index f617597a..28e63b69 100644 --- a/Tests/APNSTests/LiveActivity/APNSLiveActivityNotificationTests.swift +++ b/Tests/APNSTests/LiveActivity/APNSLiveActivityNotificationTests.swift @@ -21,7 +21,7 @@ final class APNSLiveActivityNotificationTests: XCTestCase { let string: String = "Test" let number: Int = 123 } - + func testEncodeUpdate() throws { let notification = APNSLiveActivityNotification( expiration: .immediately, @@ -29,41 +29,46 @@ final class APNSLiveActivityNotificationTests: XCTestCase { appID: "test.app.id", contentState: State(), event: .update, - timestamp: 1672680658) - + timestamp: 1_672_680_658) + let encoder = JSONEncoder() let data = try encoder.encode(notification) let expectedJSONString = """ - {"aps":{"event":"update","content-state":{"string":"Test","number":123},"timestamp":1672680658}} - """ + {"aps":{"event":"update","content-state":{"string":"Test","number":123},"timestamp":1672680658}} + """ let jsonObject1 = try JSONSerialization.jsonObject(with: data) as! NSDictionary - let jsonObject2 = try JSONSerialization.jsonObject(with: expectedJSONString.data(using: .utf8)!) as! NSDictionary + let jsonObject2 = + try JSONSerialization.jsonObject(with: expectedJSONString.data(using: .utf8)!) + as! NSDictionary XCTAssertEqual(jsonObject1, jsonObject2) } - func testEncodeStart() throws { - let notification = APNSLiveActivityNotification( - expiration: .immediately, - priority: .immediately, - appID: "test.app.id", - contentState: State(), - // Need the fully qualified name here - event: APNSLiveActivityNotificationEventStart(attributes: .init(type: "State", state: State())), - timestamp: 1672680658) + func testEncodeStart() throws { + let notification = APNSLiveActivityNotification( + expiration: .immediately, + priority: .immediately, + appID: "test.app.id", + contentState: State(), + // Need the fully qualified name here + event: APNSLiveActivityNotificationEventStart( + attributes: .init(type: "State", state: State())), + timestamp: 1_672_680_658) - let encoder = JSONEncoder() - let data = try encoder.encode(notification) + let encoder = JSONEncoder() + let data = try encoder.encode(notification) - let expectedJSONString = """ - {"aps":{"event":"start", "attributes-type": "State", "attributes": {"string":"Test","number":123},"content-state":{"string":"Test","number":123},"timestamp":1672680658}} - """ + let expectedJSONString = """ + {"aps":{"event":"start", "attributes-type": "State", "attributes": {"string":"Test","number":123},"content-state":{"string":"Test","number":123},"timestamp":1672680658}} + """ - let jsonObject1 = try JSONSerialization.jsonObject(with: data) as! NSDictionary - let jsonObject2 = try JSONSerialization.jsonObject(with: expectedJSONString.data(using: .utf8)!) as! NSDictionary - XCTAssertEqual(jsonObject1, jsonObject2) - } + let jsonObject1 = try JSONSerialization.jsonObject(with: data) as! NSDictionary + let jsonObject2 = + try JSONSerialization.jsonObject(with: expectedJSONString.data(using: .utf8)!) + as! NSDictionary + XCTAssertEqual(jsonObject1, jsonObject2) + } func testEncodeEndNoDismiss() throws { let notification = APNSLiveActivityNotification( @@ -72,17 +77,19 @@ final class APNSLiveActivityNotificationTests: XCTestCase { appID: "test.app.id", contentState: State(), event: .end, - timestamp: 1672680658) - + timestamp: 1_672_680_658) + let encoder = JSONEncoder() let data = try encoder.encode(notification) let expectedJSONString = """ - {"aps":{"event":"end","content-state":{"string":"Test","number":123},"timestamp":1672680658}} - """ + {"aps":{"event":"end","content-state":{"string":"Test","number":123},"timestamp":1672680658}} + """ let jsonObject1 = try JSONSerialization.jsonObject(with: data) as! NSDictionary - let jsonObject2 = try JSONSerialization.jsonObject(with: expectedJSONString.data(using: .utf8)!) as! NSDictionary + let jsonObject2 = + try JSONSerialization.jsonObject(with: expectedJSONString.data(using: .utf8)!) + as! NSDictionary XCTAssertEqual(jsonObject1, jsonObject2) } @@ -93,19 +100,21 @@ final class APNSLiveActivityNotificationTests: XCTestCase { appID: "test.app.id", contentState: State(), event: .end, - timestamp: 1672680658, - dismissalDate: .timeIntervalSince1970InSeconds(1672680800)) - + timestamp: 1_672_680_658, + dismissalDate: .timeIntervalSince1970InSeconds(1_672_680_800)) + let encoder = JSONEncoder() let data = try encoder.encode(notification) let expectedJSONString = """ - {"aps":{"event":"end","content-state":{"string":"Test","number":123},"timestamp":1672680658, - "dismissal-date":1672680800}} - """ + {"aps":{"event":"end","content-state":{"string":"Test","number":123},"timestamp":1672680658, + "dismissal-date":1672680800}} + """ let jsonObject1 = try JSONSerialization.jsonObject(with: data) as! NSDictionary - let jsonObject2 = try JSONSerialization.jsonObject(with: expectedJSONString.data(using: .utf8)!) as! NSDictionary + let jsonObject2 = + try JSONSerialization.jsonObject(with: expectedJSONString.data(using: .utf8)!) + as! NSDictionary XCTAssertEqual(jsonObject1, jsonObject2) } } From e20b90a6f696102b6bc240c7b3bd6f2a679bf89d Mon Sep 17 00:00:00 2001 From: Pat Nakajima Date: Wed, 3 Apr 2024 14:24:24 -0700 Subject: [PATCH 04/16] Remove unnecessary imports --- Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift | 1 - .../LiveActivity/APNSLiveActivityNotificationAPSStorage.swift | 1 - 2 files changed, 2 deletions(-) diff --git a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift index 5a232700..345cf07e 100644 --- a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift +++ b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift @@ -12,7 +12,6 @@ // //===----------------------------------------------------------------------===// -import struct Foundation.Data import struct Foundation.UUID /// A live activity notification. diff --git a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationAPSStorage.swift b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationAPSStorage.swift index 15c8a93a..295fb962 100644 --- a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationAPSStorage.swift +++ b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationAPSStorage.swift @@ -11,7 +11,6 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// -import struct Foundation.Data struct APNSLiveActivityNotificationAPSStorage: Encodable, Sendable, Hashable From 93565bda2238e6b4b1995bf553dd3a37981f1048 Mon Sep 17 00:00:00 2001 From: Pat Nakajima Date: Wed, 3 Apr 2024 15:07:49 -0700 Subject: [PATCH 05/16] Add alert to live activity start notifications --- .../Alert/APNSAlertNotificationContent.swift | 2 +- .../APNSLiveActivityNotification.swift | 20 +++++++++++++++---- ...NSLiveActivityNotificationAPSStorage.swift | 15 +++++++++----- .../APNSLiveActivityNotificationEvent.swift | 14 ++++++++----- .../APNSLiveActivityNotificationTests.swift | 6 ++++-- 5 files changed, 40 insertions(+), 17 deletions(-) diff --git a/Sources/APNSCore/Alert/APNSAlertNotificationContent.swift b/Sources/APNSCore/Alert/APNSAlertNotificationContent.swift index ab0f81e1..062d4fc4 100644 --- a/Sources/APNSCore/Alert/APNSAlertNotificationContent.swift +++ b/Sources/APNSCore/Alert/APNSAlertNotificationContent.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// /// The information for displaying an alert. -public struct APNSAlertNotificationContent: Encodable, Sendable { +public struct APNSAlertNotificationContent: Encodable, Sendable, Hashable { public struct StringValue: Encodable, Hashable, Sendable { internal enum Configuration: Encodable, Hashable { case raw(String) diff --git a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift index 345cf07e..4acf021a 100644 --- a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift +++ b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift @@ -38,6 +38,16 @@ public struct APNSLiveActivityNotification.Attributes?, + event: any APNSLiveActivityNotificationEvent, contentState: ContentState, dismissalDate: Int? ) { self.timestamp = timestamp - self.event = event - self.attributesType = attributes?.type - self.attributesContent = attributes?.state self.contentState = contentState self.dismissalDate = dismissalDate + self.event = event.rawValue + + if let event = event as? APNSLiveActivityNotificationEventStart { + self.attributesType = event.attributes.type + self.attributesContent = event.attributes.state + self.alert = event.alert + } } } diff --git a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift index b0331b4b..fa792360 100644 --- a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift +++ b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift @@ -44,9 +44,11 @@ public struct APNSLiveActivityNotificationEventStart APNSLiveActivityNotificationEventStart< - State - > { - .init(attributes: .init(type: type, state: state)) + public static func start(type: String, state: State, alert: APNSAlertNotificationContent) + -> APNSLiveActivityNotificationEventStart< + State + > + { + .init(attributes: .init(type: type, state: state), alert: alert) } } diff --git a/Tests/APNSTests/LiveActivity/APNSLiveActivityNotificationTests.swift b/Tests/APNSTests/LiveActivity/APNSLiveActivityNotificationTests.swift index 28e63b69..1df74ea0 100644 --- a/Tests/APNSTests/LiveActivity/APNSLiveActivityNotificationTests.swift +++ b/Tests/APNSTests/LiveActivity/APNSLiveActivityNotificationTests.swift @@ -53,14 +53,16 @@ final class APNSLiveActivityNotificationTests: XCTestCase { contentState: State(), // Need the fully qualified name here event: APNSLiveActivityNotificationEventStart( - attributes: .init(type: "State", state: State())), + attributes: .init(type: "State", state: State()), + alert: .init(title: .raw("Update")) + ), timestamp: 1_672_680_658) let encoder = JSONEncoder() let data = try encoder.encode(notification) let expectedJSONString = """ - {"aps":{"event":"start", "attributes-type": "State", "attributes": {"string":"Test","number":123},"content-state":{"string":"Test","number":123},"timestamp":1672680658}} + {"aps":{"event":"start", "alert": { "title": "Update" }, "attributes-type": "State", "attributes": {"string":"Test","number":123},"content-state":{"string":"Test","number":123},"timestamp":1672680658}} """ let jsonObject1 = try JSONSerialization.jsonObject(with: data) as! NSDictionary From 5af6d92d57faddf3e9412303a8df81e383a87299 Mon Sep 17 00:00:00 2001 From: Pat Nakajima Date: Thu, 4 Apr 2024 09:08:47 -0700 Subject: [PATCH 06/16] Remove additional conformances --- .../LiveActivity/APNSLiveActivityNotification.swift | 4 ++-- .../APNSLiveActivityNotificationAPSStorage.swift | 4 +--- .../APNSLiveActivityNotificationEvent.swift | 10 +++++----- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift index 4acf021a..3ec0c584 100644 --- a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift +++ b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift @@ -17,7 +17,7 @@ import struct Foundation.UUID /// A live activity notification. /// /// It is **important** that you do not encode anything with the key `aps`. -public struct APNSLiveActivityNotification: +public struct APNSLiveActivityNotification: APNSMessage { enum CodingKeys: CodingKey { @@ -49,7 +49,7 @@ public struct APNSLiveActivityNotification: - Encodable, Sendable, Hashable -{ +struct APNSLiveActivityNotificationAPSStorage: Encodable { enum CodingKeys: String, CodingKey { case timestamp = "timestamp" case event = "event" diff --git a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift index fa792360..cf79caaf 100644 --- a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift +++ b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -public protocol APNSLiveActivityNotificationEvent: Hashable, Encodable { +public protocol APNSLiveActivityNotificationEvent: Encodable { var rawValue: String { get } } @@ -24,15 +24,15 @@ public struct APNSLiveActivityNotificationEventEnd: APNSLiveActivityNotification public let rawValue = "end" } -public protocol APNSLiveActivityNotificationEventStartStateProtocol: Encodable & Hashable & Sendable +public protocol APNSLiveActivityNotificationEventStartStateProtocol: Encodable { - associatedtype State: Encodable & Hashable & Sendable + associatedtype State: Encodable } -public struct APNSLiveActivityNotificationEventStart: +public struct APNSLiveActivityNotificationEventStart: APNSLiveActivityNotificationEvent, APNSLiveActivityNotificationEventStartStateProtocol { - public struct Attributes: Encodable, Hashable, Sendable { + public struct Attributes: Encodable { public let type: String public let state: State From fffc9eaa7f9eaa95892c2e6c52080773e08f43d8 Mon Sep 17 00:00:00 2001 From: Pat Nakajima Date: Thu, 4 Apr 2024 09:10:18 -0700 Subject: [PATCH 07/16] Remove another conformance --- Sources/APNSCore/Alert/APNSAlertNotificationContent.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/APNSCore/Alert/APNSAlertNotificationContent.swift b/Sources/APNSCore/Alert/APNSAlertNotificationContent.swift index 062d4fc4..ab0f81e1 100644 --- a/Sources/APNSCore/Alert/APNSAlertNotificationContent.swift +++ b/Sources/APNSCore/Alert/APNSAlertNotificationContent.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// /// The information for displaying an alert. -public struct APNSAlertNotificationContent: Encodable, Sendable, Hashable { +public struct APNSAlertNotificationContent: Encodable, Sendable { public struct StringValue: Encodable, Hashable, Sendable { internal enum Configuration: Encodable, Hashable { case raw(String) From 3d78f5262790fd98996ba759b6ad4c21c4407fb4 Mon Sep 17 00:00:00 2001 From: Pat Nakajima Date: Thu, 4 Apr 2024 09:10:50 -0700 Subject: [PATCH 08/16] formatting tweak --- .../APNSCore/LiveActivity/APNSLiveActivityNotification.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift index 3ec0c584..669adb08 100644 --- a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift +++ b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift @@ -17,9 +17,7 @@ import struct Foundation.UUID /// A live activity notification. /// /// It is **important** that you do not encode anything with the key `aps`. -public struct APNSLiveActivityNotification: - APNSMessage -{ +public struct APNSLiveActivityNotification: APNSMessage { enum CodingKeys: CodingKey { case aps } From 4671d0ada8a65122fd7ee33e8916d5ec5439bf15 Mon Sep 17 00:00:00 2001 From: Pat Nakajima Date: Thu, 4 Apr 2024 09:25:59 -0700 Subject: [PATCH 09/16] Move APNSLiveActivityNotificationEvent back to a struct. --- .../APNSLiveActivityNotification.swift | 33 ++------- ...NSLiveActivityNotificationAPSStorage.swift | 11 +-- .../APNSLiveActivityNotificationEvent.swift | 73 +++++-------------- .../APNSLiveActivityNotificationTests.swift | 7 +- 4 files changed, 34 insertions(+), 90 deletions(-) diff --git a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift index 669adb08..017eb073 100644 --- a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift +++ b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift @@ -49,25 +49,7 @@ public struct APNSLiveActivityNotification: APNSMessage /// Event type e.g. update public var event: APNSLiveActivityNotificationEvent { get { - switch self.aps.event { - case "end": - return APNSLiveActivityNotificationEventEnd() - case "update": - return APNSLiveActivityNotificationEventUpdate() - default: - guard let attributesType = self.aps.attributesType, - let state = self.aps.attributesContent, - let alert = self.aps.alert - else { - // Default to update - return APNSLiveActivityNotificationEventUpdate() - } - - return APNSLiveActivityNotificationEventStart( - attributes: .init(type: attributesType, state: state), - alert: alert - ) - } + return APNSLiveActivityNotificationEvent(rawValue: self.aps.event) } set { @@ -136,7 +118,8 @@ public struct APNSLiveActivityNotification: APNSMessage priority: APNSPriority, appID: String, contentState: ContentState, - event: any APNSLiveActivityNotificationEvent, + event: APNSLiveActivityNotificationEvent, + startOptions: APNSLiveActivityNotificationEventStartOptions? = nil, timestamp: Int, dismissalDate: APNSLiveActivityDismissalDate = .none, apnsID: UUID? = nil @@ -147,6 +130,7 @@ public struct APNSLiveActivityNotification: APNSMessage topic: appID + ".push-type.liveactivity", contentState: contentState, event: event, + startOptions: startOptions, timestamp: timestamp, dismissalDate: dismissalDate ) @@ -173,18 +157,15 @@ public struct APNSLiveActivityNotification: APNSMessage topic: String, apnsID: UUID? = nil, contentState: ContentState, - event: any APNSLiveActivityNotificationEvent, + event: APNSLiveActivityNotificationEvent, + startOptions: APNSLiveActivityNotificationEventStartOptions? = nil, timestamp: Int, dismissalDate: APNSLiveActivityDismissalDate = .none ) { - var attributes: APNSLiveActivityNotificationEventStart.Attributes? - if let event = event as? APNSLiveActivityNotificationEventStart { - attributes = event.attributes - } - self.aps = APNSLiveActivityNotificationAPSStorage( timestamp: timestamp, event: event, + startOptions: startOptions, contentState: contentState, dismissalDate: dismissalDate.dismissal ) diff --git a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationAPSStorage.swift b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationAPSStorage.swift index adf6a100..f089f0c7 100644 --- a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationAPSStorage.swift +++ b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationAPSStorage.swift @@ -33,7 +33,8 @@ struct APNSLiveActivityNotificationAPSStorage: Encodabl init( timestamp: Int, - event: any APNSLiveActivityNotificationEvent, + event: APNSLiveActivityNotificationEvent, + startOptions: APNSLiveActivityNotificationEventStartOptions?, contentState: ContentState, dismissalDate: Int? ) { @@ -42,10 +43,10 @@ struct APNSLiveActivityNotificationAPSStorage: Encodabl self.dismissalDate = dismissalDate self.event = event.rawValue - if let event = event as? APNSLiveActivityNotificationEventStart { - self.attributesType = event.attributes.type - self.attributesContent = event.attributes.state - self.alert = event.alert + if let startOptions { + self.attributesType = startOptions.attributeType + self.attributesContent = startOptions.attributes + self.alert = startOptions.alert } } } diff --git a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift index cf79caaf..2b71197d 100644 --- a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift +++ b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift @@ -12,65 +12,30 @@ // //===----------------------------------------------------------------------===// -public protocol APNSLiveActivityNotificationEvent: Encodable { - var rawValue: String { get } -} - -public struct APNSLiveActivityNotificationEventUpdate: APNSLiveActivityNotificationEvent { - public let rawValue = "update" -} - -public struct APNSLiveActivityNotificationEventEnd: APNSLiveActivityNotificationEvent { - public let rawValue = "end" -} -public protocol APNSLiveActivityNotificationEventStartStateProtocol: Encodable -{ - associatedtype State: Encodable -} - -public struct APNSLiveActivityNotificationEventStart: - APNSLiveActivityNotificationEvent, APNSLiveActivityNotificationEventStartStateProtocol -{ - public struct Attributes: Encodable { - public let type: String - public let state: State +public struct APNSLiveActivityNotificationEvent: Encodable { + /// The underlying raw value that is send to APNs. + @usableFromInline + internal let rawValue: String - public init(type: String, state: State) { - self.type = type - self.state = state - } - } + /// Specifies that live activity should be updated + public static let update = Self(rawValue: "update") - public let rawValue = "start" - public let attributes: Attributes - public let alert: APNSAlertNotificationContent + /// Specifies that live activity should be ended + public static let end = Self(rawValue: "end") - public init(attributes: Attributes, alert: APNSAlertNotificationContent) { - self.attributes = attributes - self.alert = alert - } + /// The underlying raw value that is send to APNs. + public static let start = Self(rawValue: "start") } -extension APNSLiveActivityNotificationEvent where Self == APNSLiveActivityNotificationEventUpdate { - public static var update: APNSLiveActivityNotificationEventUpdate { - APNSLiveActivityNotificationEventUpdate() - } -} - -extension APNSLiveActivityNotificationEvent where Self == APNSLiveActivityNotificationEventEnd { - public static var end: APNSLiveActivityNotificationEventEnd { - APNSLiveActivityNotificationEventEnd() - } -} +public struct APNSLiveActivityNotificationEventStartOptions { + var attributeType: String + var attributes: State + var alert: APNSAlertNotificationContent -extension APNSLiveActivityNotificationEvent -where Self: APNSLiveActivityNotificationEventStartStateProtocol { - public static func start(type: String, state: State, alert: APNSAlertNotificationContent) - -> APNSLiveActivityNotificationEventStart< - State - > - { - .init(attributes: .init(type: type, state: state), alert: alert) - } + public init(attributeType: String, attributes: State, alert: APNSAlertNotificationContent) { + self.attributeType = attributeType + self.attributes = attributes + self.alert = alert + } } diff --git a/Tests/APNSTests/LiveActivity/APNSLiveActivityNotificationTests.swift b/Tests/APNSTests/LiveActivity/APNSLiveActivityNotificationTests.swift index 1df74ea0..f9477070 100644 --- a/Tests/APNSTests/LiveActivity/APNSLiveActivityNotificationTests.swift +++ b/Tests/APNSTests/LiveActivity/APNSLiveActivityNotificationTests.swift @@ -51,11 +51,8 @@ final class APNSLiveActivityNotificationTests: XCTestCase { priority: .immediately, appID: "test.app.id", contentState: State(), - // Need the fully qualified name here - event: APNSLiveActivityNotificationEventStart( - attributes: .init(type: "State", state: State()), - alert: .init(title: .raw("Update")) - ), + event: .start, + startOptions: .init(attributeType: "State", attributes: State(), alert: .init(title: .raw("Update"))), timestamp: 1_672_680_658) let encoder = JSONEncoder() From 4fb1daf9dcac8b566f4fe0819f1f5169ae44880d Mon Sep 17 00:00:00 2001 From: Pat Nakajima Date: Thu, 4 Apr 2024 09:27:30 -0700 Subject: [PATCH 10/16] format --- .../APNSLiveActivityNotification.swift | 10 +++--- ...NSLiveActivityNotificationAPSStorage.swift | 10 +++--- .../APNSLiveActivityNotificationEvent.swift | 35 +++++++++---------- .../APNSLiveActivityNotificationTests.swift | 5 +-- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift index 017eb073..8f16ad56 100644 --- a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift +++ b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift @@ -49,7 +49,7 @@ public struct APNSLiveActivityNotification: APNSMessage /// Event type e.g. update public var event: APNSLiveActivityNotificationEvent { get { - return APNSLiveActivityNotificationEvent(rawValue: self.aps.event) + return APNSLiveActivityNotificationEvent(rawValue: self.aps.event) } set { @@ -119,7 +119,7 @@ public struct APNSLiveActivityNotification: APNSMessage appID: String, contentState: ContentState, event: APNSLiveActivityNotificationEvent, - startOptions: APNSLiveActivityNotificationEventStartOptions? = nil, + startOptions: APNSLiveActivityNotificationEventStartOptions? = nil, timestamp: Int, dismissalDate: APNSLiveActivityDismissalDate = .none, apnsID: UUID? = nil @@ -130,7 +130,7 @@ public struct APNSLiveActivityNotification: APNSMessage topic: appID + ".push-type.liveactivity", contentState: contentState, event: event, - startOptions: startOptions, + startOptions: startOptions, timestamp: timestamp, dismissalDate: dismissalDate ) @@ -158,14 +158,14 @@ public struct APNSLiveActivityNotification: APNSMessage apnsID: UUID? = nil, contentState: ContentState, event: APNSLiveActivityNotificationEvent, - startOptions: APNSLiveActivityNotificationEventStartOptions? = nil, + startOptions: APNSLiveActivityNotificationEventStartOptions? = nil, timestamp: Int, dismissalDate: APNSLiveActivityDismissalDate = .none ) { self.aps = APNSLiveActivityNotificationAPSStorage( timestamp: timestamp, event: event, - startOptions: startOptions, + startOptions: startOptions, contentState: contentState, dismissalDate: dismissalDate.dismissal ) diff --git a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationAPSStorage.swift b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationAPSStorage.swift index f089f0c7..d97582c7 100644 --- a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationAPSStorage.swift +++ b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationAPSStorage.swift @@ -34,7 +34,7 @@ struct APNSLiveActivityNotificationAPSStorage: Encodabl init( timestamp: Int, event: APNSLiveActivityNotificationEvent, - startOptions: APNSLiveActivityNotificationEventStartOptions?, + startOptions: APNSLiveActivityNotificationEventStartOptions?, contentState: ContentState, dismissalDate: Int? ) { @@ -43,10 +43,10 @@ struct APNSLiveActivityNotificationAPSStorage: Encodabl self.dismissalDate = dismissalDate self.event = event.rawValue - if let startOptions { - self.attributesType = startOptions.attributeType - self.attributesContent = startOptions.attributes - self.alert = startOptions.alert + if let startOptions { + self.attributesType = startOptions.attributeType + self.attributesContent = startOptions.attributes + self.alert = startOptions.alert } } } diff --git a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift index 2b71197d..486d3368 100644 --- a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift +++ b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift @@ -12,30 +12,29 @@ // //===----------------------------------------------------------------------===// - public struct APNSLiveActivityNotificationEvent: Encodable { - /// The underlying raw value that is send to APNs. - @usableFromInline - internal let rawValue: String + /// The underlying raw value that is send to APNs. + @usableFromInline + internal let rawValue: String - /// Specifies that live activity should be updated - public static let update = Self(rawValue: "update") + /// Specifies that live activity should be updated + public static let update = Self(rawValue: "update") - /// Specifies that live activity should be ended - public static let end = Self(rawValue: "end") + /// Specifies that live activity should be ended + public static let end = Self(rawValue: "end") - /// The underlying raw value that is send to APNs. - public static let start = Self(rawValue: "start") + /// The underlying raw value that is send to APNs. + public static let start = Self(rawValue: "start") } public struct APNSLiveActivityNotificationEventStartOptions { - var attributeType: String - var attributes: State - var alert: APNSAlertNotificationContent + var attributeType: String + var attributes: State + var alert: APNSAlertNotificationContent - public init(attributeType: String, attributes: State, alert: APNSAlertNotificationContent) { - self.attributeType = attributeType - self.attributes = attributes - self.alert = alert - } + public init(attributeType: String, attributes: State, alert: APNSAlertNotificationContent) { + self.attributeType = attributeType + self.attributes = attributes + self.alert = alert + } } diff --git a/Tests/APNSTests/LiveActivity/APNSLiveActivityNotificationTests.swift b/Tests/APNSTests/LiveActivity/APNSLiveActivityNotificationTests.swift index f9477070..2da1a78c 100644 --- a/Tests/APNSTests/LiveActivity/APNSLiveActivityNotificationTests.swift +++ b/Tests/APNSTests/LiveActivity/APNSLiveActivityNotificationTests.swift @@ -51,8 +51,9 @@ final class APNSLiveActivityNotificationTests: XCTestCase { priority: .immediately, appID: "test.app.id", contentState: State(), - event: .start, - startOptions: .init(attributeType: "State", attributes: State(), alert: .init(title: .raw("Update"))), + event: .start, + startOptions: .init( + attributeType: "State", attributes: State(), alert: .init(title: .raw("Update"))), timestamp: 1_672_680_658) let encoder = JSONEncoder() From d2cdfb801b07b9449bfc8f0db0ad9ef2c21d1596 Mon Sep 17 00:00:00 2001 From: Pat Nakajima Date: Thu, 4 Apr 2024 09:32:58 -0700 Subject: [PATCH 11/16] Oops missed this one --- .../LiveActivity/APNSLiveActivityNotificationEvent.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift index 486d3368..28152a7d 100644 --- a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift +++ b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -public struct APNSLiveActivityNotificationEvent: Encodable { +public struct APNSLiveActivityNotificationEvent: Hashable { /// The underlying raw value that is send to APNs. @usableFromInline internal let rawValue: String From 2b4319808cf59289c7c5dbb0d38ae94902f10bd0 Mon Sep 17 00:00:00 2001 From: Pat Nakajima Date: Thu, 4 Apr 2024 13:11:45 -0700 Subject: [PATCH 12/16] format --- .../APNSLiveActivityNotification.swift | 14 -- ...NSLiveActivityNotificationAPSStorage.swift | 13 -- .../APNSLiveActivityNotificationEvent.swift | 12 -- .../APNSStartLiveActivityNotification.swift | 176 ++++++++++++++++++ ...rtLiveActivityNotificationAPSStorage.swift | 51 +++++ .../APNSLiveActivityNotificationTests.swift | 17 +- 6 files changed, 238 insertions(+), 45 deletions(-) create mode 100644 Sources/APNSCore/LiveActivity/APNSStartLiveActivityNotification.swift create mode 100644 Sources/APNSCore/LiveActivity/APNSStartLiveActivityNotificationAPSStorage.swift diff --git a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift index 8f16ad56..32c3074c 100644 --- a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift +++ b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift @@ -36,16 +36,6 @@ public struct APNSLiveActivityNotification: APNSMessage } } - public var alert: APNSAlertNotificationContent? { - get { - return self.aps.alert - } - - set { - self.aps.alert = newValue - } - } - /// Event type e.g. update public var event: APNSLiveActivityNotificationEvent { get { @@ -119,7 +109,6 @@ public struct APNSLiveActivityNotification: APNSMessage appID: String, contentState: ContentState, event: APNSLiveActivityNotificationEvent, - startOptions: APNSLiveActivityNotificationEventStartOptions? = nil, timestamp: Int, dismissalDate: APNSLiveActivityDismissalDate = .none, apnsID: UUID? = nil @@ -130,7 +119,6 @@ public struct APNSLiveActivityNotification: APNSMessage topic: appID + ".push-type.liveactivity", contentState: contentState, event: event, - startOptions: startOptions, timestamp: timestamp, dismissalDate: dismissalDate ) @@ -158,14 +146,12 @@ public struct APNSLiveActivityNotification: APNSMessage apnsID: UUID? = nil, contentState: ContentState, event: APNSLiveActivityNotificationEvent, - startOptions: APNSLiveActivityNotificationEventStartOptions? = nil, timestamp: Int, dismissalDate: APNSLiveActivityDismissalDate = .none ) { self.aps = APNSLiveActivityNotificationAPSStorage( timestamp: timestamp, event: event, - startOptions: startOptions, contentState: contentState, dismissalDate: dismissalDate.dismissal ) diff --git a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationAPSStorage.swift b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationAPSStorage.swift index d97582c7..fbfebdc2 100644 --- a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationAPSStorage.swift +++ b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationAPSStorage.swift @@ -18,23 +18,16 @@ struct APNSLiveActivityNotificationAPSStorage: Encodabl case event = "event" case contentState = "content-state" case dismissalDate = "dismissal-date" - case attributesType = "attributes-type" - case attributesContent = "attributes" - case alert = "alert" } var timestamp: Int var event: String - var attributesType: String? - var attributesContent: ContentState? var contentState: ContentState var dismissalDate: Int? - var alert: APNSAlertNotificationContent? init( timestamp: Int, event: APNSLiveActivityNotificationEvent, - startOptions: APNSLiveActivityNotificationEventStartOptions?, contentState: ContentState, dismissalDate: Int? ) { @@ -42,11 +35,5 @@ struct APNSLiveActivityNotificationAPSStorage: Encodabl self.contentState = contentState self.dismissalDate = dismissalDate self.event = event.rawValue - - if let startOptions { - self.attributesType = startOptions.attributeType - self.attributesContent = startOptions.attributes - self.alert = startOptions.alert - } } } diff --git a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift index 28152a7d..55877d26 100644 --- a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift +++ b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift @@ -26,15 +26,3 @@ public struct APNSLiveActivityNotificationEvent: Hashable { /// The underlying raw value that is send to APNs. public static let start = Self(rawValue: "start") } - -public struct APNSLiveActivityNotificationEventStartOptions { - var attributeType: String - var attributes: State - var alert: APNSAlertNotificationContent - - public init(attributeType: String, attributes: State, alert: APNSAlertNotificationContent) { - self.attributeType = attributeType - self.attributes = attributes - self.alert = alert - } -} diff --git a/Sources/APNSCore/LiveActivity/APNSStartLiveActivityNotification.swift b/Sources/APNSCore/LiveActivity/APNSStartLiveActivityNotification.swift new file mode 100644 index 00000000..29a38e4d --- /dev/null +++ b/Sources/APNSCore/LiveActivity/APNSStartLiveActivityNotification.swift @@ -0,0 +1,176 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the APNSwift open source project +// +// Copyright (c) 2022 the APNSwift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of APNSwift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import struct Foundation.UUID + +/// A notification that starts a live activity +/// +/// It is **important** that you do not encode anything with the key `aps`. +public struct APNSStartLiveActivityNotification: + APNSMessage +{ + enum CodingKeys: CodingKey { + case aps + } + + /// The fixed content to indicate that this is a background notification. + private var aps: APNSStartLiveActivityNotificationAPSStorage + + /// Timestamp when sending notification + public var timestamp: Int { + get { + return self.aps.timestamp + } + + set { + self.aps.timestamp = newValue + } + } + + public var alert: APNSAlertNotificationContent { + get { + return self.aps.alert + } + + set { + self.aps.alert = newValue + } + } + + /// The dynamic content of a Live Activity. + public var contentState: ContentState { + get { + return self.aps.contentState + } + + set { + self.aps.contentState = newValue + } + } + + public var dismissalDate: APNSLiveActivityDismissalDate? { + get { + return .init(dismissal: self.aps.dismissalDate) + } + set { + self.aps.dismissalDate = newValue?.dismissal + } + } + + /// A canonical UUID that identifies the notification. If there is an error sending the notification, + /// APNs uses this value to identify the notification to your server. The canonical form is 32 lowercase hexadecimal digits, + /// displayed in five groups separated by hyphens in the form 8-4-4-4-12. An example UUID is as follows: + /// `123e4567-e89b-12d3-a456-42665544000`. + /// + /// If you omit this, a new UUID is created by APNs and returned in the response. + public var apnsID: UUID? + + /// The date when the notification is no longer valid and can be discarded. If this value is not `none`, + /// APNs stores the notification and tries to deliver it at least once, + /// repeating the attempt as needed if it is unable to deliver the notification the first time. + /// If the value is `immediately`, APNs treats the notification as if it expires immediately + /// and does not store the notification or attempt to redeliver it. + public var expiration: APNSNotificationExpiration + + /// The priority of the notification. + public var priority: APNSPriority + + /// The topic for the notification. In general, the topic is your app’s bundle ID/app ID. + public var topic: String + + /// Initializes a new ``APNSStartLiveActivityNotification``. + /// + /// - Important: Your dynamic payload will get encoded to the root of the JSON payload that is send to APNs. + /// It is **important** that you do not encode anything with the key `aps` + /// + /// - Parameters: + /// - expiration: The date when the notification is no longer valid and can be discarded. + /// - priority: The priority of the notification. + /// - appID: Your app’s bundle ID/app ID. This will be suffixed with `.push-type.liveactivity`. + /// - apnsID: A canonical UUID that identifies the notification. + /// - contentState: Updated content-state of live activity + /// - timestamp: Timestamp when sending notification + /// - dismissalDate: Timestamp when to dismiss live notification when sent with `end`, if in the past + /// dismiss immediately + /// - attributes: The ActivityAttributes of the live activity to start + /// - attributesString: The type name of the ActivityAttributes you want to send + /// - alert: An alert that will be sent along with the notification + public init( + expiration: APNSNotificationExpiration, + priority: APNSPriority, + appID: String, + contentState: ContentState, + timestamp: Int, + dismissalDate: APNSLiveActivityDismissalDate = .none, + apnsID: UUID? = nil, + attributes: Attributes, + attributesType: String, + alert: APNSAlertNotificationContent + ) { + self.init( + expiration: expiration, + priority: priority, + topic: appID + ".push-type.liveactivity", + contentState: contentState, + timestamp: timestamp, + dismissalDate: dismissalDate, + attributes: attributes, + attributesType: attributesType, + alert: alert + ) + } + + /// Initializes a new ``APNSStartLiveActivityNotification``. + /// + /// - Important: Your dynamic payload will get encoded to the root of the JSON payload that is send to APNs. + /// It is **important** that you do not encode anything with the key `aps` + /// + /// - Parameters: + /// - expiration: The date when the notification is no longer valid and can be discarded. + /// - priority: The priority of the notification. + /// - topic: The topic for the notification. In general, the topic is your app’s bundle ID/app ID suffixed with `.push-type.liveactivity`. + /// - apnsID: A canonical UUID that identifies the notification. + /// - contentState: Updated content-state of live activity + /// - timestamp: Timestamp when sending notification + /// - dismissalDate: Timestamp when to dismiss live notification when sent with `end`, if in the past + /// dismiss immediately + /// - attributes: The ActivityAttributes of the live activity to start + /// - attributesString: The type name of the ActivityAttributes you want to send + /// - alert: An alert that will be sent along with the notification + public init( + expiration: APNSNotificationExpiration, + priority: APNSPriority, + topic: String, + apnsID: UUID? = nil, + contentState: ContentState, + timestamp: Int, + dismissalDate: APNSLiveActivityDismissalDate = .none, + attributes: Attributes, + attributesType: String, + alert: APNSAlertNotificationContent + ) { + self.aps = APNSStartLiveActivityNotificationAPSStorage( + timestamp: timestamp, + contentState: contentState, + dismissalDate: dismissalDate.dismissal, + alert: alert, + attributes: attributes, + attributesType: attributesType + ) + self.apnsID = apnsID + self.expiration = expiration + self.priority = priority + self.topic = topic + } +} diff --git a/Sources/APNSCore/LiveActivity/APNSStartLiveActivityNotificationAPSStorage.swift b/Sources/APNSCore/LiveActivity/APNSStartLiveActivityNotificationAPSStorage.swift new file mode 100644 index 00000000..2fd1947e --- /dev/null +++ b/Sources/APNSCore/LiveActivity/APNSStartLiveActivityNotificationAPSStorage.swift @@ -0,0 +1,51 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the APNSwift open source project +// +// Copyright (c) 2022 the APNSwift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of APNSwift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +struct APNSStartLiveActivityNotificationAPSStorage: + Encodable +{ + enum CodingKeys: String, CodingKey { + case timestamp = "timestamp" + case event = "event" + case contentState = "content-state" + case dismissalDate = "dismissal-date" + case alert = "alert" + case attributes = "attributes" + case attributesType = "attributes-type" + } + + var timestamp: Int + var event: String = "start" + var contentState: ContentState + var dismissalDate: Int? + var alert: APNSAlertNotificationContent + var attributes: Attributes + var attributesType: String + + init( + timestamp: Int, + contentState: ContentState, + dismissalDate: Int?, + alert: APNSAlertNotificationContent, + attributes: Attributes, + attributesType: String + ) { + self.timestamp = timestamp + self.contentState = contentState + self.dismissalDate = dismissalDate + self.alert = alert + self.attributes = attributes + self.attributesType = attributesType + } +} diff --git a/Tests/APNSTests/LiveActivity/APNSLiveActivityNotificationTests.swift b/Tests/APNSTests/LiveActivity/APNSLiveActivityNotificationTests.swift index 2da1a78c..01008417 100644 --- a/Tests/APNSTests/LiveActivity/APNSLiveActivityNotificationTests.swift +++ b/Tests/APNSTests/LiveActivity/APNSLiveActivityNotificationTests.swift @@ -17,6 +17,10 @@ import XCTest final class APNSLiveActivityNotificationTests: XCTestCase { + struct Attributes: Encodable { + let name: String = "Test Attribute" + } + struct State: Encodable, Hashable { let string: String = "Test" let number: Int = 123 @@ -46,21 +50,22 @@ final class APNSLiveActivityNotificationTests: XCTestCase { } func testEncodeStart() throws { - let notification = APNSLiveActivityNotification( + let notification = APNSStartLiveActivityNotification( expiration: .immediately, priority: .immediately, appID: "test.app.id", contentState: State(), - event: .start, - startOptions: .init( - attributeType: "State", attributes: State(), alert: .init(title: .raw("Update"))), - timestamp: 1_672_680_658) + timestamp: 1_672_680_658, + attributes: Attributes(), + attributesType: "Attributes", + alert: .init(title: .raw("Hi"), body: .raw("Hello")) + ) let encoder = JSONEncoder() let data = try encoder.encode(notification) let expectedJSONString = """ - {"aps":{"event":"start", "alert": { "title": "Update" }, "attributes-type": "State", "attributes": {"string":"Test","number":123},"content-state":{"string":"Test","number":123},"timestamp":1672680658}} + {"aps":{"event":"start", "alert": { "title": "Hi", "body": "Hello" }, "attributes-type": "Attributes", "attributes": {"name":"Test Attribute"},"content-state":{"string":"Test","number":123},"timestamp":1672680658}} """ let jsonObject1 = try JSONSerialization.jsonObject(with: data) as! NSDictionary From 3818be81725fa4ac991d67c2dcdb05a30675d450 Mon Sep 17 00:00:00 2001 From: Pat Nakajima Date: Thu, 4 Apr 2024 13:13:32 -0700 Subject: [PATCH 13/16] remove unrelated change --- .../APNSCore/LiveActivity/APNSLiveActivityNotification.swift | 2 +- .../LiveActivity/APNSLiveActivityNotificationAPSStorage.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift index 32c3074c..f459903f 100644 --- a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift +++ b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift @@ -151,7 +151,7 @@ public struct APNSLiveActivityNotification: APNSMessage ) { self.aps = APNSLiveActivityNotificationAPSStorage( timestamp: timestamp, - event: event, + event: event.rawValue, contentState: contentState, dismissalDate: dismissalDate.dismissal ) diff --git a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationAPSStorage.swift b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationAPSStorage.swift index fbfebdc2..756c747c 100644 --- a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationAPSStorage.swift +++ b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationAPSStorage.swift @@ -27,13 +27,13 @@ struct APNSLiveActivityNotificationAPSStorage: Encodabl init( timestamp: Int, - event: APNSLiveActivityNotificationEvent, + event: String, contentState: ContentState, dismissalDate: Int? ) { self.timestamp = timestamp self.contentState = contentState self.dismissalDate = dismissalDate - self.event = event.rawValue + self.event = event } } From f5087ba2c6c28c427e5e285ad86cd1c427394a0c Mon Sep 17 00:00:00 2001 From: Pat Nakajima Date: Thu, 4 Apr 2024 13:14:01 -0700 Subject: [PATCH 14/16] remove unnecessary event from struct --- .../LiveActivity/APNSLiveActivityNotificationEvent.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift index 55877d26..9f925f80 100644 --- a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift +++ b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotificationEvent.swift @@ -22,7 +22,4 @@ public struct APNSLiveActivityNotificationEvent: Hashable { /// Specifies that live activity should be ended public static let end = Self(rawValue: "end") - - /// The underlying raw value that is send to APNs. - public static let start = Self(rawValue: "start") } From 2d41546e2d7ffd6df8c2bba7bd3c25cf8ce7c3c2 Mon Sep 17 00:00:00 2001 From: Pat Nakajima Date: Tue, 16 Apr 2024 15:47:13 -0700 Subject: [PATCH 15/16] Fix indentation --- .../APNSCore/LiveActivity/APNSLiveActivityNotification.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift index f459903f..135a193b 100644 --- a/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift +++ b/Sources/APNSCore/LiveActivity/APNSLiveActivityNotification.swift @@ -151,7 +151,7 @@ public struct APNSLiveActivityNotification: APNSMessage ) { self.aps = APNSLiveActivityNotificationAPSStorage( timestamp: timestamp, - event: event.rawValue, + event: event.rawValue, contentState: contentState, dismissalDate: dismissalDate.dismissal ) From a142abee3531d8297a28d69e3a7f492c75cc3d10 Mon Sep 17 00:00:00 2001 From: Pat Nakajima Date: Tue, 7 May 2024 08:58:38 -0700 Subject: [PATCH 16/16] Remove unnecessary init --- .../APNSStartLiveActivityNotification.swift | 70 ++++--------------- 1 file changed, 14 insertions(+), 56 deletions(-) diff --git a/Sources/APNSCore/LiveActivity/APNSStartLiveActivityNotification.swift b/Sources/APNSCore/LiveActivity/APNSStartLiveActivityNotification.swift index 29a38e4d..4a722465 100644 --- a/Sources/APNSCore/LiveActivity/APNSStartLiveActivityNotification.swift +++ b/Sources/APNSCore/LiveActivity/APNSStartLiveActivityNotification.swift @@ -98,13 +98,13 @@ public struct APNSStartLiveActivityNotification