Skip to content

Commit 6d166fd

Browse files
committed
Return futures from "send push notification" operations
1 parent 2e36673 commit 6d166fd

File tree

1 file changed

+68
-62
lines changed

1 file changed

+68
-62
lines changed

service/src/main/java/org/whispersystems/textsecuregcm/push/PushNotificationManager.java

Lines changed: 68 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import io.micrometer.core.instrument.Tags;
1313
import java.time.Instant;
1414
import java.util.Optional;
15+
import java.util.concurrent.CompletableFuture;
1516
import java.util.function.BiConsumer;
1617
import org.apache.commons.lang3.StringUtils;
1718
import org.slf4j.Logger;
@@ -49,35 +50,38 @@ public PushNotificationManager(final AccountsManager accountsManager,
4950
this.pushLatencyManager = pushLatencyManager;
5051
}
5152

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 {
5354
final Device device = destination.getDevice(destinationDeviceId).orElseThrow(NotPushRegisteredException::new);
5455
final Pair<String, PushNotification.TokenType> tokenAndType = getToken(device);
5556

56-
sendNotification(new PushNotification(tokenAndType.first(), tokenAndType.second(),
57+
return sendNotification(new PushNotification(tokenAndType.first(), tokenAndType.second(),
5758
PushNotification.NotificationType.NOTIFICATION, null, destination, device, urgent));
5859
}
5960

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")));
6264
}
6365

64-
public void sendRateLimitChallengeNotification(final Account destination, final String challengeToken)
66+
public CompletableFuture<SendPushNotificationResult> sendRateLimitChallengeNotification(final Account destination, final String challengeToken)
6567
throws NotPushRegisteredException {
6668

6769
final Device device = destination.getPrimaryDevice();
6870
final Pair<String, PushNotification.TokenType> tokenAndType = getToken(device);
6971

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")));
7275
}
7376

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 {
7578
final Device device = destination.getDevice(Device.PRIMARY_ID).orElseThrow(NotPushRegisteredException::new);
7679
final Pair<String, PushNotification.TokenType> tokenAndType = getToken(device);
7780

78-
sendNotification(new PushNotification(tokenAndType.first(), tokenAndType.second(),
81+
return sendNotification(new PushNotification(tokenAndType.first(), tokenAndType.second(),
7982
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")));
8185
}
8286

8387
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
103107
}
104108

105109
@VisibleForTesting
106-
void sendNotification(final PushNotification pushNotification) {
110+
CompletableFuture<Optional<SendPushNotificationResult>> sendNotification(final PushNotification pushNotification) {
107111
if (pushNotification.tokenType() == PushNotification.TokenType.APN && !pushNotification.urgent()) {
108112
// APNs imposes a per-device limit on background push notifications; schedule a notification for some time in the
109113
// future (possibly even now!) rather than sending a notification directly
110-
apnPushNotificationScheduler
114+
return apnPushNotificationScheduler
111115
.scheduleBackgroundNotification(pushNotification.destination(), pushNotification.destinationDevice())
112-
.whenComplete(logErrors());
116+
.whenComplete(logErrors())
117+
.thenApply(ignored -> Optional.<SendPushNotificationResult>empty())
118+
.toCompletableFuture();
119+
}
113120

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());
161136
}
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);
164170
}
165171

166172
private static <T> BiConsumer<T, Throwable> logErrors() {

0 commit comments

Comments
 (0)