in notificationworkerlambda/src/main/scala/com/gu/notifications/worker/delivery/apns/ApnsClient.scala [44:113]
def sendNotification(notificationId: UUID, token: String, payload: Payload, dryRun: Boolean)
(onComplete: Either[DeliveryException, Success] => Unit)
(implicit ece: ExecutionContextExecutor): Unit = {
val pushNotification = new SimpleApnsPushNotification(
TokenUtil.sanitizeTokenString(token),
config.bundleId,
payload.jsonString,
//Default to no invalidation time but an hour for breaking news and 10 mins for football
//See https://stackoverflow.com/questions/12317037/apns-notifications-ttl
payload.ttl.map(invalidationTime(_).toDate).orNull,
payload.deliveryPriority,
payload.collapseId.orNull
)
type Feedback = PushNotificationFuture[SimpleApnsPushNotification, PushNotificationResponse[SimpleApnsPushNotification]]
def responseHandler(start: Instant) = new PushNotificationResponseListenerWithTimeout[SimpleApnsPushNotification]() {
def timeout(): Unit = {
logger.info(Map(
"worker.individualRequestLatency" -> Duration.between(start, Instant.now).toMillis,
"notificationId" -> notificationId,
), "Individual send request timed out")
onComplete(Left(FailedRequest(notificationId, token, new TimeoutException("No APNs response received in time"), Some("ClientTimeout"))))
}
override def operationCompleteWithoutTimeout(feedback: Feedback): Unit = {
logger.info(Map(
"worker.individualRequestLatency" -> Duration.between(start, Instant.now).toMillis,
"notificationId" -> notificationId,
), "Individual send request completed")
if (feedback.isSuccess) {
val response = feedback.getNow
if (response.isAccepted) {
onComplete(Right(ApnsDeliverySuccess(token, Instant.now())))
} else {
val invalidationTimestamp = Option(response.getTokenInvalidationTimestamp)
.map(d => new Timestamp(d.getTime).toLocalDateTime)
val error = if (invalidationTimestamp.isDefined || invalidTokenErrorCodes.contains(response.getRejectionReason)) {
InvalidToken(notificationId, token, response.getRejectionReason, invalidationTimestamp)
} else {
FailedDelivery(notificationId, token, response.getRejectionReason)
}
onComplete(Left(error))
}
} else {
val debug =
s"""Failed Request
|isSuccess: ${feedback.isSuccess}, isDone: ${feedback.isDone}, isCancelled: ${feedback.isCancelled}
|getNow: ${Option(feedback.getNow)}
|cause: ${feedback.cause()}
|""".stripMargin
logger.error(debug)
onComplete(Left(FailedRequest(notificationId, token, feedback.cause())))
}
}
}
if(dryRun) {
onComplete(Right(ApnsDeliverySuccess(token, Instant.now(), dryRun = true)))
} else {
val start = Instant.now
val futureResult = underlying.sendNotification(pushNotification)
val handler = responseHandler(start)
handler.startTimeout(timer, timeoutInMs = 20000L)
futureResult.addListener(handler)
}
}