Skip to content

Commit fc56e57

Browse files
sebalandameter
authored andcommitted
feat(PushNotifications): Updating OptOut behaviour and always registering for remote notifications (#125)
1 parent e3294c1 commit fc56e57

File tree

13 files changed

+99
-77
lines changed

13 files changed

+99
-77
lines changed

Amplify/Categories/Notifications/PushNotifications/PushNotificationsCategory+HubPayloadEventName.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ public extension HubPayload.EventName.Notifications {
1111
}
1212

1313
public extension HubPayload.EventName.Notifications.Push {
14-
/// Event triggered when the device is registered for remote notifications
15-
static let registerForRemoteNotifications = "Notifications.Push.registerForNotifications"
14+
/// Event triggered when notifications permissions are requested to the user
15+
static let requestNotificationsPermissions = "Notifications.Push.requestNotificationsPermissions"
1616
}

AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Endpoint/EndpointClient.swift

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,6 @@ actor EndpointClient: EndpointClientBehaviour {
7777
private func retrieveOrCreateEndpointProfile() async -> PinpointEndpointProfile {
7878
// 1. Look for the local endpointProfile variable
7979
if let endpointProfile = endpointProfile {
80-
// Update endpoint's optOut flag, as the user might have disabled notifications since the last time
81-
endpointProfile.isOptOut = await isNotEligibleForPinpointNotifications(endpointProfile)
8280
return endpointProfile
8381
}
8482

@@ -105,7 +103,6 @@ actor EndpointClient: EndpointClientBehaviour {
105103
endpointProfile.deviceToken = deviceToken
106104
endpointProfile.location = .init()
107105
endpointProfile.demographic = .init(device: endpointInformation)
108-
endpointProfile.isOptOut = await isNotEligibleForPinpointNotifications(endpointProfile)
109106
endpointProfile.isDebug = configuration.isDebug
110107

111108
return endpointProfile
@@ -148,14 +145,6 @@ actor EndpointClient: EndpointClientBehaviour {
148145
}
149146
}
150147

151-
private func isNotEligibleForPinpointNotifications(_ endpointProfile: PinpointEndpointProfile) async -> Bool {
152-
guard endpointProfile.deviceToken.isNotEmpty else {
153-
return true
154-
}
155-
156-
return !(await remoteNotificationsHelper.isRegisteredForRemoteNotifications)
157-
}
158-
159148
private func createUpdateInput(from endpointProfile: PinpointEndpointProfile) -> UpdateEndpointInput {
160149
let channelType = getChannelType(from: endpointProfile)
161150
let effectiveDate = getEffectiveDateIso8601FractionalSeconds(from: endpointProfile)

AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Endpoint/PinpointEndpointProfile.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ public class PinpointEndpointProfile: Codable {
6565
addCustomProperties(userProfile.customProperties)
6666
if let pinpointUser = userProfile as? PinpointUserProfile {
6767
addUserAttributes(pinpointUser.userAttributes)
68+
if let optedOutOfMessages = pinpointUser.optedOutOfMessages {
69+
isOptOut = optedOutOfMessages
70+
}
6871
}
6972

7073
if let userLocation = userProfile.location {

AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Models/PinpointUserProfile.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ public struct PinpointUserProfile: UserProfile {
4545
/// User attributes, which are mapped to the endpoint's `user attributes`.
4646
public var userAttributes: [String: [String]]?
4747

48+
/// Whether the user has opted out of receiving messages and push notifications from Pinpoint.
49+
public var optedOutOfMessages: Bool?
50+
4851
/// Initializer
4952
/// - Parameters:
5053
/// - name: The name of the user
@@ -59,13 +62,15 @@ public struct PinpointUserProfile: UserProfile {
5962
plan: String? = nil,
6063
location: UserProfileLocation? = nil,
6164
customProperties: [String: UserProfilePropertyValue]? = nil,
62-
userAttributes: [String: [String]]? = nil
65+
userAttributes: [String: [String]]? = nil,
66+
optedOutOfMessages: Bool? = nil
6367
) {
6468
self.name = name
6569
self.email = email
6670
self.plan = plan
6771
self.location = location
6872
self.customProperties = customProperties
6973
self.userAttributes = userAttributes
74+
self.optedOutOfMessages = optedOutOfMessages
7075
}
7176
}

AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Support/Utils/RemoteNotificationsHelper.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import UserNotifications
1414
public protocol RemoteNotificationsBehaviour {
1515
var isRegisteredForRemoteNotifications: Bool { get async }
1616
func requestAuthorization(_ options: UNAuthorizationOptions) async throws -> Bool
17+
func registerForRemoteNotifications() async
1718
}
1819

1920
@_spi(InternalAWSPinpoint)
@@ -46,5 +47,9 @@ public struct AmplifyRemoteNotificationsHelper: RemoteNotificationsBehaviour {
4647
)
4748
}
4849
}
50+
51+
public func registerForRemoteNotifications() async {
52+
await AUNotificationPermissions.registerForRemoteNotifications()
53+
}
4954
}
5055

AmplifyPlugins/Internal/Tests/InternalAWSPinpointUnitTests/EndpointClientTests.swift

Lines changed: 4 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -116,41 +116,6 @@ class EndpointClientTests: XCTestCase {
116116
XCTAssertEqual(endpointProfile.demographic.platformVersion, endpointInformation.platform.version)
117117
}
118118

119-
func testCurrentEndpointProfile_withStoredToken_andRemoteNotificationsEnabled_shouldSetOptOutFalse() async {
120-
archiver.decoded = PinpointEndpointProfile(applicationId: "applicationId",
121-
endpointId: "endpointId",
122-
isOptOut: true)
123-
storeToken("deviceToken")
124-
remoteNotificationsHelper.isRegisteredForRemoteNotifications = true
125-
126-
let endpointProfile = await endpointClient.currentEndpointProfile()
127-
128-
XCTAssertFalse(endpointProfile.isOptOut)
129-
}
130-
131-
func testCurrentEndpointProfile_withStoredToken_andRemoteNotificationsDisabled_shouldSetOptOutTrue() async {
132-
archiver.decoded = PinpointEndpointProfile(applicationId: "applicationId",
133-
endpointId: "endpointId",
134-
isOptOut: false)
135-
storeToken("deviceToken")
136-
remoteNotificationsHelper.isRegisteredForRemoteNotifications = false
137-
138-
let endpointProfile = await endpointClient.currentEndpointProfile()
139-
140-
XCTAssertTrue(endpointProfile.isOptOut)
141-
}
142-
143-
func testCurrentEndpointProfile_withoutStoredToken_andRemoteNotificationsEnabled_shouldSetOptOutTrue() async {
144-
archiver.decoded = PinpointEndpointProfile(applicationId: "applicationId",
145-
endpointId: "endpointId",
146-
isOptOut: false)
147-
remoteNotificationsHelper.isRegisteredForRemoteNotifications = false
148-
149-
let endpointProfile = await endpointClient.currentEndpointProfile()
150-
151-
XCTAssertTrue(endpointProfile.isOptOut)
152-
}
153-
154119
func testUpdateEndpointProfile_shouldSendUpdateRequestAndSave() async {
155120
keychain.resetCounters()
156121
try? await endpointClient.updateEndpointProfile()
@@ -214,7 +179,7 @@ class EndpointClientTests: XCTestCase {
214179
XCTAssertEqual(keychain.saveDataCountMap[EndpointClient.Constants.deviceTokenKey, default: 0], 0)
215180
}
216181

217-
func testConvertToPublicEndpoint_withoutDeviceToken_shouldReturnPublicEndpoint() async {
182+
func testConvertToPublicEndpoint_shouldReturnPublicEndpoint() async {
218183
let endpointProfile = await endpointClient.currentEndpointProfile()
219184
let publicEndpoint = endpointClient.convertToPublicEndpoint(endpointProfile)
220185
let mockModel = MockEndpointInformation()
@@ -223,25 +188,14 @@ class EndpointClientTests: XCTestCase {
223188
XCTAssertEqual(publicEndpoint.attributes?.count, 0)
224189
XCTAssertEqual(publicEndpoint.metrics?.count, 0)
225190
XCTAssertNil(publicEndpoint.channelType)
226-
XCTAssertEqual(publicEndpoint.optOut, "ALL")
191+
XCTAssertEqual(publicEndpoint.optOut, "NONE")
227192
XCTAssertEqual(publicEndpoint.demographic?.appVersion, mockModel.appVersion)
228193
XCTAssertEqual(publicEndpoint.demographic?.make, "apple")
229194
XCTAssertEqual(publicEndpoint.demographic?.model, mockModel.model)
230195
XCTAssertEqual(publicEndpoint.demographic?.platform, mockModel.platform.name)
231196
XCTAssertEqual(publicEndpoint.demographic?.platformVersion, mockModel.platform.version)
232197
}
233198

234-
func testConvertToPublicEndpoint_withDeviceToken_shouldReturnPublicEndpoint() async {
235-
let endpointProfile = await endpointClient.currentEndpointProfile()
236-
endpointProfile.setAPNsToken(Data(hexString: newTokenHex)!)
237-
endpointProfile.isOptOut = false
238-
let publicEndpoint = endpointClient.convertToPublicEndpoint(endpointProfile)
239-
XCTAssertNotNil(publicEndpoint)
240-
XCTAssertEqual(publicEndpoint.address, newTokenHex)
241-
XCTAssertEqual(publicEndpoint.channelType, .apns)
242-
XCTAssertEqual(publicEndpoint.optOut, "NONE")
243-
}
244-
245199
@discardableResult
246200
private func storeToken(_ deviceToken: String) -> Data? {
247201
let newToken = Data(hexString: deviceToken) ?? deviceToken.data(using: .utf8)
@@ -266,4 +220,6 @@ class MockRemoteNotifications: RemoteNotificationsBehaviour {
266220
func requestAuthorization(_ options: UNAuthorizationOptions) async throws -> Bool {
267221
return true
268222
}
223+
224+
func registerForRemoteNotifications() async {}
269225
}

AmplifyPlugins/Notifications/Push/Sources/AWSPinpointPushNotificationsPlugin/AWSPinpointPushNotificationsPlugin+Configure.swift

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,27 @@ extension AWSPinpointPushNotificationsPlugin {
4242
remoteNotificationsHelper: .default)
4343
}
4444

45+
private func requestNotificationsPermissions(using helper: RemoteNotificationsBehaviour) async {
46+
guard !options.isEmpty else {
47+
return
48+
}
49+
50+
do {
51+
let result = try await helper.requestAuthorization(options)
52+
Amplify.Hub.dispatchRequestNotificationsPermissions(result)
53+
} catch {
54+
Amplify.Hub.dispatchRequestNotificationsPermissions(error)
55+
}
56+
}
57+
4558
// MARK: Internal
4659
/// Internal configure method to set the properties of the plugin
4760
func configure(pinpoint: AWSPinpointBehavior,
4861
remoteNotificationsHelper: RemoteNotificationsBehaviour) {
4962
self.pinpoint = pinpoint
5063
Task {
51-
do {
52-
let result = try await remoteNotificationsHelper.requestAuthorization(options)
53-
Amplify.Hub.dispatchRegisterForRemoteNotifications(result)
54-
} catch {
55-
Amplify.Hub.dispatchRegisterForRemoteNotifications(error)
56-
}
64+
await remoteNotificationsHelper.registerForRemoteNotifications()
65+
await requestNotificationsPermissions(using: remoteNotificationsHelper)
5766
}
5867
}
5968
}

AmplifyPlugins/Notifications/Push/Sources/AWSPinpointPushNotificationsPlugin/Extensions/HubCategory+PushNotifications.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,17 @@ import Amplify
99
import Foundation
1010

1111
extension HubCategory {
12-
func dispatchRegisterForRemoteNotifications(_ result: Bool) {
12+
func dispatchRequestNotificationsPermissions(_ result: Bool) {
1313
let payload = HubPayload(
14-
eventName: HubPayload.EventName.Notifications.Push.registerForRemoteNotifications,
14+
eventName: HubPayload.EventName.Notifications.Push.requestNotificationsPermissions,
1515
data: result
1616
)
1717
dispatch(to: .pushNotifications, payload: payload)
1818
}
1919

20-
func dispatchRegisterForRemoteNotifications(_ error: Error) {
20+
func dispatchRequestNotificationsPermissions(_ error: Error) {
2121
let payload = HubPayload(
22-
eventName: HubPayload.EventName.Notifications.Push.registerForRemoteNotifications,
22+
eventName: HubPayload.EventName.Notifications.Push.requestNotificationsPermissions,
2323
data: error
2424
)
2525
dispatch(to: .pushNotifications, payload: payload)

AmplifyPlugins/Notifications/Push/Tests/AWSPinpointPushNotificationsPluginUnitTests/AWSPinpointPushNotificationsPluginClientBehaviourTests.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,18 @@ class AWSPinpointPushNotificationsPluginClientBehaviourTests: AWSPinpointPushNot
119119
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.user.userAttributes?["roles"]?.count, 2)
120120
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.user.userAttributes?["roles"]?.first, "Test")
121121
}
122+
123+
func testIdentifyUser_withPinpointProfileOptedOutOfMessages_shouldUpdateUserProfileOptOutValue() async throws {
124+
try await plugin.identifyUser(userId: "newUserId", userProfile: nil)
125+
let updatedEndpoint = try XCTUnwrap(mockPinpoint.updatedPinpointEndpointProfile)
126+
XCTAssertFalse(updatedEndpoint.isOptOut)
127+
128+
try await plugin.identifyUser(userId: "newUserId", userProfile: PinpointUserProfile(optedOutOfMessages: true))
129+
XCTAssertTrue(updatedEndpoint.isOptOut)
130+
131+
try await plugin.identifyUser(userId: "newUserId", userProfile: PinpointUserProfile(name: "User"))
132+
XCTAssertTrue(updatedEndpoint.isOptOut)
133+
}
122134

123135
// MARK: - Register Device tests
124136
func testRegisterDevice_shouldUpdateDeviceToken() async throws {

AmplifyPlugins/Notifications/Push/Tests/AWSPinpointPushNotificationsPluginUnitTests/AWSPinpointPushNotificationsPluginConfigureTests.swift

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,47 @@ class AWSPinpointPushNotificationsPluginConfigureTests: AWSPinpointPushNotificat
4747
}
4848
}
4949

50+
func testConfigure_withNotificationsPermissionsGranted_shouldRegisterForRemoteNotifications() throws {
51+
mockRemoteNotifications.mockedRequestAuthorizationResult = true
52+
mockRemoteNotifications.registerForRemoteNotificationsExpectation = expectation(description: "Permissions Granted")
53+
try plugin.configure(using: createPushNotificationsPluginConfig())
54+
waitForExpectations(timeout: 1)
55+
}
56+
57+
func testConfigure_withNotificationsPermissionsDenied_shouldRegisterForRemoteNotifications() throws {
58+
mockRemoteNotifications.mockedRequestAuthorizationResult = false
59+
mockRemoteNotifications.registerForRemoteNotificationsExpectation = expectation(description: "Permissions Denied")
60+
try plugin.configure(using: createPushNotificationsPluginConfig())
61+
waitForExpectations(timeout: 1)
62+
}
63+
64+
func testConfigure_withNotificationsPermissionsFailed_shouldRegisterForRemoteNotifications() throws {
65+
let error = PushNotificationsError.service("Description", "Recovery", nil)
66+
mockRemoteNotifications.requestAuthorizationError = error
67+
mockRemoteNotifications.registerForRemoteNotificationsExpectation = expectation(description: "Permissions Failed")
68+
try plugin.configure(using: createPushNotificationsPluginConfig())
69+
waitForExpectations(timeout: 1)
70+
}
71+
72+
func testConfigure_withEmptyOptions_shouldNotReportToHub() throws {
73+
plugin = AWSPinpointPushNotificationsPlugin(options: [])
74+
try plugin.configure(using: createPushNotificationsPluginConfig())
75+
let eventWasReported = expectation(description: "Event was reported to Hub")
76+
_ = hubPlugin.listen(to: .pushNotifications, isIncluded: nil) { payload in
77+
if payload.eventName == HubPayload.EventName.Notifications.Push.requestNotificationsPermissions {
78+
eventWasReported.fulfill()
79+
}
80+
}
81+
eventWasReported.isInverted = true
82+
waitForExpectations(timeout: 1)
83+
}
84+
5085
func testConfigure_withNotificationsPermissionsGranted_shouldReportSuccessToHub() throws {
5186
mockRemoteNotifications.mockedRequestAuthorizationResult = true
5287

5388
let eventWasReported = expectation(description: "Event was reported to Hub")
5489
_ = hubPlugin.listen(to: .pushNotifications, isIncluded: nil) { payload in
55-
if payload.eventName == HubPayload.EventName.Notifications.Push.registerForRemoteNotifications {
90+
if payload.eventName == HubPayload.EventName.Notifications.Push.requestNotificationsPermissions {
5691
eventWasReported.fulfill()
5792
guard let result = payload.data as? Bool else {
5893
XCTFail("Expected result")
@@ -72,7 +107,7 @@ class AWSPinpointPushNotificationsPluginConfigureTests: AWSPinpointPushNotificat
72107

73108
let eventWasReported = expectation(description: "Event was reported to Hub")
74109
_ = hubPlugin.listen(to: .pushNotifications, isIncluded: nil) { payload in
75-
if payload.eventName == HubPayload.EventName.Notifications.Push.registerForRemoteNotifications {
110+
if payload.eventName == HubPayload.EventName.Notifications.Push.requestNotificationsPermissions {
76111
eventWasReported.fulfill()
77112
guard let result = payload.data as? Bool else {
78113
XCTFail("Expected result")
@@ -93,7 +128,7 @@ class AWSPinpointPushNotificationsPluginConfigureTests: AWSPinpointPushNotificat
93128

94129
let eventWasReported = expectation(description: "Event was reported to Hub")
95130
_ = hubPlugin.listen(to: .pushNotifications, isIncluded: nil) { payload in
96-
if payload.eventName == HubPayload.EventName.Notifications.Push.registerForRemoteNotifications {
131+
if payload.eventName == HubPayload.EventName.Notifications.Push.requestNotificationsPermissions {
97132
eventWasReported.fulfill()
98133
guard let error = payload.data as? PushNotificationsError,
99134
case .service(let errorDescription, let recoverySuggestion, _) = error else {

0 commit comments

Comments
 (0)