diff --git a/pom.xml b/pom.xml index 14abbc82e..4155d63e6 100644 --- a/pom.xml +++ b/pom.xml @@ -129,7 +129,7 @@ org.whispersystems websocket-resources - 0.2.0 + 0.2.1 diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java index 5e9476628..7ca3b6240 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java +++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java @@ -197,10 +197,14 @@ public class AccountController { @PUT @Path("/gcm/") @Consumes(MediaType.APPLICATION_JSON) - public void setGcmRegistrationId(@Auth Account account, @Valid GcmRegistrationId registrationId) { + public void setGcmRegistrationId(@Auth Account account, @Valid GcmRegistrationId registrationId) { Device device = account.getAuthenticatedDevice().get(); device.setApnId(null); device.setGcmId(registrationId.getGcmRegistrationId()); + + if (registrationId.isWebSocketChannel()) device.setFetchesMessages(true); + else device.setFetchesMessages(false); + accounts.update(account); } @@ -210,6 +214,7 @@ public class AccountController { public void deleteGcmRegistrationId(@Auth Account account) { Device device = account.getAuthenticatedDevice().get(); device.setGcmId(null); + device.setFetchesMessages(false); accounts.update(account); } @@ -221,6 +226,7 @@ public class AccountController { Device device = account.getAuthenticatedDevice().get(); device.setApnId(registrationId.getApnRegistrationId()); device.setGcmId(null); + device.setFetchesMessages(true); accounts.update(account); } @@ -230,6 +236,25 @@ public class AccountController { public void deleteApnRegistrationId(@Auth Account account) { Device device = account.getAuthenticatedDevice().get(); device.setApnId(null); + device.setFetchesMessages(false); + accounts.update(account); + } + + @Timed + @PUT + @Path("/wsc/") + public void setWebSocketChannelSupported(@Auth Account account) { + Device device = account.getAuthenticatedDevice().get(); + device.setFetchesMessages(true); + accounts.update(account); + } + + @Timed + @DELETE + @Path("/wsc/") + public void deleteWebSocketChannel(@Auth Account account) { + Device device = account.getAuthenticatedDevice().get(); + device.setFetchesMessages(false); accounts.update(account); } diff --git a/src/main/java/org/whispersystems/textsecuregcm/entities/GcmMessage.java b/src/main/java/org/whispersystems/textsecuregcm/entities/GcmMessage.java index 69b46a4e6..e0e8b29bb 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/entities/GcmMessage.java +++ b/src/main/java/org/whispersystems/textsecuregcm/entities/GcmMessage.java @@ -20,20 +20,23 @@ public class GcmMessage { private int deviceId; @JsonProperty - @NotEmpty private String message; @JsonProperty private boolean receipt; + @JsonProperty + private boolean notification; + public GcmMessage() {} - public GcmMessage(String gcmId, String number, int deviceId, String message, boolean receipt) { - this.gcmId = gcmId; - this.number = number; - this.deviceId = deviceId; - this.message = message; - this.receipt = receipt; + public GcmMessage(String gcmId, String number, int deviceId, String message, boolean receipt, boolean notification) { + this.gcmId = gcmId; + this.number = number; + this.deviceId = deviceId; + this.message = message; + this.receipt = receipt; + this.notification = notification; } } diff --git a/src/main/java/org/whispersystems/textsecuregcm/entities/GcmRegistrationId.java b/src/main/java/org/whispersystems/textsecuregcm/entities/GcmRegistrationId.java index dc1dfec01..d7b4e2f65 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/entities/GcmRegistrationId.java +++ b/src/main/java/org/whispersystems/textsecuregcm/entities/GcmRegistrationId.java @@ -25,9 +25,15 @@ public class GcmRegistrationId { @NotEmpty private String gcmRegistrationId; + @JsonProperty + private boolean webSocketChannel; + public String getGcmRegistrationId() { return gcmRegistrationId; } + public boolean isWebSocketChannel() { + return webSocketChannel; + } } diff --git a/src/main/java/org/whispersystems/textsecuregcm/push/PushSender.java b/src/main/java/org/whispersystems/textsecuregcm/push/PushSender.java index 64499dde8..c9e0a65a5 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/push/PushSender.java +++ b/src/main/java/org/whispersystems/textsecuregcm/push/PushSender.java @@ -57,6 +57,13 @@ public class PushSender { private void sendGcmMessage(Account account, Device device, OutgoingMessageSignal message) throws TransientPushFailureException, NotPushRegisteredException + { + if (device.getFetchesMessages()) sendNotificationGcmMessage(account, device, message); + else sendPayloadGcmMessage(account, device, message); + } + + private void sendPayloadGcmMessage(Account account, Device device, OutgoingMessageSignal message) + throws TransientPushFailureException, NotPushRegisteredException { try { String number = account.getNumber(); @@ -65,7 +72,7 @@ public class PushSender { boolean isReceipt = message.getType() == OutgoingMessageSignal.Type.RECEIPT_VALUE; EncryptedOutgoingMessage encryptedMessage = new EncryptedOutgoingMessage(message, device.getSignalingKey()); GcmMessage gcmMessage = new GcmMessage(registrationId, number, (int) deviceId, - encryptedMessage.toEncodedString(), isReceipt); + encryptedMessage.toEncodedString(), isReceipt, false); pushServiceClient.send(gcmMessage); } catch (CryptoEncodingException e) { @@ -73,10 +80,26 @@ public class PushSender { } } + private void sendNotificationGcmMessage(Account account, Device device, OutgoingMessageSignal message) + throws TransientPushFailureException + { + DeliveryStatus deliveryStatus = webSocketSender.sendMessage(account, device, message, WebsocketSender.Type.GCM); + + if (!deliveryStatus.isDelivered()) { + GcmMessage gcmMessage = new GcmMessage(device.getGcmId(), account.getNumber(), + (int)device.getId(), "", false, true); + + pushServiceClient.send(gcmMessage); + } else { + logger.warn("Delivered!"); + } + + } + private void sendApnMessage(Account account, Device device, OutgoingMessageSignal outgoingMessage) throws TransientPushFailureException { - DeliveryStatus deliveryStatus = webSocketSender.sendMessage(account, device, outgoingMessage, true); + DeliveryStatus deliveryStatus = webSocketSender.sendMessage(account, device, outgoingMessage, WebsocketSender.Type.APN); if (!deliveryStatus.isDelivered() && outgoingMessage.getType() != OutgoingMessageSignal.Type.RECEIPT_VALUE) { ApnMessage apnMessage = new ApnMessage(device.getApnId(), account.getNumber(), (int)device.getId(), @@ -87,6 +110,6 @@ public class PushSender { private void sendWebSocketMessage(Account account, Device device, OutgoingMessageSignal outgoingMessage) { - webSocketSender.sendMessage(account, device, outgoingMessage, false); + webSocketSender.sendMessage(account, device, outgoingMessage, WebsocketSender.Type.WEB); } } diff --git a/src/main/java/org/whispersystems/textsecuregcm/push/WebsocketSender.java b/src/main/java/org/whispersystems/textsecuregcm/push/WebsocketSender.java index d3adece66..942073bab 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/push/WebsocketSender.java +++ b/src/main/java/org/whispersystems/textsecuregcm/push/WebsocketSender.java @@ -36,6 +36,12 @@ import static org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessag public class WebsocketSender { + public static enum Type { + APN, + GCM, + WEB + } + private static final Logger logger = LoggerFactory.getLogger(WebsocketSender.class); private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME); @@ -46,6 +52,9 @@ public class WebsocketSender { private final Meter apnOnlineMeter = metricRegistry.meter(name(getClass(), "apn_online" )); private final Meter apnOfflineMeter = metricRegistry.meter(name(getClass(), "apn_offline")); + private final Meter gcmOnlineMeter = metricRegistry.meter(name(getClass(), "gcm_online" )); + private final Meter gcmOfflineMeter = metricRegistry.meter(name(getClass(), "gcm_offline")); + private final Meter provisioningOnlineMeter = metricRegistry.meter(name(getClass(), "provisioning_online" )); private final Meter provisioningOfflineMeter = metricRegistry.meter(name(getClass(), "provisioning_offline")); @@ -57,7 +66,7 @@ public class WebsocketSender { this.pubSubManager = pubSubManager; } - public DeliveryStatus sendMessage(Account account, Device device, OutgoingMessageSignal message, boolean apn) { + public DeliveryStatus sendMessage(Account account, Device device, OutgoingMessageSignal message, Type channel) { WebsocketAddress address = new WebsocketAddress(account.getNumber(), device.getId()); PubSubMessage pubSubMessage = PubSubMessage.newBuilder() .setType(PubSubMessage.Type.DELIVER) @@ -65,13 +74,15 @@ public class WebsocketSender { .build(); if (pubSubManager.publish(address, pubSubMessage)) { - if (apn) apnOnlineMeter.mark(); - else websocketOnlineMeter.mark(); + if (channel == Type.APN) apnOnlineMeter.mark(); + else if (channel == Type.GCM) gcmOnlineMeter.mark(); + else websocketOnlineMeter.mark(); return new DeliveryStatus(true, 0); } else { - if (apn) apnOfflineMeter.mark(); - else websocketOfflineMeter.mark(); + if (channel == Type.APN) apnOfflineMeter.mark(); + else if (channel == Type.GCM) gcmOfflineMeter.mark(); + else websocketOfflineMeter.mark(); int queueDepth = messagesManager.insert(account.getNumber(), device.getId(), message); pubSubManager.publish(address, PubSubMessage.newBuilder() diff --git a/src/main/java/org/whispersystems/textsecuregcm/websocket/AuthenticatedConnectListener.java b/src/main/java/org/whispersystems/textsecuregcm/websocket/AuthenticatedConnectListener.java index 0d443ebd0..461e4c0d1 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/websocket/AuthenticatedConnectListener.java +++ b/src/main/java/org/whispersystems/textsecuregcm/websocket/AuthenticatedConnectListener.java @@ -33,30 +33,17 @@ public class AuthenticatedConnectListener implements WebSocketConnectListener { @Override public void onWebSocketConnect(WebSocketSessionContext context) { - Optional account = context.getAuthenticated(Account.class); + Account account = context.getAuthenticated(Account.class).get(); + Device device = account.getAuthenticatedDevice().get(); - if (!account.isPresent()) { - logger.debug("WS Connection with no authentication..."); - context.getClient().close(4001, "Authentication failed"); - return; - } - - Optional device = account.get().getAuthenticatedDevice(); - - if (!device.isPresent()) { - logger.debug("WS Connection with no authenticated device..."); - context.getClient().close(4001, "Device authentication failed"); - return; - } - - if (device.get().getLastSeen() != Util.todayInMillis()) { - device.get().setLastSeen(Util.todayInMillis()); - accountsManager.update(account.get()); + if (device.getLastSeen() != Util.todayInMillis()) { + device.setLastSeen(Util.todayInMillis()); + accountsManager.update(account); } final WebSocketConnection connection = new WebSocketConnection(accountsManager, pushSender, messagesManager, pubSubManager, - account.get(), device.get(), + account, device, context.getClient()); connection.onConnected(); diff --git a/src/main/java/org/whispersystems/textsecuregcm/websocket/WebSocketAccountAuthenticator.java b/src/main/java/org/whispersystems/textsecuregcm/websocket/WebSocketAccountAuthenticator.java index df2d04618..004bd18e4 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/websocket/WebSocketAccountAuthenticator.java +++ b/src/main/java/org/whispersystems/textsecuregcm/websocket/WebSocketAccountAuthenticator.java @@ -34,7 +34,9 @@ public class WebSocketAccountAuthenticator implements WebSocketAuthenticator