From d41e94c835396f5f8b33f8be05dcb76366e06d22 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Tue, 17 Oct 2017 13:50:26 +0300 Subject: notifications: APNS HTTP API --- juick-notifications/build.gradle | 2 +- .../main/java/com/juick/components/CleanUp.java | 22 ++++-------- .../java/com/juick/components/Notifications.java | 42 ++++++++++++++++++---- .../configuration/APNSConfiguration.java | 29 ++++++++++----- 4 files changed, 63 insertions(+), 32 deletions(-) diff --git a/juick-notifications/build.gradle b/juick-notifications/build.gradle index 3e641c93..74fb9081 100644 --- a/juick-notifications/build.gradle +++ b/juick-notifications/build.gradle @@ -5,7 +5,7 @@ apply plugin: 'org.akhikhl.gretty' dependencies { compile project(':juick-server-web') compile 'com.ganyo:gcm-server:1.1.0' - compile 'com.notnoop.apns:apns:1.0.0.Beta6' + compile 'com.turo:pushy:0.11.3' compile "rocks.xmpp:xmpp-core-client:0.7.4" compile "rocks.xmpp:xmpp-extensions-client:0.7.4" diff --git a/juick-notifications/src/main/java/com/juick/components/CleanUp.java b/juick-notifications/src/main/java/com/juick/components/CleanUp.java index 9714e597..d0f3e44a 100644 --- a/juick-notifications/src/main/java/com/juick/components/CleanUp.java +++ b/juick-notifications/src/main/java/com/juick/components/CleanUp.java @@ -1,15 +1,13 @@ package com.juick.components; import com.juick.components.service.TokenService; -import com.notnoop.apns.ApnsService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import javax.inject.Inject; -import java.util.Collection; -import java.util.stream.Collectors; +import java.util.ArrayList; /** * Created by vitalyster on 22.11.2016. @@ -19,8 +17,6 @@ public class CleanUp { private static Logger logger = LoggerFactory.getLogger(CleanUp.class); - @Inject - ApnsService apns; @Inject TokenService tokenService; @@ -29,20 +25,14 @@ public class CleanUp { @Scheduled(fixedRate = 600000) public void cleanupTokens() { - logger.info("initializing apns tokens cleanup"); - Collection devices = apns.getInactiveDevices().keySet(); - int count = devices.size(); - if (count > 0) { - logger.info("{} tokens to delete", count); - tokenService.deleteTokens("apns", devices.stream().collect(Collectors.toList())); - } else { - logger.debug("No APNS tokens to delete"); - } logger.debug("initializing GCM tokens cleanup: {} tokens", push.getInvalidGCMTokens().size()); - tokenService.deleteTokens("gcm", push.getInvalidGCMTokens().stream().collect(Collectors.toList())); + tokenService.deleteTokens("gcm", new ArrayList<>(push.getInvalidGCMTokens())); push.cleanupGCMTokens(); logger.debug("initializing MPNS tokens cleanup: {} tokens", push.getInvalidMPNSTokens().size()); - tokenService.deleteTokens("mpns", push.getInvalidMPNSTokens().stream().collect(Collectors.toList())); + tokenService.deleteTokens("mpns", new ArrayList<>(push.getInvalidMPNSTokens())); + push.cleanupMPNSTokens(); + logger.debug("initializing APNS tokens cleanup: {} tokens", push.getInvalidAPNSTokens().size()); + tokenService.deleteTokens("apns", new ArrayList<>(push.getInvalidAPNSTokens())); push.cleanupMPNSTokens(); } } diff --git a/juick-notifications/src/main/java/com/juick/components/Notifications.java b/juick-notifications/src/main/java/com/juick/components/Notifications.java index f0ab3b81..575f7460 100644 --- a/juick-notifications/src/main/java/com/juick/components/Notifications.java +++ b/juick-notifications/src/main/java/com/juick/components/Notifications.java @@ -21,12 +21,15 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.android.gcm.server.*; import com.juick.DeviceRegistration; import com.juick.User; -import com.notnoop.apns.APNS; -import com.notnoop.apns.ApnsService; +import com.turo.pushy.apns.ApnsClient; +import com.turo.pushy.apns.PushNotificationResponse; +import com.turo.pushy.apns.util.ApnsPayloadBuilder; +import com.turo.pushy.apns.util.SimpleApnsPushNotification; import org.apache.commons.lang3.math.NumberUtils; import org.apache.commons.text.StringEscapeUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpMethod; import org.springframework.web.client.RestTemplate; @@ -37,6 +40,8 @@ import javax.annotation.PostConstruct; import javax.inject.Inject; import java.io.IOException; import java.util.*; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; import java.util.stream.Collectors; /** @@ -57,11 +62,14 @@ public class Notifications implements NotificationClientListener, AutoCloseable private final Set invalidGCMTokens = Collections.synchronizedSet(new HashSet<>()); private final Set invalidMPNSTokens = Collections.synchronizedSet(new HashSet<>()); + private final Set invalidAPNSTokens = Collections.synchronizedSet(new HashSet<>()); @Inject private MPNSClient mpnsClient; @Inject - private ApnsService apns; + private ApnsClient apns; + @Value("${ios_app_id:}") + private String topic; @PostConstruct public void init() throws IOException { @@ -164,10 +172,27 @@ public class Notifications implements NotificationClientListener, AutoCloseable List tokens = users.stream().flatMap(u -> u.getDevices().stream()).filter(d -> d.getType().equals("apns")) .map(DeviceRegistration::getToken).collect(Collectors.toList()); if (!tokens.isEmpty()) { + ApnsPayloadBuilder apnsPayloadBuilder = new ApnsPayloadBuilder(); + String payload = apnsPayloadBuilder.setAlertTitle("@" + jmsg.getUser().getName()) + .setAlertBody(jmsg.getText()).buildWithDefaultMaximumLength(); for (String token : tokens) { - String payload = APNS.newPayload().alertTitle("@" + jmsg.getUser().getName()).alertBody(jmsg.getText()).build(); - logger.info("APNS: {}", token); - apns.push(token, payload); + final Future> notification + = apns.sendNotification(new SimpleApnsPushNotification(token, topic, payload)); + try { + final PushNotificationResponse pushNotificationResponse + = notification.get(); + if (pushNotificationResponse.isAccepted()) { + logger.info("APNS accepted: {}", token); + } else { + String reason = pushNotificationResponse.getRejectionReason(); + logger.info("APNS rejected: {}", reason); + if (reason.equals("BadDeviceToken")) { + invalidAPNSTokens.add(token); + } + } + } catch (final ExecutionException | InterruptedException ex) { + logger.info("APNS exception", ex); + } } } else { logger.info("APNS: no recipients"); @@ -182,6 +207,7 @@ public class Notifications implements NotificationClientListener, AutoCloseable @Override public void close() throws Exception { + apns.close(); if (xmpp != null) xmpp.close(); @@ -227,4 +253,8 @@ public class Notifications implements NotificationClientListener, AutoCloseable break; } } + + public Set getInvalidAPNSTokens() { + return invalidAPNSTokens; + } } diff --git a/juick-notifications/src/main/java/com/juick/components/configuration/APNSConfiguration.java b/juick-notifications/src/main/java/com/juick/components/configuration/APNSConfiguration.java index 6ac155a1..43ea3943 100644 --- a/juick-notifications/src/main/java/com/juick/components/configuration/APNSConfiguration.java +++ b/juick-notifications/src/main/java/com/juick/components/configuration/APNSConfiguration.java @@ -1,23 +1,34 @@ package com.juick.components.configuration; -import com.notnoop.apns.APNS; -import com.notnoop.apns.ApnsService; +import com.turo.pushy.apns.ApnsClient; +import com.turo.pushy.apns.ApnsClientBuilder; +import com.turo.pushy.apns.auth.ApnsSigningKey; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import java.io.File; +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + /** * Created by vital on 28.03.2017. */ @Configuration public class APNSConfiguration { - @Value("${ios_pkcs12_file:}") - private String pkcs12File; - @Value("${ios_pkcs12_password:}") - private String pkcs12secret; + @Value("${ios_p8_key:}") + private String p8key; + @Value("${ios_team_id:}") + private String teamId; + @Value("${ios_key_id:}") + private String keyId; @Bean - public ApnsService apns() { - return APNS.newService().withCert(pkcs12File, pkcs12secret) - .withProductionDestination().build(); + public ApnsClient apns() throws NoSuchAlgorithmException, InvalidKeyException, IOException { + return new ApnsClientBuilder() + .setApnsServer(ApnsClientBuilder.PRODUCTION_APNS_HOST) + .setSigningKey(ApnsSigningKey.loadFromPkcs8File(new File(p8key), + teamId, keyId)) + .build(); } } -- cgit v1.2.3