package com.juick.components; import com.juick.ExternalToken; import com.juick.Message; import com.juick.User; import com.juick.formatters.PlainTextFormatter; import com.juick.service.component.*; import com.turo.pushy.apns.ApnsClient; import com.turo.pushy.apns.ApnsClientBuilder; import com.turo.pushy.apns.PushNotificationResponse; import com.turo.pushy.apns.auth.ApnsSigningKey; import com.turo.pushy.apns.util.ApnsPayloadBuilder; import com.turo.pushy.apns.util.SimpleApnsPushNotification; import com.turo.pushy.apns.util.concurrent.PushNotificationResponseListener; import io.netty.util.concurrent.Future; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.inject.Inject; import java.io.File; import java.io.IOException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.Date; import java.util.List; import java.util.Optional; public class APNSManager implements NotificationListener { private static Logger logger = LoggerFactory.getLogger(APNSManager.class); private ApnsClient apns; @Value("${ios_p8_key:}") private String p8key; @Value("${ios_app_id:}") private String topic; @Value("${ios_team_id:}") private String teamId; @Value("${ios_key_id:}") private String keyId; @Inject private NotificationsManager notificationsManager; @PostConstruct public void initialize() throws NoSuchAlgorithmException, InvalidKeyException, IOException { apns = new ApnsClientBuilder() .setApnsServer(ApnsClientBuilder.PRODUCTION_APNS_HOST) .setSigningKey(ApnsSigningKey.loadFromPkcs8File(new File(p8key), teamId, keyId)) .build(); } @Override public void processMessageEvent(MessageEvent messageEvent) { com.juick.Message jmsg = messageEvent.getMessage(); List users = messageEvent.getUsers(); ApnsPayloadBuilder apnsPayloadBuilder = new ApnsPayloadBuilder(); apnsPayloadBuilder.addCustomProperty("mid", jmsg.getMid()); apnsPayloadBuilder.addCustomProperty("uname", jmsg.getUser().getName()); String post = PlainTextFormatter.formatPost(jmsg); String[] parts = post.split("\n", 2); apnsPayloadBuilder.setAlertTitle(parts[0]).setAlertBody(parts[1]); users.forEach( user -> { apnsPayloadBuilder.setBadgeNumber(user.getUnreadCount()); String payload = apnsPayloadBuilder.buildWithDefaultMaximumLength(); user.getTokens().stream().filter(t -> t.getType().equals("apns")) .map(ExternalToken::getToken).forEach(token -> { Future> notification = apns.sendNotification( new SimpleApnsPushNotification(token, topic, payload)); notification.addListener((PushNotificationResponseListener) future -> { if (future.isSuccess()) { processAPNSResponse(token, future.getNow()); } else { logger.warn("APNS error ", future.cause()); } }); }); }); } @Override public void processSubscribeEvent(SubscribeEvent subscribeEvent) { } @Override public void processLikeEvent(LikeEvent likeEvent) { } @Override public void processPingEvent(PingEvent pingEvent) { } @Override public void processMessageReadEvent(MessageReadEvent messageReadEvent) { List users = messageReadEvent.getUsers(); ApnsPayloadBuilder apnsPayloadBuilder = new ApnsPayloadBuilder(); users.forEach(user -> { apnsPayloadBuilder.setBadgeNumber(user.getUnreadCount()); String payload = apnsPayloadBuilder.buildWithDefaultMaximumLength(); user.getTokens().stream().filter(t -> t.getType().equals("apns")) .map(ExternalToken::getToken).forEach(token -> { Future> notification = apns.sendNotification( new SimpleApnsPushNotification(token, topic, payload)); notification.addListener((PushNotificationResponseListener) future -> { if (future.isSuccess()) { processAPNSResponse(token, future.getNow()); } else { logger.warn("APNS error ", future.cause()); } }); }); }); } @Override public void processTopEvent(TopEvent topEvent) { Message message = topEvent.getMessage(); ApnsPayloadBuilder apnsPayloadBuilder = new ApnsPayloadBuilder(); message.getUser().getTokens().stream().filter(t -> t.getType().equals("apns")) .map(ExternalToken::getToken).forEach( token -> { String payload = apnsPayloadBuilder.setAlertTitle("Top").setAlertBody("Your message became popular!") .addCustomProperty("mid", message.getMid()) .addCustomProperty("uname", message.getUser().getName()) .buildWithDefaultMaximumLength(); Future> notification = apns.sendNotification( new SimpleApnsPushNotification(token, topic, payload)); notification.addListener((PushNotificationResponseListener) future -> { if (future.isSuccess()) { processAPNSResponse(token, future.getNow()); } else { logger.warn("APNS error ", future.cause()); } }); }); } @PreDestroy public void close() { apns.close(); } private void processAPNSResponse(String token, PushNotificationResponse pushNotificationResponse) { if (pushNotificationResponse.isAccepted()) { logger.info("APNS accepted: {}", token); } else { String reason = pushNotificationResponse.getRejectionReason(); logger.info("APNS rejected: {}", reason); if (reason.equals("BadDeviceToken")) { notificationsManager.getInvalidAPNSTokens().add(token); } } Optional invalidationDate = Optional.ofNullable( pushNotificationResponse.getTokenInvalidationTimestamp()); invalidationDate.ifPresent(date -> { if (date.before(new Date())) { logger.info("Token invalidated: {}", token); notificationsManager.getInvalidAPNSTokens().add(token); } }); } }