12
12
import io .micrometer .core .instrument .Tags ;
13
13
import java .time .Instant ;
14
14
import java .util .Optional ;
15
+ import java .util .concurrent .CompletableFuture ;
15
16
import java .util .function .BiConsumer ;
16
17
import org .apache .commons .lang3 .StringUtils ;
17
18
import org .slf4j .Logger ;
@@ -49,35 +50,38 @@ public PushNotificationManager(final AccountsManager accountsManager,
49
50
this .pushLatencyManager = pushLatencyManager ;
50
51
}
51
52
52
- public void sendNewMessageNotification (final Account destination , final byte destinationDeviceId , final boolean urgent ) throws NotPushRegisteredException {
53
+ public CompletableFuture < Optional < SendPushNotificationResult >> sendNewMessageNotification (final Account destination , final byte destinationDeviceId , final boolean urgent ) throws NotPushRegisteredException {
53
54
final Device device = destination .getDevice (destinationDeviceId ).orElseThrow (NotPushRegisteredException ::new );
54
55
final Pair <String , PushNotification .TokenType > tokenAndType = getToken (device );
55
56
56
- sendNotification (new PushNotification (tokenAndType .first (), tokenAndType .second (),
57
+ return sendNotification (new PushNotification (tokenAndType .first (), tokenAndType .second (),
57
58
PushNotification .NotificationType .NOTIFICATION , null , destination , device , urgent ));
58
59
}
59
60
60
- public void sendRegistrationChallengeNotification (final String deviceToken , final PushNotification .TokenType tokenType , final String challengeToken ) {
61
- sendNotification (new PushNotification (deviceToken , tokenType , PushNotification .NotificationType .CHALLENGE , challengeToken , null , null , true ));
61
+ public CompletableFuture <SendPushNotificationResult > sendRegistrationChallengeNotification (final String deviceToken , final PushNotification .TokenType tokenType , final String challengeToken ) {
62
+ return sendNotification (new PushNotification (deviceToken , tokenType , PushNotification .NotificationType .CHALLENGE , challengeToken , null , null , true ))
63
+ .thenApply (maybeResponse -> maybeResponse .orElseThrow (() -> new AssertionError ("Responses must be present for urgent notifications" )));
62
64
}
63
65
64
- public void sendRateLimitChallengeNotification (final Account destination , final String challengeToken )
66
+ public CompletableFuture < SendPushNotificationResult > sendRateLimitChallengeNotification (final Account destination , final String challengeToken )
65
67
throws NotPushRegisteredException {
66
68
67
69
final Device device = destination .getPrimaryDevice ();
68
70
final Pair <String , PushNotification .TokenType > tokenAndType = getToken (device );
69
71
70
- sendNotification (new PushNotification (tokenAndType .first (), tokenAndType .second (),
71
- PushNotification .NotificationType .RATE_LIMIT_CHALLENGE , challengeToken , destination , device , true ));
72
+ return sendNotification (new PushNotification (tokenAndType .first (), tokenAndType .second (),
73
+ PushNotification .NotificationType .RATE_LIMIT_CHALLENGE , challengeToken , destination , device , true ))
74
+ .thenApply (maybeResponse -> maybeResponse .orElseThrow (() -> new AssertionError ("Responses must be present for urgent notifications" )));
72
75
}
73
76
74
- public void sendAttemptLoginNotification (final Account destination , final String context ) throws NotPushRegisteredException {
77
+ public CompletableFuture < SendPushNotificationResult > sendAttemptLoginNotification (final Account destination , final String context ) throws NotPushRegisteredException {
75
78
final Device device = destination .getDevice (Device .PRIMARY_ID ).orElseThrow (NotPushRegisteredException ::new );
76
79
final Pair <String , PushNotification .TokenType > tokenAndType = getToken (device );
77
80
78
- sendNotification (new PushNotification (tokenAndType .first (), tokenAndType .second (),
81
+ return sendNotification (new PushNotification (tokenAndType .first (), tokenAndType .second (),
79
82
PushNotification .NotificationType .ATTEMPT_LOGIN_NOTIFICATION_HIGH_PRIORITY ,
80
- context , destination , device , true ));
83
+ context , destination , device , true ))
84
+ .thenApply (maybeResponse -> maybeResponse .orElseThrow (() -> new AssertionError ("Responses must be present for urgent notifications" )));
81
85
}
82
86
83
87
public void handleMessagesRetrieved (final Account account , final Device device , final String userAgent ) {
@@ -103,64 +107,66 @@ Pair<String, PushNotification.TokenType> getToken(final Device device) throws No
103
107
}
104
108
105
109
@ VisibleForTesting
106
- void sendNotification (final PushNotification pushNotification ) {
110
+ CompletableFuture < Optional < SendPushNotificationResult >> sendNotification (final PushNotification pushNotification ) {
107
111
if (pushNotification .tokenType () == PushNotification .TokenType .APN && !pushNotification .urgent ()) {
108
112
// APNs imposes a per-device limit on background push notifications; schedule a notification for some time in the
109
113
// future (possibly even now!) rather than sending a notification directly
110
- apnPushNotificationScheduler
114
+ return apnPushNotificationScheduler
111
115
.scheduleBackgroundNotification (pushNotification .destination (), pushNotification .destinationDevice ())
112
- .whenComplete (logErrors ());
116
+ .whenComplete (logErrors ())
117
+ .thenApply (ignored -> Optional .<SendPushNotificationResult >empty ())
118
+ .toCompletableFuture ();
119
+ }
113
120
114
- } else {
115
- final PushNotificationSender sender = switch (pushNotification .tokenType ()) {
116
- case FCM -> fcmSender ;
117
- case APN , APN_VOIP -> apnSender ;
118
- };
119
-
120
- sender .sendNotification (pushNotification ).whenComplete ((result , throwable ) -> {
121
- if (throwable == null ) {
122
- Tags tags = Tags .of ("tokenType" , pushNotification .tokenType ().name (),
123
- "notificationType" , pushNotification .notificationType ().name (),
124
- "urgent" , String .valueOf (pushNotification .urgent ()),
125
- "accepted" , String .valueOf (result .accepted ()),
126
- "unregistered" , String .valueOf (result .unregistered ()));
127
-
128
- if (result .errorCode ().isPresent ()) {
129
- tags = tags .and ("errorCode" , result .errorCode ().get ());
130
- }
131
-
132
- Metrics .counter (SENT_NOTIFICATION_COUNTER_NAME , tags ).increment ();
133
-
134
- if (result .unregistered () && pushNotification .destination () != null
135
- && pushNotification .destinationDevice () != null ) {
136
-
137
- handleDeviceUnregistered (pushNotification .destination (),
138
- pushNotification .destinationDevice (),
139
- pushNotification .tokenType (),
140
- result .errorCode (),
141
- result .unregisteredTimestamp ());
142
- }
143
-
144
- if (result .accepted () &&
145
- pushNotification .tokenType () == PushNotification .TokenType .APN_VOIP &&
146
- pushNotification .notificationType () == PushNotification .NotificationType .NOTIFICATION &&
147
- pushNotification .destination () != null &&
148
- pushNotification .destinationDevice () != null ) {
149
-
150
- apnPushNotificationScheduler .scheduleRecurringVoipNotification (
151
- pushNotification .destination (),
152
- pushNotification .destinationDevice ())
153
- .whenComplete (logErrors ());
154
- }
155
- } else {
156
- logger .debug ("Failed to deliver {} push notification to {} ({})" ,
157
- pushNotification .notificationType (), pushNotification .deviceToken (), pushNotification .tokenType (),
158
- throwable );
159
-
160
- Metrics .counter (FAILED_NOTIFICATION_COUNTER_NAME , "cause" , throwable .getClass ().getSimpleName ()).increment ();
121
+ final PushNotificationSender sender = switch (pushNotification .tokenType ()) {
122
+ case FCM -> fcmSender ;
123
+ case APN , APN_VOIP -> apnSender ;
124
+ };
125
+
126
+ return sender .sendNotification (pushNotification ).whenComplete ((result , throwable ) -> {
127
+ if (throwable == null ) {
128
+ Tags tags = Tags .of ("tokenType" , pushNotification .tokenType ().name (),
129
+ "notificationType" , pushNotification .notificationType ().name (),
130
+ "urgent" , String .valueOf (pushNotification .urgent ()),
131
+ "accepted" , String .valueOf (result .accepted ()),
132
+ "unregistered" , String .valueOf (result .unregistered ()));
133
+
134
+ if (result .errorCode ().isPresent ()) {
135
+ tags = tags .and ("errorCode" , result .errorCode ().get ());
161
136
}
162
- });
163
- }
137
+
138
+ Metrics .counter (SENT_NOTIFICATION_COUNTER_NAME , tags ).increment ();
139
+
140
+ if (result .unregistered () && pushNotification .destination () != null
141
+ && pushNotification .destinationDevice () != null ) {
142
+
143
+ handleDeviceUnregistered (pushNotification .destination (),
144
+ pushNotification .destinationDevice (),
145
+ pushNotification .tokenType (),
146
+ result .errorCode (),
147
+ result .unregisteredTimestamp ());
148
+ }
149
+
150
+ if (result .accepted () &&
151
+ pushNotification .tokenType () == PushNotification .TokenType .APN_VOIP &&
152
+ pushNotification .notificationType () == PushNotification .NotificationType .NOTIFICATION &&
153
+ pushNotification .destination () != null &&
154
+ pushNotification .destinationDevice () != null ) {
155
+
156
+ apnPushNotificationScheduler .scheduleRecurringVoipNotification (
157
+ pushNotification .destination (),
158
+ pushNotification .destinationDevice ())
159
+ .whenComplete (logErrors ());
160
+ }
161
+ } else {
162
+ logger .debug ("Failed to deliver {} push notification to {} ({})" ,
163
+ pushNotification .notificationType (), pushNotification .deviceToken (), pushNotification .tokenType (),
164
+ throwable );
165
+
166
+ Metrics .counter (FAILED_NOTIFICATION_COUNTER_NAME , "cause" , throwable .getClass ().getSimpleName ()).increment ();
167
+ }
168
+ })
169
+ .thenApply (Optional ::of );
164
170
}
165
171
166
172
private static <T > BiConsumer <T , Throwable > logErrors () {
0 commit comments